Differences
This shows you the differences between the selected revisions of the page.
library_example_keep_local_directory_up_to_date 2016-09-06 | library_example_keep_local_directory_up_to_date 2023-06-21 (current) | ||
Line 6: | Line 6: | ||
The script is distributed in WinSCP installer as a [[extension|WinSCP extension]]. | The script is distributed in WinSCP installer as a [[extension|WinSCP extension]]. | ||
+ | |||
+ | ~~AD~~ | ||
To run the script manually, use: | To run the script manually, use: | ||
<code> | <code> | ||
- | powershell.exe -File KeepLocalUpToDate.ps1 -remotePath "/remote/path" | + | powershell.exe -File KeepLocalUpToDate.ps1 -sessionUrl "sftp://username:password;fingerprint=ssh-rsa-xxxxxxxxxxx...@example.com/" -remotePath "/remote/path"·-localPath "C:\local\path" [-delete] |
- | -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> | ||
# @name &Keep Local Directory up to Date... | # @name &Keep Local Directory up to Date... | ||
- | # @command powershell.exe -ExecutionPolicy Bypass -File "%EXTENSION_PATH%" -sessionUrl "!S" -localPath "%LocalPath%" -remotePath "%RemotePath%" %Delete% %Beep% %ContinueOnError% -interval "%Interval%" -pause -sessionLogPath "%SessionLogPath%" | + | # @command powershell.exe -ExecutionPolicy Bypass -File "%EXTENSION_PATH%" ^ |
- | # @description Periodically scans for changes in a remote directory and reflects them on a local directory | + | # -sessionUrl "!E" -localPath "%LocalPath%" -remotePath "%RemotePath%" ^ |
- | # @version 1 | + | # %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 ~~SELF~~ | # @homepage ~~SELF~~ | ||
- | # @require WinSCP 5.9.2 | + | # @require WinSCP 5.16 |
# @option - -run group "Directories" | # @option - -run group "Directories" | ||
# @option RemotePath -run textbox "&Watch for changes in the remote directory:" "!/" | # @option RemotePath -run textbox "&Watch for changes in the remote directory:" "!/" | ||
- | # @option LocalPath -run textbox "... &and automatically reflect them on the local directory:" "!\" | + | # @option LocalPath -run textbox ^ |
+ | # "... &and automatically reflect them on the local directory:" "!\" | ||
# @option - -config -run group "Options" | # @option - -config -run group "Options" | ||
# @option Delete -config -run checkbox "&Delete files" "" -delete | # @option Delete -config -run checkbox "&Delete files" "" -delete | ||
# @option Beep -config -run checkbox "&Beep on change" "" -beep | # @option Beep -config -run checkbox "&Beep on change" "" -beep | ||
- | # @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 | ||
- | # @optionspage ~~SELF~~#options | + | # @optionspage ^ |
+ | # ~~SELF~~#options | ||
param ( | param ( | ||
- | # Use Generate 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-xx-xx-xx@example.com/", | + | $sessionUrl = "sftp://user:mypassword;fingerprint=ssh-rsa-xxxxxxxxxxx...@example.com/", |
- | [Parameter(Mandatory)] | + | [Parameter(Mandatory = $True)] |
$localPath, | $localPath, | ||
- | [Parameter(Mandatory)] | + | [Parameter(Mandatory = $True)] |
$remotePath, | $remotePath, | ||
[Switch] | [Switch] | ||
- | $delete = $False, | + | $delete, |
[Switch] | [Switch] | ||
- | $beep = $False, | + | $beep, |
[Switch] | [Switch] | ||
- | $continueOnError = $False, | + | $continueOnError, |
$sessionLogPath = $Null, | $sessionLogPath = $Null, | ||
$interval = 30, | $interval = 30, | ||
+ | $fileMask = $Null, | ||
[Switch] | [Switch] | ||
- | $pause = $False | + | $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 63: | 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 78: | 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 = $session.SynchronizeDirectories([WinSCP.SynchronizationMode]::Local, $localPath, $remotePath, $delete) | + | SetConsoleTitle "Looking for changes" |
- | + | try | |
- | $changed = $False | + | |
- | + | ||
- | if (!$result.IsSuccess) | + | |
{ | { | ||
- | if ($continueOnError) | + | ················$differences = |
- | ·············{ | + | ···················$session.CompareDirectories( |
- | ················Write-Host ("Error: {0}" -f $result.Failures[0].Message) | + | ·······················[WinSCP.SynchronizationMode]::Local, $localPath, $remotePath, $delete, |
- | ···············$changed = $True | + | ·······················$False, [WinSCP.SynchronizationCriteria]::Time, $transferOptions) |
- | ·············} | + | |
- | ·············else | + | |
- | ··············{ | + | |
- | ···············$result.Check() | + | |
- | ··············} | + | |
- | ···········} | + | |
- | # Print updated files | + | ················Write-Host |
- | foreach ($download in $result.Downloads) | + | if ($differences.Count -eq 0) |
- | { | + | |
- | ················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) | + | Write-Host "No changes found." ·· |
- | $changed = $True | + | |
} | } | ||
+ | else | ||
+ | { | ||
+ | Write-Host "Synchronizing $($differences.Count) change(s)..." | ||
+ | SetConsoleTitle "Synchronizing changes" | ||
+ | Beep | ||
- | ················$localFiles = $localFiles2 | + | 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" | ||
+ | ·······················} | ||
- | ············if ($changed) | + | ························Write-Host -NoNewline $message |
- | ···········{ | + | |
- | ···············if ($beep) | + | ·······················try |
- | ···············{ | + | ·······················{ |
- | ···················[System.Console]::Beep() | + | $difference.Resolve($session, $transferOptions) | Out-Null |
+ | Write-Host " Done." | ||
+ | } | ||
+ | catch | ||
+ | ·······················{ | ||
+ | ···························Write-Host | ||
+ | ····························HandleException $_ | ||
+ | ························} | ||
+ | ····················} | ||
} | } | ||
} | } | ||
- | else | + | catch |
{ | { | ||
- | Write-Host "No change." | + | Write-Host |
+ | ···············HandleException $_ | ||
} | } | ||
- | ············ | + | |
- | Write-Host "Waiting for $interval seconds, press Ctrl+C to abort..." | + | SetConsoleTitle "Waiting" |
$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 154: | Line 194: | ||
finally | finally | ||
{ | { | ||
+ | Write-Host # to break after "Waiting..." status | ||
Write-Host "Disconnecting..." | Write-Host "Disconnecting..." | ||
# Disconnect, clean up | # Disconnect, clean up | ||
Line 159: | Line 200: | ||
} | } | ||
} | } | ||
- | catch [Exception] | + | catch |
{ | { | ||
- | Write-Host ("Error: {0}" -f $_.Exception.Message) | + | $continueOnError = $True |
+ | ····HandleException $_ | ||
+ | ···SetConsoleTitle "Error" | ||
} | } | ||
Line 175: | Line 218: | ||
</code> | </code> | ||
- | ===== Options ===== | + | ===== [[options]] Options ===== |
+ | |||
+ | &screenshotpict(extension_keep_local_directory_up_to_date) | ||
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. | 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. | ||
Line 187: | Line 232: | ||
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 //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 //File mask// box, you can specify [[file_mask|file mask]] to select/deselect files (or file types) and directories for the synchronization. |
- | + | ||
- | ===== Scripting ===== | + | 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 //Keyboard shortcut//, you can specify a [[custom_key_shortcuts|keyboard shortcut]] for the extension. The option is available on the [[ui_pref_commands|Preferences dialog]] only. | ||
+ | |||
+ | ===== [[scripting]] Scripting ===== | ||
Or you can simply use the ''[[scriptcommand_synchronize|synchronize]]'' scripting command. | Or you can simply use the ''[[scriptcommand_synchronize|synchronize]]'' scripting command. | ||
Line 195: | 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://technet.microsoft.com/en-us/library/cc754891.aspx|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 |