未经授权的访问异常无法解决Directory.GetFiles故障。

26

Directory.GetFiles方法在第一次遇到无访问权限的文件夹时会失败。

该方法会抛出UnauthorizedAccessException异常(可以被捕获),但在此时,方法已经失败/终止。

我使用的代码如下:

try
{
    // looks in stated directory and returns the path of all files found                
    getFiles = Directory.GetFiles(
        @directoryToSearch, 
        filetype, 
        SearchOption.AllDirectories);             
}
catch (UnauthorizedAccessException) 
{ 
}
据我所知,没有办法事先检查某个文件夹是否定义了访问权限。
在我的例子中,我正在跨网络搜索磁盘,当我遇到只有根访问权限的文件夹时,我的程序会失败。
6个回答

20
为了达到您想要的控制级别,您应该逐个目录进行探测,而不是整个树。以下方法将在目录树中找到所有文件,并将它们填充到给定的 IList<string> 中,但排除了用户没有访问权限的文件。
// 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.
    }
}

我会尝试一下并回复您。您能解释一下 '=>' 运算符是做什么的吗?谢谢。 - Ric
3
@Ric:=> 是 lambda 运算符。你可以在这里阅读有关 C# 中 lambda 表达式的内容:http://msdn.microsoft.com/en-us/library/bb397687.aspx。 - Fredrik Mörk
23
这将无法包括所有目录/文件,因为异常会在第一个不可访问的文件/目录上抛出,此后您将无法获取任何剩余可访问的文件。 - Malcolm

8

我知道这个帖子很老,但如果有人偶然看到并需要答案,我在这里提供了一个递归解决方案:

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

它返回一个List<string>,其中包含在给定根目录下可访问目录中所有文件的完整路径。 像这样调用它:

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

因此,一个结果可能是这样的:

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

希望这能帮助到某些人!

1
这拯救了我的一天!谢谢您先生! - Moses Aprico
3
如果你无法访问一个文件夹,那么你的迭代器会抛出未处理的异常,导致你失去所有内容。 - HackSlash

5
这是对马尔科姆答案的增强(https://dev59.com/jHM_5IYBdhLWcg3wZSTX#9831340)。它会扫描所有逻辑驱动器以查找文件匹配模式,并忽略不可访问的目录。
 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;
        }
    }

3
.Net 4的Directory.EnumerateFiles是可行的,但是你必须小心地评估可枚举并在try-catch块内完成此部分。最大的问题是确保不在第一个异常处停止处理(我认为https://dev59.com/jHM_5IYBdhLWcg3wZSTX#1393219答案存在这个问题,请纠正我如果我错了)。以下代码可以工作并为您提供一个Enumerable,因此如果您正在寻找第一个匹配项等,则不必评估整个文件树。
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);
}

以上欢迎的改进。


这个例子不起作用(var files = dirInfo.EnumerateFiles().GetEnumerator();)会抛出错误。 - Sjors Miltenburg
@sjorsmiltenburg - 已修复并更新。我最终遇到了这个问题并找到了一个解决方法,然后忘记更新我的答案。 - Malcolm
1
这段代码无法按预期工作。EnumerateFiles 创建了一个 FileSystemEnumerableIterator 对象。在迭代枚举器时调用 MoveNext 方法时,如果出现任何 IOException,则迭代器将被处理。.ElementAt 克隆您的迭代器并遍历所有先前的元素,直到它达到所请求的索引。这意味着所有这些迭代器克隆体都将在完全相同的不可访问文件处失败。换句话说,Enumerable.Range.Select { .ElementAt } 技巧什么也没做。 - Paya

3
最简单的版本:
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>();
            }
        }));
}

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

这是我对这个问题的解决方案。简单而且安全可靠。


在 Directory.GetDirectories 行中,我不得不将 search_pattern 替换为“*”,考虑到通常您希望筛选器仅适用于文件而不是文件和文件夹,这是有道理的。 - Bobby Byrnes
1
这也会在第一个异常处停止处理。 - HackSlash
可能很简单,但如果递归导致结果成千上万个文件,并且您只想要FirstOrDefault,则这也是一个问题。 - Harald Coppoolse

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接