Created
August 4, 2022 00:44
-
-
Save Seekatar/6aea0a5f80821a4fabcfe787fe2f3353 to your computer and use it in GitHub Desktop.
PowerShell functions for watching for file changes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.SYNOPSIS | |
Watch a file for changes | |
.PARAMETER File | |
File to watch, may be a folder with a mask | |
.EXAMPLE | |
$watch = Start-WatchFile .\src\test\hras\hra-heart-health.json -ChangeScript {param($ChangeType,$FullPath) Write-Host "$FullPath Changed" } | |
# do something else | |
Stop-WatchFile $watch | |
Start watching a file in the background (no -Wait), then later stop watching | |
.EXAMPLE | |
Start-WatchFile .\src\test\hras\hra-heart-health.json -ChangeScript {param($ChangeType,$FullPath) Write-Host "$FullPath Changed" } -Wait | |
Watch a file until Ctrl+C pressed | |
.NOTES | |
Based on https://powershell.one/tricks/filesystem/filesystemwatcher | |
#> | |
function Start-WatchFile { | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory)] | |
[ValidateScript({ Test-Path $_ -PathType Leaf })] | |
[string] $File, | |
[Parameter(Mandatory)] | |
[scriptblock] $ChangeScript, | |
[switch] $IncludeSubfolders, | |
[switch] $Wait | |
) | |
Set-StrictMode -Version Latest | |
$ErrorActionPreference = 'Stop' | |
$File = Convert-Path $File | |
$handlers = $null | |
# specify the path to the folder you want to monitor: | |
$Path = (Split-Path $File -parent) | |
# specify which files you want to monitor | |
$FileFilter = (Split-Path $File -leaf) | |
# specify the file or folder properties you want to monitor: | |
$AttributeFilter = [IO.NotifyFilters]::LastWrite | |
try { | |
$watcher = New-Object -TypeName System.IO.FileSystemWatcher -Property @{ | |
Path = $Path | |
Filter = $FileFilter | |
IncludeSubdirectories = [bool]$IncludeSubfolders | |
NotifyFilter = $AttributeFilter | |
} | |
$VarName = [guid]::NewGuid().ToString() | |
Set-Variable -Name $VarName -Value $null -Scope Global | |
# define the code that should execute when a change occurs: | |
$action = { | |
# the code is receiving this to work with: | |
try { | |
# change type information: | |
$details = $event.SourceEventArgs | |
$Name = $details.Name | |
$FullPath = $details.FullPath | |
$OldFullPath = $details.OldFullPath | |
$OldName = $details.OldName | |
$MessageData = $event.MessageData | |
# type of change: | |
$ChangeType = $details.ChangeType | |
# when the change occurred: | |
$Timestamp = $event.TimeGenerated | |
# handle double calls | |
$prev = Get-Variable -Name $MessageData.VarName -Scope Global | |
if ($prev -and ($prev.FullPath -eq $details.FullPath) -and (($Timestamp - $prev.TimeStamp).TotalSeconds -lt .2 )) { | |
return | |
} | |
Set-Variable -Name $MessageData.VarName -Value @{FullPath = $FullPath; Timestamp = $Timestamp } -Scope Global | |
if ($global:fw_test) { | |
$global:fw_lastEvent = $event | |
} | |
Invoke-Command -ScriptBlock $MessageData.ChangeScript -ArgumentList $ChangeType, $FullPath, $OldFullPath | |
} catch { | |
Write-Warning "Error in watcher action: $_`n$($_.ScriptStackTrace)" | |
} | |
} | |
# subscribe your event handler to all event types that are | |
# important to you. Do this as a scriptblock so all returned | |
# event handlers can be easily stored in $handlers: | |
$messageData = @{ | |
ChangeScript = $ChangeScript | |
VarName = $VarName | |
} | |
$handlers = . { | |
Register-ObjectEvent -InputObject $watcher -EventName Changed -Action $action -MessageData $messageData | |
Register-ObjectEvent -InputObject $watcher -EventName Created -Action $action -MessageData $messageData | |
Register-ObjectEvent -InputObject $watcher -EventName Deleted -Action $action -MessageData $messageData | |
Register-ObjectEvent -InputObject $watcher -EventName Renamed -Action $action -MessageData $messageData | |
} | |
# monitoring starts now: | |
$watcher.EnableRaisingEvents = $true | |
Write-Verbose "Watching for changes to $File" | |
if ($Wait) { | |
# since the FileSystemWatcher is no longer blocking PowerShell | |
# we need a way to pause PowerShell while being responsive to | |
# incoming events. Use an endless loop to keep PowerShell busy: | |
do { | |
# Wait-Event waits for a second and stays responsive to events | |
# Start-Sleep in contrast would NOT work and ignore incoming events | |
Wait-Event -Timeout 1 | |
# write a dot to indicate we are still monitoring: | |
Write-Host "." -NoNewline | |
} while ($true) | |
} else { | |
Write-Information "Call Stop-Watcher with the return value" -InformationAction Continue | |
$global:fw_watchStatus = @{ | |
Watcher = $watcher | |
Handlers = $handlers | |
} | |
return $global:fw_watchStatus | |
} | |
} catch { | |
Write-Error $_ | |
} finally { | |
if ($Wait) { | |
# this gets executed when user presses CTRL+C: | |
Stop-WatchFile $global:fw_watchStatus | |
} | |
} | |
} | |
<# | |
.SYNOPSIS | |
Stop watching a file started with Start-WatchFile | |
.PARAMETER WatchInfo | |
Value returned from Start-WatchFile without -Wait | |
.EXAMPLE | |
$watch = Start-WatchFile .\src\test\hras\hra-heart-health.json -ChangeScript {param($ChangeType,$FullPath) Write-Host "$FullPath Changed" } | |
# do something else | |
Stop-WatchFile $watch | |
Start watching a file in the background (no -Wait), then later stop watching | |
#> | |
function Stop-WatchFile { | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory)] | |
[HashTable] $WatchInfo | |
) | |
Set-StrictMode -Version Latest | |
$ErrorActionPreference = 'Stop' | |
# stop monitoring | |
$WatchInfo.watcher.EnableRaisingEvents = $false | |
if ($WatchInfo.handlers) { | |
# remove the event handlers | |
$WatchInfo.handlers | ForEach-Object { | |
Unregister-Event -SourceIdentifier $_.Name | |
} | |
# event handlers are technically implemented as a special kind | |
# of background job, so remove the jobs now: | |
$WatchInfo.handlers | Remove-Job | |
} | |
# properly dispose the FileSystemWatcher: | |
$WatchInfo.watcher.Dispose() | |
Write-Verbose "`nMonitoring ended." | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment