Visual C++ in Short: Determining whether a path refers to a file system object

If you’ve been programming Windows for a while (prior to Windows 2000) you may well have come across a number of techniques to determine whether a path refers to a file system object such as a file or directory.

One approach is to check the result of the CreateFile function but this approach has a number of pitfalls. Checking whether a path refers to a file system object should not change the state of the file system object, which CreateFile does in most cases. You can combat this by playing around with the various flags. For example, if you only specify FILE_READ_ATTRIBUTES as the desired access then the file’s last access date won’t be updated. Of course you may not have the necessary authorization to open the file handle at all. There are also other headaches associated with this approach such as remembering to close the file handle should the call to CreateFile succeed. Determining whether the file handle refers to a file or directory involves additional function calls.

Another solution is to use the FindFirstFile function but it gets a lot more data than you might need and you also need to remember to close the search handle.

The shell provides the PathFileExists function which is simpler than the approaches mentioned thus far but is limited in that it does not distinguish between files and directories. Still, if that’s good enough then here’s all you need to do:

const BOOL exists = PathFileExists(L"<some path>");

To be able to distinguish between files and directories involves checking the attributes of the file system object. The simplest way to get a file’s attributes is with the GetFileAttributes function introduced with Windows 2000 a long time ago (see comments). Of course if it fails to return the file attributes you can call the GetLastError function to find out why. That in turn provides a great way to determine whether the path refers to a file system object at all. It can also tell you other useful information such as whether the path is even valid.

The beauty of this model is that you get to decide just how much information you need. For example if you only want to know whether the path exists you can simply check whether GetFileAttributes returns INVALID_FILE_ATTRIBUTES or not. Alternatively you can distinguish between different classes of failure.

The following example shows a simple helper function that singles out the ability for the system to find the specified path from other failures. If a file system object exists it returns S_OK. If the path is well formed but the system cannot find the path or file then it returns S_FALSE. In all other cases it simply returns the specific failure as an HRESULT. It even optionally tells you whether the file system object is a directory or not.

HRESULT FileExists(PCWSTR fileName, bool* isDirectory)
{
   ASSERT(0 != fileName);

   const DWORD attributes = ::GetFileAttributes(fileName);

   if (INVALID_FILE_ATTRIBUTES == attributes)
   {
       const DWORD error = ::GetLastError();

       if (ERROR_FILE_NOT_FOUND == error ||
           ERROR_PATH_NOT_FOUND == error)
       {
           return S_FALSE;
       }
       else
       {
           return HRESULT_FROM_WIN32(error);
       }
   }

   if (0 != isDirectory)
   {
       *isDirectory = 0 != (FILE_ATTRIBUTE_DIRECTORY & attributes);
   }

   return S_OK;
}

You could for example identify syntax errors using the ERROR_INVALID_NAME error code:

bool isDirectory = false;
const HRESULT result = FileExists(L"<some path>", &isDirectory);

if (S_OK == result)
{
    if (isDirectory)
    {
        wprintf(L"Its a directory");
    }
    else
    {
        wprintf(L"Its a file");
    }
}
else if (S_FALSE == result)
{
    wprintf(L"Not found");
}
else
{
    if (HRESULT_FROM_WIN32(ERROR_INVALID_NAME) == result)
    {
        wprintf(L"The syntax is incorrect");
    }
    else
    {
        // Something else is wrong...
        wprintf(AtlGetErrorDescription(result));
    }
}

Incidentally, the PathFileExists function I mentioned above uses GetFileAttributes internally if it determines that you’re running on a supported version of Windows. In other words it’s a reasonable fallback if you need to support older operating systems.

There are also various shortcuts depending on what you intend to do with the path. For example if you just want to create a file and make sure it doesn’t already exist you can simply specify the CREATE_NEW creation disposition and the CreateFile function will fail if the specified file already exists.

If you’re looking for one of my previous articles here is a complete list of them for you to browse through.

Produce the highest quality screenshots with the least amount of effort! Use Window Clippings.

Published Thursday, July 31, 2008 8:41 PM by KennyKerr
Filed under:

Comments

# re: Visual C++ in Short: Determining whether a path refers to a file system object

Friday, August 01, 2008 7:50 AM by ak

