La suppression forcée des fichiers et des répertoires dans PowerShell échoue parfois, mais pas toujours

33

J'essaie de supprimer un répertoire de manière récursive avec rm -Force -Recurse somedirectory, je reçois plusieurs erreurs "Le répertoire n'est pas vide". Si je réessaie la même commande , cela réussit.

Exemple:

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>

Bien sûr, cela n'arrive pas toujours . En outre, cela ne se produit pas uniquement avec des _svnrépertoires, et je n'ai pas de cache TortoiseSVN ou quoi que ce soit du genre, donc rien ne bloque le répertoire.

Des idées?

Mauricio Scheffer
la source

Réponses:

31

help Remove-Item dit:

Le paramètre Recurse dans cette cmdlet ne fonctionne pas correctement.

et

Comme le paramètre Recurse de cette cmdlet est défectueux, la commande utilise la cmdlet Get-Childitem pour obtenir les fichiers souhaités et elle utilise l'opérateur de pipeline pour les transmettre à la cmdlet Remove-Item.

et propose cette alternative à titre d'exemple:

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

Donc, vous devriez pipe get-childitem -recursedans remove-item.

En pause jusqu'à nouvel ordre.
la source
Merci. Je viens de trouver ce fil de 2006: vistax64.com/powershell/… on dirait que Microsoft n'est pas vraiment intéressé à résoudre ce problème .
Mauricio Scheffer
@mausch: Voir cette référence plus récente mais non résolue: Remove-Item -Recurse
pause jusqu'à nouvel avis.
si vous effectuez une traversée et que vous supprimez, vous devrez d'abord parcourir les répertoires enfants, puis leurs fichiers.
fschwiet
2
Au moins, la documentation indique que cela ne fonctionne pas.
derekerdmann
6
Je devais mettre les deux indicateurs -force -recurse pour Remove-Item, sinon le message m'invitait "veuillez confirmer" Get-ChildItem -Path $ Destination -Recurse | Remove-Item -force -recurse
MiFreidgeim SO-arrêtez d'être maléfique
17

@JamesCW: le problème persiste dans PowerShell 4.0

J'ai essayé une autre solution de contournement et cela a fonctionné: utilisez cmd.exe:

&cmd.exe /c rd /s /q $somedirectory
Mehrdad Mirreza
la source
1
Bon vieux rd / s / q!
JamesCW
J'ai essayé toutes les variantes de Get-ChildItem; réessayer les boucles; appeler iisresetavant de supprimer et rien ne semble fonctionner de manière fiable . Je vais essayer celui-ci, même si la première fois que je l'ai vu, j'ai hésité à avoir le DOS dans mon Powershell ...
Peter McEvoy
Malheureusement, rd /séchoue également par intermittence (bien que apparemment moins souvent que Remove-Item): github.com/Microsoft/console/issues/309
mklement
Il n'aime pas le slash par le c pour moi. Devez-vous le faire précéder de la commande powershell et ne citer que la partie cmd.exe? Je reçois "Vous devez fournir une expression de valeur après l'opérateur '/'." "Jeton 'c' inattendu dans une expression ou une déclaration. Il en va de même avec la commande powershell en face de lui. Le / doit-il s'échapper?
Michele
7

ETA 20181217: PSVersion 4.0 et versions ultérieures échoueront encore dans certaines circonstances, voir réponse alternative de Mehrdad Mirreza et rapport de bogue rédigé par mklement.

mklement fournit une solution de validation de concept à cette réponse SO , car le bogue attend un correctif officiel

La nouvelle version de PowerShell( PSVersion 4.0) a entièrement résolu ce problème et Remove-Item "targetdirectory" -Recurse -Forcefonctionne sans problème de synchronisation.

Vous pouvez vérifier votre version en exécutant à $PSVersiontablepartir de l'ISE ou de l' PowerShellinvite. 4.0 est la version fournie avec Windows 8.1and Server 2012 R2, et il peut également être installé sur les versions précédentes de Windows.

JamesCW
la source
5
Se
10
Se produit toujours dans PowerShell v5 !!!!! 11 !! 1! 1 !!!
Richard Hauer
@RichardHauer et maintenant je suis confus
JamesCW
2
@JamesCW J'ai converti à la rdversion. En plus de travailler réellement, c'est environ 3 fois plus rapide
Richard Hauer
Le problème n'a pas été résolu à partir de Windows PowerShell version 5.1 / PowerShell Core 6.2.0-preview.1 - voir ce rapport de bogue . Bien que cela rd /spuisse échouer moins souvent, il est également endommagé - voir ce rapport de bogue .
mardi
4

Mise à jour : Apparemment, il est prévu de rendre les API de suppression d'éléments de système de fichiers Windows synchrones, mais elles ne sont pas encore synchrones à partir de Windows 10 version 1903 - voir ce commentaire sur GitHub .


Les réponses existantes permettent d' atténuer le problème, de sorte qu'il se produise moins souvent, mais elles ne résolvent pas la cause première , raison pour laquelle des défaillances peuvent toujours se produire.

Remove-Item -Recurseest inopinément asynchrone , car les méthodes de l’API Windows pour la suppression de fichiers et de répertoires sont intrinsèquement asynchrones et Remove-Itemne tiennent pas compte de cela.

Cela se manifeste de façon imprévisible de façon intermittente, de deux manières:

  • Votre cas: La suppression d’un répertoire non vide peut elle-même échouer si la suppression d’un sous-répertoire ou d’un fichier n’est pas terminée au moment où une tentative de suppression du répertoire parent est effectuée.

  • Plus rarement: la recréation d'un répertoire supprimé immédiatement après la suppression peut échouer, car la suppression n'est peut-être pas encore terminée au moment de la tentative de recréation.

