Force-remove files and directories in PowerShell fails sometimes, but not always
Solution 1:
help Remove-Item
says:
The Recurse parameter in this cmdlet does not work properly.
and
Because the Recurse parameter in this cmdlet is faulty, the command uses the Get-Childitem cmdlet to get the desire d files, and it uses the pipeline operator to pass them to the Remove-Item cmdlet.
and proposes this alternative as an example:
get-childitem * -include *.csv -recurse | remove-item
So you should pipe get-childitem -recurse
into remove-item
.
Solution 2:
@JamesCW: The problem still exists in PowerShell 4.0
I tried another workaround and it worked: use cmd.exe:
&cmd.exe /c rd /s /q $somedirectory
Solution 3:
Update: Starting with Windows 10 version 1909
, (at least) build 18363.657
(I don't know that Windows Server version and build that corresponds to; run winver.exe
to check your version and build), the DeleteFile
Windows API function now exhibits synchronous behavior, which implicitly solves the problems with PowerShell's Remove-Item
and .NET's System.IO.File.Delete
/ System.IO.Directory.Delete
(but, curiously, not with cmd.exe
's rd /s
).
The existing answers mitigate the problem, so that it occurs less frequently, but they don't address the root cause, which is why failures can still occur.
Remove-Item -Recurse
is unexpectedly asynchronous, ultimately because the Windows API methods for file and directory removal are inherently asynchronous and Remove-Item
doesn't account for that.
This intermittently, unpredictably manifests in one of two ways:
Your case: Removing a nonempty directory itself can fail, if removal of a subdirectory or file in it hasn't completed yet by the time an attempt is made to remove the parent directory.
Less commonly: Recreating a removed directory immediately after removal can fail, because the removal may not have completed yet by the time re-creation is attempted.
The problem not only affects PowerShell's Remove-Item
, but also cmd.exe
's rd /s
as well as .NET's [System.IO.Directory]::Delete()
:
As of 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, neither Remove-Item
, nor rd /s
, nor [System.IO.Directory]::Delete()
work reliably, because they fail to account for the asynchronous behavior of the Windows API file/directory-removal functions:
- PowerShell Core bug report
cmd.exe
bug report- .NET Core bug report
For a custom PowerShell function that provides a reliably synchronous workaround, see this SO answer.
Solution 4:
ETA 20181217: PSVersion 4.0 and later will still fail in some circumstances, see alternate answer by Mehrdad Mirreza, and bug report filed by mklement
mklement provides a Proof of Concept solution at this SO answer, as the bug is awaiting an official fix
The new version of PowerShell
(PSVersion 4.0
) has resolved this issue entirely and Remove-Item "targetdirectory" -Recurse -Force
works without any timing problems.
You can check your version by running $PSVersiontable
from within the ISE or PowerShell
prompt. 4.0 is the version that ships with Windows 8.1
and Server 2012 R2
, and it can be installed on previous versions of Windows as well.
Solution 5:
The current answer won't actually delete a directory, just its children. Furthermore it will have problems with nested directories as it will again be trying to delete a directory before its contents. I wrote something to delete the files in the correct order, would still have the same problem though sometimes the directory would still be around afterward.
So, now I use something that will catch the exception, wait, and retry (3 times):
For now I'm using this:
function EmptyDirectory($directory = $(throw "Required parameter missing")) {
if ((test-path $directory) -and -not (gi $directory | ? { $_.PSIsContainer })) {
throw ("EmptyDirectory called on non-directory.");
}
$finished = $false;
$attemptsLeft = 3;
do {
if (test-path $directory) {
rm $directory -recurse -force
}
try {
$null = mkdir $directory
$finished = $true
}
catch [System.IO.IOException] {
Start-Sleep -Milliseconds 500
}
$attemptsLeft = $attemptsLeft - 1;
}
while (-not $finished -and $attemptsLeft -gt 0)
if (-not $finished) {
throw ("Unable to clean and recreate directory " + $directory)
}
}