Como faço para excluir um diretório com arquivos somente leitura em C #?
-
03-07-2019 - |
Pergunta
Eu preciso excluir um diretório que contém arquivos somente leitura. Qual abordagem é melhor:
-
Usando
DirectoryInfo.Delete()
, ou, -
ManagementObject.InvokeMethod("Delete")
?
Com DirectoryInfo.Delete()
, eu tenho que desligar manualmente o atributo somente leitura para cada arquivo, mas ManagementObject.InvokeMethod("Delete")
não parece necessário. Existe alguma situação em que um é mais preferível para o outro?
Exemplo de código (test.txt é somente leitura).
Primeira maneira:
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);
A segunda maneira:
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)
{
}
}
Solução
Aqui está um método de extensão que conjuntos Attributes
para Normal
de forma recursiva, em seguida, exclui os itens:
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();
}
Outras dicas
Maneira mais simples de evitar chamadas recursivas é utilizando a opção AllDirectories
quando chegar FileSystemInfo
s, assim:
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);
}
Tente este,
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);
}
Outro método sem a necessidade de recursão.
public static void ForceDeleteDirectory(string path)
{
DirectoryInfo root;
Stack<DirectoryInfo> fols;
DirectoryInfo fol;
fols = new Stack<DirectoryInfo>();
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();
}
A melhor solução é para marcar todos os arquivos apenas como não-lido, e depois excluir o diretório.
// 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 ~ é um operador lógico bit a bit que retorna o complemento do valor dado binário. Eu não testei isso, mas ele deve funcionar.
Obrigado!
Eu diria que a sua primeira abordagem parece mais explícita e legível. O segundo método cheira a reflexão, não é tipo seguro e parece estranho. O ManagementObject
pode representar várias coisas, por isso não é óbvio que .InvokeMethod("Delete")
realmente exclui um diretório.
A única coisa que eu não gosto sobre a primeira abordagem (directory.delete) é o caso onde há subdiretórios que também contêm somente ler arquivos, e eles têm subdiretórios que têm ficheiros só de leitura, bem como, e assim em. Parece que você tem que desligar essa bandeira para cada arquivo no diretório e todos os subdiretórios recursivamente.
Com a segunda abordagem, você pode simplesmente excluir esse primeiro diretório, e não verifica se os arquivos são somente leitura. No entanto, esta é a primeira vez que eu usei WMI no C #, então eu não sou tudo o que confortável com ele. Então, eu tenho certeza quando ir com a abordagem WMI para outras aplicações, em vez de apenas usando os métodos System.IO.
Na superfície, usando a abordagem WMI parece mais eficiente do que a iteração sobre todo o sistema de arquivos (assumir por exemplo, o diretório tem 10 dos milhares de arquivos). Mas eu não sei que WMI também não faz iterações. Se isso acontecer, aproximando-se o metal (novamente, suposições) deve ser mais eficiente.
Para elegância, reconheço o método recursivo é legal.
O teste de desempenho deve responder à pergunta eficiência. E qualquer um pode ser elegante, se envolveu em um método de extensão de DirectoryInfo.
Aqui é outra solução que evita a recursão sobre si mesmo.
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);
}
Isso funciona por reposições atributos nas pastas e arquivos antes da eliminação, assim que você poderia apenas remover a última linha para o método e usar um 'DirectoryResetAttributes' eliminar separadamente.
Em uma nota relacionada, enquanto isso funcionou, então eu tive problemas com exclusão de caminhos que foram 'muito tempo' e acabou usando uma solução robocopy postado aqui: C # apagar uma pasta que tem longos caminhos
Para acompanhar a solução de Vitaliy Ulantikov tenho-a complementado com um método de pasta Renomear / Mover:
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();
}