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;[email protected]/" -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://beta.beta.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://beta.beta.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;[email protected]/", [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