Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting actual file name (with proper casing) on Windows

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 "SomeFile" if Explorer displays it so)?

Some ways I know, all of which seem quite backwards:

  1. Given the full path, search for each folder on the path (via FindFirstFile). This gives proper cased results of each folder. At the last step, search for the file itself.
  2. Get filename from handle (as in MSDN example). This requires opening a file, creating file mapping, getting it's name, parsing device names etc. Pretty convoluted. And it does not work for folders or zero-size files.

Am I missing some obvious WinAPI call? The simplest ones, like GetActualPathName() or GetFullPathName() return the name using casing that was passed in (e.g. returns "program files" if that was passed in, even if it should be "Program Files").

I'm looking for a native solution (not .NET one).

like image 973
NeARAZ Avatar asked Sep 16 '08 16:09

NeARAZ


People also ask

How do I get the full file name in Windows?

Open File Explorer and click the "View" tab on the ribbon. Next, click the "Options" button to the far-right of the ribbon. Switch to the "View" tab and then select the "Display the full path in the title bar" checkbox. Click "Apply" and then "OK" to close the popup.

Is Windows case sensitive for file names?

Windows file system treats file and directory names as case-insensitive.

What does ~$ mean in front of a file name?

Files that suddenly appear with a tilde are usually backups of a file that was opened or still opened. For example, with a file called myfile. doc, when it is opened in Microsoft Word, the ~$myfile. doc is created. It is a temporary backup file, used to recover data if the software crashes or stops unexpectedly.

How do I extract file names?

To extract filename from the file, we use “GetFileName()” method of “Path” class. This method is used to get the file name and extension of the specified path string. The returned value is null if the file path is null. Syntax: public static string GetFileName (string path);


2 Answers

And hereby I answer my own question, based on original answer from cspirz.

Here's a function that given absolute, relative or network path, will return the path with upper/lower case as it would be displayed on Windows. If some component of the path does not exist, it will return the passed in path from that point.

It is quite involved because it tries to handle network paths and other edge cases. It operates on wide character strings and uses std::wstring. Yes, in theory Unicode TCHAR could be not the same as wchar_t; that is an exercise for the reader :)

std::wstring GetActualPathName( const wchar_t* path )
{
    // This is quite involved, but the meat is SHGetFileInfo

    const wchar_t kSeparator = L'\\';

    // copy input string because we'll be temporary modifying it in place
    size_t length = wcslen(path);
    wchar_t buffer[MAX_PATH];
    memcpy( buffer, path, (length+1) * sizeof(path[0]) );

    size_t i = 0;

    std::wstring result;

    // for network paths (\\server\share\RestOfPath), getting the display
    // name mangles it into unusable form (e.g. "\\server\share" turns
    // into "share on server (server)"). So detect this case and just skip
    // up to two path components
    if( length >= 2 && buffer[0] == kSeparator && buffer[1] == kSeparator )
    {
        int skippedCount = 0;
        i = 2; // start after '\\'
        while( i < length && skippedCount < 2 )
        {
            if( buffer[i] == kSeparator )
                ++skippedCount;
            ++i;
        }

        result.append( buffer, i );
    }
    // for drive names, just add it uppercased
    else if( length >= 2 && buffer[1] == L':' )
    {
        result += towupper(buffer[0]);
        result += L':';
        if( length >= 3 && buffer[2] == kSeparator )
        {
            result += kSeparator;
            i = 3; // start after drive, colon and separator
        }
        else
        {
            i = 2; // start after drive and colon
        }
    }

    size_t lastComponentStart = i;
    bool addSeparator = false;

    while( i < length )
    {
        // skip until path separator
        while( i < length && buffer[i] != kSeparator )
            ++i;

        if( addSeparator )
            result += kSeparator;

        // if we found path separator, get real filename of this
        // last path name component
        bool foundSeparator = (i < length);
        buffer[i] = 0;
        SHFILEINFOW info;

        // nuke the path separator so that we get real name of current path component
        info.szDisplayName[0] = 0;
        if( SHGetFileInfoW( buffer, 0, &info, sizeof(info), SHGFI_DISPLAYNAME ) )
        {
            result += info.szDisplayName;
        }
        else
        {
            // most likely file does not exist.
            // So just append original path name component.
            result.append( buffer + lastComponentStart, i - lastComponentStart );
        }

        // restore path separator that we might have nuked before
        if( foundSeparator )
            buffer[i] = kSeparator;

        ++i;
        lastComponentStart = i;
        addSeparator = true;
    }

    return result;
}

Again, thanks to cspirz for pointing me to SHGetFileInfo.

like image 140
NeARAZ Avatar answered Oct 15 '22 15:10

NeARAZ


Have you tried using SHGetFileInfo?

like image 39
cspirz Avatar answered Oct 15 '22 16:10

cspirz