Two-way synchronization with delete with SFTP/FTP server
WinSCP synchronization functionality is state-less. So when there is a file on one side, which is absent on the opposite side, WinSCP is not able to determine, if the file was added on the first side, or removed on the latter. For this reason in the Both direction, WinSCP does not support the Delete files option.
This extension uses a cache file to remember the list of local files after the previous synchronization. It uses the cached list to determine, if the file was added or removed.
You can install this script as a WinSCP extension by using this page URL in the Add Extension command.
To run the script manually use:
powershell.exe -File C:\path\SynchronizeTwoWayDelete.ps1 -sessionUrl "sftp://user:password;fingerprint=ssh-rsa-xxxxxxxxxxx...@example.com/" -localPath "C:\local" -remotePath "/remote" -listPath "C:\cache\example.txt"
# @name Two-Way Synchronization with Delete... # @command powershell.exe -ExecutionPolicy Bypass -File "%EXTENSION_PATH%" ^ # -sessionUrl "!E" -localPath "%LocalPath%" -remotePath "%RemotePath%" ^ # -listPath "%ListPath%" -refresh -pause -sessionLogPath "%SessionLogPath%" # @description Synchronizes files on local and remote directories including file deletions ^ # by remembering a list of previous local files # @version 2 # @homepage https://winscp.net/eng/docs/library_example_two_way_synchronize_delete # @require WinSCP 5.18.1 # @option - -run group "Directories" # @option LocalPath -run textbox "&Local directory:" "!\" # @option RemotePath -run textbox "&Remote directory:" "!/" # @option - -run group "Options" # @option ListPath -run textbox "Re&member previous local files in:" ^ # "%LOCALAPPDATA%\WinSCP\TwoWayDelete\!N.txt" # @option - -config group "Logging" # @option SessionLogPath -config sessionlogfile # @optionspage https://winscp.net/eng/docs/library_example_two_way_synchronize_delete#options param ( # Use Generate Session URL function to obtain a value for -sessionUrl parameter. $sessionUrl = "sftp://user:mypassword;fingerprint=ssh-rsa-xxxxxxxxxxx...@example.com/", [Parameter(Mandatory = $True)] $localPath, [Parameter(Mandatory = $True)] $remotePath, [Parameter(Mandatory = $True)] $listPath, $sessionLogPath = $Null, [Switch] $pause, [Switch] $refresh ) try { # Load WinSCP .NET assembly $assemblyPath = if ($env:WINSCP_PATH) { $env:WINSCP_PATH } else { $PSScriptRoot } Add-Type -Path (Join-Path $assemblyPath "WinSCPnet.dll") # Setup session options from URL $sessionOptions = New-Object WinSCP.SessionOptions $sessionOptions.ParseUrl($sessionUrl) $listPath = [Environment]::ExpandEnvironmentVariables($listPath) $listDir = (Split-Path -Parent $listPath) New-Item -ItemType directory -Path $listDir -Force | Out-Null if (Test-Path $listPath) { Write-Host "Loading list of previous local files..." [string[]]$previousFiles = Get-Content $listPath } else { Write-Host "No list of previous local files" $previousFiles = @() } $needRefresh = $False $session = New-Object WinSCP.Session try { $session.SessionLogPath = $sessionLogPath Write-Host "Connecting..." $session.Open($sessionOptions) Write-Host "Comparing files..." $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) { $difference.Reverse() } else { $needRefresh = $True } } elseif ($action -eq [WinSCP.SynchronizationAction]::DownloadNew) { $localFilePath = [WinSCP.RemotePath]::TranslateRemotePathToLocal( $difference.Remote.FileName, $remotePath, $localPath) if ($previousFiles -contains $localFilePath) { $difference.Reverse() $needRefresh = $True } else { # noop } } elseif ($action -eq [WinSCP.SynchronizationAction]::DownloadUpdate) { # noop } elseif ($action -eq [WinSCP.SynchronizationAction]::UploadUpdate) { $needRefresh = $True } else { throw "Unexpected difference $action" } Write-Host -NoNewline "$difference ..." try { $difference.Resolve($session) | Out-Null Write-Host -NoNewline " Done." } finally { Write-Host } } } } finally { # Disconnect, clean up $session.Dispose() } # Refresh the remote directory in WinSCP GUI, if it was changed and -refresh switch was used if ($refresh -and $needRefresh) { Write-Host "Reloading remote directory..." & "$env:WINSCP_PATH\WinSCP.exe" "$sessionUrl" /refresh "$remotePath" } Write-Host "Saving current local file list..." $localFiles = Get-ChildItem -Recurse -Path $localPath | Select-Object -ExpandProperty FullName Set-Content $listPath $localFiles Write-Host "Done." $result = 0 } catch { Write-Host "Error: $($_.Exception.Message)" $result = 1 } # Pause if -pause switch was used if ($pause) { Write-Host "Press any key to exit..." [System.Console]::ReadKey() | Out-Null } exit $result
Options
Directories
Enter root directories for the synchronization into the two directory boxes. By default the current working directories will be used. The directories can be specified, when executing the extension only.
Options
In the Remember previous local files in box, specify the path to a list text file that will be used to remember the files that existed after the synchronization. The file (and its parent folders) are automatically created, if it does not exist yet. By default, the file is stored in %LOCALAPPDATA%\WinSCP\TwoWayDelete
and named after the current session. This options are available, when executing the extension only.
Preferences
These options are available on the Preferences dialog only.
In the Session log file, you can specify a path to a session log file.
In the Keyboard shortcut, you can specify a keyboard shortcut for the extension.