Powershell由于文件系统监视器而挂起

3
我们有一个复杂的解决方案来解决一些由Citrix和远程服务器引起的打印问题。基本上,从主服务器,我们强制推送一个PDF文件到远程电脑,然后在远程电脑上不断运行一个PowerShell脚本来“捕捉”该文件并将其推送到本地打印机。
这个方法 “基本上可以正常工作”。
但是我们遇到了随机掉线的问题。PowerShell脚本似乎没有崩溃,因为它仍在Windows上运行,但实际操作似乎无法处理新文件。
今天我做了很多阅读,有提到在完成后必须命名和注销事件,否则可能会导致缓冲区溢出问题并使PowerShell停止处理操作。但我不确定在代码中应该放在哪里。想法是这个脚本将永久运行,所以我们是在操作本身内部还是其他地方注销或删除事件呢?
之前我进行了大量虚假日志记录来尝试找到它失败的位置,但它似乎在不同的点处停止,没有任何合理的原因(即,它会在查找文件的命令处失败,其他时候在移动等命令处失败)。
### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "l:\files\cut"
$watcher.Filter = "*.pdf"
$watcher.IncludeSubdirectories = $false
$watcher.EnableRaisingEvents = $true  

### DEFINE ACTIONS AFTER AN EVENT IS DETECTED
$action = { $path = $Event.SourceEventArgs.FullPath
            $changeType = $Event.SourceEventArgs.ChangeType
            $scandir="l:\files\cut" 
            $scanbackdir="l:\files\cut\back"   
            $scanlogdir="l:\files\cut\log" 
            $sumatra="l:\SumatraPDF.exe"      
            $pdftoprint=""
            $printername= "MainLBL"

### Get the List of files in the Directory, print file, wait and then move file
Get-ChildItem -Path $scandir -filter "*.pdf" -Name | % { 
$pdftoprint=$_ 
& $sumatra -silent $scandir\$pdftoprint -print-to $printername

sleep 3 

Move-Item -force $scandir\$pdftoprint $scanbackdir
 }
 } 

### Define what happens when script fails
$erroraction = {echo $(get-date) the process crashed | Out-File -Append l:\files\cut\log\errorlog.txt}    

### DECIDE WHICH EVENTS SHOULD BE WATCHED 
Register-ObjectEvent $watcher "Error" -Action $erroraction
Register-ObjectEvent $watcher "Created" -Action $action
while ($true) {sleep 5}

如果您需要更可靠的东西,您可以尝试使用 [system.io.file]::Open 添加文件锁,或者使用 [system.io.file]::Copy($fileName, $newFilename); [system.io.file]::Delete($fileName) ; ; ,因为文件移动有时会在遇到 ACL 权限问题时删除文件而不是移动它。 - lloyd
我不相信文件移动是问题的原因。我们看到的行为是,文件系统监视器会随机停止监视。文件本身仍然存在于监视文件夹中,复制新文件不会重新触发操作。 - Todd Querruel
可能是“短暂文件系统监视器错误”? - lloyd
我相信你是对的。通过映射一个额外的驱动器,触发操作,然后在事件队列正在处理时取消映射驱动器,我能够重新创建这种行为。我已经请求将PDF文件本地移动到运行打印脚本的虚拟机上,因此将看看是否可以解决挂起问题。 - Todd Querruel
2个回答

3
如果您想让脚本在后台运行,那么请查看PowerShell后台作业。
如果您想让脚本永久运行,则需要将其制作为服务...
请参考以下内容: 如何创建用户定义的服务 如何将PowerShell脚本作为Windows服务运行 ...或将其附加到计划任务中,在重新启动时重新启动它。
实现FileSystemWatcher有两种方法。
同步
异步
同步FileSystemWatcher在检测到更改时,控件会返回到您的脚本,以便处理更改。如果在您的脚本不再等待事件时发生另一个文件更改,则会丢失它。因此导致意外结果。
使用FileSystemWatcher异步方式,它将继续记录新的文件系统更改,并在PowerShell处理先前更改后处理它们。
* 异步FileSystemWatcher示例-示例 *
### New-FileSystemWatcherAsynchronous

