FAQ: Where Do I Save Files, and How Exactly Do I Do That?

The correct ways to identify folder paths to store files depends on the programming technology you use. This blog post shows how to do it in C++, C# and VB. NET, PowerShell, Windows Script Host (VBScript and JScript), and as a last resort, environment variables.

One of the more common programming mistakes that lead to compatibility problems is the incorrect specification of folder paths in programs.  For example, it is not uncommon for programs to assume that the user’s profile is under “C:\Documents and Settings”.  These default paths are always subject to change and have changed across different versions of Windows.  User profiles were stored under %SystemDrive%\WINNT\Profiles, then under “%SystemDrive%\Documents and Settings”, and now under %SystemDrive%\Users. The “All Users” profile is now called “Public”, and what was in “%USERPROFILE%\Local Settings\Application Data” is now in “%USERPROFILE%\AppData\Local”. Also, part of what used to be under the “All Users” profile is now in a separate folder location (by default, C:\ProgramData).

How can programs work correctly when these paths keep changing?  Well, applications that are written correctly don’t require any modification to get the correct folder locations on all versions of Windows. Some rules you should follow:

  • Do not hardcode any file system paths.
  • Do not assume that Windows is installed on the C: drive, that there is a “Documents and Settings” or a “Users” folder, or a “Program Files” folder.
  • DO use symbolic constants and Windows APIs or environment variables to identify the appropriate place to put file content.
  • Distinguish between per-user and shared content.
  • Distinguish between files users should be able to browse in Explorer (e.g., documents that users create) and files that aren’t intended for direct access by users (application configuration settings, for example). These file types get stored in different locations.

Here are the correct ways to identify folder locations using a variety of programming technologies:

C++

Use the SHGetSpecialFolderPath function with CSIDL constants. For programs designed to run only on Windows Vista or newer, use the SHGetKnownFolderPath function with KNOWNFOLDERID constants. See the following links:

These two examples demonstrate retrieving the paths for the current user’s Documents folder and the computer’s shared Documents folder:

 

HRESULT hr;

TCHAR szPath[MAX_PATH];

hr = SHGetFolderPath(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, szPath);

if (SUCCEEDED(hr))

{

    ...

}

 

hr = SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, SHGFP_TYPE_CURRENT, szPath);

if (SUCCEEDED(hr))

{

    ...

}

C#/VB .NET (using Managed code)

Use the Environment.GetFolderPath method, passing in a Environment.SpecialFolder enumeration. The System.IO.Path.Combine method can be used to combine path parts. For example, the following C# code returns the path to a “MyData” subfolder in the current user’s Documents folder:

 

string sPath;

sPath = System.IO.Path.Combine(

    Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),

    "MyData");

 

This is the same code implemented in VB .NET:

 

Dim sPath As String

sPath = System.IO.Path.Combine( _

    Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), _

    "MyData")

References:

 

PowerShell

Windows PowerShell is built on .NET and can invoke many .NET methods and resources from the PowerShell command line interface or from Notepad-editable script files. This is how to implement the earlier sample building a path to a “MyData” subfolder of the user’s Documents folder using PowerShell:

 

$sPath = [System.IO.Path]::Combine(

    [Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments),

    "MyData")

 

C#/VB .NET Using Native Methods (P/Invoke)

You can invoke the native Windows SHGetFolderPath API from C# or VB .NET code using Platform Invoke (a.k.a., P/Invoke) methods. See https://pinvoke.net/default.aspx/shell32/SHGetFolderPath.html for examples. This may be useful because the native CSIDL enumeration includes many more folder locations than the .NET SpecialFolder enumeration did before .NET v4.

 

Windows Script Host – JScript and VBScript

Windows Script Host defines a SpecialFolders collection that can be used from VBScript or JScript. The following JScript example outputs the path to the common (“all users”) desktop:

 

    var oWsh = WScript.CreateObject("WScript.Shell");

    var sDesk = oWsh.SpecialFolders("AllUsersDesktop");

    WScript.Echo(sDesk);

 

And here is the same code in VBScript:

 

    Dim oWsh, sDesk

    Set oWsh = WScript.CreateObject("WScript.Shell")

    sDesk = oWsh.SpecialFolders("AllUsersDesktop")

    WScript.Echo sDesk

 

References:

Environment Variables and Batch Files

If none of the above interfaces are available (for example, from a Cmd.exe batch file), Windows defines a relatively small number of environment variables that identify some file system locations. Using these environment variables, at least for partial paths, is better than hardcoding paths. The following table lists the filepath-related environment variables on my Windows 7 SP1 x64 system and their values. Note that these are just example values as found on a particular computer. Do not assume that the same path locations are the same on other computers. See the References for the meanings and intended purposes of these variables.

Environment variable name

Example value

ALLUSERSPROFILE

C:\ProgramData

APPDATA

