Getting actual file name (with proper casing) on Windows with .NET

后端 未结 6 397
被撕碎了的回忆
被撕碎了的回忆 2020-12-05 17:31

I want to do exactly the same as in this question:

Windows file system is case insensitive. How, given a file/folder name (e.g. \"somefile\"), I get t

相关标签:
6条回答
  • 2020-12-05 17:44

    It looks like the best way is to iterate through all folders in the path and get their proper caps:

     Public Function gfnProperPath(ByVal sPath As String) As String
        If Not IO.File.Exists(sPath) AndAlso Not IO.Directory.Exists(sPath) Then Return sPath
        Dim sarSplitPath() As String = sPath.Split("\")
        Dim sAddPath As String = sarSplitPath(0).ToUpper & "\"
        For i = 1 To sarSplitPath.Length - 1
            sPath = sAddPath & "\" & sarSplitPath(i)
            If IO.File.Exists(sPath) Then
                Return IO.Directory.GetFiles(sAddPath, sarSplitPath(i), IO.SearchOption.TopDirectoryOnly)(0)
            ElseIf IO.Directory.Exists(sPath) Then
                sAddPath = IO.Directory.GetDirectories(sAddPath, sarSplitPath(i), IO.SearchOption.TopDirectoryOnly)(0)
            End If
        Next
        Return sPath
    End Function
    
    0 讨论(0)
  • 2020-12-05 17:45

    I think the only way you are going to be able to do this is by using the same Win32 API, namely the SHGetFileInfo method, mentioned in the accepted answer for the question you reference. In order to do this, you will need to use some interop p/invoke calls. Take a look at pinvoke.net for an example of how to do this and what additional structs you will need.

    0 讨论(0)
  • 2020-12-05 17:49

    I liked Yona's answer, but I wanted it to:

    • Support UNC paths
    • Tell me if the path didn't exist
    • Use iteration instead of recursion (since it only used tail recursion)
    • Minimize the number of calls to Path.Combine (to minimize string concatenations).
    /// <summary>
    /// Gets the exact case used on the file system for an existing file or directory.
    /// </summary>
    /// <param name="path">A relative or absolute path.</param>
    /// <param name="exactPath">The full path using the correct case if the path exists.  Otherwise, null.</param>
    /// <returns>True if the exact path was found.  False otherwise.</returns>
    /// <remarks>
    /// This supports drive-lettered paths and UNC paths, but a UNC root
    /// will be returned in title case (e.g., \\Server\Share).
    /// </remarks>
    public static bool TryGetExactPath(string path, out string exactPath)
    {
        bool result = false;
        exactPath = null;
    
        // DirectoryInfo accepts either a file path or a directory path, and most of its properties work for either.
        // However, its Exists property only works for a directory path.
        DirectoryInfo directory = new DirectoryInfo(path);
        if (File.Exists(path) || directory.Exists)
        {
            List<string> parts = new List<string>();
    
            DirectoryInfo parentDirectory = directory.Parent;
            while (parentDirectory != null)
            {
                FileSystemInfo entry = parentDirectory.EnumerateFileSystemInfos(directory.Name).First();
                parts.Add(entry.Name);
    
                directory = parentDirectory;
                parentDirectory = directory.Parent;
            }
    
            // Handle the root part (i.e., drive letter or UNC \\server\share).
            string root = directory.FullName;
            if (root.Contains(':'))
            {
                root = root.ToUpper();
            }
            else
            {
                string[] rootParts = root.Split('\\');
                root = string.Join("\\", rootParts.Select(part => CultureInfo.CurrentCulture.TextInfo.ToTitleCase(part)));
            }
    
            parts.Add(root);
            parts.Reverse();
            exactPath = Path.Combine(parts.ToArray());
            result = true;
        }
    
        return result;
    }
    

    For UNC paths, this cases the root (\\Server\Share) in title case rather than exact case because it would be a lot more work to try determine the remote server's exact case name and the share's exact case name. If you're interested in adding that support you'll have to P/Invoke methods like NetServerEnum and NetShareEnum. But those can be slow, and they don't support up-front filtering to just the server and share names you're concerned with.

    Here's a unit test method for TryGetExactPath (using Visual Studio Testing Extensions):

    [TestMethod]
    public void TryGetExactPathNameTest()
    {
        string machineName = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(Environment.MachineName.ToLower());
        string[] testPaths = new[]
            {
                @"C:\Users\Public\desktop.ini",
                @"C:\pagefile.sys",
                @"C:\Windows\System32\cmd.exe",
                @"C:\Users\Default\NTUSER.DAT",
                @"C:\Program Files (x86)\Microsoft.NET\Primary Interop Assemblies",
                @"C:\Program Files (x86)",
                @"Does not exist",
                @"\\Nas\Main\Setups",
                @"\\Nas\Main\Setups\Microsoft\Visual Studio\VS 2015\vssdk_full.exe",
                @"\\" + machineName + @"\C$\Windows\System32\ActionCenter.dll",
                @"..",
            };
        Dictionary<string, string> expectedExactPaths = new Dictionary<string, string>()
            {
                { @"..", Path.GetDirectoryName(Environment.CurrentDirectory) },
            };
    
        foreach (string testPath in testPaths)
        {
            string lowercasePath = testPath.ToLower();
            bool expected = File.Exists(lowercasePath) || Directory.Exists(lowercasePath);
            string exactPath;
            bool actual = FileUtility.TryGetExactPath(lowercasePath, out exactPath);
            actual.ShouldEqual(expected);
            if (actual)
            {
                string expectedExactPath;
                if (expectedExactPaths.TryGetValue(testPath, out expectedExactPath))
                {
                    exactPath.ShouldEqual(expectedExactPath);
                }
                else
                {
                    exactPath.ShouldEqual(testPath);
                }
            }
            else
            {
                exactPath.ShouldBeNull();
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-05 17:56

    My second answer here with a non recursive method. It accepts both files and dirs.
    This time translated from VB to C#:

    private string fnRealCAPS(string sDirOrFile)
    {
        string sTmp = "";
        foreach (string sPth in sDirOrFile.Split("\\")) {
            if (string.IsNullOrEmpty(sTmp)) {
                sTmp = sPth + "\\";
                continue;
            }
            sTmp = System.IO.Directory.GetFileSystemEntries(sTmp, sPth)[0];
        }
        return sTmp;
    }
    0 讨论(0)
  • 2020-12-05 18:01

    I seems that since NTFS is case insensitive it will always accept your input correctly regardless if the name is cased right.

    The only way to get the correct path name seems to find the file like John Sibly suggested.

    I created a method that will take a path (folder or file) and return the correctly cased version of it (for the entire path):

        public static string GetExactPathName(string pathName)
        {
            if (!(File.Exists(pathName) || Directory.Exists(pathName)))
                return pathName;
    
            var di = new DirectoryInfo(pathName);
    
            if (di.Parent != null) {
                return Path.Combine(
                    GetExactPathName(di.Parent.FullName), 
                    di.Parent.GetFileSystemInfos(di.Name)[0].Name);
            } else {
                return di.Name.ToUpper();
            }
        }
    

    Here are some test cases that worked on my machine:

        static void Main(string[] args)
        {
            string file1 = @"c:\documents and settings\administrator\ntuser.dat";
            string file2 = @"c:\pagefile.sys";
            string file3 = @"c:\windows\system32\cmd.exe";
            string file4 = @"c:\program files\common files";
            string file5 = @"ffffd";
    
            Console.WriteLine(GetExactPathName(file1));
            Console.WriteLine(GetExactPathName(file2));
            Console.WriteLine(GetExactPathName(file3));
            Console.WriteLine(GetExactPathName(file4));
            Console.WriteLine(GetExactPathName(file5));
    
            Console.ReadLine();
        }
    

    The method will return the supplied value if the file does not exists.

    There might be faster methods (this uses recursion) but I'm not sure if there are any obvious ways to do it.

    0 讨论(0)
  • 2020-12-05 18:08

    Inspired by Ivan's answer, here is a method that also handles drive letter casing as well:

    public string FixFilePathCasing(string filePath)
    {
        string fullFilePath = Path.GetFullPath(filePath);
    
        string fixedPath = "";
        foreach(string token in fullFilePath.Split('\\'))
        {
            //first token should be drive token
            if(fixedPath == "")
            {
                //fix drive casing
                string drive = string.Concat(token, "\\");
                drive = DriveInfo.GetDrives()
                    .First(driveInfo => driveInfo.Name.Equals(drive, StringComparison.OrdinalIgnoreCase)).Name;
    
                fixedPath = drive;
            }
            else
            {
                fixedPath = Directory.GetFileSystemEntries(fixedPath, token).First();
            }
        }
    
        return fixedPath;
    }
    
    0 讨论(0)
提交回复
热议问题