Forzar la eliminación de archivos y directorios en PowerShell falla a veces, pero no siempre

33

Estoy tratando de eliminar un directorio de forma recursiva rm -Force -Recurse somedirectory, obtengo varios errores "El directorio no está vacío". Si reintento el mismo comando , tiene éxito.

Ejemplo:

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>

Por supuesto, esto no siempre sucede . Además, no ocurre solo con _svndirectorios, y no tengo un caché TortoiseSVN ni nada de eso, así que nada está bloqueando el directorio.

¿Algunas ideas?

Mauricio Scheffer
fuente

Respuestas:

31

help Remove-Item dice:

El parámetro Recurse en este cmdlet no funciona correctamente.

y

Debido a que el parámetro Recurse en este cmdlet es defectuoso, el comando usa el cmdlet Get-Childitem para obtener los archivos d deseados, y usa el operador de canalización para pasarlos al cmdlet Remove-Item.

y propone esta alternativa como ejemplo:

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

Por lo que debe tubería get-childitem -recurseen remove-item.

Pausado hasta nuevo aviso.
fuente
Gracias. Acabo de encontrar este hilo de 2006: vistax64.com/powershell/… parece que Microsoft no está realmente interesado en solucionar esto.
Mauricio Scheffer
@mausch: vea esta referencia más reciente, pero aún sin resolver: Remove-Item -Recurse
hasta nuevo aviso.
Si realiza un recorrido y elimina, primero deberá atravesar los directorios secundarios y primero sus archivos.
fschwiet
2
Al menos la documentación dice que no funciona.
derekerdmann el
66
Tuve que poner ambas banderas -force -recurse para Remove-Item, de lo contrario me mantuvo preguntando "por favor confirme" Get-ChildItem -Path $ Destination -Recurse | Remove-Item -force -recurse
MiFreidgeim SO-deja de ser malvado
17

@JamesCW: el problema aún existe en PowerShell 4.0

Intenté otra solución y funcionó: use cmd.exe:

&cmd.exe /c rd /s /q $somedirectory
Mehrdad Mirreza
fuente
1
Buena vieja rd / s / q!
JamesCW
He probado todas las variaciones de Get-ChildItem; reintentar bucles; llamando iisresetantes de eliminar y nada parece funcionar de manera confiable . Voy a probar este, aunque cuando lo vi por primera vez me resistí a tener DOS dentro de mi Powershell ...
Peter McEvoy
Desafortunadamente, también rd /sfalla intermitentemente (aunque aparentemente con menos frecuencia que Remove-Item): github.com/Microsoft/console/issues/309
mklement
No me gusta el corte por la c para mí. ¿Tiene que precederlo mediante powershell -command y comillas simples la parte cmd.exe? Obtengo "Debe proporcionar una expresión de valor después del operador '/'". "Token inesperado 'c' en expresión o enunciado. Es lo mismo con el comando powershell frente a él. ¿Necesita / escapar?
Michele
7

ETA 20181217: PSVersion 4.0 y versiones posteriores todavía fallarán en algunas circunstancias, vea la respuesta alternativa de Mehrdad Mirreza y el informe de error presentado por mklement

mklement proporciona una solución de prueba de concepto en esta respuesta SO , ya que el error está esperando una solución oficial

La nueva versión de PowerShell( PSVersion 4.0) ha resuelto este problema por completo y Remove-Item "targetdirectory" -Recurse -Forcefunciona sin problemas de sincronización.

Puede verificar su versión ejecutándose $PSVersiontabledesde el ISE o el PowerShellindicador. 4.0 es la versión que se incluye con Windows 8.1y Server 2012 R2, y también se puede instalar en versiones anteriores de Windows.

JamesCW
fuente
55
Todavía ocurre para mí en PowerShell 4.0
ajbeaven
10
Todavía ocurre en PowerShell v5 !!!!! 11 !! 1! 1 !!!
Richard Hauer el
@RichardHauer bueno, ahora estoy confundido
JamesCW
2
@JamesCW He convertido a la rdversión. Además de funcionar, es aproximadamente 3 veces más rápido
Richard Hauer, el
El problema no se ha solucionado a partir de Windows PowerShell v5.1 / PowerShell Core 6.2.0-preview.1: consulte este informe de errores . Si bien rd /spuede fallar con menos frecuencia, también está roto: consulte este informe de error .
Mklement
4

Actualización : Al parecer, hay planes para hacer que las API de eliminación de elementos del sistema de archivos de Windows sean síncronas, pero aún no están sincronizadas a partir de la versión 1903 de Windows 10: vea este comentario en GitHub .


Las respuestas existentes mitigan el problema, por lo que ocurre con menos frecuencia, pero no abordan la causa raíz , por lo que todavía pueden ocurrir fallas.

Remove-Item -Recursees inesperadamente asíncrono , en última instancia, porque los métodos API de Windows para la eliminación de archivos y directorios son inherentemente asíncronos y Remove-Itemno tienen en cuenta eso.

