In the previous tip we introduced the FileSystemWatcher and illustrated how it can miss filesystem changes when your handler code takes too long.
To use the FileSystemWatcher correctly, you should use it asynchronously and make sure it uses a queue. So even if your script is busy processing a filesystem change, it should continue to log new filesystem changes and process them once PowerShell is done processing previous changes.
# make sure you adjust this to point to the folder you want to monitor $PathToMonitor = "c:\test" explorer $PathToMonitor $FileSystemWatcher = New-Object System.IO.FileSystemWatcher $FileSystemWatcher.Path = $PathToMonitor $FileSystemWatcher.IncludeSubdirectories = $true # make sure the watcher emits events $FileSystemWatcher.EnableRaisingEvents = $true # define the code that should execute when a file change is detected $Action = { $details = $event.SourceEventArgs $Name = $details.Name $FullPath = $details.FullPath $OldFullPath = $details.OldFullPath $OldName = $details.OldName $ChangeType = $details.ChangeType $Timestamp = $event.TimeGenerated $text = "{0} was {1} at {2}" -f $FullPath, $ChangeType, $Timestamp Write-Host "" Write-Host $text -ForegroundColor Green # you can also execute code based on change type here switch ($ChangeType) { 'Changed' { "CHANGE" } 'Created' { "CREATED"} 'Deleted' { "DELETED" # uncomment the below to mimick a time intensive handler <# Write-Host "Deletion Handler Start" -ForegroundColor Gray Start-Sleep -Seconds 4 Write-Host "Deletion Handler End" -ForegroundColor Gray #> } 'Renamed' { # this executes only when a file was renamed $text = "File {0} was renamed to {1}" -f $OldName, $Name Write-Host $text -ForegroundColor Yellow } default { Write-Host $_ -ForegroundColor Red -BackgroundColor White } } } # add event handlers $handlers = . { Register-ObjectEvent -InputObject $FileSystemWatcher -EventName Changed -Action $Action -SourceIdentifier FSChange Register-ObjectEvent -InputObject $FileSystemWatcher -EventName Created -Action $Action -SourceIdentifier FSCreate Register-ObjectEvent -InputObject $FileSystemWatcher -EventName Deleted -Action $Action -SourceIdentifier FSDelete Register-ObjectEvent -InputObject $FileSystemWatcher -EventName Renamed -Action $Action -SourceIdentifier FSRename } Write-Host "Watching for changes to $PathToMonitor" try { do { Wait-Event -Timeout 1 Write-Host "." -NoNewline } while ($true) } finally { # this gets executed when user presses CTRL+C # remove the event handlers Unregister-Event -SourceIdentifier FSChange Unregister-Event -SourceIdentifier FSCreate Unregister-Event -SourceIdentifier FSDelete Unregister-Event -SourceIdentifier FSRename # remove background jobs $handlers | Remove-Job # remove filesystemwatcher $FileSystemWatcher.EnableRaisingEvents = $false $FileSystemWatcher.Dispose() "Event Handler disabled." }
When you run this, the folder specified in $PathToMonitor will be monitored for changes, and when a change occurs, a message is emitted. When you press CTRL+C, the script ends, and all event handlers are automatically cleaned up in the finally block.
What’s more important: this code uses a queue internally, so when there are many changes in a short period of time, they are all processed once PowerShell is no longer busy. You can test this by uncommenting the marked part in the code for deletion events. Now, whenever a file is deleted, the handler takes 4 extra seconds.
Even if you delete a great number of files, they will all be listed eventually. The approach presented here is much more dependable than synchronous handlers based on WaitForChanged() as illustrated in the previous tip.