Click here to Skip to main content
15,304,259 members
Articles / Desktop Programming / Win32
Posted 20 Feb 2015


169 bookmarked

Getting All "Special Folders" in .NET

Rate me:
Please Sign up or sign in to vote.
4.87/5 (108 votes)
27 Apr 2022MIT8 min read
Retrieving the path to the new user folders like Downloads, Saved Games or Searches
How to correctly retrieve paths to all special Windows folders - like the user's Downloads folder


Since Windows 98, users have special folders in their home directory, called "My Documents", "My Music", "My Pictures" or "My Videos", properly called "Known Folders". These barely changed up to XP, and their path is easily retrieved with .NET: Calling the System.Environment.GetFolderPath function and passing in an enum value of System.Environment.SpecialFolder.

However, newer folders introduced since Windows Vista are not listed in the SpecialFolder enum and cannot be retrieved that way. .NET was not updated to mirror the additions to the user home directory (and several other "special" folders), and people (including me) made hacky and wrong attempts in finding the paths of the other folders. This article details why these attempts are incorrect and finally presents the correct way to get the new, "special" folder paths.


I typically see these two wrong attempts to retrieve the path for - let's say - the Downloads folder.

Wrong Approach #1: Append the Folder Name to the User Home Directory

The seemingly easiest way to retrieve the Downloads folder path consists of appending the special folder name to the user home path (in which the special folders are found by default):

// This returns something like C:\Users\Username:
string userRoot = System.Environment.GetEnvironmentVariable("USERPROFILE");
// Now let's get C:\Users\Username\Downloads:
string downloadFolder = Path.Combine(userRoot, "Downloads");

Since Windows Vista and newer versions use English folder names internally and the names displayed in the File Explorer are just virtually localized versions of them, this should work, shouldn't it?

No. This solution does not work if the user redirected the path of the Downloads folder. It is possible to change the path in the Downloads folder properties and simply choose another location for it. It may not be in the user root directory at all, it can be anywhere - mine, for example, are all on the D: partition.

Wrong Approach #2: Use the "Shell Folders" Registry Key

In the next attempt, developers searched through the registry for a key containing the redirected folder path. Windows must store this information somewhere, right?

Eventually, they found "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders". The key looks really promising as it contains all the paths to the user folders, even with redirected paths (mine are all on the D: partition as you can see):

Image 1

But wait, it also contains an ominous key with the name "!Do not use this registry key" and the value "Use the SHGetFolderPath or SHGetKnownFolderPath function instead". This key was put there by Microsoft developer Raymond Chen, who had the right feeling that people would continue to abuse this key to get the folder path in the future. He wrote articles about it here and here.

To summarize, using this key was only acceptable in a Windows 95 beta version, and no moment later. Microsoft noticed it would not be flexible enough to keep all the information about shell folders, it did not respect roaming user profiles, and so on. A WinAPI function was created instead, but the key was left in the registry "temporary" to not break literally four programs designed in the Win95 beta period in the RTM. It was never removed afterwards since more developers found this key and started to rely on it, and deleting it now would make even more programs incompatible than the original four.

So, don't use the values there. They're not guaranteed to be right, or to even exist, and Raymond Chen will probably hate you for doing so - and you better not get into trouble with "Microsoft's Chuck Norris". Even I ran into the "Shell Folders" key trap once and posted this as a "solution" in a StackOverflow answer (which I have updated meanwhile).

The Correct Solution

Being good developers, we follow the key's advice, and P/Invoke said SHGetKnownFolderPath function. Since P/Invoke can be a little nasty, let's carve it out together.

First, we define how we eventually want to retrieve the path. The simplest solution should be a static method accepting a parameter of our own, extended "special" folder enum:

string downloadsFolder = KnownFolders.GetPath(KnownFolder.Downloads);

enum KnownFolder
    // ...

static class KnownFolders
    public static string GetPath(KnownFolder folder)
        // TODO: Implement

Understanding the Native Method

Next, we have to understand the SHGetKnownFolderPath method, as specified in the Microsoft documentation. I quoted the most important parts below:

