C# Filepath Recasing
Asked Answered
A

6

22

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;
}
Alva answered 26/1, 2009 at 4:12 Comment(1)
Because of ClearCase. I already said that in the question.Alva
C
24

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.

Commonality answered 26/1, 2009 at 9:12 Comment(5)
This breaks on UNC paths unfortunately. I need it to work on UNC paths. Like \\SERVERNAME\Hostdir\MyDocument.docxAlva
I'm going to put this as the answer. For my needs it works the best. I can add additional hacks on top of this hack to make it work but it turns out I'm leaving the feature out because of complications.Alva
I found dirInfo.Name was just plain wrong for UNC shares, because it drops the server name. For my purposes, dirInfo.FullName.ToUpperInvariant() is good enough - it doesn't recover the correct case for the share name, but it does construct a valid path.Sessoms
For it to work with Network Shares or UNC Paths, use "return dirInfo.Root.FullName;" instead of "return dirInfo.Name;". Tested with UNC Paths, Network Paths and normal Paths.Krueger
To work with nullable references, I changed DirectoryInfo to DirectoryInfo? in both places it is used. In GetProperFilePathCapitalization I returned filename if somehow dirInfo came up null; others may want to throw an exception.Smack
G
3

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);
Georgettageorgette answered 28/1, 2009 at 12:13 Comment(1)
Nice solution for Vista. Beware that this resolves symbolic links. See remarks at: msdn.microsoft.com/en-us/library/aa364962(VS.85).aspxCommonality
P
2

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 https://mcmap.net/q/393259/-c-filepath-recasing

    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 https://mcmap.net/q/393259/-c-filepath-recasing

    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();
}
Pyrrhuloxia answered 7/8, 2012 at 22:20 Comment(0)
T
1

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?

Tegular answered 26/1, 2009 at 4:53 Comment(2)
Case will be there only if the file exists. Otherwise doesn't matter don't you think?Subservient
This fixes the case of the file, but the entire rest of the path remains improperly cased.Gunflint
C
1

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);
Commonality answered 28/1, 2009 at 7:19 Comment(0)
R
0

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.

Rapine answered 4/11, 2015 at 10:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.