Differences
This shows you the differences between the selected revisions of the page.
| library_example_keep_local_directory_up_to_date 2020-07-15 | library_example_keep_local_directory_up_to_date 2023-06-21 (current) | ||
| Line 12: | Line 12: | ||
| <code> | <code> | ||
| - | powershell.exe -File KeepLocalUpToDate.ps1 -sessionUrl "sftp://username:password@example.com/" -remotePath "/remote/path" -localPath "C:\local\path" [-delete] | + | powershell.exe -File KeepLocalUpToDate.ps1 -sessionUrl "sftp://username:password;fingerprint=ssh-rsa-xxxxxxxxxxx...@example.com/" -remotePath "/remote/path" -localPath "C:\local\path" [-delete] |
| </code> | </code> | ||
| - | //If you just want to scan for changes, but not download them, see [[library_example_watch_for_changes|Watching for changes in SFTP/FTP server]].// | + | //If you just want to scan for changes, but not download them, see [[library_example_watch_for_changes|*]].// |
| <code powershell - KeepLocalUpToDate.ps1> | <code powershell - KeepLocalUpToDate.ps1> | ||
| Line 21: | Line 21: | ||
| # @command powershell.exe -ExecutionPolicy Bypass -File "%EXTENSION_PATH%" ^ | # @command powershell.exe -ExecutionPolicy Bypass -File "%EXTENSION_PATH%" ^ | ||
| # -sessionUrl "!E" -localPath "%LocalPath%" -remotePath "%RemotePath%" ^ | # -sessionUrl "!E" -localPath "%LocalPath%" -remotePath "%RemotePath%" ^ | ||
| - | # %Delete% %Beep% %ContinueOnError% -interval "%Interval%" -pause ^ | + | # %Delete% %Beep% %ContinueOnError% -interval "%Interval%" ^ |
| - | # -sessionLogPath "%SessionLogPath%" | + | # -fileMask "%FileMask%" -pause -sessionLogPath "%SessionLogPath%" |
| # @description Periodically scans for changes in a remote directory and ^ | # @description Periodically scans for changes in a remote directory and ^ | ||
| # reflects them on a local directory | # reflects them on a local directory | ||
| - | # @version 6 | + | # @version 11 |
| # @homepage ~~SELF~~ | # @homepage ~~SELF~~ | ||
| # @require WinSCP 5.16 | # @require WinSCP 5.16 | ||
| Line 37: | Line 37: | ||
| # @option ContinueOnError -config -run checkbox "Continue on &error" "" -continueOnError | # @option ContinueOnError -config -run checkbox "Continue on &error" "" -continueOnError | ||
| # @option Interval -config -run textbox "&Interval (in seconds):" "30" | # @option Interval -config -run textbox "&Interval (in seconds):" "30" | ||
| + | # @option FileMask -config -run textbox "File &mask:" "" | ||
| # @option - -config group "Logging" | # @option - -config group "Logging" | ||
| # @option SessionLogPath -config sessionlogfile | # @option SessionLogPath -config sessionlogfile | ||
| Line 44: | Line 45: | ||
| param ( | param ( | ||
| # Use Generate Session URL function to obtain a value for -sessionUrl parameter. | # Use Generate Session URL function to obtain a value for -sessionUrl parameter. | ||
| - | $sessionUrl = "sftp://user:mypassword;fingerprint=ssh-rsa-xxxxxxxxxxx...=@example.com/", | + | $sessionUrl = "sftp://user:mypassword;fingerprint=ssh-rsa-xxxxxxxxxxx...@example.com/", |
| [Parameter(Mandatory = $True)] | [Parameter(Mandatory = $True)] | ||
| $localPath, | $localPath, | ||
| Line 57: | Line 58: | ||
| $sessionLogPath = $Null, | $sessionLogPath = $Null, | ||
| $interval = 30, | $interval = 30, | ||
| + | $fileMask = $Null, | ||
| [Switch] | [Switch] | ||
| $pause | $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 | try | ||
| Line 70: | Line 102: | ||
| $sessionOptions = New-Object WinSCP.SessionOptions | $sessionOptions = New-Object WinSCP.SessionOptions | ||
| $sessionOptions.ParseUrl($sessionUrl) | $sessionOptions.ParseUrl($sessionUrl) | ||
| + | |||
| + | $transferOptions = New-Object WinSCP.TransferOptions -Property @{ FileMask = $fileMask }; | ||
| $session = New-Object WinSCP.Session | $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 | try | ||
| { | { | ||
| Line 85: | Line 112: | ||
| Write-Host "Connecting..." | Write-Host "Connecting..." | ||
| + | SetConsoleTitle "Connecting" | ||
| $session.Open($sessionOptions) | $session.Open($sessionOptions) | ||
| while ($True) | while ($True) | ||
| { | { | ||
| - | Write-Host "Synchronizing changes..." | + | Write-Host -NoNewline "Looking for changes..." |
| - | $result = | + | SetConsoleTitle "Looking for changes" |
| - | ···············$session.SynchronizeDirectories( | + | try |
| - | ···················[WinSCP.SynchronizationMode]::Local, $localPath, $remotePath, $delete) | + | { |
| - | + | ················$differences = | |
| - | ···········$changed = $False | + | ···················$session.CompareDirectories( |
| + | ·······················[WinSCP.SynchronizationMode]::Local, $localPath, $remotePath, $delete, | ||
| + | ·······················$False, [WinSCP.SynchronizationCriteria]::Time, $transferOptions) | ||
| - | ············if (!$result.IsSuccess) | + | ················Write-Host |
| - | { | + | if ($differences.Count -eq 0) |
| - | if ($continueOnError) | + | |
| { | { | ||
| - | Write-Host "Error: $($result.Failures[0].Message)" | + | Write-Host "No changes found." ·· |
| - | $changed = $True | + | |
| } | } | ||
| else | else | ||
| { | { | ||
| - | $result.Check() | + | Write-Host "Synchronizing $($differences.Count) change(s)..." |
| - | ···············} | + | SetConsoleTitle "Synchronizing changes" |
| - | ···········} | + | ···················Beep |
| - | ············# Print updated files | + | ····················foreach ($difference in $differences) |
| - | ···········foreach ($download in $result.Downloads) | + | ···················{ |
| - | ···········{ | + | ·······················$action = $difference.Action |
| - | ···············Write-Host "$($download.Destination)·<= $($download.FileName)" | + | if ($action -eq [WinSCP.SynchronizationAction]::DownloadNew) |
| - | ···············$changed = $True | + | ·······················{ |
| - | ···········} | + | ···························$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" | ||
| + | ·······················} | ||
| - | ············if ($delete) | + | ························Write-Host -NoNewline $message |
| - | { | + | |
| - | # scan for removed local files (the $result does not include them) | + | |
| - | $localFiles2 = Get-ChildItem -Recurse -Path $localPath | + | |
| - | ················if ($localFiles) | + | ························try |
| - | ···············{ | + | ·······················{ |
| - | ···················$changes = | + | ···························$difference.Resolve($session, $transferOptions) | Out-Null |
| - | ························Compare-Object -DifferenceObject $localFiles2 ` | + | ···························Write-Host " Done." |
| - | -ReferenceObject $localFiles | + | } |
| - | + | ·······················catch | |
| - | $removedFiles = | + | ·······················{ |
| - | $changes | | + | ···························Write-Host |
| - | Where-Object -FilterScript { $_.SideIndicator -eq "<=" } | | + | ···························HandleException $_ |
| - | Select-Object -ExpandProperty InputObject | + | } |
| - | + | ||
| - | ···················# Print removed local files | + | |
| - | foreach ($removedFile in $removedFiles) | + | |
| - | ····················{ | + | |
| - | ·······················Write-Host "$removedFile deleted" | + | |
| - | $changed = $True | + | |
| } | } | ||
| } | } | ||
| - | |||
| - | $localFiles = $localFiles2 | ||
| } | } | ||
| - | + | ············catch | |
| - | ············if ($changed) | + | |
| { | { | ||
| - | if ($beep) | + | Write-Host |
| - | { | + | HandleException $_ |
| - | [System.Console]::Beep() | + | |
| - | ················} | + | |
| } | } | ||
| - | else | + | |
| - | { | + | SetConsoleTitle "Waiting" |
| - | Write-Host "No change." | + | |
| - | } | + | |
| - | + | ||
| - | Write-Host "Waiting for $interval seconds, press Ctrl+C to abort..." | + | |
| $wait = [int]$interval | $wait = [int]$interval | ||
| # Wait for 1 second in a loop, to make the waiting breakable | # Wait for 1 second in a loop, to make the waiting breakable | ||
| while ($wait -gt 0) | while ($wait -gt 0) | ||
| { | { | ||
| + | Write-Host -NoNewLine "`rWaiting for $wait seconds, press Ctrl+C to abort... " | ||
| Start-Sleep -Seconds 1 | Start-Sleep -Seconds 1 | ||
| $wait-- | $wait-- | ||
| } | } | ||
| + | Write-Host | ||
| Write-Host | Write-Host | ||
| } | } | ||
| Line 169: | Line 194: | ||
| finally | finally | ||
| { | { | ||
| + | Write-Host # to break after "Waiting..." status | ||
| Write-Host "Disconnecting..." | Write-Host "Disconnecting..." | ||
| # Disconnect, clean up | # Disconnect, clean up | ||
| Line 176: | Line 202: | ||
| catch | catch | ||
| { | { | ||
| - | Write-Host "Error: $($_.Exception.Message)" | + | $continueOnError = $True |
| + | ····HandleException $_ | ||
| + | ····SetConsoleTitle "Error" | ||
| } | } | ||
| Line 203: | Line 231: | ||
| In the //Interval// box, specify an interval between the checks for changes. | In the //Interval// box, specify an interval between the checks for changes. | ||
| + | |||
| + | In the //File mask// box, you can specify [[file_mask|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 [[logging|session log file]]. The option is available on the [[ui_pref_commands|Preferences dialog]] only. | In the //Session log file//, you can specify a path to a [[logging|session log file]]. The option is available on the [[ui_pref_commands|Preferences dialog]] only. | ||
| Line 214: | Line 244: | ||
| To make the synchronization run periodically, you can either [[guide_schedule|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 make the synchronization run periodically, you can either [[guide_schedule|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:((Note that the ''[[https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/timeout_1|timeout]]'' command is available since Windows 7 only. For alternatives in older versions of Windows, refer to [[https://stackoverflow.com/q/1672338/850848|How to sleep for 5 seconds in Windows's Command Prompt?]] article.)) | + | To run the script in the loop, you can use following batch file:((Note that the ''[[https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/timeout|timeout]]'' command is available since Windows 7 only. For alternatives in older versions of Windows, refer to [[https://stackoverflow.com/q/1672338/850848|How to sleep for 5 seconds in Windows's Command Prompt?]] article.)) |
| <code batch> | <code batch> | ||
| @echo off | @echo off | ||