【问题标题】:Powershell doesn't read files size correctly if these are in progress of change (i.e files being downloaded)如果这些文件正在进行更改(即正在下载的文件),Powershell 无法正确读取文件大小
【发布时间】:2022-01-21 17:14:54
【问题描述】:

我正在编写一个 Powershell 脚本,旨在在完成一个或多个文件的下载后关闭 Windows 机器(笔记本电脑/PC)(为了示例,我们假设这里是一个大文件) .

简而言之,我们每 x 秒读取整个下载文件夹的大小,并比较延迟前后的大小。如果最近时间没有变化,说明下载要么完成,要么卡住,两种情况都会导致关机。

给定以下脚本(大小获取器在某些时候可以是一个独立的函数,是的):

#Powershell5
#Major  Minor  Build  Revision
# -----  -----  -----  --------
# 5      1      19041  1320  

$downloadDir = "C:\Users\edi\Downloads"

while (1) 
{
    $s1 = Get-ChildItem -Path $downloadDir | Measure-Object -Property Length -Sum | Select-Object Sum
    write-host "S1:" $s1.sum

    # this 3 seconds time is just for testing purposes; in real case scenario this will most
    # likely be set to 60 or 120 seconds.
    Start-Sleep -s 3

    $s2 = Get-ChildItem -Path $downloadDir | Measure-Object -Property Length -Sum | Select-Object Sum
    write-host "S2:" $s2.sum

    if ($s1.sum -eq $s2.sum) 
    {
        write-host "Download complete, shutting down.."
        # loop exit is this, actual shutdown; commented out for testing purposes.
        #shutdown /s
    } 
}

我面临的问题是文件大小读取不是“实时”完成的。换句话说,文件大小并没有像您通常在资源管理器视图中看到的那样发生变化。我需要能够实时读取这些数字(更改文件大小)。

有趣的事实:在下载正在进行和脚本运行时,如果手动转到下载文件夹并按 F5 / 刷新...数字会发生变化(大小读数是准确的)。

旁注:我的研究让我看到了这篇可能提出根本原因的文章,但我不能 100% 确定它:https://devblogs.microsoft.com/oldnewthing/20111226-00/?p=8813

感谢您对此的任何想法。提前致谢!

