¿Cómo elimino un directorio con archivos de solo lectura en C #?

Necesito eliminar un directorio que contiene archivos de solo lectura. Qué enfoque es mejor:

  • Usando DirectoryInfo.Delete() , o,

  • ManagementObject.InvokeMethod("Delete") ?

Con DirectoryInfo.Delete() , tengo que desactivar manualmente el atributo de solo lectura para cada archivo, pero ManagementObject.InvokeMethod("Delete") no parece necesitarlo. ¿Hay alguna situación en la que uno es más preferible que el otro?

Código de muestra (test.txt es de solo lectura).

Primera forma:

 DirectoryInfo dir = new DirectoryInfo(@"C:\Users\David\Desktop\"); dir.CreateSubdirectory("Test"); DirectoryInfo test = new DirectoryInfo(@"C:\Users\David\Desktop\Test\"); File.Copy(@"C:\Users\David\Desktop\test.txt", @"C:\Users\David\Desktop\Test\test.txt"); File.SetAttributes(@"C:\Users\David\Desktop\Test\test.txt", FileAttributes.Archive); test.Delete(true); 

Segunda forma:

 DirectoryInfo dir = new DirectoryInfo(@"C:\Users\David\Desktop\"); dir.CreateSubdirectory("Test"); DirectoryInfo test = new DirectoryInfo(@"C:\Users\David\Desktop\Test\"); File.Copy(@"C:\Users\David\Desktop\test.txt", @"C:\Users\David\Desktop\Test\test.txt"); string folder = @"C:\Users\David\Desktop\Test"; string dirObject = "Win32_Directory.Name='" + folder + "'"; using (ManagementObject managementObject = new ManagementObject(dirObject)) { managementObject.Get(); ManagementBaseObject outParams = managementObject.InvokeMethod("Delete", null, null); // ReturnValue should be 0, else failure if (Convert.ToInt32(outParams.Properties["ReturnValue"].Value) != 0) { } } 

Aquí hay un método de extensión que establece Attributes en Normal recursivamente, luego elimina los elementos:

 public static void DeleteReadOnly(this FileSystemInfo fileSystemInfo) { var directoryInfo = fileSystemInfo as DirectoryInfo; if (directoryInfo != null) { foreach (FileSystemInfo childInfo in directoryInfo.GetFileSystemInfos()) { childInfo.DeleteReadOnly(); } } fileSystemInfo.Attributes = FileAttributes.Normal; fileSystemInfo.Delete(); } 

La forma más simple de evitar llamadas recursivas es mediante la utilización de la opción AllDirectories al obtener FileSystemInfo s, así:

 public static void ForceDeleteDirectory(string path) { var directory = new DirectoryInfo(path) { Attributes = FileAttributes.Normal }; foreach (var info in directory.GetFileSystemInfos("*", SearchOption.AllDirectories)) { info.Attributes = FileAttributes.Normal; } directory.Delete(true); } 

Prueba esto,

 private void DeleteRecursiveFolder(string pFolderPath) { foreach (string Folder in Directory.GetDirectories(pFolderPath)) { DeleteRecursiveFolder(Folder); } foreach (string file in Directory.GetFiles(pFolderPath)) { var pPath = Path.Combine(pFolderPath, file); FileInfo fi = new FileInfo(pPath); File.SetAttributes(pPath, FileAttributes.Normal); File.Delete(file); } Directory.Delete(pFolderPath); } 

Otro método sin la necesidad de recursión.

 public static void ForceDeleteDirectory(string path) { DirectoryInfo root; Stack fols; DirectoryInfo fol; fols = new Stack(); root = new DirectoryInfo(path); fols.Push(root); while (fols.Count > 0) { fol = fols.Pop(); fol.Attributes = fol.Attributes & ~(FileAttributes.Archive | FileAttributes.ReadOnly | FileAttributes.Hidden); foreach (DirectoryInfo d in fol.GetDirectories()) { fols.Push(d); } foreach (FileInfo f in fol.GetFiles()) { f.Attributes = f.Attributes & ~(FileAttributes.Archive | FileAttributes.ReadOnly | FileAttributes.Hidden); f.Delete(); } } root.Delete(true); } 
 private void DeleteRecursiveFolder(DirectoryInfo dirInfo) { foreach (var subDir in dirInfo.GetDirectories()) { DeleteRecursiveFolder(subDir); } foreach (var file in dirInfo.GetFiles()) { file.Attributes=FileAttributes.Normal; file.Delete(); } dirInfo.Delete(); } 

La mejor solución es marcar todos los archivos como no solo de lectura, y luego eliminar el directorio.

 // delete/clear hidden attribute File.SetAttributes(filePath, File.GetAttributes(filePath) & ~FileAttributes.Hidden); // delete/clear archive and read only attributes File.SetAttributes(filePath, File.GetAttributes(filePath) & ~(FileAttributes.Archive | FileAttributes.ReadOnly)); 

Observe que ~ es un operador lógico Bitwise que devuelve el complemento del valor binario dado. No lo he probado, pero debería funcionar.

¡Gracias!

Diría que su primer enfoque parece más explícito y legible. El segundo método huele a reflection, no es seguro y se ve raro. El ManagementObject puede representar varias cosas, por lo que no es obvio que. .InvokeMethod("Delete") realidad borre un directorio.

Lo que no me gusta del primer enfoque (directory.delete) es el caso donde hay subdirectorios que también contienen archivos de solo lectura, y también tienen subdirectorios que también tienen archivos de solo lectura, y así sucesivamente. Parece que tendrías que desactivar esa bandera para cada archivo en el directorio y todos los subdirectorios recursivamente.

Con el segundo enfoque, puede simplemente eliminar ese primer directorio y no verifica si los archivos son de solo lectura. Sin embargo, esta es la primera vez que uso WMI en C #, así que no estoy tan cómodo con eso. Así que no estoy seguro de qué hacer con el enfoque WMI para otras aplicaciones, en lugar de simplemente usar los métodos System.IO.

En la superficie, usar el enfoque WMI parece más eficiente que iterar sobre todo el sistema de archivos (supongamos, por ejemplo, que el directorio tiene 10 de miles de archivos). Pero no sé que WMI tampoco hace iteraciones. Si lo hace, estar más cerca del metal (nuevamente, suposiciones) debería ser más eficiente.

Por elegancia, reconozco que el método recursivo es genial.

Las pruebas de rendimiento deben responder a la pregunta de eficiencia. Y cualquiera puede ser elegante si está envuelto en un método de extensión de DirectoryInfo.

Aquí hay otra solución que evita la recursividad en sí misma.

 public static void DirectoryDeleteAll(string directoryPath) { var rootInfo = new DirectoryInfo(directoryPath) { Attributes = FileAttributes.Normal }; foreach (var fileInfo in rootInfo.GetFileSystemInfos()) fileInfo.Attributes = FileAttributes.Normal; foreach (var subDirectory in Directory.GetDirectories(directoryPath, "*", SearchOption.AllDirectories)) { var subInfo = new DirectoryInfo(subDirectory) { Attributes = FileAttributes.Normal }; foreach (var fileInfo in subInfo.GetFileSystemInfos()) fileInfo.Attributes = FileAttributes.Normal; } Directory.Delete(directoryPath, true); } 

Esto funciona mediante los atributos de restablecimiento en las carpetas y archivos antes de la eliminación, por lo que podría eliminar la última línea para un método ‘DirectoryResetAttributes’ y usar eliminar por separado.

En una nota relacionada, mientras esto funcionó, tuve problemas para eliminar rutas que eran ‘demasiado largas’ y terminé usando una solución robocopy publicada aquí: C # eliminando una carpeta que tiene rutas largas

Para dar seguimiento a la solución de Vitaliy Ulantikov lo he complementado con un método de cambio de nombre / mover carpeta:

  public static void renameFolder(String sourcePath, String targetPath) { try { if (System.IO.Directory.Exists(targetPath)) DeleteFileSystemInfo(new DirectoryInfo(targetPath)); System.IO.Directory.Move(sourcePath, targetPath); } catch (Exception ex) { Console.WriteLine("renameFolder: " + sourcePath + " " + targetPath + " " + ex.Message); throw ex; } } private static void DeleteFileSystemInfo(FileSystemInfo fsi) { fsi.Attributes = FileAttributes.Normal; var di = fsi as DirectoryInfo; if (di != null) { foreach (var dirInfo in di.GetFileSystemInfos()) { DeleteFileSystemInfo(dirInfo); } } fsi.Delete(); }