Powershell FileSystemWatcher script firing twice on some new files
I'm using FileSystemWatcher to monitor a folder where documents are scanned to. When a new file is detected, it will send an email to notify someone. It's working as is, but sometimes (not every file) it will trigger 2 or 3 times on a new file and send the email 2-3 times for the same file. I'm guessing it has to do with the way the file is created by the scanner or something like that.
I'm trying to figure out a way to protect against this happening, to ensure it only sends one email per file. Any suggestions would be greatly appreciated.
$PathToMonitor = "\\path\to\folder"
$FileSystemWatcher = New-Object System.IO.FileSystemWatcher
$FileSystemWatcher.Path = $PathToMonitor
$FileSystemWatcher.Filter = "*.*"
$FileSystemWatcher.IncludeSubdirectories = $false
$FileSystemWatcher.EnableRaisingEvents = $true
$Action = {
if ($EventArgs.Name -notlike "*.pdf" -and $EventArgs.Name -notlike "*.tif") {
return
}
$details = $event.SourceEventArgs
$Name = $details.Name
$Timestamp = $event.TimeGenerated
$text = "{0} was submitted on {1}." -f $Name, $Timestamp
$FromAddress = "Email1 <[email protected]>"
$ToAddress = "Email2 <[email protected]>"
$Subject = "New File"
$SMTPserver = "123.4.5.678"
Send-MailMessage -From $FromAddress -To $ToAddress -Subject $Subject -Body $text -SmtpServer $SMTPserver
}
$handlers = . {
Register-ObjectEvent -InputObject $FileSystemWatcher -EventName Created -Action $Action -SourceIdentifier FSCreateConsumer
}
try {
do {
Wait-Event -Timeout 5
} while ($true)
}
finally {
Unregister-Event -SourceIdentifier FSCreateConsumer
$handlers | Remove-Job
$FileSystemWatcher.EnableRaisingEvents = $false
$FileSystemWatcher.Dispose()
}
This may be because you listen too many notifications. The default is LastWrite
, FileName
, and DirectoryName
FileName
is sufficient for your need and may prevent your issue.
$FileSystemWatcher.NotifyFilter = [System.IO.NotifyFilters]::FileName
As a remark, I don't know why you use Wait-Event -Timeout 5
. Script is working fine without the try{}
block.
EDIT: Add a ConcurrentDictionary to avoid duplicate events
Try this sample code. I've included only the beginning part of your script. End is untouched.
$PathToMonitor = "\\path\to\folder"
$KeepFiles = 5 #minutes
$MonitoredFiles = New-Object -TypeName 'System.Collections.Concurrent.ConcurrentDictionary[[System.String],[System.DateTime]]'
$FileSystemWatcher = New-Object System.IO.FileSystemWatcher
$FileSystemWatcher.Path = $PathToMonitor
$FileSystemWatcher.Filter = "*.*"
$FileSystemWatcher.IncludeSubdirectories = $false
$FileSystemWatcher.NotifyFilter = [System.IO.NotifyFilters]::FileName
$FileSystemWatcher.EnableRaisingEvents = $true
$Action = {
if ($EventArgs.Name -notlike "*.pdf" -and $EventArgs.Name -notlike "*.tif") {
return
}
#Cleaning events -gt 5mn
$Now = [System.DateTime]::Now
$OriginEventDate = [System.DateTime]::MinValue
foreach($MonitoredFile in [System.Linq.Enumerable]::ToList(($MonitoredFiles.Keys))) {
if ($MonitoredFiles.TryGetValue($MonitoredFile, [ref]$OriginEventDate)) {
if ($OriginEventDate.AddMinutes($KeepFiles) -gt $Now) {
try {
[void]$MonitoredFiles.Remove($MonitoredFile)
}
catch {}
}
}
}
$ProcessEvent = $false
# any same file creation event within 5mn are discarded
$OriginEventDate = [System.DateTime]::MinValue
if ($MonitoredFiles.TryGetValue($event.SourceEventArgs.Name, [ref]$OriginEventDate)) {
if ($OriginEventDate -ne [System.DateTime]::MinValue -and $OriginEventDate.AddMinutes($KeepFiles) -le $Now) {
return
}
else {
$ProcessEvent = $true
}
}
else {
#not successful means a concurrent event was successful, so discard this one.
if ($MonitoredFiles.TryAdd($event.SourceEventArgs.Name, $event.SourceEventArgs.TimeGenerated)) {
$ProcessEvent = $true
}
else {
return
}
}
if ($ProcessEvent) {
$details = $event.SourceEventArgs
$Name = $details.Name
$Timestamp = $event.TimeGenerated
$text = "{0} was submitted on {1}." -f $Name, $Timestamp
$FromAddress = "Email1 <[email protected]>"
$ToAddress = "Email2 <[email protected]>"
$Subject = "New File"
$SMTPserver = "123.4.5.678"
Send-MailMessage -From $FromAddress -To $ToAddress -Subject $Subject -Body $text -SmtpServer $SMTPserver
}
}