"GetFileAttributes function introduced with Windows 2000" WTF Kenny, this function is supported on every 32bit version of windows, hell GetFileAttributesEx is supported on every version except Win95 (NT4+ and Win98+)

You can't trust MSDN when it comes to min versions of functions, they have removed Win9x and NT4 from pretty much anywhere. Use the platform SDK from 2003

# re: Visual C++ in Short: Determining whether a path refers to a file system object

Friday, August 01, 2008 8:21 AM by KennyKerr

ak: Granted the Windows SDK documentation requirements section is perhaps not the most accurate way of determining when a function was introduced, but that wasn’t really the point and I couldn’t be bothered to install Windows NT 4 in a VM just to confirm.

The point I was trying to make is that GetFileAttributes has been around for a long time (I consider the release of Windows 2000 a long time ago) and you shouldn’t have to resort to other more complex techniques to determine whether a file/directory exists. I do however know developers who still need to support Windows NT 4 which is unfortunate.

Code in the .NET Framework and shell code like PathFileExists still check whether GetFileAttributes(Ex) is available based on OS version since that code was originally written to support operating systems that did not in fact support it.

# re: Visual C++ in Short: Determining whether a path refers to a file system object

Friday, August 01, 2008 10:07 AM by ak

If that was not the point, why include the information at all then, a missing export would come up real fast in testing anyway. It would be better to let people find out for themselves instead of spreading wrong info.

See msdn.microsoft.com/.../aa383748(VS.85).aspx for a list of functions added in 2k according to MS

See www.geoffchappell.com/.../index.htm for a nice list of apis and min/max version (Too bad they don't have one for user32 and kernel32)

There is no need for a WM, just getting a hold of kernel32.dll from a old system or a CD and checking its exports is enough, or like I said, install a older SDK

# re: Visual C++ in Short: Determining whether a path refers to a file system object

Friday, August 01, 2008 10:10 AM by ak

...and one more thing:

"Code in the .NET Framework and shell code like PathFileExists still check whether GetFileAttributes(Ex) is available based on OS version since that code was originally written to support operating systems that did not in fact support it" makes no sense to me, since GetFileAttributes is supported on every OS where PathFileExists and/or .NET could exist

# re: Visual C++ in Short: Determining whether a path refers to a file system object

Friday, August 01, 2008 10:34 AM by KennyKerr

ak: Why all the nitpicking? As I admitted already, I took the documentation at face value without thinking much more about it. Anyway, whether or not a function is introduced in Windows 2000 or Windows NT 3.51 I reckon it matters very little to the vast majority of developers who only target Windows XP and later. But I thank you for pointing it out – I have updated my original post.

re: “makes no sense to me, since GetFileAttributes is supported on every OS where PathFileExists and/or .NET could exist”

Attach a debugger and look for yourself. Pre-emptive nitpick: your mileage may vary depending on which OS version you choose to spelunk in with your disassembler.  :)

# re: Visual C++ in Short: Determining whether a path refers to a file system object

Friday, August 01, 2008 11:31 AM by ak

I'm sorry for the nitpicking, I'll try to keep my trolling to a minimum.

I'm not a .NET guy and still using VC6 for my C/C++ needs, but from what I could tell from Reflector, there is indeed two code paths that it could go down:

if ((Environment.OSInfo == Environment.OSName.Win95) || tryagain)

{

FindFirstFile code...

}

If its not Win95 or the first try, it calls  Win32Native.GetFileAttributesEx (note the Ex)

if that fails, it tries again down the FindFirstFile codepath, but the function I'm talking about (System.IO.File.FillAttributeInfo) retrieves a WIN32_FILE_ATTRIBUTE_DATA struct, not just the attributes of a file. Why System.IO.File.Exists does not just call the non Ex version directly, I don't know (I did not even think .NET was supported on Win95...ever)

# re: Visual C++ in Short: Determining whether a path refers to a file system object

Friday, August 01, 2008 1:57 PM by KennyKerr

ak: No problem.  :)

The .NET code is really old thus the heavy legacy implementation. You can also take a look inside PathFileExists with Visual C++ if you load the OS symbols and you’ll see similar logic (except it calls the more appropriate GetFileAttributes), although I don’t remember whether VC6 uses the same symbol format.

