26

Directory.GetFiles method fails on the first encounter with a folder it has no access rights to.

The method throws an UnauthorizedAccessException (which can be caught) but by the time this is done, the method has already failed/terminated.

The code I am using is listed below:

try
{
    // looks in stated directory and returns the path of all files found                
    getFiles = Directory.GetFiles(
        @directoryToSearch, 
        filetype, 
        SearchOption.AllDirectories);             
}
catch (UnauthorizedAccessException) 
{ 
}

As far as I am aware, there is no way to check beforehand whether a certain folder has access rights defined.

In my example, I'm searching on a disk across a network and when I come across a root access only folder, my program fails.

yanckst
  • 438
  • 4
  • 17
Ric
  • 683
  • 2
  • 11
  • 19

6 Answers6

20

In order to gain control on the level that you want, you should probably probe one directory at a time, instead of a whole tree. The following method populates the given IList<string> with all files found in the directory tree, except those where the user doesn't have access:

// using System.Linq
private static void AddFiles(string path, IList<string> files)
{
    try
    {
        Directory.GetFiles(path)
            .ToList()
            .ForEach(s => files.Add(s));

        Directory.GetDirectories(path)
            .ToList()
            .ForEach(s => AddFiles(s, files));
    }
    catch (UnauthorizedAccessException ex)
    {
        // ok, so we are not allowed to dig into that directory. Move on.
    }
}
Fredrik Mörk
  • 155,851
  • 29
  • 291
  • 343
  • I shall try this out and get back to you. Could you explain what the '=>' operator does please? Thanks – Ric Sep 08 '09 at 10:35
  • 3
    @Ric: `=>` is the lambda operator. You can read about lambda expressions in C# here: http://msdn.microsoft.com/en-us/library/bb397687.aspx – Fredrik Mörk Sep 08 '09 at 10:42
  • 23
    This will fail to include all directories/files as the exception gets thrown on the first inaccessible file/directory and you never get any remaining accessible files after that point. – Malcolm Jul 25 '12 at 22:26
8

I know this thread is old, but in case someone stumbles upon this and needs an answer, i got a recursive solution here:

public static List<string> GetAllAccessibleFiles(string rootPath, List<string> alreadyFound = null)
    {
        if (alreadyFound == null)
            alreadyFound = new List<string>();
        DirectoryInfo di = new DirectoryInfo(rootPath);
        var dirs = di.EnumerateDirectories();
        foreach (DirectoryInfo dir in dirs)
        {
            if (!((dir.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden))
            {
                alreadyFound = GetAllAccessibleFiles(dir.FullName, alreadyFound);
            }
        }

        var files = Directory.GetFiles(rootPath);
        foreach (string s in files)
        {
            alreadyFound.Add(s);                
        }

        return alreadyFound;
    }

It returns an List<string> containing the full path to all files that are in accessible directories below the given root-directory. Call it like this:

var files = GetAllAccessibleFiles(@"C:\myDirectory");

So one result could be like this:

C:\myDirectory\a\a.txt
C:\myDirectory\a\b.mp3
C:\myDirectory\b\a\a\foo.txt
C:\myDirectory\b\b\b\hello.exe
C:\myDirectory\b\c\bar.jpg
C:\myDirectory\and\so\on.bar
C:\myDirectory\a_file_in_root.bmp

Hope it helps someone!

schnaidar
  • 190
  • 4
  • 11
5

This is an enhancement to Malcolm's answer (http://stackoverflow.com/a/9831340/226181). This scans all logical drives for a file match pattern and ignores the directories that are not accessible.

 static List<string> SearchFiles(string pattern)
    {
        var result = new List<string>();

        foreach (string drive in Directory.GetLogicalDrives())
        {
            Console.WriteLine("searching " + drive);
            var files = FindAccessableFiles(drive, pattern, true);
            Console.WriteLine(files.Count().ToString() + " files found.");
            result.AddRange(files);
        }

        return result;
    }

    private static IEnumerable<String> FindAccessableFiles(string path, string file_pattern, bool recurse)
    {
        Console.WriteLine(path);
        var list = new List<string>();
        var required_extension = "mp4";

        if (File.Exists(path))
        {
            yield return path;
            yield break;
        }

        if (!Directory.Exists(path))
        {
            yield break;
        }

        if (null == file_pattern)
            file_pattern = "*." + required_extension;

        var top_directory = new DirectoryInfo(path);

        // Enumerate the files just in the top directory.
        IEnumerator<FileInfo> files;
        try
        {
            files = top_directory.EnumerateFiles(file_pattern).GetEnumerator();
        }
        catch (Exception ex)
        {
            files = null;
        }

        while (true)
        {
            FileInfo file = null;
            try
            {
                if (files != null && files.MoveNext())
                    file = files.Current;
                else
                    break;
            }
            catch (UnauthorizedAccessException)
            {
                continue;
            }
            catch (PathTooLongException)
            {
                continue;
            }

            yield return file.FullName;
        }

        if (!recurse)
            yield break;

        IEnumerator<DirectoryInfo> dirs;
        try
        {
            dirs = top_directory.EnumerateDirectories("*").GetEnumerator();
        }
        catch (Exception ex)
        {
            dirs = null;
        }


        while (true)
        {
            DirectoryInfo dir = null;
            try
            {
                if (dirs != null && dirs.MoveNext())
                    dir = dirs.Current;
                else
                    break;
            }
            catch (UnauthorizedAccessException)
            {
                continue;
            }
            catch (PathTooLongException)
            {
                continue;
            }

            foreach (var subpath in FindAccessableFiles(dir.FullName, file_pattern, recurse))
                yield return subpath;
        }
    }
matrix
  • 3,000
  • 3
  • 24
  • 35
3

The simplest version:

IEnumerable<String> GetAllFiles(string path, string searchPattern)
{
    return System.IO.Directory.EnumerateFiles(path, searchPattern).Union(
        System.IO.Directory.EnumerateDirectories(path).SelectMany(d =>
        {
            try
            {
                return GetAllFiles(d, searchPattern);
            }
            catch (UnauthorizedAccessException e)
            {
                return Enumerable.Empty<String>();
            }
        }));
}
Bohdan
  • 468
  • 5
  • 6
3

.Net 4's Directory.EnumerateFiles does work, but you've got to be careful how you evaluate the enumerable and do that part inside the try-catch block. The biggest issue is making sure you don't stop processing at the first exception (which I think answer https://stackoverflow.com/a/1393219/89584 above has this problem, please correct me if I'm wrong there).