# Set the folder target
$PathToMonitor = Read-Host -Prompt 'Enter a folder path'

$FileSystemWatcher = New-Object System.IO.FileSystemWatcher
$FileSystemWatcher.Path  = $PathToMonitor
$FileSystemWatcher.IncludeSubdirectories = $true

# Set emits events
$FileSystemWatcher.EnableRaisingEvents = $true

# Define change actions
$Action = {
    $details = $event.SourceEventArgs
    $Name = $details.Name
    $FullPath = $details.FullPath
    $OldFullPath = $details.OldFullPath
    $OldName = $details.OldName
    $ChangeType = $details.ChangeType
    $Timestamp = $event.TimeGenerated
    $text = "{0} was {1} at {2}" -f $FullPath, $ChangeType, $Timestamp

    Write-Host $text -ForegroundColor Green

    # Define change types
    switch ($ChangeType)
    {
        'Changed' { "CHANGE" }
        'Created' { "CREATED"}
        'Deleted' { "DELETED"
                    # Set time intensive handler
                    Write-Host "Deletion Started" -ForegroundColor Gray
                    Start-Sleep -Seconds 3    
                    Write-Warning -Message 'Deletion complete'
                  }
        'Renamed' { 
                    $text = "File {0} was renamed to {1}" -f $OldName, $Name
                    Write-Host $text -ForegroundColor Yellow
                  }
        default { Write-Host $_ -ForegroundColor Red -BackgroundColor White }
    }
}

# Set event handlers
$handlers = . {
    Register-ObjectEvent -InputObject $FileSystemWatcher -EventName Changed -Action $Action -SourceIdentifier FSChange
    Register-ObjectEvent -InputObject $FileSystemWatcher -EventName Created -Action $Action -SourceIdentifier FSCreate
    Register-ObjectEvent -InputObject $FileSystemWatcher -EventName Deleted -Action $Action -SourceIdentifier FSDelete
    Register-ObjectEvent -InputObject $FileSystemWatcher -EventName Renamed -Action $Action -SourceIdentifier FSRename
}

Write-Host "Watching for changes to $PathToMonitor" -ForegroundColor Cyan

try
{
    do
    {
        Wait-Event -Timeout 1
        Write-Host '.' -NoNewline

    } while ($true)
}
finally
{
    # End script actions + CTRL+C executes the remove event handlers
    Unregister-Event -SourceIdentifier FSChange
    Unregister-Event -SourceIdentifier FSCreate
    Unregister-Event -SourceIdentifier FSDelete
    Unregister-Event -SourceIdentifier FSRename

    # Remaining cleanup
    $handlers | 
    Remove-Job

    $FileSystemWatcher.EnableRaisingEvents = $false
    $FileSystemWatcher.Dispose()

    Write-Warning -Message 'Event Handler completed and disabled.'
}