Le problème ne touche que PowerShell de Remove-Item, mais aussi cmd.exeest rd /sainsi que de .NET[System.IO.Directory]::Delete() :

À partir de Windows PowerShell v5.1 / PowerShell de base 6.2.0-preview.1 / cmd.exe10.0.17134.407 / .NET Framework 4.7.03056, Core .NET 2.1, ni Remove-Item, ni rd /s, ni [System.IO.Directory]::Delete()travail fiable , car ils ne tiennent pas compte de l'asynchrone comportement des fonctions de suppression de fichiers / répertoires de l'API Windows :

Pour une fonction PowerShell personnalisée fournissant une solution de contournement fiable et synchrone , reportez-vous à cette réponse au responsable de la sécurité .

mklement
la source
Lorsque vous manipulez des fichiers dont la suppression est certaine:while($true) { if ( (Remove-Item [...] *>&1) -ne $null) { Start-Sleep 0.5 } else { break } }
Farway
3

La réponse actuelle ne supprimera pas réellement un répertoire, mais seulement ses enfants. De plus, il aura des problèmes avec les répertoires imbriqués, car il essaiera à nouveau de supprimer un répertoire avant son contenu. J'ai écrit quelque chose pour supprimer les fichiers dans le bon ordre, aurait toujours le même problème bien que parfois le répertoire soit toujours là après.

Donc, maintenant, j'utilise quelque chose qui va attraper l'exception, attendre et réessayer (3 fois):

Pour l'instant j'utilise ceci:

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
la source
1
C'est bien, mais j'ai toujours eu des problèmes avec ça. Si la commande mkdir est exécutée avant que le système ne termine la commande rm, elle peut générer une exception System.UnauthorizedAccessException avec un FullyQualifiedErrorId de ItemExistsUnauthorizedAccessError. C'est-à-dire que le répertoire n'a pas encore été supprimé par le système d'exploitation (sur mon disque dur lent). Donc, cette erreur doit être attrapée aussi. Et comme il s’agit d’une erreur qui ne se termine pas, ErrorAction doit être défini sur Stop. Je mets également la commande rm dans le bloc try également, juste au cas où il y aurait des erreurs d'E / S passagères lors de la suppression.
Mark Lapierre
Je ne peux pas croire que cela soit même nécessaire. Putain, Powershell est nul!
décembre
3

Pour supprimer le répertoire et son contenu, vous devez suivre deux étapes. Supprimez d'abord le contenu, puis le dossier lui-même. En utilisant la solution de contournement pour l'élément de suppression récursif défectueux, la solution ressemblerait à ceci:

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

De cette façon, vous pouvez également supprimer le répertoire parent.

Carl Baker
la source
1
C'est exactement ce que la réponse acceptée a dit. Avez-vous quelque chose à ajouter?
Michael Hampton
1
Ils font remarquer que la réponse acceptée ne supprime pas le répertoire lui-même et qu’elle prend donc deux étapes.
Paul George
2
La Remove-Itemcommande dans laquelle votre tuyauterie a le même problème que celui énoncé à l’origine. Il pourrait tomber sur un élément de répertoire non vide de la même manière.
Dejan
@Dejan Ce répertoire ne peut toujours pas être vide si la première ligne de ce code fonctionne, n'est-ce pas?
Ifedi Okonkwo
1
Bien que cela puisse réduire le risque d'échec, il peut toujours échouer, étant donné que cela Remove-Item -Recurseest toujours en cause. Le problème sous-jacent existe toujours à partir de Windows PowerShell version 5.1 / PowerShell Core 6.2.0-preview.1 - voir ce rapport de bogue .
Mklement
3

Gosh. Beaucoup de réponses. Honnêtement, je préfère celui-ci à tous. C'est super simple, complet, lisible et fonctionne sur n'importe quelle machine Windows. Il utilise la fonctionnalité de suppression récursive (fiable) de .NET et en cas d'échec pour une raison quelconque, il génère une exception appropriée pouvant être gérée avec un bloc try / catch.

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

Notez que la Resolve-Pathligne est importante car .NET ne connaît pas votre répertoire actuel lors de la résolution des chemins de fichiers relatifs. C'est à peu près le seul casse-tête auquel je puisse penser.

Phil
la source
2

C'est ce que je travaille:

$Target = "c:\folder_to_delete"

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

Remove-Item -Recurse -Force $Target

Cette première ligne supprime tous les fichiers de l'arborescence. La seconde supprime tous les dossiers, y compris le haut.

James Copeland
la source
Bien que cela puisse réduire le risque d'échec, il peut toujours échouer, étant donné que cela Remove-Item -Recurseest toujours en cause. Le problème sous-jacent existe toujours à partir de Windows PowerShell version 5.1 / PowerShell Core 6.2.0-preview.1 - voir ce rapport de bogue .
Mklement
0

J'ai eu ce problème avec un répertoire qui ne serait pas supprimer. J'ai constaté qu'un des sous-dossiers était corrompu et lorsque j'ai essayé de déplacer ou de renommer ce répertoire enfant, un message d'erreur indiquant que quelque chose manquait était affiché. J'ai essayé d'utiliser rm-Force et j'ai eu la même erreur que vous.

Ce qui a fonctionné pour moi, c’était de compresser le répertoire parent à l’aide de 7-zip avec l’option "Supprimer les fichiers après compression" cochée. Une fois compressé, j'ai pu supprimer le fichier zip.

RedDawnRising
la source