# re: Visual C++ in Short: Determining whether a path refers to a file system object

Wednesday, September 24, 2008 5:54 AM by Dioksin

Hello!

First of all, thank you for your blog. It’s very interesting. I tried this function and I had minor problem. I want to use this function to check network path. Something likes this: \\Server\SharedFolder\SomeFolder. When I run this method first time I get error ERROR_BAD_NETPATH. But second call of this function (and all subsequent ones) is successful. To reproduce the error I make log off and log on at once I run this code. Do you have any suggestion?

# re: Visual C++ in Short: Determining whether a path refers to a file system object

Wednesday, September 24, 2008 6:24 AM by KennyKerr

Dioksin: Not too sure off the top of my head. You may want to look into use records. For example, how is your access to the UNC path being authenticated... is a session being established... You can check with “net use” on the command like or the NetUseXxx functions. Hope that helps.

# re: Visual C++ in Short: Determining whether a path refers to a file system object

Wednesday, September 24, 2008 7:28 AM by Dioksin

Authentication is successful. I can access to this network folder from, say, File Explorer. After that the call of your function is successful too. But if I make log off from Windows and back log on with the same credentials in this case only FIRST call of ::GetFileAttributes is wrong.

# re: Visual C++ in Short: Determining whether a path refers to a file system object

Friday, September 26, 2008 4:30 PM by Geoff Chappell

In the original Windows 95 SHELL32, PathFileExists is entirely a test of whether GetFileAttributesA succeeds, i.e., returns anything other than INVALID_FILE_ATTRIBUTES.

This is also how PathFileExists is coded in SHELL32 from Windows 95 OSR2, but the A and W codings in SHLWAPI arrange to fail critical errors during that call to GetFileAttributes (A and W respectively).

NT 4.0 didn't have SHLWAPI, but the PathFileExists in SHELL32 is the same as the PathFileExistsW in the SHLWAPI from Windows 95 OSR2, except for adding a defence against the argument being NULL or empty. This test, and even then only for whether the argument is NULL, didn't find its way into SHLWAPI until the version for Windows 2000.

The early history, whether in SHELL32 or SHLWAPI, wasn't documented by Microsoft. I don't think PathFileExists got documented until late 1997. When it did get documented, it was presented as another of those Windows features that programmers could code for their Windows 95 and NT 4.0 users provided that these users install Internet Explorer.

In Internet Explorer 4.0, the exports from SHLWAPI are as for Windows 95 OSR2, but the export from SHELL32 has got more work done. It makes a special case for network paths. If PathIsUNCServer or PathIsUNCServerShare succeeds for the given path, then GetFileAttributes is skipped in favour of checking WNetGetNetworkInformation.

The IE4 SHELL32 is notable for a coding detail. The work of PathFileExists is done by calling an internal function which looks very much like what later turned up in SHLWAPI as PathFileExistsAndAttributes.

Windows 2000 (finally) settled PathFileExists as a SHLWAPI function. The PathFileExists functionality in SHELL32 is imported from SHLWAPI through PathFileExistsAndAttributes, choosing between A and W according to application compatibility flags, and discarding the attributes.

The coding of PathFileExistsAndAttributes in SHLWAPI looks stable. It differs from the IE4 coding in SHELL32 in a few minor details, plus one that may have practical importance: the network case is considered only after finding that GetFileAttributes has failed. There's also been a change to WNetGetResourceInformation.

Note that PathFileExists in SHLWAPI, even in Windows Vista, is coded pretty much the same as in the NT 4.0 SHELL32. The network sophistication is only in PathFileExistsAndAttributes.

Now, you may be wondering what this PathFileExistsAndAttributes function is. It looks to be exactly what you wanted. For who knows what reason, it isn't documented. Give the path as the first argument. Pass an address as the second argument for receiving the attributes, if you want them, else pass NULL.

> (Too bad they don't have one for user32 and > kernel32)

Thank you, ak, for the reference to my website. Cataloguing these things is a necessary first step for informed analysis, but it's not very interesting work in and of itself. There's only one of me and the work at that website is all done unpaid. I simply can't afford to do anything that doesn't pull its weight in terms of interest.