使用 PowerShell 和 System.IO.FileSystemWatcher 检测特定文件夹的下载何时完成

使用 PowerShell 和 System.IO.FileSystemWatcher 检测特定文件夹的下载何时完成

我正在尝试监控我的下载文件夹并在下载完成时触发命令行操作。这与另一个问题非常相似,如何监视文件夹并在创建或编辑文件时触发命令行操作?

我已经实现了得票最多的答案,但是当我运行它时,我发现输出令人困惑。

### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
    $watcher = New-Object System.IO.FileSystemWatcher
    $watcher.Path = "Z:\UnprocessedDownloads"
    $watcher.Filter = "*.*"
    $watcher.IncludeSubdirectories = $true
    $watcher.EnableRaisingEvents = $true  

### DEFINE ACTIONS AFTER AN EVENT IS DETECTED
    $action = { $path = $Event.SourceEventArgs.FullPath
                $changeType = $Event.SourceEventArgs.ChangeType
                $logline = "$(Get-Date -f o), $changeType, $path"
                Add-content "Z:\log.txt" -value $logline
              }    
### DECIDE WHICH EVENTS SHOULD BE WATCHED 
    Register-ObjectEvent $watcher "Created" -Action $action
    Register-ObjectEvent $watcher "Changed" -Action $action
    Register-ObjectEvent $watcher "Deleted" -Action $action
    Register-ObjectEvent $watcher "Renamed" -Action $action
    while ($true) {sleep 5}

我应该指出,有问题的下载是由 Chrome 创建的,这也许可以解释发生了什么。据我所知,这一系列事件如下:

  1. 将创建一个具有预期文件名的文件。
  2. 先前创建的文件被立即删除。
  3. 具有预期文件名的文件,以及.cr下载已附加,已创建。
  4. 该文件记录了两到三个变化(我测试的文件中,如果变化是几 KB 则显示两个,但 100MB 到 10GB 的文件记录了三个)。
  5. 该文件被重命名,删除了.cr下载
  6. 然后,此文件又记录了另外两到三处更改(我找不到发生多少次更改的原因或韵律)。
  7. 已完成,无需人工干预,其他任何事情都不会被记录。

就是这样。我不知道如何判断文件是否真正“完成”,除非观察重命名事件,然后等待任意秒数(有时,最后记录的更改需要大约 5 秒才能发生 - 我刚刚意识到这可能是由于脚本中的 sleep 5),也许是 10。

有人能解释一下这是怎么回事吗?有什么建议或替代方案吗?我并不依赖 PowerShell,只是觉得它和文件系统监视程序很适合这项任务。我更喜欢在保留自动化的同时尽可能减少开销的东西。

FWIW,这是我的测试的摘录(并不广泛;注意 - 我确实调整了日志以显示毫秒):

2019-01-02T22:03:03.6712039-05:00, Created, Z:\Unprocessed\test (1).jpg
2019-01-02T22:03:03.7242040-05:00, Deleted, Z:\Unprocessed\test (1).jpg
2019-01-02T22:03:03.7252054-05:00, Created, Z:\Unprocessed\test (1).jpg.crdownload
2019-01-02T22:03:03.7252054-05:00, Changed, Z:\Unprocessed\test (1).jpg.crdownload
2019-01-02T22:03:08.7265875-05:00, Changed, Z:\Unprocessed\test (1).jpg.crdownload
2019-01-02T22:03:08.7305994-05:00, Renamed, Z:\Unprocessed\test (1).jpg
2019-01-02T22:03:08.7315887-05:00, Changed, Z:\Unprocessed\test (1).jpg
2019-01-02T22:03:08.7315887-05:00, Changed, Z:\Unprocessed\test (1).jpg
2019-01-02T22:03:13.7348367-05:00, Changed, Z:\Unprocessed\test (1).jpg
2019-01-02T22:09:28.7729475-05:00, Deleted, Z:\Unprocessed\10GB.bin
2019-01-02T22:09:33.7742846-05:00, Created, Z:\Unprocessed\1GB.bin
2019-01-02T22:09:33.7762852-05:00, Deleted, Z:\Unprocessed\1GB.bin
2019-01-02T22:09:33.7772866-05:00, Created, Z:\Unprocessed\1GB.bin.crdownload
2019-01-02T22:09:33.7782853-05:00, Changed, Z:\Unprocessed\1GB.bin.crdownload
2019-01-02T22:09:33.7792825-05:00, Changed, Z:\Unprocessed\1GB.bin.crdownload
2019-01-02T22:10:28.7850646-05:00, Changed, Z:\Unprocessed\1GB.bin.crdownload
2019-01-02T22:10:28.7860648-05:00, Renamed, Z:\Unprocessed\1GB.bin
2019-01-02T22:10:28.7870652-05:00, Changed, Z:\Unprocessed\1GB.bin
2019-01-02T22:10:28.7870652-05:00, Changed, Z:\Unprocessed\1GB.bin
2019-01-02T22:10:28.7880654-05:00, Changed, Z:\Unprocessed\1GB.bin
2019-01-02T22:11:13.7928495-05:00, Created, Z:\Unprocessed\test (2).jpg
2019-01-02T22:11:13.7938482-05:00, Deleted, Z:\Unprocessed\test (2).jpg
2019-01-02T22:11:13.7938482-05:00, Created, Z:\Unprocessed\test (2).jpg.crdownload
2019-01-02T22:11:13.7948490-05:00, Changed, Z:\Unprocessed\test (2).jpg.crdownload
2019-01-02T22:11:18.7972830-05:00, Changed, Z:\Unprocessed\test (2).jpg.crdownload
2019-01-02T22:11:18.7982945-05:00, Renamed, Z:\Unprocessed\test (2).jpg
2019-01-02T22:11:18.7992839-05:00, Changed, Z:\Unprocessed\test (2).jpg
2019-01-02T22:11:18.8002947-05:00, Changed, Z:\Unprocessed\test (2).jpg
2019-01-02T22:11:23.8011169-05:00, Changed, Z:\Unprocessed\test (2).jpg

