Try this. (had a similar concern previously).
The event trigger is collecting all affected items in a synchronized hashtable and a additional scriptblock processes all items, but files only when they are ready to read and not locked.
If a file is locked, you'll see it in the console. Just try to copy a file > 1 GB and watch the output.
The script block $fSItemEventProcessingJob which processes the items (like copying to backup files) was initially created to use it for "Start-Job".
But you are not able to access and modify the hashtable from the session in that background job. Therefore it's a simple scriptblock execution.
To stop everything, just press CTRL + C.
This will still execute the "finally" block and unregisters and disposes everything.
PS:
The script was just a test and was only tested locally on my PC.
Clear-Host
$ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
$fileSystemWatcherDirPath = 'C:\temp'
$fileSystemWatcherFilter = '*.*'
$fileSystemWatcher = [System.IO.FileSystemWatcher]::new($fileSystemWatcherDirPath , $fileSystemWatcherFilter)
$fileSystemWatcher.IncludeSubdirectories = $true
$fileSystemWatcher.EnableRaisingEvents = $true
$fileSystemWatcher.NotifyFilter = [System.IO.NotifyFilters]::FileName -bor [System.IO.NotifyFilters]::DirectoryName -bor [System.IO.NotifyFilters]::LastWrite # [System.Linq.Enumerable]::Sum([System.IO.NotifyFilters].GetEnumValues())
# Create syncronized hashtable
$syncdFsItemEventHashT = [hashtable]::Synchronized([hashtable]::new())
$fileSystemWatcherAction = {
try {
$fsItemEvent = [pscustomobject]@{
EventIdentifier = $Event.EventIdentifier
SourceIdentifier = $Event.SourceIdentifier
TimeStamp = $Event.TimeGenerated
FullPath = $Event.SourceEventArgs.FullPath
ChangeType = $Event.SourceEventArgs.ChangeType
}
# Collecting event in synchronized hashtable (overrides existing keys so that only the latest event details are available)
$syncdFsItemEventHashT[$fsItemEvent.FullPath] = $fsItemEvent
} catch {
Write-Host ($_ | Format-List * | Out-String ) -ForegroundColor red
}
}
# Script block which processes collected events and do further actions like copying for backup, etc...
# That scriptblock was initially used to test "Start-Job". Unfortunately it's not possible to access and modify the synchronized hashtable created within this scope.
$fSItemEventProcessingJob = {
$keys = [string[]]$syncdFsItemEventHashT.psbase.Keys
foreach ($key in $keys) {
$fsEvent = $syncdFsItemEventHashT[$key]
try {
# in case changetype eq DELETED or the item can't be found on the filesystem by the script -> remove the item from hashtable without any further actions.
# This affects temporary files from applications. BUT: Could also affect files with file permission issues.
if (($fsEvent.ChangeType -eq [System.IO.WatcherChangeTypes]::Deleted) -or (! (Test-Path -LiteralPath $fsEvent.FullPath)) ) {
$syncdFsItemEventHashT.Remove($key )
Write-Host ("==> Item '$key' with changetype '$($fsEvent.ChangeType)' removed from hashtable without any further actions!") -ForegroundColor Blue
continue
}
# get filesystem object
$fsItem = Get-Item -LiteralPath $fsEvent.FullPath -Force
if ($fsItem -is [System.IO.FileInfo]) {
# file processing
try {
# Check whether the file is still locked / in use by another process
[System.IO.FileStream]$fileStream = [System.IO.File]::Open( $fsEvent.FullPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read)
$fileStream.Close()
} catch [System.IO.IOException] {
Write-Host ("==> Item '$key' with changetype '$($fsEvent.ChangeType)' is still in use and can't be read!") -ForegroundColor Yellow
continue
}
} elseIf ($fsItem -is [System.IO.DirectoryInfo]) {
# directory processing
}
$syncdFsItemEventHashT.Remove($key )
Write-Host ("==> Item '$key' with changetype '$($fsEvent.ChangeType)' has been processed and removed from hashtable.") -ForegroundColor Blue
} catch {
Write-Host ($_ | Format-List * | Out-String ) -ForegroundColor red
}
}
}
[void] (Register-ObjectEvent -InputObject $fileSystemWatcher -EventName 'Created' -SourceIdentifier 'FSCreated' -Action $fileSystemWatcherAction)
[void] (Register-ObjectEvent -InputObject $fileSystemWatcher -EventName 'Changed' -SourceIdentifier 'FSChanged' -Action $fileSystemWatcherAction)
[void] (Register-ObjectEvent -InputObject $fileSystemWatcher -EventName 'Renamed' -SourceIdentifier 'FSRenamed' -Action $fileSystemWatcherAction)
[void] (Register-ObjectEvent -InputObject $fileSystemWatcher -EventName 'Deleted' -SourceIdentifier 'FSDeleted' -Action $fileSystemWatcherAction)
Write-Host "Watching for changes in '$fileSystemWatcherDirPath'.`r`nPress CTRL+C to exit!"
try {
do {
Wait-Event -Timeout 1
if ($syncdFsItemEventHashT.Count -gt 0) {
Write-Host "`r`n"
Write-Host ('-' * 50) -ForegroundColor Green
Write-Host "Collected events in hashtable queue:" -ForegroundColor Green
$syncdFsItemEventHashT.Values | Format-Table | Out-String
}
# Process hashtable items and do something with them (like copying, ..)
.$fSItemEventProcessingJob
# Garbage collector
[GC]::Collect()
} while ($true)
} finally {
# unregister
Unregister-Event -SourceIdentifier 'FSChanged'
Unregister-Event -SourceIdentifier 'FSCreated'
Unregister-Event -SourceIdentifier 'FSDeleted'
Unregister-Event -SourceIdentifier 'FSRenamed'
# dispose
$FileSystemWatcher.Dispose()
Write-Host "`r`nEvent Handler removed."
}