Powershell FileSystemWatcher 脚本在一些新文件上触发两次

Posted

技术标签:

【中文标题】Powershell FileSystemWatcher 脚本在一些新文件上触发两次【英文标题】:Powershell FileSystemWatcher script firing twice on some new files 【发布时间】:2022-01-06 14:03:27 【问题描述】:

我正在使用 FileSystemWatcher 监控文档扫描到的文件夹。当检测到新文件时,它将发送电子邮件通知某人。它按原样工作,但有时(不是每个文件)它会在新文件上触发 2 或 3 次,并为同一文件发送 2-3 次电子邮件。我猜这与扫描仪创建文件的方式或类似的方式有关。

我正在尝试找出一种方法来防止这种情况发生,以确保每个文件只发送一封电子邮件。任何建议将不胜感激。

$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 <email1@email.com>"
        $ToAddress = "Email2 <Email2@email.com>"
        $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()

【问题讨论】:

【参考方案1】:

这可能是因为你听了太多notifications。默认为LastWriteFileNameDirectoryName FileName 足以满足您的需要,并且可能会阻止您的问题。

$FileSystemWatcher.NotifyFilter = [System.IO.NotifyFilters]::FileName

顺便说一句,我不知道你为什么使用Wait-Event -Timeout 5。没有try 块,脚本可以正常工作。

编辑:添加 ConcurrentDictionary 以避免重复事件

试试这个示例代码。我只包含了您脚本的开头部分。结局未受影响。

$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 <email1@email.com>"
        $ToAddress = "Email2 <Email2@email.com>"
        $Subject = "New File"
        $SMTPserver = "123.4.5.678"
    
        Send-MailMessage -From $FromAddress -To $ToAddress -Subject $Subject -Body $text -SmtpServer $SMTPserver
    

【讨论】:

我的理解是带有等待事件的 try 块是使其异步,因此如果有多个事件进入,它将排队并处理每个事件。否则,您可能会错过活动。如此处所述:powershell.one/tricks/filesystem/filesystemwatcher 感谢链接,很有趣。 感谢您的建议。我已经添加了 notifyfilter 并将监视一下,看看是否有帮助。 不幸的是,这似乎没有解决它。 :( 作为替代解决方案,您可以使用 Dictionary / HashTable 来存储文件名和时间戳,这样您就可以在任何事件中检查文件名是否在字典中以及时间戳是否最后一次发生在最后一次5 分钟后您放弃该事件,否则您照常发送邮件。【参考方案2】:

我必须调整这两个东西才能让它工作:

if ($MonitoredFiles.TryAdd($Event.SourceEventArgs.Name, $Event.TimeGenerated))

if ($OriginEventDate -ne [System.DateTime]::MinValue -and $OriginEventDate.AddMinutes($KeepFiles) -ge $Now)

【讨论】:

以上是关于Powershell FileSystemWatcher 脚本在一些新文件上触发两次的主要内容,如果未能解决你的问题,请参考以下文章

powershell PowerShell:启动PowerShell作为其他用户提升

powershell [提示输入powershell] #powershell #input #prompt

powershell 测量PowerShell命令或PowerShell脚本的速度

powershell远程下载exe并执行

powershell 使用啥端口

powershell PowerShell使用PowerShell创建本地管理员帐户