【问题标题】:PowerShell Remove-Item not waitingPowerShell 删除项目不等待
【发布时间】:2019-04-11 22:59:26
【问题描述】:

如果有这段代码

if(Test-Path -Path $OUT) 
{ 
    Remove-Item $OUT -Recurse 
}
New-Item -ItemType directory -Path $OUT

有时它有效,但有时 New-Item 行会产生 PermissionDenied ItemExistsUnauthorizedAccessError 错误。我认为这意味着之前的 Remove-Item 尚未完全执行,并且无法创建文件夹,因为它仍然存在。

如果我在那里插入睡眠

if(Test-Path -Path $OUT) 
{ 
    Remove-Item $OUT -Recurse 
    Start-Sleep -s 1
}
New-Item -ItemType directory -Path $OUT

那么它总是有效的。 如何强制 Remove-Item 以确保该文件夹被真正删除? 还是我想念别的东西?

【问题讨论】:

    标签: powershell filesystems synchronous ntfs


    【解决方案1】:

    更新从(至少[1])Windows 10 版本20H2开始(我不知道Windows Server 版本和对应的构建;运行 winver.exe 以检查您的版本和构建),DeleteFile Windows API 函数现在表现出同步行为,这隐式解决了 PowerShell 的 Remove-Item 和 .NET 的 System.IO.File.Delete / System.IO.Directory.Delete 的问题(但奇怪的是,不是 cmd.exerd /s)。


    Remove-Item -Recurse 出乎意料地异步,最终是因为用于文件和目录删除的 Windows API 方法本质上是异步的,而Remove-Item 没有不要考虑这个。

    间歇性地、不可预测地表现为以下两种方式之一:

    • 您的情况:删除后立即重新创建已删除的目录可能会失败,因为在尝试重新创建时删除可能尚未完成。

    • 更常见的是:如果在尝试删除父目录时尚未完成子目录或文件的删除,则删除非空目录本身可能会失败 - 这在 ServerFault answer marsze 链接中得到了证明.

    潜在的解决方法是通过清空现有目录重用 - 而不是删除和重新创建它.

    但是,清空带有Get-ChildItem $OUT -Recurse | Remove-Item -Recurse 的目录容易受到间歇性故障的影响,尽管这种情况不太常见。

    该问题不仅影响PowerShell的Remove-Item,还影响cmd.exerd /s以及.NET的[System.IO.Directory]::Delete()

    从 Windows PowerShell v5.1 / PowerShell Core 6.2.0-preview.1 / cmd.exe 10.0.17134.407 / .NET Framework 4.7.03056、.NET Core 2.1 开始,既不是 Remove-Item,也不是 @ 987654346@ 和 [System.IO.Directory]::Delete() 工作可靠,因为它们未能考虑 Windows API 文件/目录删除函数的异步行为

    有关提供可靠同步解决方法自定义 PowerShell 函数,请参阅this answer


    [1] 我已亲自验证问题已在版本20H2 中解决,通过在GitHub issue #27958 中运行测试数小时而没有失败; this answer 建议问题早在版本 1909 就已解决,从构建 18363.657 开始,但 Dinh Tran 发现问题在构建 18363.1316解决大型目录树,例如node_modules。我找不到有关该主题的任何官方信息。

    【讨论】:

      【解决方案2】:

      Remove-Item 命令有一个known issue

      试试这个:

      if (Test-Path $OUT) 
      { 
          # if exists: empty contents and reuse the directory itself
          Get-ChildItem $OUT -Recurse | Remove-Item -Recurse
      }
      else
      {
          # else: create
          New-Item -ItemType Directory -Path $OUT
      }
      

      注意:

      • Get-ChildItem 命令只查找非隐藏文件和子目录,因此清空目标目录可能不完整;要包含隐藏项目,请添加 -Force

      • 同样,将-Force 添加到-RemoveItem 以强制删除设置了只读 属性的文件。

        • 没有-Force,清空可能再次不完整,但在这种情况下会出现非终止错误;如果您想将它们视为终止错误,请同时添加 -ErrorAction Stop

      【讨论】:

        【解决方案3】:

        为了完整起见:您还可以使用安全快速的 .NET 方法:

        if ([System.IO.Directory]::Exists($OUT)) {
            [System.IO.Directory]::Delete($OUT, $true)
        }
        [System.IO.Directory]::CreateDirectory($OUT)
        

        注意:

        根据您获得 $OUT 值的位置,您可能需要先将其转换为完整路径,以确保 .NET 方法删除正确的目录(请参阅 @mklement0 的评论):

        $fullPath = Convert-Path $OUT
        

        【讨论】:

        • 不错的选择,但我强烈建议传递(Convert-Path $OUT) 而不仅仅是$OUT,因为.NET 框架的工作目录概念通常与PowerShell 不同,因此在$OUT 中具有相对路径可能找不到目标目录,或者更糟糕的是,可能会删除其他目录。
        • 我在许多自定义 cmdlet 中使用 [System.IO.Directory]::SetCurrentDirectory((Get-Location).Path),这在与任何 dll 交互时都非常有用。
        • 如果你知道你是进程中唯一的运行空间,那很好(这在交互式 shell 中通常是正确的);多个运行空间的可能性是目录不同步的原因 - 请参阅github.com/PowerShell/PowerShell/issues/3428。请注意,最好使用.ProviderPath 而不是.Path,因为.Path 可以基于.NET 不知道的PS 驱动器
        • @mklement0 全部正确。尽管如此,为了简单起见,我通常选择忽略边缘情况的这些高级考虑,就像我不费心让我的所有类都完全线程安全一样。如果 OP 难以删除目录,他肯定不会用他的脚本做非常高级的事情。
        • 事实证明,[System.IO.Directory]::Delete() 也有问题:github.com/dotnet/corefx/issues/33603
        【解决方案4】:

        如果你输入Get-Help Remove-Item -Detailed,你会看到:

        Example 4: Delete files in subfolders recursively
        PS C:\>Get-ChildItem * -Include *.csv -Recurse | Remove-Item
        
        This command deletes all of the CSV files in the current folder and all subfolder recursively.
        

        由于 Remove-Item 中的 Recurse 参数存在一个已知问题,因此 此示例中的命令使用 Get-ChildItem 来获取所需的文件, 然后使用管道运算符将它们传递给 Remove-Item 。

        按照规范建议做:

        if(Test-Path -Path $OUT) 
        { 
            Get-ChildItem $OUT -Recurse | Remove-Item
        }
        New-Item -ItemType directory -Path $OUT
        

        【讨论】:

        • 您引用的错误是另一个错误;它与-Include 参数有关,此处不发挥作用。您的解决方法不会删除目标目录本身,因此如果您想无条件调用New-Item 而不会导致错误,则必须将-Force 添加到其中。 -Recurse 应添加到 Remove-Item,否则您将收到目标目录子树中每个子目录的确认提示。
        • 为了更清楚地表明引用的错误是不相关的:-Recurse 参数在same help topic 中的描述更明确(添加了重点),“当它与 Include 参数一起使用时,Recurse 参数可能不会删除所有子文件夹或所有子项。这是一个已知问题。"
        猜你喜欢
        • 1970-01-01
        • 2012-11-24
        • 2021-11-03
        • 1970-01-01
        • 2014-09-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多