Finding the paths to Windows' special folders in VFP

A Visual FoxPro function for locating all those directories that have a special meaning in Windows.

By Mike Lewis

When you develop software for Microsoft Windows, you often need to know the names and locations of the "special" folders. I'm thinking of things like My Documents, My Pictures, Application Data, Program Files, and the like. For example, you might need to locate My Documents so that you can use it to save user-generated files. Or you might want to access the Fonts directory to see which fonts are installed on the user's system.

As you probably know, you can't hard-code the paths to these folders. That's because their names and locations vary with different versions of Windows. A good example of this is the personal documents folder. In Windows XP, this is named My Documents and is typically found under C:\Documents and Settings\<user>; Windows Vista and above refer to it simply as Documents, and usually store it under C:\Users\<user>.

A programmatic solution

Fortunately, it's a simple matter to determine the paths to these folders programmatically, thanks to a couple of Windows API functions.

The first of these is SHGetSpecialFolderLocation(). This receives a "special item ID" (CSIDL), which is a numeric constant that uniquely identifies each of the special folders. Some of the more common CSIDLs are 5 (My Documents), 26 (Application Data), 36 (Windows) and 38 (Program Files). I've included a more complete list near the end of the article.

The function returns a value known as a PIDL (pointer to ID list). This specifies the folder's location relative to the root of the namespace. (Don't worry about the PIDL; you don't need to understand it in order to use what follows.)

The second function, SHGetPathFromIDList(), receives the PIDL and returns the path to the folder in question.

Creating new folders

Of course, there's no guarantee that the required folder will actually exist on the user's system. It's hard to imagine a Windows computer without My Documents or Program Files. But some of the less common directories, such as My Videos, might well be absent.

When calling SHGetSpecialFolderLocation(), you can optionally add a special value (32768) to the CSIDL to specify that you want the function to create the folder if it doesn't already exist (and to return the path to the folder thus created). No harm is done if you use this value for a folder that's already present.

A Visual FoxPro function

To make life easier for you, I've written a simple VFP wrapper for the two API functions. It's called GetSpecial(). You pass the CLSID, and optionally a logical .T. to say that you want to create the folder if it doesn't already exist. The function returns the required path. If the CLSID is invalid, or if the folder cannot be found (and you didn't specify that you wanted to create it), the function returns an empty string.

FUNCTION GetSpecial
* Returns the path to one of Windows' "special folders", 
* such as My Documents, Application Data, Program Files, 
* etc.

* Parameters:
* - The folder's CSIDL;
* - A flag to say the folder should be created if it
*   doesn't already exist.

* Returns the path to the specified folder. If the CLSID
* is invalid, or the folder doesn't exist (and the second
* parameter is .F.), returns an empty string.

LPARAMETERS tnFolder, tlCreate

LOCAL lcPath, lnPidl, lnPidlOK, lnFolderOK

#DEFINE MAX_PATH 260
#DEFINE CREATE_FLAG 32768 

* Delcare the API functions
DECLARE LONG SHGetSpecialFolderLocation IN shell32 ;
	LONG Hwnd, LONG lnFolder, LONG @pPidl
DECLARE LONG SHGetPathFromIDList IN shell32 ;
	LONG pPidl, STRING @pszPath
DECLARE CoTaskMemFree IN ole32 LONG pVoid

lcPath = SPACE(MAX_PATH)
lnPidl = 0

* If .T. is passed as second param, we want to create the 
* folder if it doesn't already exist.
IF tlCreate
	tnFolder = tnFolder + CREATE_FLAG
ENDIF 
	
* Get the PIDL
lnPidlOK = SHGetSpecialFolderLocation(0, tnFolder, @lnPidl)

IF lnPidlOK = 0
  * Pidl found OK; get the folder
  lnFolderOK = SHGetPathFromIDList(lnPidl, @lcPath)
  IF lnFolderOK = 1
    * Folder found OK; trim the string at the null terminator
    lcPath = LEFT(lcPath, AT(CHR(0), lcPath) - 1)
  ENDIF 
ENDIF 

* Free the ID List
CoTaskMemFree(lnPidl)

RETURN ALLTRIM(lcPath)

And here is my list of the more common CSIDLs (you can find more values here).

CSIDL
(decimal)
Description Typical values
5My DocumentsC:\Documents and Settings\username\My Documents
C:\Users\username\Documents
6FavoritesC:\Documents and Settings\username\Favorites
C:\Users\IserName\Favorites
8Recent DocumentsC:\Documents and Settings\username\Recent
9Send ToC:\Documents and Settings\username\SendTo
13My MusicC:\Documents and Settings\User\My Documents\My Music
C:\Users\UserName\Music
14My VideosC:\Documents and Settings\User\My Documents\My Videos
C:\Users\UserName\Videos
16DesktopC:\Documents and Settings\username\Desktop
C:\Users\username\Desktop
19Network NeighborhoodC:\Documents and Settings\username\NetHood
20FontsC:\Windows\Fonts
26Application DataC:\Documents and Settings\username\Application Data
C:\Users\username\AppData\Roaming
28Local (non-roaming) application dataC:\Documents and Settings\username\Local Settings\Application Data
C:\Users\username\AppData\Local
33CookiesC:\Documents and Settings\username\Cookies
35Common Application DataC:\Documents and Settings\All Users\Application Data
C:\ProgramData
36WindowsC:\Windows
37SystemC:\Windows\System32
38Program FilesC:\Program Files
39My PicturesC:\Documents and Settings\User\My Documents\My Pictures
C:\Users\UserName\Pictures
43Common FilesC:\Program Files\Common

Acknowledgement

The above function is based on code that was given to me (many years ago) by Doug Hennig. I no longer have Doug's original code, but I do know that, if there any imperfections or flaws in the function, these were added by me.

April 2012

Please note: The information given on this site has been carefully checked and is believed to be correct, but no legal liability can be accepted for its use. Do not use code, components or techniques unless you are satisfied that they will work correctly with your sites or applications.

If you found this article useful, please tell your friends: