C#: How to Find All Files Including Hidden Files?

April 27, 2022
Cover Image

I was writing some code yesterday that needed to enumerate all files in a directory. So I wrote what I've been writing since the beginning of time:

var files = Directory.GetFiles("*.*", new EnumerationOptions {
RecurseSubdirectories = true
});

At first, it seemed to be working just fine but then I noticed that hidden files were not showing up. And then I noticed that when I ran it on my Mac (essentially Linux) that files like ".gitignore" and directories like ".vscode", ".config", and ".git" weren't showing up.

After much searching, I was surprised to not find a simple answer. Pretty much everything I found was focused on Windows filesystem and assumed that the standard "*.*" wildcard pattern was going to work. Well, in the cross-platform world, that doesn't always work.

The Problem

The standard *.* wildcard that we've all been using without thinking too hard about it actually means "match any file that contains a dot in the filename with characters before and after the dot. If the file you want is something like readme.txt because there's a dot, there's text before the dot, and there's text after the dot so it works. But if your filename is .gitignore or your directory name is .vscode then *.* doesn't match because there's a dot but there isn't any text before the dot.

EnumerationOptions has a property called AttributesToSkip and the default skips value for that property skips hidden files and system files. On linux and MacOS, any file that starts with a dot ('.') is automatically considered a hidden file or a hidden directory.

The Solution

There are two parts to the solution. The first part of the solution is to dump *.* as the wildcard in favor of simply *. This says bring back all files as long as there are any characters at all in the filename -- never mind any of those pesky dots.

The second part of the solution is to provide a new value for the AttributesToSkip property of EnumerationOptions.

var files = Directory.GetFiles(path, "*", new EnumerationOptions { RecurseSubdirectories = true, **AttributesToSkip = default** });

BTW, if you're using an older version of .NET Core then you might need to replace that
AttributesToSkip = default with
AttributesToSkip = default(FileAttributes).

Summary

Well anyway...that whole thing was harder than I would have expected. Hopefully this blog post gets someone else to a solution in a lot less time than it took me.

-Ben