How do I delete a directory with read-only files in C#?
Asked Answered
A

11

62

I need to delete a directory that contains read-only files. Which approach is better:

  • Using DirectoryInfo.Delete(), or,

  • ManagementObject.InvokeMethod("Delete")?

With DirectoryInfo.Delete(), I have to manually turn off the read-only attribute for each file, but ManagementObject.InvokeMethod("Delete") doesn't appear to need to. Is there any situation where one is more preferable to the other?

Sample code (test.txt is read only).

First way:

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);

Second way:

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)
    {
    }
}
Ansley answered 4/3, 2009 at 18:47 Comment(0)
S
98

Here is an extension method which sets Attributes to Normal recursively, then deletes the items:

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();
}
Sentiment answered 15/3, 2009 at 16:10 Comment(4)
I have a situation where using this code deletes all the files and directories. Then when the process exits two empty folders reappear. I delete them manual and they stay disappeared.Overkill
I've added this answer in the comments of a handful of other similar questions that are using the removal of RO instead of setting to normal. Your method is better.Sim
I had to add this line of code just before the Delete() operation: fileSystemInfo.Refresh(). Otherwise I got an Exception saying the directory is not empty.Klatt
Note that we need to skip directory logic when the fileSystemInfo is symbolic link or junction. So I recommend that checking fileSystemInfo.Attributes.HasFlag(FileAttributes.ReparsePoint) before calling GetFileSystemInfos().Ilarrold
M
105

Simplest way of avoiding recursive calls is by utilising the AllDirectories option when getting FileSystemInfos, like so:

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);
}
Menton answered 3/1, 2012 at 15:12 Comment(1)
The SearchOption argument was added to GetFileSystemInfos with.NET 4.0.Lohse
S
98

Here is an extension method which sets Attributes to Normal recursively, then deletes the items:

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();
}
Sentiment answered 15/3, 2009 at 16:10 Comment(4)
I have a situation where using this code deletes all the files and directories. Then when the process exits two empty folders reappear. I delete them manual and they stay disappeared.Overkill
I've added this answer in the comments of a handful of other similar questions that are using the removal of RO instead of setting to normal. Your method is better.Sim
I had to add this line of code just before the Delete() operation: fileSystemInfo.Refresh(). Otherwise I got an Exception saying the directory is not empty.Klatt
Note that we need to skip directory logic when the fileSystemInfo is symbolic link or junction. So I recommend that checking fileSystemInfo.Attributes.HasFlag(FileAttributes.ReparsePoint) before calling GetFileSystemInfos().Ilarrold
T
10

Try this,

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);
}
Tartrazine answered 9/6, 2010 at 19:15 Comment(1)
FileInfo fi is uselessWinfredwinfrey
A
6

Another method without the need for recursion.

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);
}
Amey answered 21/11, 2009 at 19:50 Comment(0)
M
4
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();
}
Mozellamozelle answered 7/2, 2011 at 12:49 Comment(1)
Great solution, I stole it from you and just added a check before the dirInfo.Delete(); to see if the directory itself was read-only, would throw an exception in this case. ThanksCerography
I
3

The best solution is to mark all the files as non-read only, and then delete the directory.

// 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));

Notice that ~ is a Bitwise logical operator which returns the complement of the given binary value. I haven't tested this, but it should work.

Thanks!

Indubitability answered 4/3, 2009 at 18:47 Comment(0)
H
1

I would say that your first approach looks more explicit and readable. The second method smells like reflection, is not type safe and looks weird. The ManagementObject can represent multiple things, so it's not obvious that .InvokeMethod("Delete") actually deletes a directory.

Hintz answered 5/3, 2009 at 0:6 Comment(0)
A
0

On the surface, using the WMI approach seems more efficient than iterating over the entire file system (assume for example the directory has 10's of thousands of files). But I do not know that WMI also doesn't do iterations. If it does, being closer to the metal (again, assumptions) it should be more efficient.

For elegance, I concede the recursive method is cool.

Performance testing should answer the efficiency question. And either can be elegant if wrapped in an extension method of DirectoryInfo.

Addam answered 8/6, 2009 at 0:37 Comment(0)
D
0

Here is another solution that avoids recursion on itself.

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);
}

This works by resettings attributes on the folders and files before the delete, so you could just remove the last line for a 'DirectoryResetAttributes' method and use delete separately.

On a related note, while this worked, I then had issues with deleting paths that were 'too long' and ended up using a robocopy solution posted here: C# deleting a folder that has long paths

Downhill answered 19/10, 2011 at 14:7 Comment(0)
V
0

To follow up on Vitaliy Ulantikov's solution I have supplemented it with a rename/move folder method:

  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();
  }
Vo answered 28/2, 2013 at 6:18 Comment(0)
W
0

My solution for NET framework 3.5 and for NET framework version 4 and higher:

#region DeleteWithReadOnly
internal static void DeleteWithReadOnly(this DirectoryInfo di)
{
   foreach (FileSystemInfo fsi in di.EnumerateFileSystemInfos("*", SearchOption.AllDirectories))
   {
      fsi.Attributes = FileAttributes.Normal;
   }
   di.Delete(true);
}
#endregion

#region DeleteWithReadOnlyNET3_5
internal static void DeleteWithReadOnlyNET3_5(this DirectoryInfo di)
{
   foreach (FileSystemInfo fsi in di.GetFiles("*", SearchOption.AllDirectories))
   {
      fsi.Attributes = FileAttributes.Normal;
   }
   di.Delete(true);
}
#endregion

Usage:

DirectoryInfo di = new DirectoryInfo(@"C:\TMP");
di.DeleteWithReadOnly();
Wamble answered 15/3, 2021 at 9:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.