A remoção forçada de arquivos e diretórios no PowerShell falha algumas vezes, mas nem sempre

33

Estou tentando excluir um diretório recursivamente rm -Force -Recurse somedirectory, recebo vários erros "O diretório não está vazio". Se eu tentar novamente o mesmo comando , ele será bem-sucedido.

Exemplo:

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>

Claro, isso nem sempre acontece . Além disso, isso não acontece apenas com _svndiretórios, e eu não tenho um cache do TortoiseSVN ou algo assim, então nada está bloqueando o diretório.

Alguma ideia?

Mauricio Scheffer
fonte

Respostas:

31

help Remove-Item diz:

O parâmetro Recurse neste cmdlet não funciona corretamente.

e

Como o parâmetro Recurse nesse cmdlet está com defeito, o comando usa o cmdlet Get-Childitem para obter os arquivos desejados e usa o operador de pipeline para passá-los ao cmdlet Remove-Item.

e propõe essa alternativa como um exemplo:

get-childitem * -include *.csv -recurse | remove-item

Então você deve get-childitem -recurseentrar remove-item.

Pausado até novo aviso.
fonte
Obrigado. Acabei de encontrar este tópico de 2006: vistax64.com/powershell/… parece que a Microsoft não está realmente interessada em consertar isso.
Mauricio Scheffer
@mausch: veja esta referência mais recente, mas ainda não resolvida: Remove-Item -Recurse
Pausada até novo aviso.
se você fizer uma travessia e exclusão, precisará percorrer primeiro os diretórios filhos e seus arquivos.
fschwiet
2
Pelo menos a documentação diz que não funciona.
derekerdmann
6
Eu tive que colocar os dois sinalizadores -force -recurse para Remove-Item, caso contrário, ele me fazia perguntar "por favor confirme" Get-ChildItem -Path $ Destination -Recurse | Remove-Item -force -recurse
MiFreidgeim SO-stop being evil
17

@ JamesCW: O problema ainda existe no PowerShell 4.0

Tentei outra solução alternativa e funcionou: use cmd.exe:

&cmd.exe /c rd /s /q $somedirectory
Mehrdad Mirreza
fonte
1
Bom velho rd / s / q!
JamesCW 5/05
Eu tentei todas as variações do Get-ChildItem; repetir loops; ligar iisresetantes de excluir e nada parece funcionar de maneira confiável . Vou tentar um presente, mesmo que quando eu vi pela primeira vez eu recusou-se a ter DOS dentro da minha Powershell ...
Peter McEvoy
Infelizmente, também rd /sfalha intermitentemente (embora aparentemente com menos frequência Remove-Item): github.com/Microsoft/console/issues/309
mklement
Não gosta da barra pelo c para mim. Você precisa precedê-lo pelo comando powershell e aspas simples na parte cmd.exe? Recebo "Você deve fornecer uma expressão de valor após o operador '/'". "Símbolo inesperado 'c' na expressão ou na declaração. É o mesmo com o comando powershell na frente dele. O / precisa escapar?
Michele
7

ETA 20181217: PSVersão 4.0 e posterior ainda falhará em algumas circunstâncias, consulte resposta alternativa de Mehrdad Mirreza e relatório de bug arquivado por mklement

O mklement fornece uma solução de Prova de Conceito nesta resposta SO , pois o bug aguarda uma correção oficial

A nova versão do PowerShell( PSVersion 4.0) resolveu esse problema completamente e Remove-Item "targetdirectory" -Recurse -Forcefunciona sem problemas de tempo.

Você pode verificar sua versão executando $PSVersiontablede dentro do ISE ou PowerShellprompt. 4.0 é a versão que acompanha Windows 8.1e Server 2012 R2, e também pode ser instalada em versões anteriores do Windows.

JamesCW
fonte
5
Ainda ocorre para mim no PowerShell 4.0
ajbeaven 3/15
10
Ainda ocorre no PowerShell v5 !!!!! 11 !! 1! 1 !!!
Richard Hauer
@RichardHauer bem, agora eu só estou confuso
JamesCW
2
@ JamesCW eu converti para a rdversão. Além de realmente trabalhando, é sobre 3x mais rápido
Richard Hauer
O problema não foi corrigido no Windows PowerShell v5.1 / PowerShell Core 6.2.0-preview.1 - consulte este relatório de bug . Embora rd /spossa falhar com menos frequência, ele também está quebrado - consulte este relatório de bug .
mklement
4

Atualização : aparentemente há planos de tornar as APIs de remoção de itens do sistema de arquivos do Windows síncronas, mas ainda não são síncronas a partir da versão 10 do Windows 10 1903 - veja este comentário no GitHub .


As respostas existentes atenuam o problema, para que ocorra com menos frequência, mas não abordam a causa raiz , e é por isso que as falhas ainda podem ocorrer.

Remove-Item -Recurseé inesperadamente assíncrono , em última análise, porque os métodos da API do Windows para remoção de arquivos e diretórios são inerentemente assíncronos e Remove-Itemnão são responsáveis ​​por isso.