Esto se manifiesta de manera intermitente e impredecible de una de dos maneras:

  • Su caso: la eliminación de un directorio no vacío en sí mismo puede fallar, si la eliminación de un subdirectorio o archivo en él aún no se ha completado para cuando se intente eliminar el directorio padre.

  • Con menos frecuencia: la recreación de un directorio eliminado inmediatamente después de la eliminación puede fallar, ya que es posible que la eliminación aún no se haya completado para cuando se intente la recreación.

El problema no solo afecta a PowerShell Remove-Item, sino también a cmd.exelos de rd /s.NET[System.IO.Directory]::Delete() :

A partir de Windows PowerShell v5.1 / PowerShell Core 6.2.0-preview.1 / cmd.exe10.0.17134.407 / .NET Framework 4.7.03056, .NET Core 2.1, ni Remove-Item, ni rd /s, ni [System.IO.Directory]::Delete()funcionan de manera confiable , ya que no tienen en cuenta el asíncrono comportamiento de las funciones de eliminación de directorio / archivo API de Windows :

Para obtener una función personalizada de PowerShell que brinde una solución sincronizada confiable , vea esta respuesta SO .

mklement
fuente
Al manejar archivos donde la eliminación es segura:while($true) { if ( (Remove-Item [...] *>&1) -ne $null) { Start-Sleep 0.5 } else { break } }
Farway
3

La respuesta actual en realidad no eliminará un directorio, solo sus hijos. Además, tendrá problemas con los directorios anidados, ya que nuevamente intentará eliminar un directorio antes de su contenido. Escribí algo para eliminar los archivos en el orden correcto, todavía tendría el mismo problema, aunque a veces el directorio seguiría existiendo después.

Entonces, ahora uso algo que detectará la excepción, espere y vuelva a intentarlo (3 veces):

Por ahora estoy usando esto:

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
fuente
1
Esto es bueno pero todavía tuve problemas con eso. Si el comando mkdir se ejecuta antes de que el sistema complete el comando rm, puede lanzar un System.UnauthorizedAccessException con un FullyQualifiedErrorId de ItemExistsUnauthorizedAccessError. Es decir, el sistema operativo todavía no ha eliminado el directorio (en mi HDD lento). Por lo tanto, ese error también debe detectarse. Y es un error que no termina, por lo que ErrorAction debe establecerse en Stop. También puse el comando rm en el bloque try también, solo en caso de que haya errores transitorios de E / S al eliminar.
Mark Lapierre
No puedo creer que esto deba hacerse. ¡Maldición, Powershell apesta!
jcollum
3

Para eliminar el directorio y su contenido se requieren dos pasos. Primero elimine el contenido, luego la carpeta misma. Usando la solución para el elemento de eliminación recursivo defectuoso, la solución se vería así:

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

De esta manera, también puede eliminar el directorio principal.

Carl Baker
fuente
1
Esto es exactamente lo que dice la respuesta aceptada. ¿Tienes algo que añadir?
Michael Hampton
1
Señalan que la respuesta aceptada no elimina el directorio en sí mismo, por lo tanto, toma dos pasos.
Paul George
2
El Remove-Itemcomando en el que está entrando la tubería tiene el mismo problema que el indicado originalmente. Podría tropezar con un elemento de directorio que no esté vacío de la misma manera.
Dejan
@Dejan Este directorio aún no podría estar vacío si la primera línea de este código funcionara, ¿verdad?
Ifedi Okonkwo
1
Si bien esto puede disminuir la probabilidad de falla, aún puede fallar, dado que Remove-Item -Recursetodavía está involucrado. El problema subyacente aún existe a partir de Windows PowerShell v5.1 / PowerShell Core 6.2.0-preview.1: consulte este informe de errores .
Mklement
3

Gosh Muchas respuestas Sinceramente, prefiero este sobre todos ellos. Es súper simple, completo, legible y funciona en cualquier máquina con Windows. Utiliza la funcionalidad de eliminación recursiva (confiable) de .NET y si falla por algún motivo, arroja una excepción adecuada que puede manejarse con un bloque try / catch.

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

Tenga en cuenta que la Resolve-Pathlínea es importante porque .NET no conoce su directorio actual cuando resuelve rutas de archivos relativas. Ese es el único problema que se me ocurre.

Phil
fuente
2

Esto es lo que tengo trabajando:

$Target = "c:\folder_to_delete"

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

Remove-Item -Recurse -Force $Target

Esta primera línea elimina todos los archivos en el árbol. El segundo elimina todas las carpetas, incluida la parte superior.

James Copeland
fuente
Si bien esto puede disminuir la probabilidad de falla, aún puede fallar, dado que Remove-Item -Recursetodavía está involucrado. El problema subyacente aún existe a partir de Windows PowerShell v5.1 / PowerShell Core 6.2.0-preview.1: consulte este informe de errores .
Mklement
0

Tuve este problema con un directorio que no se eliminaría. Descubrí que una de las subcarpetas estaba dañada y cuando intenté mover o cambiar el nombre de ese directorio secundario, recibí un mensaje de error que decía que faltaba algo. Intenté usar rm -Force y obtuve el mismo error que tú.

Lo que funcionó para mí fue comprimir el directorio principal usando 7-zip con la opción "Eliminar archivos después de la compresión" marcada. Una vez comprimido, pude eliminar el archivo zip.

RedDawnRising
fuente