The following works and gives you an Enumerable so you don't have to evaluate the entire file tree if you're looking for the first match, etc.

private IEnumerable<String> FindAccessableFiles(string path, string file_pattern, bool recurse)
{
  IEnumerable<String> emptyList = new string[0];

  if (File.Exists(path))
    return new string[] { path };

  if (!Directory.Exists(path))
    return emptyList;

  var top_directory = new DirectoryInfo(path);

  // Enumerate the files just in the top directory.
  var files = top_directory.EnumerateFiles(file_pattern);
  var filesLength = files.Count();
  var filesList = Enumerable
            .Range(0, filesLength)
            .Select(i =>
            {
              string filename = null;
              try
              {
                var file = files.ElementAt(i);
                filename = file.FullName;
              }
              catch (UnauthorizedAccessException)
              {
              }
              catch (InvalidOperationException)
              {
                    // ran out of entries
              }
              return filename;
            })
            .Where(i => null != i);

        if (!recurse)
          return filesList;

        var dirs = top_directory.EnumerateDirectories("*");
        var dirsLength = dirs.Count();
        var dirsList = Enumerable
            .Range(0, dirsLength)
            .SelectMany(i =>
            {
              string dirname = null;
              try
              {
                var dir = dirs.ElementAt(i);
                dirname = dir.FullName;
                return FindAccessableFiles(dirname, file_pattern, required_extension, recurse);
              }
              catch (UnauthorizedAccessException)
              {
              }
              catch (InvalidOperationException)
              {
                 // ran out of entries
              }

              return emptyList;
            })

  return Enumerable.Concat(filesList, dirsList);
}

improvements to the above welcome.

Community
  • 1
  • 1
Malcolm
  • 1,239
  • 1
  • 14
  • 25
  • This example does not work (var files = dirInfo.EnumerateFiles().GetEnumerator();) throws the error – Sjors Miltenburg Jul 25 '12 at 10:27
  • @sjorsmiltenburg - fixed and updated. I ended up running into that issue and finding a workaround, then forgot to update my answer. – Malcolm Jul 25 '12 at 22:24
  • 1
    This code is not going to work as intended. The `EnumerateFiles` creates a `FileSystemEnumerableIterator` object. When calling the `MoveNext` method while iterating the enumerator, the iterator gets disposed in the event of ANY `IOException`. `.ElementAt` clones your iterator and iterates over all previous elements until it reaches the requested index. This means that all these iterator clones are going to fail at the exact same inaccessible file. In other words, the `Enumerable.Range.Select { .ElementAt }` trick is doing nothing. – Paya Jan 31 '15 at 00:15
0
public string[] GetFilesFrom(string dir, string search_pattern, bool recursive)
{
    List<string> files = new List<string>();

    string[] temp_files = new string[0];

    try { temp_files = Directory.GetFiles(dir, search_pattern, SearchOption.TopDirectoryOnly); }
    catch { }

    files.AddRange(temp_files);

    if (recursive)
    {
        string[] temp_dirs = new string[0];

        try { temp_dirs = Directory.GetDirectories(dir, search_pattern, SearchOption.TopDirectoryOnly); }
        catch { }

        for (int i = 0; i < temp_dirs.Length; i++)
            files.AddRange(GetFilesFrom(temp_dirs[i], search_pattern, recursive));
    }

    return files.ToArray();
}

This is my solution for this problem. Simple and fail safe.

Kosmo零
  • 4,001
  • 9
  • 45
  • 88
  • On the Directory.GetDirectories line, I had to replace search_pattern with "*", which makes sense considering you generally want the filter to be on the files, not both the files and the folders. – Bobby Byrnes Jun 26 '17 at 19:11
  • 1
    This also stops processing at the first exception. – HackSlash Jul 30 '20 at 19:20
  • It might be simple, but it is also a problem if recursive makes that your result will be thousands of files, and you only want the FirstOrDefault. – Harald Coppoolse Feb 19 '21 at 13:27