Move-Item causes "File Already Exists" error, despite folder having been deleted
In the end I settled for this solution; not perfect, but it works.
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
}
This does the same as the job in update #2 of the question; only doesn't require the overhead of a job; just loops until the file's removed.
Here's the above logic wrapped as a reuable 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
JohnLBevan
I work as a technical architect for an advertising company. Most of my day job revolves around ERP, Finance and Operations systems, though I also get involved with any technical issues which fall into knowledge gaps between the various IT teams' / people's roles. My (IT related) interests are more towards web applications, artificial intelligence, productivity utilities, and scripting. If you want to know more about me than that, just Google my username - it's a perfect example of how not to ensure personal privacy. by JohnLBevan (added for Google Authorship)
Updated on June 05, 2022Comments
-
JohnLBevan almost 2 years
I have some code which deletes a folder, then copies files from a temporary directory to where that folder had been.
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 }
I get an intermittent error,
Move-Item : Cannot create a file when that file already exists.
on the Move-Item line for thegenerated
path.I've been able to prevent this by adding a hacky
Start-Sleep -Seconds 10
after the secondRemove-Item
statement; though that's not a great solution.I assume the issue is that the Remove-Item statement completes / code moves on to the next line, before the OS has caught up with the actual file deletion; though that seems odd/worrying. NB: There are ~2,500 files in the generated folder (all between 1-100 KBs).
There are no other processes accessing the folders (i.e. I've even closed my explorer windows & tested with this directory being excluded from my AV).
I've considered other options:
using
Copy-Item
instead ofMove-Item
. I don't like this as it requires creating new files when they're not required (i.e. a copy is slower than a move)... It's faster than my current sleep hack; but still not ideal.deleting the files & not the folder, then iterating through the subfolders & copying files to the new locations. This would work, but is a lot more code for something that should be simple; so I don't want to pursue that option.
Robocopy would do the trick; but I'd prefer a pure PowerShell solution. This is the option I'll eventually pick if there is no clean solution though.
Question
- Has anyone seen this before?
- Is it a bug, or have I missed something?
- Is anyone aware of a fix / good workaround?
Update
Running the remove in a separate job (i.e. using the code below) did not resolve the issue.
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 }
Update #2
Adding this works; i.e. rather than waiting a fixed time, we wait for the path to be removed / checking every second. If it's not removed after 30 seconds we assume it's not going to be; so carry on regardless (which will cause the
move-item
to throw an error which gets handled elsewhere).# ... 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 ...
-
Andrew Savinykh over 6 yearsI'm not sure how it works.
break
breaks from a loop%
is a shorthand forForeach-Object
which is not a loop but cmdlet. Thus, thebreak
statement will break out something else in outer scope, which can be completely unexpected.