42

I am trying to delete a directory recursively with rm -Force -Recurse somedirectory, I get several "The directory is not empty" errors. If I retry the same command, it succeeds.

Example:

PS I:\Documents and Settings\m\My Documents\prg\net> rm -Force -Recurse .\FileHelpers
Remove-Item : Cannot remove item I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\FileHelpers.Tests\Data\RunTime\_svn: The directory is not empty.
At line:1 char:3
+ rm <<<<  -Force -Recurse .\FileHelpers
    + CategoryInfo          : WriteError: (_svn:DirectoryInfo) [Remove-Item], IOException
    + FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Cannot remove item I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\FileHelpers.Tests\Data\RunTime: The directory is not empty.
At line:1 char:3
+ rm <<<<  -Force -Recurse .\FileHelpers
    + CategoryInfo          : WriteError: (RunTime:DirectoryInfo) [Remove-Item], IOException
    + FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Cannot remove item I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\FileHelpers.Tests\Data: The directory is not empty.
At line:1 char:3
+ rm <<<<  -Force -Recurse .\FileHelpers
    + CategoryInfo          : WriteError: (Data:DirectoryInfo) [Remove-Item], IOException
    + FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Cannot remove item I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\FileHelpers.Tests: The directory is not empty.
At line:1 char:3
+ rm <<<<  -Force -Recurse .\FileHelpers
    + CategoryInfo          : WriteError: (FileHelpers.Tests:DirectoryInfo) [Remove-Item], IOException
    + FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Cannot remove item I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\Libs\nunit\_svn: The directory is not empty.
At line:1 char:3
+ rm <<<<  -Force -Recurse .\FileHelpers
    + CategoryInfo          : WriteError: (_svn:DirectoryInfo) [Remove-Item], IOException
    + FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Cannot remove item I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\Libs\nunit: The directory is not empty.
At line:1 char:3
+ rm <<<<  -Force -Recurse .\FileHelpers
    + CategoryInfo          : WriteError: (nunit:DirectoryInfo) [Remove-Item], IOException
    + FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Cannot remove item I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\Libs: The directory is not empty.
At line:1 char:3
+ rm <<<<  -Force -Recurse .\FileHelpers
    + CategoryInfo          : WriteError: (Libs:DirectoryInfo) [Remove-Item], IOException
    + FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Cannot remove item I:\Documents and Settings\m\My Documents\prg\net\FileHelpers: The directory is not empty.
At line:1 char:3
+ rm <<<<  -Force -Recurse .\FileHelpers
    + CategoryInfo          : WriteError: (I:\Documents an...net\FileHelpers:DirectoryInfo) [Remove-Item], IOException
    + FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
PS I:\Documents and Settings\m\My Documents\prg\net> rm -Force -Recurse .\FileHelpers
PS I:\Documents and Settings\m\My Documents\prg\net>

Of course, this doesn't happen always. Also, it doesn't happen only with _svn directories, and I don't have a TortoiseSVN cache or anything like that so nothing is blocking the directory.

Any ideas?

Peter Mortensen
  • 2,318
  • 5
  • 23
  • 24
Mauricio Scheffer
  • 831
  • 2
  • 10
  • 23

11 Answers11

41

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.