答案1

试试这个。我修改了原版以添加更多逻辑。不确定这个版本是否可靠。让我知道它是否可靠。

# create an array that tracks each file
$global:files = @()
# where we will move the file once it is free
$destination = "C:\destination"

$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "C:\source"
$watcher.Filter = "*.*"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true  

$action = {
    $path = $Event.SourceEventArgs.FullPath
    $changeType = $Event.SourceEventArgs.ChangeType
    $logline = "$(Get-Date -f o), $changeType, $path"
    Add-content "C:\log.txt" -value $logline
    Write-Host $logline

    if($changeType -eq "Renamed" -and $path.Split(".")[-1] -ne "crdownload") {
        Write-Host -ForegroundColor Green "Found the file we need! $path"
        Add-content "C:\log.txt" -value "Found the file we need! $path"
        $global:files += $path
    }
}    

Register-ObjectEvent $watcher "Created" -Action $action
Register-ObjectEvent $watcher "Changed" -Action $action
Register-ObjectEvent $watcher "Deleted" -Action $action
Register-ObjectEvent $watcher "Renamed" -Action $action

while ($true) {

    # if there are any files to process, check if they are locked
    if($global:files) {

        $global:files | % {

            $file = $_
            # assume the file is locked
            $fileFree = $false

            Write-Host -ForegroundColor Yellow "Checking if the file is locked... ($file)"
            Add-content "C:\log.txt" -value "Checking if the file is locked... ($_)"

            # true  = file is free
            # false = file is locked
            try {
                [IO.File]::OpenWrite($file).close();Write-Host -ForegroundColor Green "File is free! ($file)"
                Add-content "C:\log.txt" -value "File is free! ($file)"
                $fileFree = $true
            }
            catch {
                Write-Host -ForegroundColor Red "File is Locked ($file)"
                Add-content "C:\log.txt" -value "File is Locked ($file)"
            }

            if($fileFree) {

                # do what we want with the file, since it is free
                Move-Item $file $destination
                Write-Host -ForegroundColor Green "Moving file ($file)"
                Add-content "C:\log.txt" -value "Moving file ($file)"

                # make sure we don't progress until the file has finished moving
                while(Test-Path $file) {
                    Sleep 1
                }

                # remove the current file from the array
                Write-Host -ForegroundColor Green "Done processing this file. ($file)"
                Add-content "C:\log.txt" -value "Done processing this file. ($file)"
                $global:files = $global:files | ? { $_ -ne $file }
            }

        }
    }

    sleep 2
}
  • 这将监听Renamed文件扩展名不是“crdownload”的事件,帮助我们获取实际路径/文件/扩展名,而不是临时文件的信息。找到匹配项后,它会存储在数组中,这样我们就可以同时处理多个文件。
  • 一旦数组包含一个或多个文件名,就会检查每个文件,看它当前是否被锁定或是否空闲。如果空闲,则将其移动到$destination。您可以将所需操作从更改Move-Item为您想要的任何操作。
  • 一旦文件移动成功,它将从阵列中删除。

要知道,你所看到的多个Changed事件将是预期的(从 Chrome 下载时也会出现这种情况):

常见的文件系统操作可能会引发多个事件。例如,当文件从一个目录移动到另一个目录时,可能会引发多个 OnChanged 事件以及一些 OnCreated 和 OnDeleted 事件。移动文件是一项复杂的操作,由多个简单操作组成,因此会引发多个事件。同样,某些应用程序(例如防病毒软件)可能会引发 FileSystemWatcher 检测到的其他文件系统事件。

您可能想要删除添加的Write-Host行。您可能还想在终止捕获中添加以下内容,以正常终止已注册的事件:

Get-EventSubscriber | ? { $_.EventName -in "Created","Changed","Deleted","Renamed" } | Unregister-Event

灵感

检查文件是否被锁定

相关内容