HRESULT SHGetKnownFolderPath(
  [in]           REFKNOWNFOLDERID rfid,
  [in]           DWORD            dwFlags,
  [in, optional] HANDLE           hToken,
  [out]          PWSTR            *ppszPath
  • [in] REFKNOWNFOLDERID rfid: "A reference to the KNOWNFOLDERID that identifies the folder."

    KNOWNFOLDERID is actually a GUID. The available GUIDs are found here. We have to map them to our KnownFolder enum values. For simplicity, we will only use some of them and a dictionary in our static class.

  • [in] DWORD dwFlags: "Flags that specify special retrieval options. This value can be 0; otherwise, one or more of the KNOWN_FOLDER_FLAG values."

    For simplicity, we will indeed use 0, though you can adjust and optimize the behavior to meet your expectations. KF_FLAG_DONT_VERIFY may be useful if you don't need to ensure if the folder is created if it does not exist yet, an operation that can be slow if the folder was relocated to network drives.

  • [in, optional] HANDLE hToken: "If this parameter is NULL, which is the most common usage, the function requests the known folder for the current user."

    For simplicity, we will only bother about the executing user's folder paths. You can pass in the handle of a System.Security.Principal.WindowsIdentity.AccessToken instead to impersonate another user.

  • [out] PWSTR *ppszPath: "When this method returns, contains the address of a pointer to a null-terminated Unicode string that specifies the path of the known folder. The calling process is responsible for freeing this resource once it is no longer needed by calling CoTaskMemFree, whether SHGetKnownFolderPath succeeds or not."

    This will eventually return the path we are interested in.

Calling the Native Method in C#

To invoke this method in C#, the following import can be used. The list below explains how this was determined. You can skip it if you are not interested in how P/Invoke works under the hood.

[DllImport("shell32", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
private static extern string SHGetKnownFolderPath(
    [MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, nint hToken = default);
  • The documentation states that the method resides in the Shell32.dll module, so we provide this file name to the DllImport attribute (you do not have to specify the file extension).
  • Since the path returned to us as an [out] parameter is a unicode (UTF16) string, we ensure to override C#'s default CharSet to CharSet.Unicode. This allows us to directly convert PWSTR *ppszPath to a string, with the marshaller freeing the native memory allocated for it - note that this only works because the marshaller assumes such memory blocks were previously allocated with CoTaskMemAlloc (which is what the WinAPI method does), always calling CoTaskMemFree on them for us.
  • We provide ExactSpelling = true since there are no A or W versions of the method, and prevent the runtime from searching for such.
  • Using PreserveSig = false allows us to convert any HRESULT failure codes returned by the method into .NET exceptions. It also makes the method return void, but we can further change this to return the last [out] parameter, in this case, our string.
  • We could map REFKNOWNFOLDERID GUID to ref Guid, but to not have to deal with references, we can instruct the marshaller to do this for us when we provide a Guid "by value" with [MarshalAs(UnmanagedType.LPStruct)].
  • The next parameter is a DWORD which, in this case, could map to a .NET enum consisting of the available flags, but since we are not interested in them, we just use a raw uint accordingly.
  • A HANDLE is the size of a native integer, so we use C# 9's new nint for this - alternatively, you can still use an IntPtr. The parameter is optional, and even if we do not require to do so, we state the same with = default.

Putting It All Together

Filling out the implementation of our static class, we will end up with the following:

using System.Runtime.InteropServices;

static class KnownFolders
    private static readonly Dictionary<KnownFolder, Guid> _knownFolderGuids = new()
        [KnownFolder.Documents] = new("FDD39AD0-238F-46AF-ADB4-6C85480369C7"),
        [KnownFolder.Downloads] = new("374DE290-123F-4565-9164-39C4925E467B"),
        [KnownFolder.Music] = new("4BD8D571-6D19-48D3-BE97-422220080E43"),
        [KnownFolder.Pictures] = new("33E28130-4E1E-4676-835A-98395C3BC3BB"),
        [KnownFolder.SavedGames] = new("4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4"),

    public static string GetPath(KnownFolder folder)
        return SHGetKnownFolderPath(_knownFolderGuids[folder], 0);

    [DllImport("shell32", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
    private static extern string SHGetKnownFolderPath(
        [MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, nint hToken = default);

Using the Code

We've made it pretty simple to print all the known folder paths exposed through our enum:

foreach (KnownFolder knownFolder in Enum.GetValues<KnownFolder>())
        Console.Write($"{knownFolder}: ");
    catch (Exception ex)
        Console.WriteLine($"<Exception> {ex.Message}");

A very loose try catch is required as P/Invoke converts HRESULTs to a wide range of exceptions. You may run into FileNotFoundExceptions in case a folder is not available on a system, but that will not typically happen for user folders.

Points of Interest

We have not touched all related known folder functionality, and mentioned some possible optimization potential:

  • Add the remaining folders to our enum.
  • Wrapping exceptions in a custom KnownFolderException to catch them more specifically.
  • Using attributes instead of a dictionary to assign the GUIDs to each KnownFolder enum value and retrieving them via reflection.
  • Changing the path of a known folder with SHSetKnownFolderPath.
  • Querying the paths of another user by passing in an identity access token handle.
  • Retrieving an IShellItem COM object instance through SHGetKnownFolderItem and extracting the user friendly folder name with its GetDisplayName method.
  • Adding compatibility for Windows XP operating systems and earlier, which do not have the SHGetKnownFolderPath function, creating a wrapper capable of retrieving paths for them aswell, possibly just falling back to System.Environment.GetFolderPath().
  • Use CsWin32 to automatically generate P/Invoke signatures from WinAPI metadata. Note that its signatures use raw unsafe semantics, differing from the signature determined in this article.

If you liked this article, feel free to upvote my answer on StackOverflow.

NuGet Package

I created a NuGet package providing most of the functionality discussed in the previous paragraph. More information about it on its project site. Note that due to the additional features, the API is slightly different, check the README for more details.


  • 19th April, 2022 - Rewrite of code samples and most paragraphs
  • 18th December, 2018 - Updated NuGet project site link
  • 11th June, 2018 - Free string memory, updated sample, reworded some sentences
  • 21st March, 2016 - Added note about newly created NuGet package
  • 20th February, 2015 - First release


This article, along with any associated source code and files, is licensed under The MIT License


About the Author

Ray Koopa
No Biography provided

Comments and Discussions

QuestionWhere are the others? Pin
SDSpivey2-May-22 5:29
MemberSDSpivey2-May-22 5:29 
AnswerRe: Where are the others? Pin
Ray Koopa3-May-22 5:58
MemberRay Koopa3-May-22 5:58 
GeneralRe: Where are the others? Pin
SDSpivey3-May-22 12:42
MemberSDSpivey3-May-22 12:42 
GeneralRe: Where are the others? Pin
Ray Koopa4-May-22 21:18
MemberRay Koopa4-May-22 21:18 
Praisevery good Pin
Southmountain22-Apr-22 14:49
MemberSouthmountain22-Apr-22 14:49 
GeneralMy vote of 5 Pin
CometFather22-Apr-22 5:51
MemberCometFather22-Apr-22 5:51 
QuestionMessage Closed Pin
21-Apr-22 18:38
MemberBharat saini21-Apr-22 18:38 
GeneralMy vote of 5 Pin
Member 1370414321-Apr-22 3:56
MemberMember 1370414321-Apr-22 3:56 
GeneralMy vote of 5 Pin
wmjordan20-Apr-22 15:11
professionalwmjordan20-Apr-22 15:11 
QuestionBeside of this you can ... Pin
LightTempler20-Apr-22 7:23
MemberLightTempler20-Apr-22 7:23 
AnswerRe: Beside of this you can ... Pin
Ray Koopa20-Apr-22 7:32
MemberRay Koopa20-Apr-22 7:32 
SuggestionThanks and a question Pin
Member 149093757-Aug-20 9:57
MemberMember 149093757-Aug-20 9:57 
GeneralRe: Thanks and a question Pin
Ray Koopa12-Nov-20 3:33
MemberRay Koopa12-Nov-20 3:33 
Questionhow will get special folders path for windows user profile other than current user profile Pin
jude_aj15-Jun-20 2:20
Memberjude_aj15-Jun-20 2:20 
AnswerRe: how will get special folders path for windows user profile other than current user profile Pin
Member 149093757-Aug-20 8:58
MemberMember 149093757-Aug-20 8:58 
GeneralRe: how will get special folders path for windows user profile other than current user profile Pin
Ray Koopa12-Nov-20 3:30
MemberRay Koopa12-Nov-20 3:30 
PraiseKudos- and THANK YOU Pin
DavidInvenioDavid16-Jun-19 0:05
MemberDavidInvenioDavid16-Jun-19 0:05 
QuestionNuget package doesn't work in VS 2015 (March 2019) Pin
Member 138987886-Mar-19 10:52
MemberMember 138987886-Mar-19 10:52 
AnswerRe: Nuget package doesn't work in VS 2015 (March 2019) Pin
Ray Koopa1-May-19 5:37
MemberRay Koopa1-May-19 5:37 
QuestionVS 2015 can't compile (March 2019) Pin
Member 138987886-Mar-19 10:49
MemberMember 138987886-Mar-19 10:49 
AnswerRe: VS 2015 can't compile (March 2019) Pin
Ray Koopa1-May-19 5:37
MemberRay Koopa1-May-19 5:37 
QuestionIsn't there already a .NET class that does this?: "SpecialDirectoriesProxy" Pin
Austin @ fluorgov19-Dec-18 9:40
MemberAustin @ fluorgov19-Dec-18 9:40 
AnswerRe: Isn't there already a .NET class that does this?: "SpecialDirectoriesProxy" Pin
Ray Koopa1-May-19 5:38
MemberRay Koopa1-May-19 5:38 
QuestionAll known folders system enum Pin
jvierra19-Dec-18 6:50
Memberjvierra19-Dec-18 6:50 
AnswerRe: All known folders system enum Pin
Ray Koopa1-May-19 5:39
MemberRay Koopa1-May-19 5:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.