How can I obtain the case-sensitive path on Windows?

You can use this function:

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

As an old-timer, I always used FindFirstFile for this purpose. The .Net translation is:

Directory.GetFiles(Path.GetDirectoryName(userSuppliedName), Path.GetFileName(userSuppliedName)).FirstOrDefault();

This only gets you the correct casing for the filename portion of the path, not then entire path.

JeffreyLWhitledge's comment provides a link to a recursive version that can work (though not always) to resolve the full path.


The way to get the actual path of a file (this won't work for folders) is to follow these steps:

  1. Call CreateFileMapping to create a mapping for the file.
  2. Call GetMappedFileName to get the name of the file.
  3. Use QueryDosDevice to convert it to an MS-DOS-style path name.

If you feel like writing a more robust program that also works with directories (but with more pain and a few undocumented features), follow these steps:

  1. Get a handle to the file/folder with CreateFile or NtOpenFile.
  2. Call NtQueryObject to get the full path name.
  3. Call NtQueryInformationFile with FileNameInformation to get the volume-relative path.
  4. Using the two paths above, get the component of the path that represents the volume itself. For example, if you get \Device\HarddiskVolume1\Hello.txt for the first path and \Hello.txt for the second, you now know the volume's path is \Device\HarddiskVolume1.
  5. Use either the poorly-documented Mount Manager I/O Control Codes or QueryDosDevice to convert substitute the volume portion of the full NT-style path with the drive letter.

Now you have the real path of the file.


Alternative Solution

Here is a solution that worked for me to move files between Windows and a server using case sensitive paths. It walks down the directory tree and corrects each entry with GetFileSystemEntries(). If part of the path is invalid (UNC or folder name), then it corrects the path only up to that point and then uses the original path for what it can't find. Anyway, hopefully this will save others time when dealing with the same issue.

private string GetCaseSensitivePath(string path)
{
    var root = Path.GetPathRoot(path);
    try
    {
        foreach (var name in path.Substring(root.Length).Split(Path.DirectorySeparatorChar))
            root = Directory.GetFileSystemEntries(root, name).First();
    }
    catch (Exception e)
    {
        // Log("Path not found: " + path);
        root += path.Substring(root.Length);
    }
    return root;
}

Tags:

C#

.Net

Filepath