2
我没有遇到可以在Windows上永久运行的脚本。因此,我们认为你无法控制会发生一些问题,例如网络、电力或系统关闭。考虑到这一点,我们针对此脚本有一个生命周期,并且在结束时应该清理所有内容。在这种情况下,我们有一个while循环,理论上永远不会结束,但是如果抛出异常,它将会结束。在while循环中,如果任何事件已被注销,我们可以重新注册它们。如果监视器已被处理,我们可以重新创建它和事件。如果这确实是关键任务代码,则可以考虑使用像hangfirenlog作为Windows服务的.net替代方案。
### WRAP Everything in a try finally so we dispose of events 
try {
    ### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
    $watcherArgs = @{
        Path = "l:\files\cut"
        Filter = "*.pdf"
        IncludeSubdirectories = $false
        EnableRaisingEvents = $true  
    }
    $watcher = New-Object System.IO.FileSystemWatcher
    $watcher.Path = $watcherArgs.Path
    $watcher.Filter = $watcherArgs.Filter
    $watcher.IncludeSubdirectories = $watcherArgs.IncludeSubdirectories
    $watcher.EnableRaisingEvents = $watcherArgs.EnableRaisingEvents

    ### DEFINE ACTIONS AFTER AN EVENT IS DETECTED
    $action = { $path = $Event.SourceEventArgs.FullPath
                $changeType = $Event.SourceEventArgs.ChangeType
                $scandir="l:\files\cut" 
                $scanbackdir="l:\files\cut\back"   
                $scanlogdir="l:\files\cut\log" 
                $sumatra="l:\SumatraPDF.exe"      
                $pdftoprint=""
                $printername= "MainLBL"

    ### Get the List of files in the Directory, print file, wait and then move file
    Get-ChildItem -Path $scandir -filter "*.pdf" -Name | % { 
            $pdftoprint=$_ 
            if($LASTEXITCODE -ne 0) { 
                # Do something
                # Reset so we know when sumatra fails
                $LASTEXITCODE = 0
            }
            & $sumatra -silent $scandir\$pdftoprint -print-to $printername
            if($LASTEXITCODE -ne 0) { 
                # Do something to handle sumatra
            }
            sleep 3 

            # Split up copy and delete so we never loose files
            [system.io.file]::Copy("$scandir\$pdftoprint", "$scanbackdir", $true)
            [system.io.file]::Delete("$scandir\$pdftoprint")
        }
    } 

    ### Define what happens when script fails
    $erroraction = { 
        echo "$(get-date) the process crashed" | Out-File -Append "l:\files\cut\log\errorlog.txt"
    }    

    ### DECIDE WHICH EVENTS SHOULD BE WATCHED 
    $ErrorEvent  = Register-ObjectEvent $watcher "Error" -Action $erroraction
    $CreatedEvent = Register-ObjectEvent $watcher "Created" -Action $action
    $ListOfEvents = @(
        $ErrorEvent  
        $CreatedEvent
    )

    while ($true) {

        $eventMissing = $false
        $ListOfEvents | % { 
            $e = $_

            if (!(Get-Event -SourceIdentifier $e.Name -ErrorAction SilentlyContinue)) {
                # Event does not exist 
                $eventMissing = $true
            }
        }

        if(!$watcher || $eventMissing -eq $true) {
            # deregister events 
            $ListOfEvents | % { 
                $e = $_
                try {
                    Unregister-Event -SourceIdentifier $e.Name
                } catch { 
                    # Do Nothing
                }
            }
            if($watcher) {
                $watcher.Dispose()
                $watcher = $null   
            } else {
                # Create watcher 
                $watcher = New-Object System.IO.FileSystemWatcher
                $watcher.Path = $watcherArgs.Path
                $watcher.Filter = $watcherArgs.Filter
                $watcher.IncludeSubdirectories = $watcherArgs.IncludeSubdirectories
                $watcher.EnableRaisingEvents = $watcherArgs.EnableRaisingEvents
                $ErrorEvent  = Register-ObjectEvent $watcher "Error" -Action $erroraction
                $CreatedEvent = Register-ObjectEvent $watcher "Created" -Action $action
                $ListOfEvents = @(
                    $ErrorEvent  
                    $CreatedEvent
                )
            }

        }

        if ($watcher.EnableRaisingEvents -eq $false) {
            $watcher.EnableRaisingEvents = $watcherArgs.EnableRaisingEvents
        }

        sleep 5
    }

} finally {
    $ListOfEvents | % { 
        $e = $_
        try {
            Unregister-Event -SourceIdentifier $e.Name
        } catch { 
            # Do Nothing
        }
    }
    if($watcher) {
        $watcher.Dispose();
    }
}

我相信我已经找到了问题的根本原因(文件系统监视器轮询的映射驱动器间歇性掉线),我们正在考虑通过将 PDF 推送到本地驱动器来“修复”它但我也会尝试你的代码更改,以确保我们有一个明确的解决方案感谢你的建议 :) - Todd Querruel

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