C#: Recursively get all files in a folder and its subfolders

How to recursively get all files in a folder, including files contained within subfolders.

Or, in other words, how to find every file contained within a parent folder, and within any folders inside the parent folder.

The easy way

There is a method built into .NET, though it is buried as an overload, so you may not have known it was there until someone points it out to you…

foreach (string file in System.IO.Directory.GetFiles(
    parentDirectory, "*",SearchOption.AllDirectories))
{
    //do something with file
}

This loops through every file contained within the folder, including all files contained within any subfolders. Each loop returns a string of the address of each file.

The second parameter is a search filter. The value above of "*" simply means “return anything”. We could filter for Word documents by changing this to "*.docx", for example.

The alternative way

Microsoft offered a solution of their own in this old article, which provides an interesting iterative approach.

I have modified the above method slightly to also include files immediately within the parent directory. Microsoft’s solution currently doesn’t do this

The code below adds all filepaths to a List. If you wish to do something else, you need to change what happens within DoAction(). You might actually want to give DoAction a more sensible name too, depending on what your action is.

        List<string> files = new List<string>();

        private void getFilesRecursive(string sDir)
        {
            try
            {
                foreach (string d in Directory.GetDirectories(sDir))
                {
                    getFilesRecursive(d);
                }
                foreach (var file in Directory.GetFiles(sDir))
                {
                    //This is where you would manipulate each file found, e.g.:
                    DoAction(file);
                }
            }
            catch (System.Exception e)
            {
                MessageBox.Show(e.Message);
            }
        }

        private void DoAction(string filepath)
        {
            files.Add(filepath);
        }

Get file types supported by BitmapImage

The BitmapImage class is the preferred way of handing image files in a WPF application.

A great advantage of BitmapImage is that it can open and handle a wide range of image types. It uses the Windows Imaging Component, which allows third parties to incorporate custom image formats into BitmapImage.

Ultimately, what this means is that, quite amazingly, I am able to open fancy file types like Nikon’s NEF raw format in a way that feels native in my C# applications.

But this presents a challenge. If a user wants to open an image in my application, how do I know if BitmapImage can handle it?

I could use a try-catch, but this is quite inefficient. And since BitmapImage is extensible, I can’t just check the file extension against a hard-coded list. I need to be able to check what file types are supported on a particular machine.

I presented this problem on this Stack Overflow post, where NineBerry provided a solution.

How the solution works

The gist of NineBerry’s solution is that we look in the registry for any third party codecs that help BitmapImage to open any additional file types. Once we have found them, we can then read which file extensions are typically associated with these file types.

NineBerry’s solution doesn’t find natively supported file types. We can use Microsoft’s documentation to hard-code these natively-supported types.

Then, when a user wants to open a file, we can compare its file extension with the extensions we found above.

Note that this isn’t fool-proof. A file with a supposedly valid extension may still not open. The file may be corrupted, have an incorrect extension, or the extension may have multiple uses. Still, this method should catch the vast majority of unsuitable files, and help to speed up your program.

How to use the class in your program

I took NineBerry’s code and turned it into a class with a few useful methods and properties. Here you can…

Check if a filename contains a BitmapImage-suitable file extension

string path = "myImage.jpg";
BitmapImageCheck bic = new BitmapImageCheck();
bool imageValid = bic.IsExtensionSupported(path); //returns true

path can be a file name with extension, an extension by itself, or a full address.

Get a list of supported file extensions

Get a list of file extensions provided natively, provided by third party decoders, or these two lists combined.

BitmapImageCheck bic = new BitmapImageCheck();
string[] supportedExtensions = bic.AllSupportedExtensions;

Get information about the decoders found on your system

Use the ToString() method to get information about where support for different file extensions is coming from.

BitmapImageCheck bic = new BitmapImageCheck();
MessageBox.Show(bic.ToString());

bitmapimage image extensions messagebox

C# class to get supported file types

Copy the code below into a new C# file in your project. The only thing you might want to change is the namespace.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Win32;

namespace WpfApplication1.Classes
{
    /// <summary>
    /// Provides methods for checking whether a file can likely be opened as a BitmapImage, based upon its file extension
    /// </summary>
    public class BitmapImageCheck : IDisposable
    {
        #region class variables
        string baseKeyPath;
        RegistryKey baseKey;
        private const string WICDecoderCategory = "{7ED96837-96F0-4812-B211-F13C24117ED3}";
        private string[] allExtensions;
        private string[] nativeExtensions;
        private string[] customExtensions;
        #endregion

        #region constructors
        public BitmapImageCheck()
        {
            if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess)
            {
                baseKeyPath = "Wow6432Node\\CLSID";
            }
            else
            {
                baseKeyPath = "CLSID";
            }
            baseKey = Registry.ClassesRoot.OpenSubKey(baseKeyPath, false);
            recalculateExtensions();
        }
        #endregion

        #region properties
        /// <summary>
        /// File extensions that are supported by decoders found elsewhere on the system
        /// </summary>
        public string[] CustomSupportedExtensions
        {
            get
            {
                return customExtensions;
            }
        }

