尽管文件夹已被删除,移动项仍会导致“文件已存在”错误

Joh*_*van 5 directory powershell file windows-7-x64 powershell-5.0

我有一些代码删除一个文件夹,然后将文件从一个临时目录复制到该文件夹​​所在的位置。

Remove-Item -Path '.\index.html' -Force
Remove-Item -Path '.\generated' -Force -Recurse  #folder containing generated files

#Start-Sleep -Seconds 10 #uncommenting this line fixes the issue

#$tempDir contains index.html and a sub folder, "generated", which contains additional files.  
#i.e. we're replacing the content we just deleted with new versions.
Get-ChildItem -Path $tempDir | %{
    Move-Item -Path $_.FullName -Destination $RelativePath -Force 
}
Run Code Online (Sandbox Code Playgroud)

Move-Item : Cannot create a file when that file already exists.generated路径的“移动项”行上出现间歇性错误。

我已经可以通过Start-Sleep -Seconds 10在第二个Remove-Item声明之后添加一个hacky来防止这种情况;尽管这不是一个很好的解决方案。

我认为问题在于,在操作系统赶上实际文件删除之前,Remove-Item语句完成/代码移至下一行。尽管这看起来很奇怪/令人担忧。注意:生成的文件夹中大约有2500个文件(全部在1-100 KB之间)。

没有其他进程可以访问文件夹(即,我什至已经关闭了资源管理器窗口并测试了将该目录从我的AV中排除)。

我考虑过其他选择:

  • 使用Copy-Item代替Move-Item。我不喜欢这样,因为它需要在不需要文件时创建新文件(即,副本比移动慢)...比我当前的睡眠技巧快;但还是不理想。

  • 删除文件而不是文件夹,然后遍历子文件夹并将文件复制到新位置。这可以工作,但是对于一些应该很简单的事情来说,要编写更多的代码。所以我不想追求这种选择。

  • Robocopy可以解决问题。但我希望使用纯PowerShell解决方案。如果没有干净的解决方案,我将最终选择此选项。

  • 谁看过这个吗?
  • 是一个错误,还是我错过了什么?
  • 有谁知道修复程序/良好的解决方法?

更新资料

在单独的作业中运行删除操作(即使用下面的代码)不能解决问题。

Start-Job -ScriptBlock {
    Remove-Item -Path '.\index.html' -Force
    Remove-Item -Path '.\generated' -Force -Recurse  #folder containing generated files
} | Wait-Job | Out-Null

#$tempDir contains index.html and a sub folder, "generated", which contains additional files.  
#i.e. we're replacing the content we just deleted with new versions.
Get-ChildItem -Path $tempDir | %{
    Move-Item -Path $_.FullName -Destination $RelativePath -Force 
}
Run Code Online (Sandbox Code Playgroud)

更新#2

添加这项工作;即我们不等待固定的时间,而是等待路径被删除/每秒检查一次。如果30秒钟后仍未将其删除,我们认为它不会被删除。因此请继续进行下去(这将导致move-item抛出将在其他地方处理的错误)。

# ... remove-item code ...
Start-Job -ScriptBlock {
    param($Path)
    while(Test-Path $Path){start-sleep -Seconds 1}
} -ArgumentList '.\generated' | Wait-Job -Timeout 30 | Out-Null
# ... move-item code ...
Run Code Online (Sandbox Code Playgroud)

Joh*_*van 2

最终我选择了这个解决方案;并不完美,但它有效。

Remove-Item -Path '.\index.html' -Force
Remove-Item -Path '.\generated' -Force -Recurse  #folder containing generated files

#wait until the .\generated directory is full removed; or until ~30 seconds has elapsed
1..30 | %{
    if (-not (Test-Path -Path '.\generated' -PathType Container)) {break;}
    Start-Sleep -Seconds 1
}

Get-ChildItem -Path $tempDir | %{
    Move-Item -Path $_.FullName -Destination $RelativePath -Force 
}
Run Code Online (Sandbox Code Playgroud)

这与问题更新 #2 中的工作相同;只是不需要工作的开销;只是循环直到文件被删除。

以下是封装为可重用 cmdlet 的上述逻辑:

function Wait-Item {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipeline, HelpMessage = 'The path of the item you wish to wait for')]
        [string]$Path
        ,
        [Parameter(HelpMessage = 'How many seconds to wait for the item before giving up')]
        [ValidateRange(1,[int]::MaxValue)]
        [int]$TimeoutSeconds = 30
        ,
        [Parameter(HelpMessage = 'By default the function waits for an item to appear.  Adding this switch causes us to wait for the item to be removed.')]
        [switch]$Remove
    )
    process {
        [bool]$timedOut = $true
        1..$TimeoutSeconds | %{
            if ((Test-Path -Path $Path) -ne ($Remove.IsPresent)){$timedOut=$false; return;}
            Start-Sleep -Seconds 1
        }
        if($timedOut) {
            Write-Error "Wait-Item timed out after $TimeoutSeconds waiting for item '$Path'"
        }
    }
}

#example usage:
Wait-Item -Path '.\generated' -TimeoutSeconds 30 -Remove
Run Code Online (Sandbox Code Playgroud)