5

While enumerating I would like to Skip/Ignore exception.

I try to add a try catch in the selector:

static IEnumerable<string> GetSafeAllFiles
    (string path, string searchPattern, SearchOption searchOption = SearchOption.AllDirectories)
{
    return Directory.EnumerateFiles(path, searchPattern, searchOption)
                    .Select(f =>
                    {
                        try
                        {
                            return f;
                        }
                        catch (Exception e)
                        {
                            return string.Empty;
                        }
                    });
}

I try using a solution from an accepted answer:

var test23 = Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories)
                      .SkipExceptions().Take(100);

With no result, as it will stop after the first error. So I try to implement my own :

static IEnumerable<string> test12(string path, string searchPattern, SearchOption searchOption = SearchOption.AllDirectories)
{
    if (string.IsNullOrEmpty(path))
    {
        throw new ArgumentNullException("path");
    }
    if (string.IsNullOrEmpty(searchPattern))
    {
        throw new ArgumentNullException("searchPattern");
    }

    Queue<string> stillToProcess = new Queue<string>(new[] { path });

    foreach (var dir in Directory.EnumerateDirectories(path))
    {
        stillToProcess.Enqueue(dir);
    }

    while (stillToProcess.Count > 0)
    {
        string currentPath = stillToProcess.Dequeue();
        IEnumerable<string> ret = Enumerable.Empty<string>();
        try
        {
            ret = Directory.EnumerateFiles(currentPath, searchPattern);
        }
        catch (UnauthorizedAccessException e)
        { }
                    // yield! keyword
        foreach (var i in ret) { yield return i; }
    }
    yield break;
}

But it skip directory if there is one error. When I want to skip only the error file.

In order to test, possible solution please do it on c:\$Recycle.Bin, as it's the easiest source of UnauthorizedAccessException.

xdtTransform
  • 1,986
  • 14
  • 34
  • In your process of flagging for duplicate please leave a comments. With 25 + links in my Notepad, I can't provide an exemple of why it doesnt work for every answers to thoses questions. But I honestly tryed. – xdtTransform Oct 23 '18 at 12:58
  • 1
    You swallow the exception, but still continue to evaluate the `foreach`. – Kenneth K. Oct 23 '18 at 12:58
  • 3
    I don't think `return f;` can ever throw an exception, making the `try` / `catch` useless. – Bradley Uffner Oct 23 '18 at 13:01
  • @KennethK., in last code? Yes. That's only a try. If `Directory.EnumerateFiles(currentPath, searchPattern)`. return an error for one file i found no way to skip it. But skip only this file. no the whole directory. And this try also miss the sub dir in the sub dir – xdtTransform Oct 23 '18 at 13:03
  • @BradleyUffner, Yes exactly. the error hapend "before" the `Select`. This code is the best explanation of what I wan't even if it's a broken try. It's simple an illustrat the purpuse. – xdtTransform Oct 23 '18 at 13:04
  • Last try is not going deeper than 2 level for now because it's enought to illustrate that it doesn't work on "c:/". – xdtTransform Oct 23 '18 at 13:10
  • Exception handling is a pet peeve of mine and that first code really raised alarm in me, as you caught exception. Luckily later you did the right thing and only caught a specific exogenous exception. Do keep the difference of those two cases in mind and do not mix them up: https://blogs.msdn.microsoft.com/ericlippert/2008/09/10/vexing-exceptions/ | https://www.codeproject.com/Articles/9538/Exception-Handling-Best-Practices-in-NET Swallowing Fatal Exceptions is a deadly sin of exception handling. – Christopher Oct 23 '18 at 13:17
  • You are catching an exception that happens at `Directory.EnumerateFiles`. You don't handle exceptions that happen when the consumer of your method uses one of the strings you return. – Tim Schmelter Oct 23 '18 at 13:20
  • What is your goal? To detect all readable files in the directory and all subdirectories? Or only the exception handling? – P. Grote Oct 23 '18 at 13:21
  • @P.Grote, I'm trying to guard against the `SecurityException` and `UnauthorizedAccessException` . Listing the files that I can list. It's before any read or write. i will define them as "reachable" file. There is no handle to the exception if a get file in a directory throw an error about Security or Autorisation it's safe to say that I can ignore the file. – xdtTransform Oct 23 '18 at 13:47
  • @Christopher, I understand your concern about exception, in the first exemple it's a big catch. a sign of "Wrote a function that is clear about my intend". Will take my time and read those latter. – xdtTransform Oct 23 '18 at 14:09
  • @TimSchmelter, That's an affirmation. So yes you re right, as you expected for now I'm only trying to enumerate the files ignoring the ones I can't list. happen "when the consumer of your method uses one of the strings" For now there is no return. – xdtTransform Oct 23 '18 at 15:17

