Keep local directory up to date (download changed files from remote SFTP/FTP server)

This script periodically synchronizes changes from the remote server to a local directory.

WinSCP supports natively keeping a remote directory up to date function thanks to a possibility to get notified by Windows (as a local operating system) about changes in a local directory. None of the supported file transfer protocols unfortunately offer a functionality to watch for changes in a remote directory. So the only solution is to run full remote to local synchronization in regular intervals.

The script is distributed in WinSCP installer as a WinSCP extension.

Advertisement

To run the script manually, use:

powershell.exe -File KeepLocalUpToDate.ps1 -sessionUrl "sftp://username:password;fingerprint=ssh-rsa-xxxxxxxxxxx...@example.com/" -remotePath "/remote/path" -localPath "C:\local\path" [-delete]

If you just want to scan for changes, but not download them, see Watching for changes in SFTP/FTP server.

# @name         &Keep Local Directory up to Date...
# @command      powershell.exe -ExecutionPolicy Bypass -File "%EXTENSION_PATH%" ^
#                   -sessionUrl "!E" -localPath "%LocalPath%" -remotePath "%RemotePath%" ^
#                   %Delete% %Beep% %ContinueOnError% -interval "%Interval%" ^
#                   -fileMask "%FileMask%" -pause -sessionLogPath "%SessionLogPath%"
# @description  Periodically scans for changes in a remote directory and ^
#                   reflects them on a local directory
# @version      11
# @homepage     https://winscp.net/eng/docs/library_example_keep_local_directory_up_to_date
# @require      WinSCP 5.16
# @option       - -run group "Directories"
# @option         RemotePath -run textbox "&Watch for changes in the remote directory:" "!/"
# @option         LocalPath -run textbox ^
#                     "... &and automatically reflect them on the local directory:" "!\"
# @option       - -config -run group "Options"
# @option         Delete -config -run checkbox "&Delete files" "" -delete 
# @option         Beep -config -run checkbox "&Beep on change" "" -beep
# @option         ContinueOnError -config -run checkbox "Continue on &error" "" -continueOnError
# @option         Interval -config -run textbox "&Interval (in seconds):" "30"
# @option         FileMask -config -run textbox "File &mask:" ""
# @option       - -config group "Logging"
# @option         SessionLogPath -config sessionlogfile
# @optionspage  ^
#     https://winscp.net/eng/docs/library_example_keep_local_directory_up_to_date#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,
    [Switch]
    $delete,
    [Switch]
    $beep,
    [Switch]
    $continueOnError,
    $sessionLogPath = $Null,
    $interval = 30,
    $fileMask = $Null,
    [Switch]
    $pause
)
 
function Beep()
{
    if ($beep)
    {
        [System.Console]::Beep()
    }
}
 
function HandleException ($e)
{
    if ($continueOnError)
    {
        Write-Host -ForegroundColor Red $_.Exception.Message
        Beep
    }
    else
    {
        throw $e
    }
}
 
function SetConsoleTitle ($status)
{
    if ($sessionOptions)
    {
        $status = "$sessionOptions - $status"
    }
    $Host.UI.RawUI.WindowTitle = $status
}
 
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
    $sessionOptions = New-Object WinSCP.SessionOptions
    $sessionOptions.ParseUrl($sessionUrl)
 
    $transferOptions = New-Object WinSCP.TransferOptions -Property @{ FileMask = $fileMask };
 
    $session = New-Object WinSCP.Session
    
    try
    {
        $session.SessionLogPath = $sessionLogPath
 
        Write-Host "Connecting..."
        SetConsoleTitle "Connecting"
        $session.Open($sessionOptions)
 
        while ($True)
        {
            Write-Host -NoNewline "Looking for changes..."
            SetConsoleTitle "Looking for changes"
            try
            {
                $differences =
                    $session.CompareDirectories(
                        [WinSCP.SynchronizationMode]::Local, $localPath, $remotePath, $delete,
                        $False, [WinSCP.SynchronizationCriteria]::Time, $transferOptions)
 
                Write-Host
                if ($differences.Count -eq 0)
                {
                    Write-Host "No changes found."   
                }
                else
                {
                    Write-Host "Synchronizing $($differences.Count) change(s)..."
                    SetConsoleTitle "Synchronizing changes"
                    Beep
 
                    foreach ($difference in $differences)
                    {
                        $action = $difference.Action
                        if ($action -eq [WinSCP.SynchronizationAction]::DownloadNew)
                        {
                            $message = "Downloading new $($difference.Remote.FileName)..."
                        }
                        elseif ($action -eq [WinSCP.SynchronizationAction]::DownloadUpdate)
                        {
                            $message = "Downloading updated $($difference.Remote.FileName)..."
                        }
                        elseif ($action -eq [WinSCP.SynchronizationAction]::DeleteLocal)
                        {
                            $message = "Deleting $($difference.Local.FileName)..."
                        }
                        else
                        {
                            throw "Unexpected difference $action"
                        }
 
                        Write-Host -NoNewline $message
 
                        try
                        {
                            $difference.Resolve($session, $transferOptions) | Out-Null
                            Write-Host " Done."
                        }
                        catch
                        {
                            Write-Host
                            HandleException $_
                        }
                    }
                }
            }
            catch
            {
                Write-Host
                HandleException $_
            }
 
            SetConsoleTitle "Waiting"
            $wait = [int]$interval
            # Wait for 1 second in a loop, to make the waiting breakable
            while ($wait -gt 0)
            {
                Write-Host -NoNewLine "`rWaiting for $wait seconds, press Ctrl+C to abort... "
                Start-Sleep -Seconds 1
                $wait--
            }
 
            Write-Host
            Write-Host
        }
    }
    finally
    {
        Write-Host # to break after "Waiting..." status
        Write-Host "Disconnecting..."
        # Disconnect, clean up
        $session.Dispose()
    }
}
catch
{
    $continueOnError = $True
    HandleException $_
    SetConsoleTitle "Error"
}
 
# Pause if -pause switch was used
if ($pause)
{
    Write-Host "Press any key to exit..."
    [System.Console]::ReadKey() | Out-Null
}
 
# Never exits cleanly
exit 1

Advertisement

Options

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.

The Delete files checkbox makes the extension delete files and subdirectories in local directory that you delete in a corresponding remote directory. Before using this, learn how it works, so you know what you are doing.

Check the Beep on change to make the extension beep when a change is detected.

Check the Continue on error not to interrupt watching for changes, when an error occurs. Typically you want to enable the option to skip files opened for writing and similar errors.

In the Interval box, specify an interval between the checks for changes.

In the File mask box, you can specify file mask to select/deselect files (or file types) and directories for the synchronization.

In the Session log file, you can specify a path to a session log file. The option is available on the Preferences dialog only.

In the Keyboard shortcut, you can specify a keyboard shortcut for the extension. The option is available on the Preferences dialog only.

Scripting

Or you can simply use the synchronize scripting command.

To make the synchronization run periodically, you can either schedule the script or run the script in a loop. The latter may be more convenient, as it allows you to see all runs in a single console window, so that you can review the updates easily.

To run the script in the loop, you can use following batch file:1

@echo off
:loop
winscp.com /script=full_remote_to_local_synchronization.txt
timeout /t 30
goto loop

Advertisement

  1. Note that the timeout command is available since Windows 7 only. For alternatives in older versions of Windows, refer to How to sleep for 5 seconds in Windows’s Command Prompt? article.Back

Last modified: by martin