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

3
我正在使用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()
}
3个回答

2
这可能是因为您收听了太多的通知。默认设置为LastWriteFileNameDirectoryNameFileName对您的需求已经足够,并且可能可以解决您的问题。
$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
    }
}

谢谢您的建议。我已经添加了notifyfilter,并将进行一段时间的监控,看看是否有所帮助。 - xeric080
1
作为另一种解决方案,您可以使用字典/哈希表来存储文件名和时间戳,以便在任何事件中检查文件名是否在字典中,并且如果时间戳最后发生在不到5分钟之前,则丢弃该事件,否则像往常一样发送邮件。 - Hazrelle
我在考虑尝试这样的方法,但我对powershell还很陌生,所以需要仔细研究。我是否应该担心在某个时候清除hashtable中的记录,以防止无限累积? - xeric080
好主意,我认为这会起作用。谢谢!标记为答案。 - xeric080
你可以在测试文件夹上以调试模式运行它,在Action脚本块内插入断点,并将文件放入文件夹中。如果我有时间,我会在我的一侧尝试。 - Hazrelle
显示剩余6条评论

1

我必须调整这两个东西才能使其正常工作:

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

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

1
我的情况比较简单,只是想避免处理具有相同时间戳的事件。 我使用了一个全局变量,像这样
$global:lastTimestamp = "empty"

...

$currTimestamp = $Event.TimeGenerated.toString()

if ($lastTimestamp -ne $currTimestamp) {
    ... do actions
}

$lastTimestamp = $currTimestamp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接