PowerShell File Watcher Register-ObjectEvent - 等待文件完成复制
PowerShell File Watcher Register-ObjectEvent - Wait For File To Complete Copying
下面的代码正在检查指定目录中的新文件 $folderCompleted
。
目前,当一个小文件被放入该目录 (~1MB) 时,Move-Item 命令和其他文件读取检查成功完成。
但是,当将大文件移入此目录时,会在文件完全移入(复制)到该目录之前调用 Object 事件。这导致文件检查和 Move-Item 命令失败,因为该文件仍在使用中。
# File Watcher
$filter = '*.*'
$fsw = New-Object IO.FileSystemWatcher $folderCompleted, $filter -Property @{
IncludeSubdirectories = $true
NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'
}
$onCreated = Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action {
$path = $Event.SourceEventArgs.FullPath
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
Write-Host "The file '$name' was $changeType at $timeStamp"
# File Checks
# Move File
Move-Item $path -Destination $destinationPath -Verbose
}
如何检查文件是否仍在复制?
您需要构建一个测试来查看文件是否被锁定(仍在复制)。
为此,您可以使用此功能:
function Test-LockedFile {
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[Alias('FullName', 'FilePath')]
[ValidateScript({Test-Path $_ -PathType Leaf})]
[string]$Path
)
$file = [System.IO.FileInfo]::new($Path)
# old PowerShell versions use:
# $file = New-Object System.IO.FileInfo $Path
try {
$stream = $file.Open([System.IO.FileMode]::Open,
[System.IO.FileAccess]::ReadWrite,
[System.IO.FileShare]::None)
if ($stream) { $stream.Close() }
return $false
}
catch {
return $true
}
}
有了它,在当前代码之上的某处,您可以:
# File Checks
while (Test-LockedFile $path) {
Start-Sleep -Seconds 1
}
# Move File
Move-Item $path -Destination $destinationPath -Verbose
试试这个。 (之前也有类似的担忧)。
事件触发器正在将所有受影响的项目收集到一个同步的哈希表中,并且一个额外的脚本块处理所有项目,但仅当文件准备好读取且未锁定时。
如果文件被锁定,您将在控制台中看到它。只需尝试复制大于 1 GB 的文件并观察输出。
处理项目(如复制到备份文件)的脚本块 $fSItemEventProcessingJob 最初是为 "Start-Job" 创建的。
但是您无法从该后台作业的会话中访问和修改哈希表。因此这是一个简单的脚本块执行。
要停止一切,只需按 CTRL + C。
这仍然会执行 "finally" 块并取消注册并处理所有内容。
PS:
该脚本只是一个测试,仅在我的 PC 上进行了本地测试。
Clear-Host
$ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
$fileSystemWatcherDirPath = 'C:\temp'
$fileSystemWatcherFilter = '*.*'
$fileSystemWatcher = [System.IO.FileSystemWatcher]::new($fileSystemWatcherDirPath , $fileSystemWatcherFilter)
$fileSystemWatcher.IncludeSubdirectories = $true
$fileSystemWatcher.EnableRaisingEvents = $true
$fileSystemWatcher.NotifyFilter = [System.IO.NotifyFilters]::FileName -bor [System.IO.NotifyFilters]::DirectoryName -bor [System.IO.NotifyFilters]::LastWrite # [System.Linq.Enumerable]::Sum([System.IO.NotifyFilters].GetEnumValues())
# Create syncronized hashtable
$syncdFsItemEventHashT = [hashtable]::Synchronized([hashtable]::new())
$fileSystemWatcherAction = {
try {
$fsItemEvent = [pscustomobject]@{
EventIdentifier = $Event.EventIdentifier
SourceIdentifier = $Event.SourceIdentifier
TimeStamp = $Event.TimeGenerated
FullPath = $Event.SourceEventArgs.FullPath
ChangeType = $Event.SourceEventArgs.ChangeType
}
# Collecting event in synchronized hashtable (overrides existing keys so that only the latest event details are available)
$syncdFsItemEventHashT[$fsItemEvent.FullPath] = $fsItemEvent
} catch {
Write-Host ($_ | Format-List * | Out-String ) -ForegroundColor red
}
}
# Script block which processes collected events and do further actions like copying for backup, etc...
# That scriptblock was initially used to test "Start-Job". Unfortunately it's not possible to access and modify the synchronized hashtable created within this scope.
$fSItemEventProcessingJob = {
$keys = [string[]]$syncdFsItemEventHashT.psbase.Keys
foreach ($key in $keys) {
$fsEvent = $syncdFsItemEventHashT[$key]
try {
# in case changetype eq DELETED or the item can't be found on the filesystem by the script -> remove the item from hashtable without any further actions.
# This affects temporary files from applications. BUT: Could also affect files with file permission issues.
if (($fsEvent.ChangeType -eq [System.IO.WatcherChangeTypes]::Deleted) -or (! (Test-Path -LiteralPath $fsEvent.FullPath)) ) {
$syncdFsItemEventHashT.Remove($key )
Write-Host ("==> Item '$key' with changetype '$($fsEvent.ChangeType)' removed from hashtable without any further actions!") -ForegroundColor Blue
continue
}
# get filesystem object
$fsItem = Get-Item -LiteralPath $fsEvent.FullPath -Force
if ($fsItem -is [System.IO.FileInfo]) {
# file processing
try {
# Check whether the file is still locked / in use by another process
[System.IO.FileStream]$fileStream = [System.IO.File]::Open( $fsEvent.FullPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read)
$fileStream.Close()
} catch [System.IO.IOException] {
Write-Host ("==> Item '$key' with changetype '$($fsEvent.ChangeType)' is still in use and can't be read!") -ForegroundColor Yellow
continue
}
} elseIf ($fsItem -is [System.IO.DirectoryInfo]) {
# directory processing
}
$syncdFsItemEventHashT.Remove($key )
Write-Host ("==> Item '$key' with changetype '$($fsEvent.ChangeType)' has been processed and removed from hashtable.") -ForegroundColor Blue
} catch {
Write-Host ($_ | Format-List * | Out-String ) -ForegroundColor red
}
}
}
[void] (Register-ObjectEvent -InputObject $fileSystemWatcher -EventName 'Created' -SourceIdentifier 'FSCreated' -Action $fileSystemWatcherAction)
[void] (Register-ObjectEvent -InputObject $fileSystemWatcher -EventName 'Changed' -SourceIdentifier 'FSChanged' -Action $fileSystemWatcherAction)
[void] (Register-ObjectEvent -InputObject $fileSystemWatcher -EventName 'Renamed' -SourceIdentifier 'FSRenamed' -Action $fileSystemWatcherAction)
[void] (Register-ObjectEvent -InputObject $fileSystemWatcher -EventName 'Deleted' -SourceIdentifier 'FSDeleted' -Action $fileSystemWatcherAction)
Write-Host "Watching for changes in '$fileSystemWatcherDirPath'.`r`nPress CTRL+C to exit!"
try {
do {
Wait-Event -Timeout 1
if ($syncdFsItemEventHashT.Count -gt 0) {
Write-Host "`r`n"
Write-Host ('-' * 50) -ForegroundColor Green
Write-Host "Collected events in hashtable queue:" -ForegroundColor Green
$syncdFsItemEventHashT.Values | Format-Table | Out-String
}
# Process hashtable items and do something with them (like copying, ..)
.$fSItemEventProcessingJob
# Garbage collector
[GC]::Collect()
} while ($true)
} finally {
# unregister
Unregister-Event -SourceIdentifier 'FSChanged'
Unregister-Event -SourceIdentifier 'FSCreated'
Unregister-Event -SourceIdentifier 'FSDeleted'
Unregister-Event -SourceIdentifier 'FSRenamed'
# dispose
$FileSystemWatcher.Dispose()
Write-Host "`r`nEvent Handler removed."
}
在上面发表我的评论后不久就解决了这个问题。其他答案可能类似,但最终将此功能添加到脚本顶部。
# function Test-FileLock
function Test-FileLock {
param ([parameter(Mandatory=$true)][string]$Path)
$oFile = New-Object System.IO.FileInfo $Path
if ((Test-Path -Path $Path) -eq $false)
{
return $false
}
try
{
$oStream = $oFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
if ($oStream)
{
$oStream.Close()
}
$false
}
catch
{
# file is locked by a process.
return $true
}
}
在 $onCreated 部分的变量部分之后添加了这段代码。
# Test File Lock
Do {
$filetest = Test-FileLock -Path $path
sleep -Seconds 5
} While ($filetest -eq $true)
下面的代码正在检查指定目录中的新文件 $folderCompleted
。
目前,当一个小文件被放入该目录 (~1MB) 时,Move-Item 命令和其他文件读取检查成功完成。
但是,当将大文件移入此目录时,会在文件完全移入(复制)到该目录之前调用 Object 事件。这导致文件检查和 Move-Item 命令失败,因为该文件仍在使用中。
# File Watcher
$filter = '*.*'
$fsw = New-Object IO.FileSystemWatcher $folderCompleted, $filter -Property @{
IncludeSubdirectories = $true
NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'
}
$onCreated = Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action {
$path = $Event.SourceEventArgs.FullPath
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
Write-Host "The file '$name' was $changeType at $timeStamp"
# File Checks
# Move File
Move-Item $path -Destination $destinationPath -Verbose
}
如何检查文件是否仍在复制?
您需要构建一个测试来查看文件是否被锁定(仍在复制)。 为此,您可以使用此功能:
function Test-LockedFile {
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[Alias('FullName', 'FilePath')]
[ValidateScript({Test-Path $_ -PathType Leaf})]
[string]$Path
)
$file = [System.IO.FileInfo]::new($Path)
# old PowerShell versions use:
# $file = New-Object System.IO.FileInfo $Path
try {
$stream = $file.Open([System.IO.FileMode]::Open,
[System.IO.FileAccess]::ReadWrite,
[System.IO.FileShare]::None)
if ($stream) { $stream.Close() }
return $false
}
catch {
return $true
}
}
有了它,在当前代码之上的某处,您可以:
# File Checks
while (Test-LockedFile $path) {
Start-Sleep -Seconds 1
}
# Move File
Move-Item $path -Destination $destinationPath -Verbose
试试这个。 (之前也有类似的担忧)。
事件触发器正在将所有受影响的项目收集到一个同步的哈希表中,并且一个额外的脚本块处理所有项目,但仅当文件准备好读取且未锁定时。 如果文件被锁定,您将在控制台中看到它。只需尝试复制大于 1 GB 的文件并观察输出。
处理项目(如复制到备份文件)的脚本块 $fSItemEventProcessingJob 最初是为 "Start-Job" 创建的。 但是您无法从该后台作业的会话中访问和修改哈希表。因此这是一个简单的脚本块执行。
要停止一切,只需按 CTRL + C。 这仍然会执行 "finally" 块并取消注册并处理所有内容。
PS: 该脚本只是一个测试,仅在我的 PC 上进行了本地测试。
Clear-Host
$ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
$fileSystemWatcherDirPath = 'C:\temp'
$fileSystemWatcherFilter = '*.*'
$fileSystemWatcher = [System.IO.FileSystemWatcher]::new($fileSystemWatcherDirPath , $fileSystemWatcherFilter)
$fileSystemWatcher.IncludeSubdirectories = $true
$fileSystemWatcher.EnableRaisingEvents = $true
$fileSystemWatcher.NotifyFilter = [System.IO.NotifyFilters]::FileName -bor [System.IO.NotifyFilters]::DirectoryName -bor [System.IO.NotifyFilters]::LastWrite # [System.Linq.Enumerable]::Sum([System.IO.NotifyFilters].GetEnumValues())
# Create syncronized hashtable
$syncdFsItemEventHashT = [hashtable]::Synchronized([hashtable]::new())
$fileSystemWatcherAction = {
try {
$fsItemEvent = [pscustomobject]@{
EventIdentifier = $Event.EventIdentifier
SourceIdentifier = $Event.SourceIdentifier
TimeStamp = $Event.TimeGenerated
FullPath = $Event.SourceEventArgs.FullPath
ChangeType = $Event.SourceEventArgs.ChangeType
}
# Collecting event in synchronized hashtable (overrides existing keys so that only the latest event details are available)
$syncdFsItemEventHashT[$fsItemEvent.FullPath] = $fsItemEvent
} catch {
Write-Host ($_ | Format-List * | Out-String ) -ForegroundColor red
}
}
# Script block which processes collected events and do further actions like copying for backup, etc...
# That scriptblock was initially used to test "Start-Job". Unfortunately it's not possible to access and modify the synchronized hashtable created within this scope.
$fSItemEventProcessingJob = {
$keys = [string[]]$syncdFsItemEventHashT.psbase.Keys
foreach ($key in $keys) {
$fsEvent = $syncdFsItemEventHashT[$key]
try {
# in case changetype eq DELETED or the item can't be found on the filesystem by the script -> remove the item from hashtable without any further actions.
# This affects temporary files from applications. BUT: Could also affect files with file permission issues.
if (($fsEvent.ChangeType -eq [System.IO.WatcherChangeTypes]::Deleted) -or (! (Test-Path -LiteralPath $fsEvent.FullPath)) ) {
$syncdFsItemEventHashT.Remove($key )
Write-Host ("==> Item '$key' with changetype '$($fsEvent.ChangeType)' removed from hashtable without any further actions!") -ForegroundColor Blue
continue
}
# get filesystem object
$fsItem = Get-Item -LiteralPath $fsEvent.FullPath -Force
if ($fsItem -is [System.IO.FileInfo]) {
# file processing
try {
# Check whether the file is still locked / in use by another process
[System.IO.FileStream]$fileStream = [System.IO.File]::Open( $fsEvent.FullPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read)
$fileStream.Close()
} catch [System.IO.IOException] {
Write-Host ("==> Item '$key' with changetype '$($fsEvent.ChangeType)' is still in use and can't be read!") -ForegroundColor Yellow
continue
}
} elseIf ($fsItem -is [System.IO.DirectoryInfo]) {
# directory processing
}
$syncdFsItemEventHashT.Remove($key )
Write-Host ("==> Item '$key' with changetype '$($fsEvent.ChangeType)' has been processed and removed from hashtable.") -ForegroundColor Blue
} catch {
Write-Host ($_ | Format-List * | Out-String ) -ForegroundColor red
}
}
}
[void] (Register-ObjectEvent -InputObject $fileSystemWatcher -EventName 'Created' -SourceIdentifier 'FSCreated' -Action $fileSystemWatcherAction)
[void] (Register-ObjectEvent -InputObject $fileSystemWatcher -EventName 'Changed' -SourceIdentifier 'FSChanged' -Action $fileSystemWatcherAction)
[void] (Register-ObjectEvent -InputObject $fileSystemWatcher -EventName 'Renamed' -SourceIdentifier 'FSRenamed' -Action $fileSystemWatcherAction)
[void] (Register-ObjectEvent -InputObject $fileSystemWatcher -EventName 'Deleted' -SourceIdentifier 'FSDeleted' -Action $fileSystemWatcherAction)
Write-Host "Watching for changes in '$fileSystemWatcherDirPath'.`r`nPress CTRL+C to exit!"
try {
do {
Wait-Event -Timeout 1
if ($syncdFsItemEventHashT.Count -gt 0) {
Write-Host "`r`n"
Write-Host ('-' * 50) -ForegroundColor Green
Write-Host "Collected events in hashtable queue:" -ForegroundColor Green
$syncdFsItemEventHashT.Values | Format-Table | Out-String
}
# Process hashtable items and do something with them (like copying, ..)
.$fSItemEventProcessingJob
# Garbage collector
[GC]::Collect()
} while ($true)
} finally {
# unregister
Unregister-Event -SourceIdentifier 'FSChanged'
Unregister-Event -SourceIdentifier 'FSCreated'
Unregister-Event -SourceIdentifier 'FSDeleted'
Unregister-Event -SourceIdentifier 'FSRenamed'
# dispose
$FileSystemWatcher.Dispose()
Write-Host "`r`nEvent Handler removed."
}
在上面发表我的评论后不久就解决了这个问题。其他答案可能类似,但最终将此功能添加到脚本顶部。
# function Test-FileLock
function Test-FileLock {
param ([parameter(Mandatory=$true)][string]$Path)
$oFile = New-Object System.IO.FileInfo $Path
if ((Test-Path -Path $Path) -eq $false)
{
return $false
}
try
{
$oStream = $oFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
if ($oStream)
{
$oStream.Close()
}
$false
}
catch
{
# file is locked by a process.
return $true
}
}
在 $onCreated 部分的变量部分之后添加了这段代码。
# Test File Lock
Do {
$filetest = Test-FileLock -Path $path
sleep -Seconds 5
} While ($filetest -eq $true)