MrUpsidown
MrUpsidown

Reputation: 22497

WinSCP delete files on both sides when synchronizing

Using WinSCP, is there any way that I could sync both ways (between SFTP and local directory) including deletions?

The scenario would be the following: If a file is present on either side and not on the other, it should be created (synchronize both would do that just fine). If a file was deleted on either side, it should be deleted on the other side.

I suppose I would need some kind of log to be able to track when a file was deleted, on one or the other side so the change can be reflected on the other side.

I don't think that WinSCP has any command that does that, if I am right. Has anyone done that already?

Upvotes: 1

Views: 1266

Answers (1)

Martin Prikryl
Martin Prikryl

Reputation: 202504

WinSCP cannot do that. Its synchronization is stateless. For what you want to do, you would have to indeed remember the previous state of the directories. To be able to decide, if an unmatched file was added on one side or removed on the other.

This is hardly doable with plain WinSCP scripting and a batch file. But with WinSCP .NET assembly from a PowerShell script, it is not that difficult. You can use the Session.CompareDirectories method with the "both" mode. For the UploadNew and DownloadNew differences, customize them to removals, when appropriate (using a cached file list for the decision).

There is WinSCP extension Two-Way Synchronization with Delete with SFTP/FTP server:

try
{
    Add-Type -Path "WinSCPnet.dll"
 
    $sessionOptions = New-Object WinSCP.SessionOptions -Property @{
        Protocol = [WinSCP.Protocol]::Sftp
        HostName = "example.com"
        UserName = "user"
        Password = "password"
        SshHostKeyFingerprint = "ssh-rsa 2048 xxxxxxxxxxx...="
    }

    $localPath = "C:\local\path"
    $remotePath = "/remote/path"

    $previousFilesFile = "previous.txt"
    if (Test-Path $previousFilesFile)
    {
        Write-Host "Loading list of previous local files..."
        [string[]]$previousFiles = Get-Content $previousFilesFile
    }
    else
    {
        Write-Host "No list of previous local files"
        $previousFiles = @()
    }

    $session = New-Object WinSCP.Session
 
    try
    {
        # Connect
        Write-Host "Connecting..."
        $session.Open($sessionOptions)

        $differences =
            $session.CompareDirectories(
                [WinSCP.SynchronizationMode]::Both, $localPath, $remotePath,
                $False)

        if ($differences.Count -eq 0)
        {
            Write-Host "No changes found."   
        }
        else
        {
            Write-Host "Synchronizing $($differences.Count) change(s)..."

            foreach ($difference in $differences)
            {
                $action = $difference.Action
                if ($action -eq [WinSCP.SynchronizationAction]::UploadNew)
                {
                    if ($previousFiles -contains $difference.Local.FileName)
                    {
                        Write-Host "Removing local file $path..."
                        Remove-Item -Recurse $path
                    }
                    else
                    {
                        $path = $difference.Local.FileName
                        Write-Host "Uploading new $path..."
                        $difference.Resolve($session) | Out-Null
                    }
                }
                elseif ($action -eq [WinSCP.SynchronizationAction]::DownloadNew)
                {
                    $localFilePath =
                        [WinSCP.RemotePath]::TranslateRemotePathToLocal(
                            $difference.Remote.FileName, $remotePath, $localPath)
                    if ($previousFiles -contains $localFilePath)
                    {
                        $path = $difference.Remote.FileName
                        Write-Host "Removing remote file $path..."
                        $path = [WinSCP.RemotePath]::EscapeFileMask($path)
                        $session.RemoveFiles($path).Check()
                    }
                    else
                    {
                        $path = $difference.Remote.FileName
                        Write-Host "Downloading new $path..."
                        $difference.Resolve($session) | Out-Null
                    }
                }
                elseif ($action -eq [WinSCP.SynchronizationAction]::DownloadUpdate)
                {
                    $path = $difference.Remote.FileName
                    Write-Host "Downloading updated $path..."
                    $difference.Resolve($session) | Out-Null
                }
                elseif ($action -eq [WinSCP.SynchronizationAction]::UploadUpdate)
                {
                    $path = $difference.Local.FileName
                    Write-Host "Uploading updated $path..."
                    $difference.Resolve($session) | Out-Null
                }
                else
                {
                    throw "Unexpected difference $action"
                }
            }
        }
    }
    finally
    {
        # Disconnect, clean up
        $session.Dispose()
    }

    Write-Host "Saving current local file list..."
    $localFiles =
        Get-ChildItem -Recurse -Path $localPath |
        Select-Object -ExpandProperty FullName
    Set-Content $previousFilesFile $localFiles

    Write-Host "Done."
    exit 0
}
catch
{
    Write-Host "Error: $($_.Exception.Message)"
    exit 1
}

Upvotes: 3

Related Questions