Powershell : WinSCP

WinSCP has a series of inbuilt libraries to help keep a remote site synchronised with a local site.

You will need WinSCP (obviously) and winscp.ino as well as WINSCPnet.dll.

I adapted the code a little more than listed here, but the premise is the same.

function Sync
{
param (
# Use Generate URL function to obtain a value for -sessionUrl parameter.
$sessionUrl = "sftp://somewhereelse-;fingerprint=ssh-rsa-long ssh key@notherecom",
[Parameter(Mandatory)]
$localPath,
[Parameter(Mandatory)]
$remotePath,
[Switch]
$delete = $False,
[Switch]
$beep = $False,
[Switch]
$continueOnError = $False,
$sessionLogPath = $Null,
$interval = 30,
[Switch]
$pause = $False
)

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)

$session = New-Object WinSCP.Session

# Optimization
# (do not waste time enumerating files, if you do not need to scan for deleted files)
if ($delete)
{
$localFiles = Get-ChildItem -Recurse -Path $localPath
}

try
{
$session.SessionLogPath = $sessionLogPath

Write-Host "Connecting..."
$session.Open($sessionOptions)

while ($True)
{
Write-Host "Synchronizing changes..."
$result = $session.SynchronizeDirectories([WinSCP.SynchronizationMode]::Local, $localPath, $remotePath, $delete)

$changed = $False

if (!$result.IsSuccess)
{
if ($continueOnError)
{
Write-Host ("Error: {0}" -f $result.Failures[0].Message)
$changed = $True
}
else
{
$result.Check()
}
}

# Print updated files
foreach ($download in $result.Downloads)
{
Write-Host ("{0} <= {1}" -f $download.Destination, $download.FileName)
$changed = $True
}

if ($delete)
{
# scan for removed local files (the $result does not include them)
$localFiles2 = Get-ChildItem -Recurse -Path $localPath
$changes = Compare-Object -DifferenceObject $localFiles2 -ReferenceObject $localFiles

$removedFiles =
$changes |
Where-Object -FilterScript { $_.SideIndicator -eq "<=" } |
Select-Object -ExpandProperty InputObject

# Print removed local files
foreach ($removedFile in $removedFiles)
{
Write-Host ("{0} deleted" -f $removedFile)
$changed = $True
}

$localFiles = $localFiles2
}

if ($changed)
{
if ($beep)
{
[System.Console]::Beep()
}
}
else
{
Write-Host "No change."
}

Write-Host "Waiting for $interval seconds, press Ctrl+C to abort..."
$wait = [int]$interval
# Wait for 1 second in a loop, to make the waiting breakable
while ($wait -gt 0)
{
Start-Sleep -Seconds 1
$wait--
}

Write-Host
}
}
finally
{
Write-Host "Disconnecting..."
# Disconnect, clean up
$session.Dispose()
}
}
catch [Exception]
{
Write-Host ("Error: {0}" -f $_.Exception.Message)
}

# 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

}