2 Answers2

1

This is what i came up with:

public static IEnumerable<FileInfo> EnumerateFilesIgnoreErrors(IEnumerable<FileInfo> files)
{
    using (var e1 = files.GetEnumerator())
    {
        while (true)
        {
            FileInfo cur = null;

            try
            {
                // MoveNext() can throw an Exception
                if (! e1.MoveNext())
                    break;

                cur = e1.Current;

            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }

            if (cur != null)
            {
                yield return cur;
            }
        }
    }
}

Use like this:

var myFiles = new DirectoryInfo(@"C:\")
           .EnumerateFiles("*", SearchOption.AllDirectories);
           
foreach (FileInfo fi in EnumerateFilesIgnoreErrors(myFiles))
{
    Debug.WriteLine(fi.Name);
} 

You can also use it like this:

var myList = EnumerateFilesIgnoreErrors(myFiles).ToList();

Works on "c:\$Recycle.Bin" etc
Ignores UnauthorizedAccessException

Charles
  • 2,721
  • 1
  • 9
  • 15
0

Some times ago, I wrote this piece of code, to get all files in a directory and all subdirectories. I would go this way to achieve your goal. You have to customize it to work with search pattern and search options. Otherwise, the CanRead method could be solve your problem, by preventing the code to throw exceptions overall.

public class DirectoryAnalyser
{
    private List<string> _files;
    private int _directoryCounter;

    public async Task<List<string>> GetFilesAsync(string directory, CancellationTokenSource cancellationToken, IProgress<DirectoryAnalyserProgress> progress)
    {
        this._files = new List<string>();
        this._directoryCounter = 0;

        await this.GetFilesInternalAsync(directory, cancellationToken, progress);
        return this._files;
    }

    private async Task GetFilesInternalAsync(string directory, CancellationTokenSource cancellationToken, IProgress<DirectoryAnalyserProgress> progress)
    {
        if (cancellationToken.IsCancellationRequested)
        {
            return;
        }

        if (!this.CanRead(directory))
        {
            return;
        }

        this._files.AddRange(Directory.GetFiles(directory));
        this._directoryCounter++;
        progress?.Report(new DirectoryAnalyserProgress()
        {
            DirectoriesSearched = this._directoryCounter,
            FilesFound = this._files.Count
        });

        foreach (var subDirectory in Directory.GetDirectories(directory))
        {
            await this.GetFilesInternalAsync(subDirectory, cancellationToken, progress);
        }
    }

    public bool CanRead(string path)
    {
        var readAllow = false;
        var readDeny = false;

        var accessControlList = Directory.GetAccessControl(path);
        if (accessControlList == null)
        {
            return false;
        }

        var accessRules = accessControlList.GetAccessRules(true, true, typeof(SecurityIdentifier));
        if (accessRules == null)
        {
            return false;
        }

        foreach (FileSystemAccessRule rule in accessRules)
        {
            if ((FileSystemRights.Read & rule.FileSystemRights) != FileSystemRights.Read)
            {
                continue;
            }

            if (rule.AccessControlType == AccessControlType.Allow)
            {
                readAllow = true;
            }
            else if (rule.AccessControlType == AccessControlType.Deny)
            {
                readDeny = true;
            }
        }

        return readAllow && !readDeny;
    }
}


public class DirectoryAnalyserProgress
{
    public int FilesFound { get; set; }
    public int DirectoriesSearched { get; set; }
}
P. Grote
  • 249
  • 1
  • 2
  • 13
  • Hi, as expected the `this._files.AddRange(Directory.GetFiles(directory));` does not work. Or more exactly `Directory.GetFiles(directory)` return Nothing if one of the files is too secret. Try it on `var testSO = Directory.GetFiles("c:\\$Recycle.Bin");` Don't get bate by your explorer this directory is [full of files](https://superuser.com/questions/1335359/what-does-c-recycle-bin-contain). – xdtTransform Oct 23 '18 at 14:12
  • Grabbing each directories files one at a time is very slow on SMB when dealing with large numbers of directories, so in those cases this is probably not going to be a good solutuon. – user169771 Jun 28 '22 at 17:16