Monday, April 5, 2010

How To: Use Recursion in .NET To Delete All Files and Directories in .NET

Have you ever needed to have some code delete all of the folders and files below a given directory? Maybe the reason is for an automated cleanup that runs on schedule, or just cleaning up cache files after a given period of time. Whatever the reason, a good way to do this in .NET is to write some methods and call them recursively to delete out all files in the lowest level directory, making its way back up to the top level. And of course deleting the directories (folders) themselves after all files within are deleted.

At this point you may be saying, "Can’t I just write some code to iterate through all of the directories, and delete the directories and files in a single operation?" Well you could try it by just calling the basic overload for .Delete(), but you would quickly come across the following exception being thrown:

"The directory is not empty."

This is because the default overload of the .Delete() method of the directory object, will not let you delete it if there are any files still within that directory. Regardless, you may actually need to observe each file prior to deletion to examine its creation date, file attributes, etc. prior to deletion and this is not possible when the single delete is done on the entire directory and all of its files. Therefore we must traverse downward through all directories, deleting out all of the files within before deleting the containing directory. This can all be done using recursion and some operations from the System.IO namespace in .NET.


You may have also gone down this path, but hit another bump when you received the following exception upon deleting a file:

"Access to the path 'MyFile.doc' is denied."

This occurs because read-only files can also not be deleted; we will take care of this by changing the file attributes to not be 'Read-Only' prior to deletion. This however brings up an important last point: many times file deletion regardless of file attributes requires an elevated permission. Therefore you may need to wrap the following code using impersonation of an elevated account, or just make sure that the directories and files being deleted have the proper permissions allowed at the root level. Either solution will work fine.


I want to emphasize this entry more around how to use recursion in operations such as deleting files, but not focus too heavily on the file delete example. Recursion is a powerful and often overlooked method of writing good streamlined clean code. I find those that were formally educated in Software Engineering or Computer Science are well familiar with concepts like recursion, where as the Barnes and Noble book learned developer may have not used some of these methods before. That's perfectly fine, and hopefully this will help you understand recursion better and be able to use it in the future. In fact, if you are 100% only interested in deleting all files in a directory without needing the ability to observe each file prior to deletion then you can provide a boolean value to the 2nd parameter in the overload of the 'Delete()' method that will delete all files in a directory before deleting the directory for you all in 1 line of code shown below:

System.IO.Directory.Delete("C:\TempTest", True)
So without further ado, let's get to the code. It has quite a bit of commenting injected to help explain the process. You may want to test it out on a directory that has multiple levels and files, before using it on anything live to make sure you have your version working properly.


Public Sub TestDelete()

'Get an object repesenting the directory path below
Dim di As New DirectoryInfo("C:\MyTestDirectory")

'Traverse all of the child directors in the root; get to the lowest child
'and delte all files, working our way back up to the top. All files
'must be deleted in the directory, before the directory itself can be deleted.
For Each diChild As DirectoryInfo In di.GetDirectories()
TraverseDirectory(diChild)
Next

'Finally, clean all of the files directly in the root directory
CleanAllFilesInDirectory(di)

End Sub

'''
''' A method to traverse down through child directories until
''' we have reached the lowest level and then clean (delete) all
''' files before deleting the directory itself.
'''

'''
''' All files must be deleted in a directory prior to deleting the
''' directory itself to prevent the following exception:
''' "The directory is not empty."
'''

Private Sub TraverseDirectory(ByVal di As DirectoryInfo)

'If the current directory has more child directories, then continure
'to traverse down until we are at the lowest level. At that point all of the
'files will be deleted.
For Each diChild As DirectoryInfo In di.GetDirectories()
TraverseDirectory(diChild)
Next

'Now that we have no more child directories to traverse, delete all of the files
'in the current directory, and then delete the directory itself.
CleanAllFilesInDirectory(di)


'The containing directory can only be deleted if the directory
'is now completely empty and all files previously within
'were deleted.
If di.GetFiles().Count = 0 Then
di.Delete()
End If

End Sub

'''
''' Iterates through all files in the directory passed into
''' method and deletes them.
'''

'''
''' It may be necessary to wrap this call in impersonation or ensure parent directory
''' permissions prior, because delete permissions are not guaranteed.
'''

Private Sub CleanAllFilesInDirectory(ByVal DirectoryToClean As DirectoryInfo)

For Each fi As FileInfo In DirectoryToClean.GetFiles()
'The following code is NOT required, but shows how some logic can be wrapped
'around the deletion of files. For example, only delete files with
'a creation date older than 1 hour from the current time. If you
'always want to delete all of the files regardless, just remove
'the next 'If' statement.
If fi.CreationTime < Now.Subtract(New TimeSpan(0, 0, 1)) Then
'Read only files can not be deleted, so mark the attribute as 'IsReadOnly = False'
fi.IsReadOnly = False
fi.Delete()

'On a rare occasion, files being deleted might be slower than program execution, and upon returning
'from this call, attempting to delete the directory will throw an exception stating it is not yet
'empty, even though a fraction of a second later it actually is. Therefore the 'Optional' code below
'can stall the process just long enough to ensure the file is deleted before proceeding. The value
'can be adjusted as needed from testing and running the process repeatedly.
System.Threading.Thread.Sleep(50) '50 millisecond stall (0.05 Seconds)
End If
Next
End Sub


This code should be almost 'copy and paste' ready to run in your application. The main modification needed is to change the root directory referenced in the initial DirectoryInfo object. Also notice the optional code I added to show how you can wrap the file deletion in conditional code stating to only delete files older than 1 hour. This is not required, but shows you how the code can easily be modified.

4 comments:

  1. To clean up a directory using LINQ:

    DirectoryInfo di = new DirectoryInfo(dirToClean);
    di.GetDirectories().ToList().ForEach(d => d.Delete(true));
    di.GetFiles().ToList().ForEach(f => f.Delete());

    ReplyDelete
  2. Very nice, but surely we should get rid of the files before the directories?

    DirectoryInfo di = new DirectoryInfo(dirToClean);
    di.GetFiles().ToList().ForEach(f => f.Delete());
    di.GetDirectories().ToList().ForEach(d => d.Delete(true));

    ReplyDelete
  3. Allen, this is great! I had some additional questions and your "contact me" doesn't work on this page...how can I contact you to ask my questions?

    ReplyDelete
  4. Thank you providing this. It assisted on some work I was doing on directories. This saved me a ton of time trying figure what needed to be in various sections. I especially appreciate your use of recursion.

    ReplyDelete