C:\Users\username\AppData\Roaming

CommonProgramFiles

C:\Program Files\Common Files

CommonProgramFiles(x86)

C:\Program Files (x86)\Common Files

CommonProgramW6432

C:\Program Files\Common Files

ComSpec

C:\Windows\system32\cmd.exe

HOMEDRIVE

C:

HOMEPATH

\Users\username

LOCALAPPDATA

C:\Users\username\AppData\Local

ProgramData

C:\ProgramData

ProgramFiles

C:\Program Files

ProgramFiles(x86)

C:\Program Files (x86)

ProgramW6432

C:\Program Files

PSModulePath

C:\Windows\system32\WindowsPowerShell\v1.0\Modules\

PUBLIC

C:\Users\Public

SystemDrive

C:

SystemRoot

C:\Windows

TEMP

C:\Users\username\AppData\Local\Temp

TMP

C:\Users\username\AppData\Local\Temp

USERPROFILE

C:\Users\username

windir

C:\Windows

 

References:

Comments

  • Anonymous
    September 25, 2011
    Nice article, However i recommend using the following way with vbscript: spablog.ontrex.ch/.../zusatzliche-special-folders (Article is in german and focuses onto Windows Installer) You get access to much more special folders with that way. Regards, Fabio [Aaron Margosis]  Thanks - you're right, that is a more powerful way to get folder names in Windows Script Host.  More documentation about the object you used is here: http://msdn.microsoft.com/library/bb774085(VS.85).aspx and http://msdn.microsoft.com/library/bb774096(v=VS.85).aspx Thanks again.  

  • Anonymous
    October 02, 2011
    "The All Users profile is now called Public" This is not really the full story; GetAllUsersProfileDirectory() and %ALLUSERSPROFILE% resolve to "C:Documents and SettingsAll Users" on NT5 and "C:ProgramData" on NT6 so it is probably more correct to say that the All Users profile was renamed and moved out of GetProfilesDirectory() and some of its sub folders were moved to the new Public folder. On NT5, CSIDL_COMMON_APPDATA resolves to "C:Documents and SettingsAll UsersApplication Data" but on NT6 this was merged with the %ALLUSERSPROFILE% path and is just "C:ProgramData". Some of the COMMON constants (CSIDL_COMMON_STARTMENU etc) still resolve to a folder inside %ALLUSERSPROFILE% and the "user files" constants like CSIDL_COMMON_DOCUMENTS now point to folders inside the new "Public" folder that was added in Vista (FOLDERID_Public). And for people that want to install apps per-user like Chrome; Win7 added FOLDERID_UserProgramFiles so you can now store the binary there and the local data (cache etc) under FOLDERID_LocalAppData/CSIDL_LOCAL_APPDATA.

  • Anonymous
    October 29, 2011
    Since you bring it up in one of your code samples, I wonder if you say anything about the size of MAX_PATH and whether you've seen any advances towards increasing it from the limited (almost crippling in an increasing number of circumstances) 255? I admit being surprised that in 2012 and Windows 7 64-bit we will still be limited to path lengths of less than 300 characters. Thanks! [Aaron Margosis]  Great question.  I haven't heard anything (I can ask) but the problem was greatly mitigated beginning in Vista when many of the long path names became much shorter.  For example, what used to be "C:Documents and SettingsusernameLocal SettingsApplication Data" is now "C:UsersusernameAppDataLocal".

  • Anonymous
    January 13, 2012
    "...it is not uncommon for programs to assume that the user’s profile is under “C:Documents and Settings”.  These default paths are always subject to change and have changed across different versions of Windows.  User profiles were stored under %SystemDrive%WINNTProfiles, then under “%SystemDrive%Documents and Settings”, and now under %SystemDrive%Users." I don't understand why there is no machine environment value %ProfilesDirectory% - which would resolve to the profiles root path (as specified by the value ProfilesDirectory under HKLMSOFTWAREMicrosoftWindows NTCurrentVersionProfileList). It seems odd to have an env var for AllUsersProfile, Public and UserProfile, but not one for the default location of users profiles. This would be handy in a batch script that backed up all profiles (except the CU):> robocopy "%ProfilesDirectory%" <backup path> /MIR /XJ /XD "%userprofile%" Also handy would be a simple command that lists a profiles folder locations, like the 'NET PROFILE' command i requested at social.msdn.microsoft.com/.../5891df3c-5060-4cb6-8c80-c4dd11c9df35 (#28)

  • Anonymous
    June 17, 2013
    For the use of CSIDLs in *.INF see (and "install") <home.arcor.de/.../LDID.INF> and <home.arcor.de/.../DIRID.INF>

  • Anonymous
    February 15, 2014
    It could also be useful to know which of the special folders get localized names.  This is another source of application bugs, and another reason to do programmatic :lookup" to resolve paths.