Introduction
The standard .NET file access API raises an UnauthorizedAccessException
when recursively traversing the file system, making it difficult to enumerate large directory structures. Microsoft notes that this is by design (Directory.EnumerateDirectory etc. unusable due to frequent UnauthorizedAccessExceptions (even runas Administrator)[^]), but most developers would likely prefer it to function more like good old fashioned DOS's "DIR /A/S/B". This code functions similar to the latter.
Background
This code was originally written by BlueMonkMN for the String file and directory enumerators (stackoverflow[^]), which work great. The code posted here extends that idea to use the FileSystemInfo
enumerators to get more file details. If you only need string
details, use the original code. If you need full file details, use this code.
Using the Code
Review the attached test cases for more detailed usage, but basic usage is similar to the standard .NET API:
var dir = new DirectoryInfo(@"C:\MyFolder");
var sfe = new SafeFileEnumerator(dir, "*", SearchOption.AllDirectories);
foreach (var fsi in sfe){
if (fsi is FileInfo){
var fi = fsi as FileInfo;
Debug.Write(String.Format("File: {0}: {1}", fi.Name, fi.Length));
} else if (fsi is DirectoryInfo){
Debug.Write(String.Format("Folder: {0}", fsi.FullName));
}
}
As tested out in the UnitTests, this example explains what is accomplished by this code. When recursively scanning the root folder "UnitTest1", we should return all folders except "Sub4", and all files except files in folders "Sub3" and "Sub4". The key difference between this code and the stock .NET code is that the stock code will not return folders "Sub6" and "Sub8".
UnitTest1
Sub1
Sub2
Sub5
Sub7
Sub3 <-- Denied Access
Sub4
Sub6
Sub8
Points of Interest
There are some differences between this code and the original String version of the code, including:
- Checks for
UnauthorizedAccessExceptions
on the root folder.
- Has
Abort()
function that is handy for canceling large background searches.
- Cleanup moved to
private Dispose()
function to centralize how cleanups are done.
The key part of the code, recursively creating two enumerators for each directory structure so you know where to continue from an error, is logically identical to the original code. The most noteworthy part is the MoveNext()
function.
public bool MoveNext(){
try
{
if ((fileEnumerator != null) && (fileEnumerator.MoveNext()))
return true;
}
catch (PathTooLongException){}
while ((directoryEnumerator != null) && (directoryEnumerator.MoveNext())) {
Dispose(true, false);
try {
fileEnumerator = new SafeFileEnumerator(
directoryEnumerator.Current,
pattern,
SearchOption.AllDirectories,
errors
).GetEnumerator();
} catch (Exception ex){
errors.Add(ex);
continue;
}
if (fileEnumerator.MoveNext())
return true;
}
Dispose(true, true);
return false;
}
History
- 11th December, 2013: Initial public draft
- 20th July 2014: Added some additional catch blocks to check for PathTooLong exceptions. Encapsulated the "Smother Errors" routine in to a single function, so you can update to do something other than just print to Debug Output.