21

Is there a way to have a case sensitive Directory.Exists / File.Existssince

Directory.Exists(folderPath)

and

Directory.Exists(folderPath.ToLower())

both return true?

Most of the time it doesn't matter but I'm using a macro which seems not to work if the path doesn't match cases 100%.

theknut
  • 2,533
  • 5
  • 26
  • 41
  • 1
    MSDN clearly noted this: "The path parameter is not case-sensitive.", see http://msdn.microsoft.com/en-us/library/system.io.directory.exists.aspx – David Apr 24 '13 at 05:32
  • 16
    I know, that's why I'm asking... – theknut Apr 24 '13 at 05:40
  • I know this is 10 years later and maybe the document has changed, but it specifically says "The case-sensitivity of the path parameter corresponds to that of the file system on which the code is running. For example, it's case-insensitive on NTFS (the default Windows file system) and case-sensitive on Linux file systems." meaning it can be case sensitive. – Peter Jul 19 '23 at 19:53

6 Answers6

5

Since Directory.Exists uses FindFirstFile which is not case-sensitive, no. But you can PInvoke FindFirstFileEx with an additionalFlags parameter set to FIND_FIRST_EX_CASE_SENSITIVE

Jacob Seleznev
  • 8,013
  • 3
  • 24
  • 34
  • 1
    Sorry, might be a dump question, but is this working for C#? All your references relate to C++ *confused* – theknut Apr 24 '13 at 05:44
  • Yes it should do, you can use P/Invoke to call out to Win32 from C#, read up on P/Invoke and then check out the link to pinvoke.net – Matt Apr 24 '13 at 05:53
  • +1. @theknut, PInvoke link gives you detail on how to make it callable in C#/other .Net languages. – Alexei Levenkov Apr 24 '13 at 05:53
  • I tried it and it doesn't really work for my string. It is D:\Dir1\dir2\Dir3 (physical dir on drive is D:\Dir1\Dir2\Dir3). When I try FindFirstFile it will only return me Dir3. FindFirstFileEx(bla, FINDEX_INFO_LEVELS.FindExInfoBasic, out findData, FINDEX_SEARCH_OPS.FindExSearchLimitToDirectories, IntPtr.Zero, FIND_FIRST_EX_CASE_SENSITIVE); – theknut Apr 24 '13 at 06:36
  • Does [this](http://stackoverflow.com/questions/2239872/how-to-get-list-of-folders-in-this-folder/2241811) help? If not post your code and will try to figure it out together. – Jacob Seleznev Apr 24 '13 at 09:35
  • This doesn't help either. It wil still return Dir3 and FindNextFile returns false. The code I used is the exact one before your comment and the path is D:\Dir1\dir2\Dir3 to be compared with D:\Dir1\Dir2\Dir3. – theknut Apr 24 '13 at 10:50
  • What does your `bla` equal to? – Jacob Seleznev Apr 24 '13 at 11:43
  • `bla = "D:\Dir1\Dir2\Dir3"` but the directory on my drive is `"D:\Dir1\dir2\Dir3"`. This is why I need to compare them on cases. – theknut Apr 24 '13 at 12:15
  • In [example](http://stackoverflow.com/questions/2239872/how-to-get-list-of-folders-in-this-folder/2241811) **FindFirstFileEx** is used to get _list of folders in this folder_. In your case if your `lpFileName="D:\\"` It should return "Dir1","dir2" and "Dir3". Please confirm if that's the case – Jacob Seleznev Apr 24 '13 at 12:40
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/28808/discussion-between-jacob-seleznev-and-theknut) – Jacob Seleznev Apr 24 '13 at 12:46
2

Try this function:

public static bool FileExistsCaseSensitive(string filename)
{
    string name = Path.GetDirectoryName(filename);

    return name != null 
           && Array.Exists(Directory.GetFiles(name), s => s == Path.GetFullPath(filename));
}

Update:

As stated in comments, this only check cases in filename, not in the path. This is because GetFullPath method doesn't return the Windows original path with original cases, but a copy of the path from the parameter.

Ex:

GetFullPath("c:\TEST\file.txt") -> "c:\TEST\file.txt"
GetFullPath("c:\test\file.txt") -> "c:\test\file.txt"

All methods I tried work the same way: Fileinfo, DirectoryInfo.

Here is a solution using a kernel32.dll method:

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    public static extern int GetLongPathName(
        string path,
        StringBuilder longPath,
        int longPathLength
        );

    /// <summary>
    /// Return true if file exists. Non case sensitive by default.
    /// </summary>
    /// <param name="filename"></param>
    /// <param name="caseSensitive"></param>
    /// <returns></returns>
    public static bool FileExists(string filename, bool caseSensitive = false)
    {
        if (!File.Exists(filename))
        {
            return false;
        }

        if (!caseSensitive)
        {
            return true;
        }

        //check case
        StringBuilder longPath = new StringBuilder(255);
        GetLongPathName(Path.GetFullPath(filename), longPath, longPath.Capacity);

        string realPath = Path.GetDirectoryName(longPath.ToString());
        return Array.Exists(Directory.GetFiles(realPath), s => s == filename);
    }
Eric Bole-Feysot
  • 13,949
  • 7
  • 47
  • 53
  • It works here: If you have 'c:\temp\TEST.txt', FileExistsCaseSensitive(@"c:\temp\test.txt") will return false – Eric Bole-Feysot Sep 10 '14 at 08:59
  • This is working for me. (tested on windows platform) – A-Sharabiani Oct 06 '15 at 20:58
  • Problem with this is, it's only case sensitive for the _file name_, not for the whole path. Example: Testing the method with `c:\tEst\test.txt` and `c:\test\test.txt`, while the real path on Windows is: `c:\Test\test.txt`, both return `true`. I except both to return `false` (case-sensitivity for the whole path). – A-Sharabiani Oct 07 '15 at 14:14
  • Right. I only use the filename part. Anyway, I updated the answer with a fully working solution. – Eric Bole-Feysot Oct 08 '15 at 13:16
1

Try these 2 simpler options that do not need to use PInvoke and return a nullable Boolean (bool?). I am not a subject expert so I do know if this is the most efficient code but it works for me.

Simply pass in a path and if the result is null (HasValue = false) no match is found, if the result is false there is an exact match, otherwise if true there is a match with a difference case.

The methods GetFiles, GetDirectories and GetDrives all return the exact case as saved on your file system so you can use a case sensitive compare method.

NB: for the case where the path is an exact drive (e.g. @"C:\") I have to use a slightly different approach.

using System.IO;
class MyFolderFileHelper {
    public static bool? FileExistsWithDifferentCase(string fileName)
    {
        bool? result = null;
        if (File.Exists(fileName))
        {
            result = false;
            string directory = Path.GetDirectoryName(fileName);
            string fileTitle = Path.GetFileName(fileName);
            string[] files = Directory.GetFiles(directory, fileTitle);
            if (String.Compare(files[0], fileName, false) != 0)
                result = true;                
        }
        return result;
    }

    public static bool? DirectoryExistsWithDifferentCase(string directoryName)
    {
        bool? result = null;
        if (Directory.Exists(directoryName))
        {
            result = false;
            directoryName = directoryName.TrimEnd(Path.DirectorySeparatorChar);

            int lastPathSeparatorIndex = directoryName.LastIndexOf(Path.DirectorySeparatorChar);
            if (lastPathSeparatorIndex >= 0)
            {                       
                string baseDirectory = directoryName.Substring(lastPathSeparatorIndex + 1);
                string parentDirectory = directoryName.Substring(0, lastPathSeparatorIndex);

                string[] directories = Directory.GetDirectories(parentDirectory, baseDirectory);
                if (String.Compare(directories[0], directoryName, false) != 0)
                    result = true;
            }
            else
            {
                //if directory is a drive
                directoryName += Path.DirectorySeparatorChar.ToString();
                DriveInfo[] drives = DriveInfo.GetDrives();
                foreach(DriveInfo driveInfo in drives)
                {
                    if (String.Compare(driveInfo.Name, directoryName, true) == 0)
                    {
                        if (String.Compare(driveInfo.Name, directoryName, false) != 0)
                            result = true;
                        break;
                    }
                }

            }
        }
        return result;
    }
}
Glen
  • 802
  • 1
  • 11
  • 27
1

Based on the solution of this question, I wrote the code below which is case sensitive for the whole path except the Windows Drive letter:

 static void Main(string[] args)
    {
        string file1 = @"D:\tESt\Test.txt";
        string file2 = @"d:\Test\test.txt";
        string file3 = @"d:\test\notexists.txt";

        bool exists1 = Case_Sensitive_File_Exists(file1);
        bool exists2 = Case_Sensitive_File_Exists(file2);
        bool exists3 = Case_Sensitive_File_Exists(file3);

        Console.WriteLine("\n\nPress any key...");
        Console.ReadKey();
    }

   static bool Case_Sensitive_File_Exists(string filepath)
   {
        string physicalPath = GetWindowsPhysicalPath(filepath);
        if (physicalPath == null) return false;
        if (filepath != physicalPath) return false;
        else return true;
   }

I copied the code for GetWindowsPhysicalPath(string path) from the question

  [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    static extern uint GetLongPathName(string ShortPath, StringBuilder sb, int buffer);

    [DllImport("kernel32.dll")]
    static extern uint GetShortPathName(string longpath, StringBuilder sb, int buffer);

    protected static string GetWindowsPhysicalPath(string path)
    {
        StringBuilder builder = new StringBuilder(255);

        // names with long extension can cause the short name to be actually larger than
        // the long name.
        GetShortPathName(path, builder, builder.Capacity);

        path = builder.ToString();

        uint result = GetLongPathName(path, builder, builder.Capacity);

        if (result > 0 && result < builder.Capacity)
        {
            //Success retrieved long file name
            builder[0] = char.ToLower(builder[0]);
            return builder.ToString(0, (int)result);
        }

        if (result > 0)
        {
            //Need more capacity in the buffer
            //specified in the result variable
            builder = new StringBuilder((int)result);
            result = GetLongPathName(path, builder, builder.Capacity);
            builder[0] = char.ToLower(builder[0]);
            return builder.ToString(0, (int)result);
        }

        return null;
    }

Note the only problem I found with this function is, the drive letter seems to be always in lowercase. Example: The physical path on Windows is: D:\Test\test.txt, the GetWindowsPhysicalPath(string path)function returns d:\Test\test.txt

Community
  • 1
  • 1
A-Sharabiani
  • 17,750
  • 17
  • 113
  • 128
  • 1
    Id recommend making a field for `[ThreadStatic]StringBuilder _builder=new StringBuilder(255);` and thereby avoid adding an allocation to each call. Actually.. eliminating allocations would be a bit bigger change than just that... hm. – James Apr 25 '17 at 11:40
1

Here's a relatively simple way to check if a directory actually exists as named. I needed this because I have an option to rename a folder, and it checks that there won't be a clash first. So, for example, if I want to rename the folder "cars" to "Cars".

It's pretty straight-foward really. If the system reports that the folder exists, then I just call GetDirectories on the parent folder, and passing the directory name as the wildcard (thus returning exactly 1 result). Just a simple comparison gives me the answer.

static public bool DirExistsMatchCase(string path)
{
    // If it definitely doesn't return false
    if (!Directory.Exists(path)) return false;

    // Figure out if the case (of the final part) is the same
    string thisDir = Path.GetFileName(path);
    string actualDir = Path.GetFileName(Directory.GetDirectories(Path.GetDirectoryName(path), thisDir)[0]);
    return thisDir == actualDir;
}
xtempore
  • 5,260
  • 4
  • 36
  • 43
0

If the (relative or absolute) path of your file is:

string AssetPath = "...";

The following ensures that the file both exists and has the correct casing:

if(File.Exists(AssetPath) && Path.GetFullPath(AssetPath) == Directory.GetFiles(Path.GetDirectoryName(Path.GetFullPath(AssetPath)), Path.GetFileName(Path.GetFullPath(AssetPath))).Single())
{
}

Enjoy!

Simon Mattes
  • 4,866
  • 2
  • 33
  • 53