You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
System.Environment.GetFolderPath is used to query paths to known folders like the user's file folders ("Documents", "Pictures", etc.). The paths to these folders can be redirected in system configuration, which is queried by this method. To use it, a value of the System.Environment.SpecialFolder enum is passed in to query the accompanying folder:
However, this enum only provides folders that were available in Windows XP days, and does not contain entries for nowadays commonly available personal folders, leaving a "hole" in support for those introduced in Windows Vista or other platforms like Linux and macOS (for example, the user "Downloads" folder).
It is currently required to manually p/invoke the WinAPI or rewrite the existing Linux / macOS folder retrieval logic to ensure getting correct paths. Adding support for these folders via extending the enum will provide a natural and expected way to retrieve these paths from the system configuration without having to go through all that.
Judging from forum discussions, it will also prevent users from "solving" this issue incorrectly by hardcoding and concatenating parts of the folder paths (which would not reflect any redirected paths stored in system configuration) or incorrectly p/invoking system methods themselves:
The added values start at 0x10000 due to the existing values historically mapping to Windows' CSIDLs, and 0x10000 is the first invalid CSIDL combination that would not clash should more ever be added later (note that the CSIDL API is deprecated, not used by .NET anymore, and does not allow querying the new folders anyway).
The new values map to the following FOLDERID's / paths:
XDG... variables are used on Linux if they exist, otherwise the non-XDG path is used as a fallback.
The public (sub) folder is per-system on Windows, and per-user on Linux and macOS. iOS has no such folders.
Adding Public... sub folders is required for Windows as they may be redirected outside of the parent Public directory. Otherwise it could lead to developers looking for them to incorrectly hardcode parts of their path again by assuming they are children (the same issue currently with the Downloads and Public folders themselves).
Some of the Public... folders were previously available under a legacy Common... prefix, and aliases were introduced to better communicate the relation between the folders and which path is actually meant on current Windows versions.
Naming an enum member Public may clash with reserved keywords like in VB.NET, and PublicDirectory might be a better choice.
User folders are prefixed with My as it is currently the case for other user folders like MyPictures or MyDocuments, so that related folders group together more clearly in the enum.
MySavedGames is included as it is the only remaining Windows user folder meaningful to developers. While there are other user folders available on Windows, I do not recommend adding as they have a very special meaning only to one Windows app / component:
SpecialFolder
Windows only
Used by / meant for
❌ MyContacts
FOLDERID_Contacts
Windows Contacts contact files.
❌ MyLinks
FOLDERID_Links
File Explorer pinned location shortcut files (< Windows 10 only).
❌ MySearches
FOLDERID_Searches
Parameterized Windows Search query files.
Folders that do not exist on specific platforms will return String.Empty as they already do for others.
VB.NET's SpecialDirectories class will not be extended as it is understood to be a utility class for VB6-style backwards compatibility.
API Usage
The usage naturally extends the options made available through the existing enum:
A completely new API available additionally to the existing System.Environment.GetFolderPath (and possibly deprecating it) to make extending special folders more flexibly in the future was discussed at Add a more flexible special folder API #19047, but abandoned.
Alternatively, a new enum KnownFolder could be introduced if the existing SpecialFolder should not be touched in any case (s. possible "Risks" below), and an overload could be provided for System.Environment.GetFolderPath accepting those.
Risks
I rate the risks as Low:
Code that enumerated over all values of the SpecialFolder enum and depended on the undocumented fact that each underlying value is a Windows CSIDL value now has to check whether the value is < 0x10000 in case it wants to pass only valid values to the deprecated CSIDL Windows API.
Code that relied on the undocumented fact that the enum values could be stored in just 2 bytes now has to use a larger data type or ignore the new values with the < 0x10000 check.
The additions here should be merged after fixes to existing paths are done in #68610 to not clash with the changes.
Background and motivation
System.Environment.GetFolderPathis used to query paths to known folders like the user's file folders ("Documents", "Pictures", etc.). The paths to these folders can be redirected in system configuration, which is queried by this method. To use it, a value of theSystem.Environment.SpecialFolderenum is passed in to query the accompanying folder:However, this enum only provides folders that were available in Windows XP days, and does not contain entries for nowadays commonly available personal folders, leaving a "hole" in support for those introduced in Windows Vista or other platforms like Linux and macOS (for example, the user "Downloads" folder).
It is currently required to manually p/invoke the WinAPI or rewrite the existing Linux / macOS folder retrieval logic to ensure getting correct paths. Adding support for these folders via extending the enum will provide a natural and expected way to retrieve these paths from the system configuration without having to go through all that.
Judging from forum discussions, it will also prevent users from "solving" this issue incorrectly by hardcoding and concatenating parts of the folder paths (which would not reflect any redirected paths stored in system configuration) or incorrectly p/invoking system methods themselves:
There have also been several issues requesting the missing folders in the past, but most staled or haven't been tackled since long:
SpecialFolder, first mention of new (but internal) GUID based APISpecialFolderAPI in favor of a new, public GUID based API:SpecialFolderenum in any way would break code assuming it maps to deprecated Windows' CSIDLsSpecialFolderagainSpecialFolderagainSpecialFolderagainAPI Proposal
I propose extending the
System.Environment.SpecialFolderenum with these values:namespace System { public static partial class Environment { public enum SpecialFolder { ApplicationData = SpecialFolderValues.CSIDL_APPDATA, CommonApplicationData = SpecialFolderValues.CSIDL_COMMON_APPDATA, LocalApplicationData = SpecialFolderValues.CSIDL_LOCAL_APPDATA, Cookies = SpecialFolderValues.CSIDL_COOKIES, Desktop = SpecialFolderValues.CSIDL_DESKTOP, Favorites = SpecialFolderValues.CSIDL_FAVORITES, History = SpecialFolderValues.CSIDL_HISTORY, InternetCache = SpecialFolderValues.CSIDL_INTERNET_CACHE, Programs = SpecialFolderValues.CSIDL_PROGRAMS, MyComputer = SpecialFolderValues.CSIDL_DRIVES, MyMusic = SpecialFolderValues.CSIDL_MYMUSIC, MyPictures = SpecialFolderValues.CSIDL_MYPICTURES, MyVideos = SpecialFolderValues.CSIDL_MYVIDEO, Recent = SpecialFolderValues.CSIDL_RECENT, SendTo = SpecialFolderValues.CSIDL_SENDTO, StartMenu = SpecialFolderValues.CSIDL_STARTMENU, Startup = SpecialFolderValues.CSIDL_STARTUP, System = SpecialFolderValues.CSIDL_SYSTEM, Templates = SpecialFolderValues.CSIDL_TEMPLATES, DesktopDirectory = SpecialFolderValues.CSIDL_DESKTOPDIRECTORY, Personal = SpecialFolderValues.CSIDL_PERSONAL, MyDocuments = SpecialFolderValues.CSIDL_PERSONAL, ProgramFiles = SpecialFolderValues.CSIDL_PROGRAM_FILES, CommonProgramFiles = SpecialFolderValues.CSIDL_PROGRAM_FILES_COMMON, AdminTools = SpecialFolderValues.CSIDL_ADMINTOOLS, CDBurning = SpecialFolderValues.CSIDL_CDBURN_AREA, CommonAdminTools = SpecialFolderValues.CSIDL_COMMON_ADMINTOOLS, CommonDocuments = SpecialFolderValues.CSIDL_COMMON_DOCUMENTS, CommonMusic = SpecialFolderValues.CSIDL_COMMON_MUSIC, CommonOemLinks = SpecialFolderValues.CSIDL_COMMON_OEM_LINKS, CommonPictures = SpecialFolderValues.CSIDL_COMMON_PICTURES, CommonStartMenu = SpecialFolderValues.CSIDL_COMMON_STARTMENU, CommonPrograms = SpecialFolderValues.CSIDL_COMMON_PROGRAMS, CommonStartup = SpecialFolderValues.CSIDL_COMMON_STARTUP, CommonDesktopDirectory = SpecialFolderValues.CSIDL_COMMON_DESKTOPDIRECTORY, CommonTemplates = SpecialFolderValues.CSIDL_COMMON_TEMPLATES, CommonVideos = SpecialFolderValues.CSIDL_COMMON_VIDEO, Fonts = SpecialFolderValues.CSIDL_FONTS, NetworkShortcuts = SpecialFolderValues.CSIDL_NETHOOD, PrinterShortcuts = SpecialFolderValues.CSIDL_PRINTHOOD, UserProfile = SpecialFolderValues.CSIDL_PROFILE, CommonProgramFilesX86 = SpecialFolderValues.CSIDL_PROGRAM_FILES_COMMONX86, ProgramFilesX86 = SpecialFolderValues.CSIDL_PROGRAM_FILESX86, Resources = SpecialFolderValues.CSIDL_RESOURCES, LocalizedResources = SpecialFolderValues.CSIDL_RESOURCES_LOCALIZED, SystemX86 = SpecialFolderValues.CSIDL_SYSTEMX86, Windows = SpecialFolderValues.CSIDL_WINDOWS, + PublicDesktop = CommonDesktopDirectory, + PublicDocuments = CommonDocuments, + PublicMusic = CommonMusic, + PublicPictures = CommonPictures, + PublicVideos = CommonVideos, + MyDownloads = 0x10000, + MySavedGames, + Public, + PublicDownloads, } } }The added values start at
0x10000due to the existing values historically mapping to Windows'CSIDLs, and0x10000is the first invalidCSIDLcombination that would not clash should more ever be added later (note that theCSIDLAPI is deprecated, not used by .NET anymore, and does not allow querying the new folders anyway).The new values map to the following FOLDERID's / paths:
~is the home directory as it is already determined.XDG...variables are used on Linux if they exist, otherwise the non-XDG path is used as a fallback.The public (sub) folder is per-system on Windows, and per-user on Linux and macOS. iOS has no such folders.
Public...sub folders is required for Windows as they may be redirected outside of the parentPublicdirectory. Otherwise it could lead to developers looking for them to incorrectly hardcode parts of their path again by assuming they are children (the same issue currently with the Downloads and Public folders themselves).Public...folders were previously available under a legacyCommon...prefix, and aliases were introduced to better communicate the relation between the folders and which path is actually meant on current Windows versions.Publicmay clash with reserved keywords like in VB.NET, andPublicDirectorymight be a better choice.User folders are prefixed with
Myas it is currently the case for other user folders likeMyPicturesorMyDocuments, so that related folders group together more clearly in the enum.MySavedGamesis included as it is the only remaining Windows user folder meaningful to developers. While there are other user folders available on Windows, I do not recommend adding as they have a very special meaning only to one Windows app / component:Folders that do not exist on specific platforms will return
String.Emptyas they already do for others.VB.NET's SpecialDirectories class will not be extended as it is understood to be a utility class for VB6-style backwards compatibility.
API Usage
The usage naturally extends the options made available through the existing enum:
Alternative Designs
System.Environment.GetFolderPath(and possibly deprecating it) to make extending special folders more flexibly in the future was discussed at Add a more flexible special folder API #19047, but abandoned.enum KnownFoldercould be introduced if the existingSpecialFoldershould not be touched in any case (s. possible "Risks" below), and an overload could be provided forSystem.Environment.GetFolderPathaccepting those.Risks
I rate the risks as Low:
SpecialFolderenum and depended on the undocumented fact that each underlying value is a WindowsCSIDLvalue now has to check whether the value is< 0x10000in case it wants to pass only valid values to the deprecated CSIDL Windows API.< 0x10000check.The additions here should be merged after fixes to existing paths are done in #68610 to not clash with the changes.