Isso de forma intermitente e imprevisível se manifesta de uma das duas maneiras:

  • Seu caso: A remoção de um diretório não vazio em si pode falhar, se a remoção de um subdiretório ou arquivo nele ainda não tiver sido concluída no momento em que for feita uma tentativa de remover o diretório pai.

  • Menos comum: A recriação de um diretório removido imediatamente após a remoção pode falhar, porque a remoção pode não ter sido concluída ainda no momento em que a recriação é tentada.

O problema não afeta apenas os PowerShell Remove-Item, mas também cmd.exeos rd /s.NET[System.IO.Directory]::Delete() :

A partir do Windows PowerShell v5.1 / PowerShell Core 6.2.0- cmd.exepreview.1 / 10.0.17134.407 / .NET Framework 4.7.03056, .NET Core 2.1, nem Remove-Item, nem rd /s, nem [System.IO.Directory]::Delete()funciona de maneira confiável , porque eles não respondem pelos dados assíncronos comportamento das funções de remoção de arquivos / diretórios da API do Windows :

Para uma função personalizada do PowerShell que fornece uma solução confiável e síncrona , consulte esta resposta do SO .

mklement
fonte
Ao manipular arquivos em que a remoção é certa:while($true) { if ( (Remove-Item [...] *>&1) -ne $null) { Start-Sleep 0.5 } else { break } }
Farway 25/07
3

A resposta atual não excluirá um diretório, apenas seus filhos. Além disso, ele terá problemas com diretórios aninhados, pois novamente tentará excluir um diretório antes de seu conteúdo. Eu escrevi algo para excluir os arquivos na ordem correta, ainda teria o mesmo problema, embora às vezes o diretório ainda estivesse por aí depois.

Então, agora eu uso algo que irá capturar a exceção, aguardar e tentar novamente (3 vezes):

Por enquanto estou usando isso:

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
fonte
1
Isso é bom, mas eu ainda tinha problemas com isso. Se o comando mkdir for executado antes que o sistema conclua o comando rm, ele poderá lançar uma System.UnauthorizedAccessException com um FullyQualifiedErrorId de ItemExistsUnauthorizedAccessError. Ou seja, o diretório ainda não foi excluído pelo sistema operacional (no meu disco rígido lento). Portanto, esse erro também precisa ser detectado. E é um erro sem fim, portanto, o ErrorAction precisa ser definido como Stop. Também coloquei o comando rm no bloco try, apenas nos casos em que houver erros transitórios de E / S ao excluir.
Mark Lapierre
Eu não acredito que isso precisa ser feito. Porra, o PowerShell é uma merda!
jcollum
3

Para excluir o diretório e seu conteúdo, são necessários dois passos. Primeiro exclua o conteúdo e depois a própria pasta. Usando a solução alternativa para o item de remoção recursiva com defeito, a solução seria assim:

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

Dessa forma, você também pode remover o diretório pai.

Carl Baker
fonte
1
Isso é exatamente o que a resposta aceita disse. Você tem algo a acrescentar?
Michael Hampton
1
Eles estão apontando que a resposta aceita não exclui o diretório em si, portanto, são necessárias duas etapas.
Paul George
2
O Remove-Itemcomando no qual sua tubulação tem o mesmo problema que foi declarado originalmente. Pode tropeçar em um item de diretório que não está vazio da mesma maneira.
26416 Dejan
@Dejan Este diretório ainda não poderia estar vazio se a primeira linha desse código funcionasse, poderia?
Ifedi Okonkwo
1
Embora isso possa diminuir a probabilidade de falha, ainda pode falhar, dado que Remove-Item -Recurseainda está envolvido. O problema subjacente ainda existe no Windows PowerShell v5.1 / PowerShell Core 6.2.0-preview.1 - consulte este relatório de bug .
mklement
3

Poxa. Muitas respostas. Sinceramente, prefiro este sobre todos eles. É super simples, completo, legível e funciona em qualquer máquina Windows. Ele usa a funcionalidade de exclusão recursiva do .NET (confiável) e, se falhar por algum motivo, lança uma exceção adequada que pode ser tratada com um bloco try / catch.

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

Observe que a Resolve-Pathlinha é importante porque o .NET não está ciente de seu diretório atual ao resolver caminhos de arquivos relativos. Essa é a única pegadinha que consigo pensar.

Phil
fonte
2

Isto é o que eu tenho trabalhando:

$Target = "c:\folder_to_delete"

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

Remove-Item -Recurse -Force $Target

Essa primeira linha exclui todos os arquivos da árvore. O segundo exclui todas as pastas, incluindo a parte superior.

James Copeland
fonte
Embora isso possa diminuir a probabilidade de falha, ainda pode falhar, dado que Remove-Item -Recurseainda está envolvido. O problema subjacente ainda existe no Windows PowerShell v5.1 / PowerShell Core 6.2.0-preview.1 - consulte este relatório de bug .
mklement
0

Eu tive esse problema com um diretório que não seria excluído. Descobri que uma das subpastas estava corrompida e quando tentei mover ou renomear o diretório filho, recebi uma mensagem de erro dizendo algo sobre a falta dele. Tentei usar o rm -Force e obtive o mesmo erro que você.

O que funcionou para mim foi compactar o diretório pai usando o 7-zip com a opção "Excluir arquivos após a compactação" marcada. Uma vez compactado, pude excluir o arquivo zip.

RedDawnRising
fonte