A recent project I worked on required me to retrieve a large listing of files and folders from a given computer. My original idea was to use the built in Directory.GetDirectories and Directory.GetFiles.
However, as I soon found out, there is a problem with both of these functions, especially when used in Windows Vista and Windows 7. These functions will error out (and return an empty array), if any of the folders/files are not accessible to the user. So, using Directory.GetDirectories(“C:\\”), would return an empty array and an exception, depending on the user’s security
The main problem comes from how some folders and files are ACL’d in Vista, and especially in Windows 7. Most users (including admin), cannot parse the path of some ACL’d folders, even if they have permission.
My solution was to create a class which can handle the security errors and still return all the files as needed. In addition, I wanted to make this as extensible as possible, so I also added a Predicate to each function, which allows you create your own filter.
example:
Get a list of all “jpg” files on the “C:” drive
TList<string> JpgFolders = DirectoryList(@"C:\\", true, x => (Path.GetExtension(x) == ".jpg"));
Because I use this heavily in multi-threaded environments, I have it returning the lists in a thread safe manner, using the thread safe List<> collection I wrote (TList).
Also, because of the number of files and folders being returned (sometimes in the hundreds of thousands or millions), I decided to use PLINQ to help speed things up. PLINQ allows me to branch each ForEach loop to a new thread, if a thread is available; otherwise, it will wait for an available thread. PLINQ can also send multiple iterations of the loop to one thread, which also helps to improve efficiency.
You’ll also notice the use multiple catch blocks. I found that two types of exceptions were raised if a user did not have access to a file/folder – IOException and UnauthorizedAccessException. There is no reason to do anything when these exceptions are raised, since it simply means the user does not have permission for this file/folder, so we just need to skip it.
The full class is below:
using System;
using System.IO;
using MerlinWebUtil.Error;
using Threaded;
using System.Threading;
namespace MerlinWebUtil.Utilities
{
public static class DirectoryListing
{
#region DirectoryList
/// <summary>
/// Returns a list of directories under RootDirectory
/// </summary>
/// <param name="RootDirectory">starting directory</param>
/// <param name="SearchAllDirectories">when true, all sub directories will be searched as well</param>
/// <param name="Filter">filter to be done on directory. use null for no filtering</param>
public static TList<string> DirectoryList(string RootDirectory, bool SearchAllDirectories, Predicate<string> Filter)
{
TList<string> retList = new TList<string>();
try
{
// create a directory info object
DirectoryInfo di = new DirectoryInfo(RootDirectory);
// loop through directories populating the list
Parallel.ForEach(di.GetDirectories(), folder =>
{
try
{
// add the folder if it passes the filter
if ((Filter == null) || (Filter(folder.FullName)))
{
// add the folder
retList.Add(folder.FullName);
// get it’s sub folders
if (SearchAllDirectories)
retList.AddRange(DirectoryList(folder.FullName, true, Filter));
}
}
catch (IOException)
{
// don’t really need to do anything
// user just doesn’t have access
}
catch (UnauthorizedAccessException)
{
// don’t really need to do anything
// user just doesn’t have access
}
catch (Exception excep)
{
clsError.SaveException(excep);
}
});
}
catch (IOException)
{
// don’t really need to do anything
// user just doesn’t have access
}
catch (UnauthorizedAccessException)
{
// don’t really need to do anything
// user just doesn’t have access
}
catch (Exception excep)
{
clsError.SaveException(excep);
}
finally
{
// force garbage collection
GC.Collect();
Thread.Sleep(10);
}
// return the list
return retList;
}
// DirectoryList
#endregion
#region FileList
/// <summary>
/// Returns a list of files under RootDirectory
/// </summary>
/// <param name="RootDirectory">starting directory</param>
/// <param name="SearchAllDirectories">when true, all sub directories will be searched as well</param>
/// <param name="Filter">filter to be done on files/directory. use null for no filtering</param>
public static TList<string> FileList(string RootDirectory, bool SearchAllDirectories, Predicate<string> Filter)
{
TList<string> retList = new TList<string>();
try
{
// get the list of directories
TList<string> DirList = new TList<string> { RootDirectory };
// get sub directories if allowed
if (SearchAllDirectories)
DirList.AddRange(DirectoryList(RootDirectory, true, null));
// loop through directories populating the list
Parallel.ForEach(DirList, folder =>
{
// get a directory object
DirectoryInfo di = new DirectoryInfo(folder);
try
{
// loop through the files in this directory
foreach (FileInfo file in di.GetFiles())
{
try
{
// add the file if it passes the filter
if ((Filter == null) || (Filter(file.FullName)))
retList.Add(file.FullName);
}
catch (IOException)
{
// don’t really need to do anything
// user just doesn’t have access
}
catch (UnauthorizedAccessException)
{
// don’t really need to do anything
// user just doesn’t have access
}
catch (Exception excep)
{
clsError.SaveException(excep);
}
}
}
catch (IOException)
{
// don’t really need to do anything
// user just doesn’t have access
}
catch (UnauthorizedAccessException)
{
// don’t really need to do anything
// user just doesn’t have access
}
catch (Exception excep)
{
clsError.SaveException(excep);
}
});
}
catch (IOException)
{
// don’t really need to do anything
// user just doesn’t have access
}
catch (UnauthorizedAccessException)
{
// don’t really need to do anything
// user just doesn’t have access
}
catch (Exception excep)
{
clsError.SaveException(excep);
}
finally
{
// force garbage collection
GC.Collect();
Thread.Sleep(10);
}
// return the list
return retList;
}
// FileList
#endregion
}
}
