xdtTransform
xdtTransform

Reputation: 2057

Ignore exceptions of Directory.EnumerateFiles, skip those files

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.

Upvotes: 5

Views: 1988

Answers (2)

Charles
Charles

Reputation: 3061

Update: In newer DotNet Versions you can specify the Enumeration Options like this:

//  SearchOption.TopDirectoryOnly
public static EnumerationOptions Compatible => new EnumerationOptions
{
    MatchType = MatchType.Win32,
    AttributesToSkip = (FileAttributes)0,
    IgnoreInaccessible = false
};

// SearchOption.AllDirectories
public static EnumerationOptions CompatibleRecursive => new EnumerationOptions
{
    RecurseSubdirectories = true,
    MatchType = MatchType.Win32,
    AttributesToSkip = (FileAttributes)0,
    IgnoreInaccessible = false
};

Here you can set IgnoreInaccessible for the EnumerationOptions.
Pass the EnumerationOptions to EnumerateFiles as the 2nd Parameter to ignore errors.


Original Solution:

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

Upvotes: 3

P. Grote
P. Grote

Reputation: 249

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

Upvotes: 0

Related Questions