Dennis Williamson
  • 62,149
  • 16
  • 116
  • 151
  • Thanks. Just found this thread from 2006: http://www.vistax64.com/powershell/25349-help-output-what-do-they-mean.html looks like Microsoft is not really interested in fixing this. – Mauricio Scheffer Nov 09 '10 at 20:00
  • @mausch: See this more recent, but still unresolved, reference: [Remove-Item -Recurse](https://connect.microsoft.com/PowerShell/feedback/details/549039/remove-item-recurse-does-not-work-properly-is-faulty) – Dennis Williamson Nov 09 '10 at 22:21
  • if you do a traversal and delete, you'll have to traverse the child directories first, and their files first. – fschwiet Dec 26 '10 at 22:26
  • 2
    At least the documentation says it doesn't work. – derekerdmann Nov 20 '11 at 04:12
  • 8
    I had to put both -force -recurse flags for Remove-Item, otherwise it kept me prompting "please confirm" Get-ChildItem -Path $Destination -Recurse | Remove-Item -force -recurse – Michael Freidgeim Jan 17 '12 at 06:37
  • 2
    does not work for me, i still get the error – Blub Dec 03 '14 at 12:42
  • PSVersion 4.0 fixes this problem – JamesCW Jun 16 '15 at 14:55
  • 1
    The known problem mentioned in the help relates only to _combining_ `-Recurse` _with `-Include`_ - it doesn't explain the OP's errors. Your sample command removes files from a directory tree, whereas the OP is looking to remove a nonempty directory as a whole. The true cause of the problem still exists as of Windows PowerShell v5.1 / PowerShell Core 6.2.0-preview.1 and is explained in [this bug report](https://github.com/PowerShell/PowerShell/issues/8211). – mklement Nov 28 '18 at 15:01
24

@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
Mehrdad Mirreza
  • 343
  • 2
  • 5
  • 1
    Good old rd /s /q! – JamesCW May 05 '16 at 18:24
  • I've tried every variation of Get-ChildItem; retry loops; calling `iisreset` before deleting and nothing seems to work *reliably*. I'm gonna try this one, even though when I first saw it I balked at having DOS inside my Powershell... – Peter McEvoy Aug 23 '16 at 08:04
  • 2
    Unfortunately, `rd /s` fails intermittently too (though seemingly less often than `Remove-Item`): https://github.com/Microsoft/console/issues/309 – mklement Nov 27 '18 at 20:45
  • It doesn't like the slash by the c for me. Do you have to precede it by powershell -command and single quote the cmd.exe part? I get "You must provide a value expression following the '/' operator." "Unexpected token 'c' in expression or statement. It's the same with powershell -command in front of it. Does the / need escaping? – Michele Aug 22 '19 at 15:05
  • 1
    This worked for me when PowerShell Remove-Item did not (including -recurse -force). However @mklement answer explains how most solutions only mitigate the problem, but some can completely fix it. – RichVel Jan 28 '20 at 07:40
  • I upvoted this when I saw it, but this also fails for me. – Jonathan Hartley Oct 13 '20 at 04:12
13

Update: Starting with (at least) Windows 10 version 20H2 (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:

For a custom PowerShell function that provides a reliably synchronous workaround, see this SO answer.

mklement
  • 566
  • 5
  • 11
  • When handling files where removal is certain: `while($true) { if ( (Remove-Item [...] *>&1) -ne $null) { Start-Sleep 0.5 } else { break } }` – Farway Jul 25 '19 at 22:55
8

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.

JamesCW
  • 309
  • 1
  • 4
  • 16
5

Gosh. Lots of answers. I honestly prefer this one over all of them. It's super simple, complete, readable, and works on any Windows machine. It uses .NET's (reliable) recursive delete functionality and if it fails for some reason, it throws a proper exception that can be handled with a try/catch block.

$fullPath = (Resolve-Path "directory\to\remove").ProviderPath
[IO.Directory]::Delete($fullPath, $true)

Note that the Resolve-Path line is important because .NET is not aware of your current directory when resolving relative file paths. That's about the only gotcha I can think of.

Phil
  • 1,013
  • 2
  • 12
  • 16
3

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)
    }
}
fschwiet
  • 272
  • 1
  • 8
  • 1
    This is good but I still had problems with it. If the mkdir command runs before the system completes the rm command it can throw a System.UnauthorizedAccessException with a FullyQualifiedErrorId of ItemExistsUnauthorizedAccessError. I.e., the directory hasn't been deleted by the OS yet (on my slow HDD). So that error needs to be caught too. And it's a non-terminating error, so the ErrorAction needs to be set to Stop. I also put the rm command in the try block too, just in cases there are transient IO errors when deleting. – Mark Lapierre May 26 '16 at 14:02
  • I can't believe this even has to be done. Damn, Powershell sucks! – jcollum Jul 12 '17 at 16:20
3

To delete the directory and its contents takes two steps. First delete the contents, then the folder itself. Using the workaround for the faulty recursive remove item the solution would look like this:

Get-ChildItem -Path "$folder\\*" -Recurse | Remove-Item -Force -Recurse
Remove-Item $folder

This way you can remove the parent directory as well.

Carl Baker
  • 31
  • 1
  • 1
    This is exactly what the accepted answer said. Do you have anything to add? – Michael Hampton Dec 14 '13 at 00:11
  • 1
    They are pointing out that the accepted answer does not delete the directory itself, hence it takes two steps. – Paul George May 02 '14 at 15:01
  • 2
    The `Remove-Item` command your piping into has the same problem that originally stated. It might stumble on a directory item that is non-empty in the same way. – Dejan Feb 26 '16 at 12:16
  • @Dejan This directory could not still be empty if the first line of this code worked, could it? – Ifedi Okonkwo Jun 07 '16 at 14:22
  • 2
    While this may _decrease_ the likelihood of failure, it can still fail, given that `Remove-Item -Recurse` is still involved. The underlying problem still exists as of Windows PowerShell v5.1 / PowerShell Core 6.2.0-preview.1 - see [this bug report](https://github.com/PowerShell/PowerShell/issues/8211). – mklement Nov 28 '18 at 14:55
2

This is what I have working:

$Target = "c:\folder_to_delete"

Get-ChildItem -Path $Target -Recurse -force |
  Where-Object { -not ($_.psiscontainer) } |
   Remove-Item –Force

Remove-Item -Recurse -Force $Target

This first line deletes all files in the tree. The second deletes all the folders including the top.

jscott
  • 24,484
  • 8
  • 79
  • 100
  • 1
    While this may _decrease_ the likelihood of failure, it can still fail, given that `Remove-Item -Recurse` is still involved. The underlying problem still exists as of Windows PowerShell v5.1 / PowerShell Core 6.2.0-preview.1 - see [this bug report](https://github.com/PowerShell/PowerShell/issues/8211). – mklement Nov 28 '18 at 14:57
0

The solution above works for most of my scripts. But then I found some folders that the above solution didn't work on. What fixed it for me on the stubborn folder is robocopy /PURGE /PURGE: Delete dest files/folders that no longer exist in the source So if you make an empty fold, copy it to the destination folder with robocopy /PURGE. Robocopy will clean it out then you can del the folder with $output_path | Remove-Item -Recurse -force

0

Take ownership of the files / directories first using Takeown.exe then delete

https://learn-powershell.net/2014/06/24/changing-ownership-of-file-or-folder-using-powershell/

AveryFreeman
  • 289
  • 1
  • 3
  • 14
0

I had this issue with a directory that would not delete. I found that one of the subfolders was corrupted and when I tried to move or rename that child dir I got an error message saying something about it being missing. I tried using rm -Force and got the same error as you did.

What worked for me was compressing the parent dir using 7-zip with the "Delete files after compression" option checked. Once it was compressed I was able to delete the zip file.