Getting actual file name (with proper casing) on Windows

前端 未结 7 995
暗喜
暗喜 2020-12-05 19:29

Windows file system is case insensitive. How, given a file/folder name (e.g. \"somefile\"), I get the actual name of that file/folder (e.g. it should return \"SomeF

相关标签:
7条回答
  • 2020-12-05 19:48

    Okay, this is VBScript, but even so I'd suggest using the Scripting.FileSystemObject object

    Dim fso
    Set fso = CreateObject("Scripting.FileSystemObject")
    Dim f
    Set f = fso.GetFile("C:\testfile.dat") 'actually named "testFILE.dAt"
    wscript.echo f.Name
    

    The response I get is from this snippet is

    testFILE.dAt
    

    Hope that at least points you in the right direction.

    0 讨论(0)
  • 2020-12-05 19:50

    After a quick test, GetLongPathName() does what you want.

    0 讨论(0)
  • 2020-12-05 19:54

    There is another solution. First call GetShortPathName() and then GetLongPathName(). Guess what character case will be used then? ;-)

    0 讨论(0)
  • 2020-12-05 20:10

    Have you tried using SHGetFileInfo?

    0 讨论(0)
  • 2020-12-05 20:10

    Just found that the Scripting.FileSystemObject suggested by @bugmagnet 10 years ago is a treasure. Unlike my old method, it works on Absolute Path, Relative Path, UNC Path and Very Long Path (path longer than MAX_PATH). Shame on me for not testing his method earlier.

    For future reference, I would like to present this code which can be compiled in both C and C++ mode. In C++ mode, the code will use STL and ATL. In C mode, you can clearly see how everything is working behind the scene.

    #include <Windows.h>
    #include <objbase.h>
    #include <conio.h> // for _getch()
    
    #ifndef __cplusplus
    #   include <stdio.h>
    
    #define SafeFree(p, fn) \
        if (p) { fn(p); (p) = NULL; }
    
    #define SafeFreeCOM(p) \
        if (p) { (p)->lpVtbl->Release(p); (p) = NULL; }
    
    
    static HRESULT CorrectPathCasing2(
        LPCWSTR const pszSrc, LPWSTR *ppszDst)
    {
        DWORD const clsCtx = CLSCTX_INPROC_SERVER;
        LCID const lcid = LOCALE_USER_DEFAULT;
        LPCWSTR const pszProgId = L"Scripting.FileSystemObject";
        LPCWSTR const pszMethod = L"GetAbsolutePathName";
        HRESULT hr = 0;
        CLSID clsid = { 0 };
        IDispatch *pDisp = NULL;
        DISPID dispid = 0;
        VARIANT vtSrc = { VT_BSTR };
        VARIANT vtDst = { VT_BSTR };
        DISPPARAMS params = { 0 };
        SIZE_T cbDst = 0;
        LPWSTR pszDst = NULL;
    
        // CoCreateInstance<IDispatch>(pszProgId, &pDisp)
    
        hr = CLSIDFromProgID(pszProgId, &clsid);
        if (FAILED(hr)) goto eof;
    
        hr = CoCreateInstance(&clsid, NULL, clsCtx,
            &IID_IDispatch, (void**)&pDisp);
        if (FAILED(hr)) goto eof;
        if (!pDisp) {
            hr = E_UNEXPECTED; goto eof;
        }
    
        // Variant<BSTR> vtSrc(pszSrc), vtDst;
        // vtDst = pDisp->InvokeMethod( pDisp->GetIDOfName(pszMethod), vtSrc );
    
        hr = pDisp->lpVtbl->GetIDsOfNames(pDisp, NULL,
            (LPOLESTR*)&pszMethod, 1, lcid, &dispid);
        if (FAILED(hr)) goto eof;
    
        vtSrc.bstrVal = SysAllocString(pszSrc);
        if (!vtSrc.bstrVal) {
            hr = E_OUTOFMEMORY; goto eof;
        }
        params.rgvarg = &vtSrc;
        params.cArgs = 1;
        hr = pDisp->lpVtbl->Invoke(pDisp, dispid, NULL, lcid,
            DISPATCH_METHOD, &params, &vtDst, NULL, NULL);
        if (FAILED(hr)) goto eof;
        if (!vtDst.bstrVal) {
            hr = E_UNEXPECTED; goto eof;
        }
    
        // *ppszDst = AllocWStrCopyBStrFrom(vtDst.bstrVal);
    
        cbDst = SysStringByteLen(vtDst.bstrVal);
        pszDst = HeapAlloc(GetProcessHeap(),
            HEAP_ZERO_MEMORY, cbDst + sizeof(WCHAR));
        if (!pszDst) {
            hr = E_OUTOFMEMORY; goto eof;
        }
        CopyMemory(pszDst, vtDst.bstrVal, cbDst);
        *ppszDst = pszDst;
    
    eof:
        SafeFree(vtDst.bstrVal, SysFreeString);
        SafeFree(vtSrc.bstrVal, SysFreeString);
        SafeFreeCOM(pDisp);
        return hr;
    }
    
    static void Cout(char const *psz)
    {
        printf("%s", psz);
    }
    
    static void CoutErr(HRESULT hr)
    {
        printf("Error HRESULT 0x%.8X!\n", hr);
    }
    
    static void Test(LPCWSTR pszPath)
    {
        LPWSTR pszRet = NULL;
        HRESULT hr = CorrectPathCasing2(pszPath, &pszRet);
        if (FAILED(hr)) {
            wprintf(L"Input: <%s>\n", pszPath);
            CoutErr(hr);
        }
        else {
            wprintf(L"Was: <%s>\nNow: <%s>\n", pszPath, pszRet);
            HeapFree(GetProcessHeap(), 0, pszRet);
        }
    }
    
    
    #else // Use C++ STL and ATL
    #   include <iostream>
    #   include <iomanip>
    #   include <string>
    #   include <atlbase.h>
    
    static HRESULT CorrectPathCasing2(
        std::wstring const &srcPath,
        std::wstring &dstPath)
    {
        HRESULT hr = 0;
        CComPtr<IDispatch> disp;
        hr = disp.CoCreateInstance(L"Scripting.FileSystemObject");
        if (FAILED(hr)) return hr;
    
        CComVariant src(srcPath.c_str()), dst;
        hr = disp.Invoke1(L"GetAbsolutePathName", &src, &dst);
        if (FAILED(hr)) return hr;
    
        SIZE_T cch = SysStringLen(dst.bstrVal);
        dstPath = std::wstring(dst.bstrVal, cch);
        return hr;
    }
    
    static void Cout(char const *psz)
    {
        std::cout << psz;
    }
    
    static void CoutErr(HRESULT hr)
    {
        std::wcout
            << std::hex << std::setfill(L'0') << std::setw(8)
            << "Error HRESULT 0x" << hr << "\n";
    }
    
    static void Test(std::wstring const &path)
    {
        std::wstring output;
        HRESULT hr = CorrectPathCasing2(path, output);
        if (FAILED(hr)) {
            std::wcout << L"Input: <" << path << ">\n";
            CoutErr(hr);
        }
        else {
            std::wcout << L"Was: <" << path << ">\n"
                << "Now: <" << output << ">\n";
        }
    }
    
    #endif
    
    
    static void TestRoutine(void)
    {
        HRESULT hr = CoInitialize(NULL);
    
        if (FAILED(hr)) {
            Cout("CoInitialize failed!\n");
            CoutErr(hr);
            return;
        }
    
        Cout("\n[ Absolute Path ]\n");
        Test(L"c:\\uSers\\RayMai\\docuMENTs");
        Test(L"C:\\WINDOWS\\SYSTEM32");
    
        Cout("\n[ Relative Path ]\n");
        Test(L".");
        Test(L"..");
        Test(L"\\");
    
        Cout("\n[ UNC Path ]\n");
        Test(L"\\\\VMWARE-HOST\\SHARED FOLDERS\\D\\PROGRAMS INSTALLER");
    
        Cout("\n[ Very Long Path ]\n");
        Test(L"\\\\?\\C:\\VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
            L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
            L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
            L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
            L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
            L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
            L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
            L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
            L"VERYVERYVERYLOOOOOOOONGFOLDERNAME");
    
        Cout("\n!! Worth Nothing Behavior !!\n");
        Test(L"");
        Test(L"1234notexist");
        Test(L"C:\\bad\\PATH");
    
        CoUninitialize();
    }
    
    int main(void)
    {
        TestRoutine();
        _getch();
        return 0;
    }
    

    Screenshot:


    Old Answer:

    I found that FindFirstFile() will return the proper casing file name (last part of path) in fd.cFileName. If we pass c:\winDOWs\exPLORER.exe as first parameter to FindFirstFile(), the fd.cFileName would be explorer.exe like this:

    If we replace the last part of path with fd.cFileName, we will get the last part right; the path would become c:\winDOWs\explorer.exe.

    Assuming the path is always absolute path (no change in text length), we can just apply this 'algorithm' to every part of path (except the drive letter part).

    Talk is cheap, here is the code:

    #include <windows.h>
    #include <stdio.h>
    
    /*
        c:\windows\windowsupdate.log --> c:\windows\WindowsUpdate.log
    */
    static HRESULT MyProcessLastPart(LPTSTR szPath)
    {
        HRESULT hr = 0;
        HANDLE hFind = NULL;
        WIN32_FIND_DATA fd = {0};
        TCHAR *p = NULL, *q = NULL;
        /* thePart = GetCorrectCasingFileName(thePath); */
        hFind = FindFirstFile(szPath, &fd);
        if (hFind == INVALID_HANDLE_VALUE) {
            hr = HRESULT_FROM_WIN32(GetLastError());
            hFind = NULL; goto eof;
        }
        /* thePath = thePath.ReplaceLast(thePart); */
        for (p = szPath; *p; ++p);
        for (q = fd.cFileName; *q; ++q, --p);
        for (q = fd.cFileName; *p = *q; ++p, ++q);
    eof:
        if (hFind) { FindClose(hFind); }
        return hr;
    }
    
    /*
        Important! 'szPath' should be absolute path only.
        MUST NOT SPECIFY relative path or UNC or short file name.
    */
    EXTERN_C
    HRESULT __stdcall
    CorrectPathCasing(
        LPTSTR szPath)
    {
        HRESULT hr = 0;
        TCHAR *p = NULL;
        if (GetFileAttributes(szPath) == -1) {
            hr = HRESULT_FROM_WIN32(GetLastError()); goto eof;
        }
        for (p = szPath; *p; ++p)
        {
            if (*p == '\\' || *p == '/')
            {
                TCHAR slashChar = *p;
                if (p[-1] == ':') /* p[-2] is drive letter */
                {
                    p[-2] = toupper(p[-2]);
                    continue;
                }
                *p = '\0';
                hr = MyProcessLastPart(szPath);
                *p = slashChar;
                if (FAILED(hr)) goto eof;
            }
        }
        hr = MyProcessLastPart(szPath);
    eof:
        return hr;
    }
    
    int main()
    {
        TCHAR szPath[] = TEXT("c:\\windows\\EXPLORER.exe");
        HRESULT hr = CorrectPathCasing(szPath);
        if (SUCCEEDED(hr))
        {
            MessageBox(NULL, szPath, TEXT("Test"), MB_ICONINFORMATION);
        }
        return 0;
    }
    

    Advantages:

    • The code works on every version of Windows since Windows 95.
    • Basic error-handling.
    • Highest performance possible. FindFirstFile() is very fast, direct buffer manipulation makes it even faster.
    • Just C and pure WinAPI. Small executable size.

    Disadvantages:

    • Only absolute path is supported, other are undefined behavior.
    • Not sure if it is relying on undocumented behavior.
    • The code might be too raw too much DIY for some people. Might get you flamed.

    Reason behind the code style:

    I use goto for error-handling because I was used to it (goto is very handy for error-handling in C). I use for loop to perform functions like strcpy and strchr on-the-fly because I want to be certain what was actually executed.

    0 讨论(0)
  • 2020-12-05 20:10

    FindFirstFileNameW will work with a few drawbacks:

    • it doesn't work on UNC paths
    • it strips the drive letter so you need to add it back
    • if there are more than one hard link to your file you need to identify the right one
    0 讨论(0)
提交回复
热议问题