        /// <summary>
        /// File extensions that are supported natively by .NET
        /// </summary>
        public string[] NativeSupportedExtensions
        {
            get
            {
                return nativeExtensions;
            }
        }

        /// <summary>
        /// File extensions that are supported both natively by NET, and by decoders found elsewhere on the system
        /// </summary>
        public string[] AllSupportedExtensions
        {
            get
            {
                return allExtensions;
            }
        }
        #endregion

        #region public methods
        /// <summary>
        /// Check whether a file is likely to be supported by BitmapImage based upon its extension
        /// </summary>
        /// <param name="extension">File extension (with or without leading full stop), file name or file path</param>
        /// <returns>True if extension appears to contain a supported file extension, false if no suitable extension was found</returns>
        public bool IsExtensionSupported(string extension)
        {
            //prepare extension, should a full path be given
            if (extension.Contains("."))
            {
                extension = extension.Substring(extension.LastIndexOf('.') + 1);
            }
            extension = extension.ToUpper();
            extension = extension.Insert(0, ".");

            if (AllSupportedExtensions.Contains(extension)) return true;
            return false;
        }
        #endregion

        #region private methods
        /// <summary>
        /// Re-calculate which extensions are available on this system. It's unlikely this ever needs to be called outside of the constructor.
        /// </summary>
        private void recalculateExtensions()
        {
            customExtensions = GetSupportedExtensions().ToArray();
            nativeExtensions = new string[] { ".BMP", ".GIF", ".ICO", ".JPEG", ".PNG", ".TIFF", ".DDS", ".JPG", ".JXR", ".HDP", ".WDP" };

            string[] cse = customExtensions;
            string[] nse = nativeExtensions;
            string[] ase = new string[cse.Length + nse.Length];
            Array.Copy(nse, ase, nse.Length);
            Array.Copy(cse, 0, ase, nse.Length, cse.Length);
            allExtensions = ase;
        }

        /// <summary>
        /// Represents information about a WIC decoder
        /// </summary>
        private struct DecoderInfo
        {
            public string FriendlyName;
            public string FileExtensions;
        }

        /// <summary>
        /// Gets a list of additionally registered WIC decoders
        /// </summary>
        /// <returns></returns>
        private IEnumerable<DecoderInfo> GetAdditionalDecoders()
        {
            var result = new List<DecoderInfo>();

            foreach (var codecKey in GetCodecKeys())
            {
                DecoderInfo decoderInfo = new DecoderInfo();
                decoderInfo.FriendlyName = Convert.ToString(codecKey.GetValue("FriendlyName", ""));
                decoderInfo.FileExtensions = Convert.ToString(codecKey.GetValue("FileExtensions", ""));
                result.Add(decoderInfo);
            }
            return result;
        }

        private List<string> GetSupportedExtensions()
        {
            var decoders = GetAdditionalDecoders();
            List<string> rtnlist = new List<string>();

            foreach (var decoder in decoders)
            {
                string[] extensions = decoder.FileExtensions.Split(',');
                foreach (var extension in extensions) rtnlist.Add(extension);
            }
            return rtnlist;
        }

        private IEnumerable<RegistryKey> GetCodecKeys()
        {
            var result = new List<RegistryKey>();

            if (baseKey != null)
            {
                var categoryKey = baseKey.OpenSubKey(WICDecoderCategory + "\\instance", false);
                if (categoryKey != null)
                {
                    // Read the guids of the registered decoders
                    var codecGuids = categoryKey.GetSubKeyNames();

                    foreach (var codecGuid in GetCodecGuids())
                    {
                        // Read the properties of the single registered decoder
                        var codecKey = baseKey.OpenSubKey(codecGuid);
                        if (codecKey != null)
                        {
                            result.Add(codecKey);
                        }
                    }
                }
            }

            return result;
        }

        private string[] GetCodecGuids()
        {
            if (baseKey != null)
            {
                var categoryKey = baseKey.OpenSubKey(WICDecoderCategory + "\\instance", false);
                if (categoryKey != null)
                {
                    // Read the guids of the registered decoders
                    return categoryKey.GetSubKeyNames();
                }
            }
            return null;
        }

        #endregion


        #region overrides and whatnot

        public override string ToString()
        {
            string rtnstring = "";

            rtnstring += "\nNative support for the following extensions is available: ";
            foreach (var item in nativeExtensions)
            {
                rtnstring += item + ",";
            }
            if (nativeExtensions.Count() > 0) rtnstring = rtnstring.Remove(rtnstring.Length - 1);

            var decoders = GetAdditionalDecoders();
            if (decoders.Count() == 0) rtnstring += "\n\nNo custom decoders found.";
            else
            {
                rtnstring += "\n\nThese custom decoders were also found:";
                foreach (var decoder in decoders)
                {
                    rtnstring += "\n" + decoder.FriendlyName + ", supporting extensions " + decoder.FileExtensions;
                }
            }

            return rtnstring;
        }

        public void Dispose()
        {
            baseKey.Dispose();
        }
        #endregion
    }
}

Get more information about the decoders on your system

The public methods implemented were intended to make available a basic level of file extension information.

For more advanced tasks, some of the private methods in the class could be made public. It is possible to expose further information about each codec, such as their locations in the registry.