Synchronize with Another Remote Server

This extension allows you to synchronize a directory on a server with a directory on another server.

This is an official WinSCP extension and is distributed in WinSCP installer.

# @name         &Synchronize with Another Remote Server...
# @command      powershell.exe -ExecutionPolicy Bypass -File "%EXTENSION_PATH%" ^
#                   -sessionUrl1 "!E" -remotePath1 "!/" ^
#                   -sessionUrl2 "%SessionUrl2%" %PasswordPrompt% -remotePath2 "%RemotePath2%" ^
#                   %Delete% %Preview% %ContinueOnError% -pause -sessionLogPath ^
#                   "%SessionLogPath%"
# @description  Synchronizes a directory on another server (or another directory on this ^
#               server) against a directory on this server
# @version      2
# @homepage     https://beta.beta.winscp.net/eng/docs/extension_synchronize_another_server
# @require      WinSCP 5.18
# @require      PowerShell 3.0
# @option       - -run group "Synchronize directory from &this server:"
# @option       RemotePath1 -run textbox "&Directory:" "!/"
# @option       - -run group "... to &another server:"
# @option       SessionUrl2 -run textbox "&Session:" "!S"
# @option       PasswordPrompt -run checkbox "&Prompt for session password" ^
#                 -passwordPrompt  -passwordPrompt 
# @option       RemotePath2 -run textbox "Di&rectory:" "!/"
# @option       - -config -run group "Options"
# @option         Delete -config -run checkbox "&Delete files" "" -delete 
# @option         Preview -config -run checkbox "&Preview changes" -preview -preview
# @option         ContinueOnError -config -run checkbox "Continue on &error" "" -continueOnError
# @option       - -config group "Logging"
# @option         SessionLogPath -config sessionlogfile
# @optionspage  https://beta.beta.winscp.net/eng/docs/extension_synchronize_another_server#options
 
param (
    # Use Generate Session URL function to obtain a value
    # for -sessionUrl1 and -sessionUrl2 parameters.
    $sessionUrl1 = "sftp://user:mypassword;[email protected]/",
    [Parameter(Mandatory = $True)]
    $remotePath1,
    $sessionUrl2 = "sftp://user:mypassword;[email protected]/",
    [Switch]
    $passwordPrompt,
    [Parameter(Mandatory = $True)]
    $remotePath2,
    [Switch]
    $delete,
    [Switch]
    $preview,
    [Switch]
    $continueOnError,
    [Switch]
    $pause,
    $sessionLogPath = $Null
)
 
Set-StrictMode -Version 3.0
 
function SetConsoleTitle ($status)
{
    $Host.UI.RawUI.WindowTitle = $status
}
 
function HandleException ($e)
{
    if ($continueOnError)
    {
        Write-Host -ForegroundColor Red $_.Exception.Message
    }
    else
    {
        throw $e
    }
}
 
function CompareDirectories ($remotePath1, $remotePath2)
{
    Write-Host -NoNewline "Comparing $remotePath1 with $remotePath2..."
    try
    {
        $enumerationOptions = [WinSCP.EnumerationOptions]::MatchDirectories
        $files1 = $session1.EnumerateRemoteFiles($remotePath1, $Null, $enumerationOptions)
        $files2 = $session2.EnumerateRemoteFiles($remotePath2, $Null, $enumerationOptions)
        $first = $True
 
        $directories = [System.Collections.ArrayList]@()
        $existing2 = [System.Collections.ArrayList]@()
 
        foreach ($file1 in $files1)
        {
            $file2 = $files2 | Where-Object Name -EQ $file1.Name
            $modified = $False
            if ($file2 -eq $Null)
            {
                $modified = $True
            }
            else
            {
                $existing2.Add($file2.Name) | Out-Null
                if ($file1.IsDirectory -and $file2.IsDirectory)
                {
                    $file1 | Add-Member -NotePropertyName _Other -NotePropertyValue $file2
                    $directories.Add($file1) | Out-Null
                }
                else
                {
                    if ($file1.LastWriteTime -gt $file2.LastWriteTime)
                    {
                        $modified = $True
                    }
                }
            }
 
            if ($modified)
            {
                if ($first)
                {
                    Write-Host
                    $first = $False
                }
                if ($file2 -eq $Null)
                {
                    Write-Host "$($file1.FullName) is new"
                }
                else
                {
                    Write-Host "$($file1.FullName) is modified comparing to $($file2.FullName)"
                }
                $file1 | Add-Member -NotePropertyName _Target -NotePropertyValue $remotePath2
                $changes.Add($file1) | Out-Null
            }
        }
 
        if ($delete)
        {
            foreach ($file2 in $files2)
            {
                if (-not $existing2.Contains($file2.Name))
                {
                    if ($first)
                    {
                        Write-Host
                        $first = $False
                    }
                    Write-Host "$($file2.FullName) is orphan"
                    $file2 | Add-Member -NotePropertyName _Target -NotePropertyValue $Null
                    $changes.Add($file2) | Out-Null
                }
            }
        }
 
        if ($first)
        {
            Write-Host
        }
 
        foreach ($directory1 in $directories)
        { 
            $directory2 = $directory1._Other
            if ($directory1.IsDirectory -and $directory2.IsDirectory)
            {
                CompareDirectories $directory1.FullName $directory2.FullName
            }
        }
    }
    catch
    {
        Write-Host
        HandleException $_
    }
}
 
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
    $sessionOptions1 = New-Object WinSCP.SessionOptions
    $sessionOptions1.ParseUrl($sessionUrl1)
    $sessionOptions2 = New-Object WinSCP.SessionOptions
    $sessionOptions2.ParseUrl($sessionUrl2)
    if ((($sessionOptions2.Protocol -eq [WinSCP.Protocol]::Sftp) -or
         ($sessionOptions2.Protocol -eq [WinSCP.Protocol]::Scp)) -and
        (-not $sessionOptions2.SshHostKeyFingerprint))
    {
        $sessionOptions2.SshHostKeyPolicy = [WinSCP.SshHostKeyPolicy]::AcceptNew
    }
 
    $session1 = New-Object WinSCP.