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 }