【问题讨论】:

    标签: powershell filesize ntfs


    【解决方案1】:

    我建议采用不同的策略:

    • 为每个下载过程设置一个整体超时,例如curl.exe--max-time选项。

    • 不幸的是,PowerShell 自己的 Invoke-WebRequestInvoke-RestMethod 似乎只有一个连接超时 (-TimeoutSec),而不是整个连接的超时。

    这样您就可以跟踪下载进程,并在所有进程终止后触发重启(无论是由于完成还是超时)。


    至于你的方法

    • 您可以通过Get-ChildItem 查询的磁盘上文件大小不会在写入文件时连续更新,正如您所观察到的,而它最终更新在文件关闭之前可能不会发生,即完整地写入

    • 但是,您可以按需更新文件大小信息,即通过System.IO.FileSystemInfo.Refresh() 方法,这相当于您通过文件执行的手动刷新探险家。

      • 但是请注意,由于写入的内部缓冲,这仍然不是实时大小信息。
    # Perform this before every Measure-Object call.
    # It refreshes the size information of all files in the specified dir.
    (Get-ChildItem -File -LiteralPath $downloadDir).Refresh()
    

    顺便说一句:正如 Santiago 指出的那样,这种方法从根本上不适用于下载实用程序/API,它们预分配输出文件与下载的完整大小,这是一些BitTorrent 客户显然提供。

    至于缩小已完成的下载与假定被卡住的下载:

    Invoke-WebRequest / Invoke-RestMethod 在下载过程中以独占方式锁定其输出文件,因此您可以尝试读取哪些文件无法读取,从中您可以推断出哪些下载(如果有)仍在进行进行中:

    # Note: In PowerShell (Core) 7+, use -AsByteStream instead of -Encoding Byte
    Get-ChildItem -File -LiteralPath $downloadDir | 
      Get-Content -Encoding Byte -First 1 -ErrorVariable errs -ErrorAction SilentlyContinue |
        Out-Null
    
    if ($errs) { Write-Warning "Incomplete downloads:`n$($errs.TargetObject)" }
    

    【讨论】:

      【解决方案2】:

      此答案旨在证明,即使不可靠,也可以实时监控文件大小。在这种情况下,[System.IO.StreamWriter] 正在写入文件,每次迭代都会增加 1 个字节,直到文件达到 1Mb。

      我也执行了相同的测试while downloading a "test file",它对我来说工作正常,文件夹的总大小正在正确更新。

      您所观察到的可能原因完全基于假设:

      • 正在下载的文件已预先分配空间 - 我不明白这怎么可能,因为正如您所解释的,通过查看资源管理器,您可以看到文件大小在增加。
      • size beforesize after 计算之间没有足够的时间 - 这可能是由于缓冲,mklement0 已详细解释了这一点。增加睡眠时间可能会解决这个问题。另外,调用我不知道的.Refresh() 方法,谢谢:)

      我的想法,我个人认为这不是解决这个问题的正确方法。过程监控将是一种更好的方法(假设这是一种可能性 - mklement0 也解释了,我完全同意他的观点)。

      $checkSize = {
          (Get-ChildItem $PWD -File | Measure-Object -Property Length -Sum).Sum
      }
      
      $currentSize = & $checkSize
      $testFile = Join-Path $pwd -ChildPath "testfile.dump"
      
      $job = Start-Job {
      
          $writer = [System.IO.StreamWriter]::new(   
              [System.IO.File]::Create($using:testFile)
          )
          0..1Mb | ForEach-Object { $writer.Write(1) }
          $writer.Close()
      
      } -Name testDump
      
      '
      Starting test:
      '
      
      do
      {
          $increasingSizeBefore = & $checkSize
          Start-Sleep -Seconds 2
          $increasingSizeAfter = & $checkSize
      
          'StartingSize: {0} - SizeBefore: {1} - SizeAfter: {2}' -f
          $currentSize, $increasingSizeBefore, $increasingSizeAfter
      
      } until ($increasingSizeBefore -eq $increasingSizeAfter)
      
      $job | Stop-Job -PassThru | Remove-Job
      

      我的结果:

      Starting test:
      
      StartingSize: 17458 - SizeBefore: 17458 - SizeAfter: 152626
      StartingSize: 17458 - SizeBefore: 152626 - SizeAfter: 406578
      StartingSize: 17458 - SizeBefore: 406578 - SizeAfter: 652338
      StartingSize: 17458 - SizeBefore: 652338 - SizeAfter: 902194
      StartingSize: 17458 - SizeBefore: 902194 - SizeAfter: 1066035
      StartingSize: 17458 - SizeBefore: 1066035 - SizeAfter: 1066035
      

      【讨论】:

      • 谢谢 - 这是一个更好的模拟,但是,如果限制写入以更好地模拟下载(通常比本地写入慢),我确实看到了 Eduard 的问题:文件大小不是t 更新直到文件关闭并被完整写入。在 FileInfo 实例上调用 .Refresh() 可以首先解决这个问题(尽管由于缓冲的原因不是完全实时的)。
      • @mklement0 是的,我明白你的意思并完全同意,我的回答更多是为了证明一个观点而不是解决一个问题,我不同意until ($increasingSizeBefore -eq $increasingSizeAfter) 以了解是否已下载已完成,但仍将其用于示例。调用 sleep 来比较 SizeBeforeSizeAfter 需要调整(并且仍然容易出现故障),如果它是由于缓冲而下载的甚至更多已经指出除了打电话.Refresh() 不知道这个,所以谢谢。
      • 是的,跟踪规模增长并不是一个好方法;如果你让间隔足够大,我想它会工作得很好,如果你不介意花额外的时间等待的话。理想情况下,您应该自己跟踪下载过程,但它们必须有自己的总体超时时间; curl.exe 支持这一点,但 Invoke-WebRequestInvoke-RestMethod 都不支持(它们只有建立 连接 需要多长时间的超时)。
      • @mklement0 是的,我同意监控进程是正确的方法,至于iwrirm 他可以使用运行空间并监控实例(这将是另一种选择)。至于那些在下载文件前预先分配文件的程序,这是一个很好解决的问题,呵呵,但是这样做的程序已经带有“完成后关闭”选项......
      • 我忘记了预分配方面 - 您知道执行此操作的特定工具吗?请注意,监视 iwrirm 将无济于事,因为您不知道它们何时停止但继续运行 - 您将不得不再次从缺乏文件增长推断停止,除非我错过了东西。
      猜你喜欢
      • 2019-06-10
      • 2021-12-07
      • 1970-01-01
      • 1970-01-01
      • 2011-03-12
      • 2020-07-19
      • 2013-01-18
      • 2018-04-06
      • 2020-09-23
      相关资源
      最近更新 更多