【问题标题】:Powershell: Move-Item : Can it sit and wait until the source fle is closed and released?Powershell:Move-Item:它可以坐等到源文件关闭并释放吗?
【发布时间】:2022-01-26 02:17:03
【问题描述】:

我有一个 ObjectEvent,它看到一个新文件被创建,然后尝试移动该文件。

它可以工作...除了移动文件时,文件仍然打开,因此移动项失败。

所以我想有两种可能的路径......我对任何一个(或两个!)都持开放态度

首先,我的 ObjectEvent 将如何仅在文件关闭后触发?当前对象事件:

Register-ObjectEvent $Watcher -EventName Created -SourceIdentifier FileCreated -Action 

其次,MoveItem 是否有可能在失败前坐下来继续尝试 5 秒左右?当前移动项目调用:

Move-Item $path -Destination $destination -Force -Verbose

【问题讨论】:

  • 您能否分享代码的其他部分,例如$Watcher 之类的?
  • @ZaferBalkan 这是一个独立的问题,特别是关于如何监视文件关闭事件或使 Move-Item 在这种情况下工作。
  • 如果文件仍在使用中并且考虑到文件是由外部进程创建的,您可以监视该进程直到它关闭然后移动文件,或者您可以在移动时实施 try catch 块文件以定时间隔重试,您可以看到重试示例here

标签: powershell


【解决方案1】:

FileSystemWatcher 类有一些可用的事件。更改、创建、删除、错误和重命名。没有任何内容表明该文件是否最近被系统解锁。因此,没有直接的方法可以在文件关闭后专门触发您的事件。

为了使文件正确移动,我个人会做的是在文件创建事件触发后将移动延迟到 X 毫秒。

以下是其工作原理的摘要。

先决条件

  • Filewatcher,用于检测正在创建的文件
  • 一个计时器,它将在文件创建后移动文件
  • 一个队列,Filewatcher 将正在创建的项目排入队列,计时器将需要处理的项目排入队列。它将在两者之间传递信息。

流程

  • 文件已创建
  • Filewatcher 创建事件触发器并将项目添加到队列中,然后启动计时器。
  • 计时器已用事件触发,自行停止并处理队列。如果队列中仍有任何项目,它会自行重新启动。

这是所有这些的基本版本。

$Params = @{
    # Path we are watching
    WatchPath         = 'C:\temp\11\test'
    # Path we are moving stuff to.
    DestinationPath   = 'C:\temp\11'
    # Stop after X attempts
    MoveAttempts      = 5
    # Timer heartbeat
    MoveAttemptsDelay = 1000 
}

# Create watcher
$Watcher = [System.IO.FileSystemWatcher]::new()
$Watcher.Path = $Params.WatchPath
$Watcher.EnableRaisingEvents = $true    

# Create Timer - stopped state
$WatchTimer = [System.Timers.Timer]::new()
$WatchTimer.Interval = 1000

#Create WatchQueue (Each items will be FullPath,Timestamp,MoveAttempts)
$WatchQueue = [System.Collections.Generic.Queue[psobject]]::new()

$FileWatcherReg = @{
    InputObject      = $Watcher
    EventName        = 'Created'
    SourceIdentifier = 'FileCreated'
    MessageData      = @{WatchQueue = $WatchQueue; Timer = $WatchTimer }
    Action           = {
        if ($null -ne $event) {
            $Queue = $Event.MessageData.WatchQueue
            $Timer = $Event.MessageData.Timer
            $Queue.Enqueue([PSCustomObject]@{
                    FullPath     = $Event.SourceArgs.FullPath
                    TimeStamp    = $Event.TimeGenerated
                    MoveAttempts = 0
                })
            
            # We only start the timer if it is not already counting down
            # We also don't want to start the timer if item is not 1 since this mean 
            # the timer logic is already running.
            if ($Queue.Count -eq 1 -and ! $Timer.Enabled) { $Timer.Start() }
        }
    }
   
}

$TimerReg = @{
    InputObject      = $WatchTimer
    EventName        = 'Elapsed'
    Sourceidentifier = 'WatchTimerElapsed'
    MessageData      = @{WatchQueue = $WatchQueue; ConfigParams = $Params }
    Action           = {
        $Queue = $Event.MessageData.WatchQueue
        $ConfigParams = $Event.MessageData.ConfigParams
        $Event.Sender.Stop()
        $SkipItemsCount = 0
        while ($Queue.Count -gt 0 + $SkipItemsCount ) {
            $Item = $Queue.Dequeue()
            $ItemName = Split-Path $item.FullPath -Leaf

            while ($Item.MoveAttempts -lt $ConfigParams.MoveAttempts) {
                try {
                    Write-Host 'test'
                    $Item.MoveAttempts += 1
                    Move-Item -Path $Item.FullPath -Destination "$($ConfigParams.DestinationPath)\$ItemName" -ErrorAction Stop
                    break
                }
                catch {
                    $ex = $_.Exception
                    if ( $Item.MoveAttempts -eq 5) {
                        # Do something about it... Log / Warn / etc...
                        Write-warning "Move attempts: $($ConfigParams.MoveAttempts)"
                        Write-Warning "FilePath: $($Item.FullPath)"
                        Write-Warning $ex
                        continue
                    }
                    else {
                        
                        $Queue.Enqueue($Item)
                        $SkipItemsCount += 1
                    }
                    
                }
            }
            # If we skipped any items, we don't want to dequeue until 0 anymore but rather we will stop
            if ($SkipItemsCount -gt 0){
                 $Event.Sender.Start()
            }
        }
    }

}

# ObjectEvent for FileWatcher
Register-ObjectEvent @FileWatcherReg 
# ObjectEvent for Timer which process stuff in a delayed fashion
Register-ObjectEvent @TimerReg

while ($true) {
    Start-Sleep -Milliseconds 100
}

# Unregister events at the end 
Unregister-Event -SourceIdentifier FileCreated | Out-Null # Will fail first time since never registered
Unregister-Event -SourceIdentifier WatchTimerElapsed | Out-Null

【讨论】:

    猜你喜欢
    • 2015-06-21
    • 2022-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-09
    • 2018-11-08
    相关资源
    最近更新 更多