Question

I'm trying to write a static member function in C# or find one in the .NET Framework that will re-case a file path to what the filesystem specifies.

Example:

string filepath = @"C:\temp.txt";
filepath = FileUtility.RecaseFilepath(filepath);

// filepath = C:\Temp.TXT
// Where the real fully qualified filepath in the NTFS volume is C:\Temp.TXT

I've tried the following code below and many variants of it and it still doesn't work. I know Windows is case-insensitive in general but I need to pass these file paths to ClearCase which considers file path casing since it's a Unix and Windows application.

public static string GetProperFilePathCapitalization(string filepath)
{
    string result = "";

    try
    {
        result = Path.GetFullPath(filepath);
        DirectoryInfo dir = new DirectoryInfo(Path.GetDirectoryName(result));
        FileInfo[] fi = dir.GetFiles(Path.GetFileName(result));
        if (fi.Length > 0)
        {
            result = fi[0].FullName;
        }
    }
    catch (Exception)
    {
        result = filepath;
    }

    return result;
}
Was it helpful?

Solution

This is a pretty simple implementation that assumes that the file and directories all exist and are accessible:

static string GetProperDirectoryCapitalization(DirectoryInfo dirInfo)
{
    DirectoryInfo parentDirInfo = dirInfo.Parent;
    if (null == parentDirInfo)
        return dirInfo.Name;
    return Path.Combine(GetProperDirectoryCapitalization(parentDirInfo),
                        parentDirInfo.GetDirectories(dirInfo.Name)[0].Name);
}

static string GetProperFilePathCapitalization(string filename)
{
    FileInfo fileInfo = new FileInfo(filename);
    DirectoryInfo dirInfo = fileInfo.Directory;
    return Path.Combine(GetProperDirectoryCapitalization(dirInfo),
                        dirInfo.GetFiles(fileInfo.Name)[0].Name);
}

There is a bug with this, though: Relative paths are converted to absolute paths. Your original code above did the same, so I'm assuming that you do want this behavior.

OTHER TIPS

The below works fine to the extent I tested... only catch is that the API used is available only in Vista.

static void Main(string[] args)
{
    using (FileStream fs = File.OpenRead(@"D:\temp\case\mytest.txt"))
    {
        StringBuilder path = new StringBuilder(512);
        GetFinalPathNameByHandle(fs.SafeFileHandle.DangerousGetHandle(), path, path.Capacity, 0);
        Console.WriteLine(path.ToString());
    }
}

[DllImport("kernel32.dll", SetLastError = true)]
static extern int GetFinalPathNameByHandle(IntPtr handle, [In, Out] StringBuilder path, int bufLen, int flags);

You can search for the file you want to get the case on and return the results of your search (you want to check the casing of a file that exists, right?). Something like this:

public static string GetProperFilePathCapitalization(string filepath) {
   string directoryPath = Path.GetDirectoryName(filepath);
   string[] files = Directory.GetFiles(directoryPath, Path.GetFileName(filepath));
   return files[0];
}

Is this what you're looking for?

I have something more efficient but:

1) It doesn't seem to work for all cases. (I've not figured out the pattern of which files and directories it correctly gets the casing, and which ones it does not.)

2) It's Windows specific.

static string GetProperFilePathCapitalization1(string filename)
{
    StringBuilder sb = new StringBuilder(260);
    int length = GetLongPathName(filename, sb, sb.Capacity);

    if (length > sb.Capacity)
    {
        sb.Capacity = length;
        length = GetLongPathName(filename, sb, sb.Capacity);
    }

    if (0 == length)
        throw new Win32Exception("GetLongPathName");

    return sb.ToString();
}

[DllImport("kernel32.dll")]
static extern int GetLongPathName(string path, StringBuilder pszPath, int cchPath);

The answer by @Ants above should absolutely get credit as the accepted answer. However, I refactored it a bit to my purposes. The approach is packaged as extension methods for FileInfo and DirectoryInfo, and return corrected ones as well.

public static DirectoryInfo GetProperCasedDirectoryInfo(this DirectoryInfo dirInfo)
{
    // Inspired by http://stackoverflow.com/a/479198/244342

    if (!dirInfo.Exists)
    {
        // Will not be able to match filesystem
        return dirInfo;
    }

    DirectoryInfo parentDirInfo = dirInfo.Parent;
    if (parentDirInfo == null)
    {
        return dirInfo;
    }
    else
    {
        return parentDirInfo.GetProperCasedDirectoryInfo().GetDirectories(dirInfo.Name)[0];
    }
}

public static FileInfo GetProperCasedFileInfo(this FileInfo fileInfo)
{
    // Inspired by http://stackoverflow.com/a/479198/244342

    if (!fileInfo.Exists)
    {
        // Will not be able to match filesystem
        return fileInfo;
    }

    return fileInfo.Directory.GetProperCasedDirectoryInfo().GetFiles(fileInfo.Name)[0];
}

I've been banging my head over some case-inconsistency issues with FileInfo. In order to ensure robustness, I convert to all caps when doing comparison or storage of the paths. To clarify the intent of the code, I also have these extension methods:

public static string GetPathForKey(this FileInfo File)
{
    return File.FullName.ToUpperInvariant();
}

public static string GetDirectoryForKey(this FileInfo File)
{
    return File.DirectoryName.ToUpperInvariant();
}

You'll want the system to find the file for you. I do this by pretending that I do not know the exact path, i.e. have the system search:

var fileName = Path.GetFileName(filePath);
var dir = Path.GetDirectoryName(filePath);
var filePaths = Directory.GetFiles(dir, fileName, SearchOption.TopDirectoryOnly);
var caseCorrectedFilePath = filePaths.FirstOrDefault();

So we search in the directory, filtering on the exact file name and limiting the search to the current directory only (no recursion).

This returns a string array containing either the single file path with correct casing (if the file exists) or nothing (if the file does not exist).

One warning: You may need to disallow wildcards in the input path, because this approach accepts them and may find multiple files as a result.

Edit

The drive letter appears to still follow the casing that we provide. Also, this needs to be tested for UNC paths.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top