Search code examples
powershellfilesystemwatcher

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()
}

Solution

  • 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
        }
    }