- File path formats on Windows systems
- Traditional DOS paths
- UNC paths
- DOS device paths
- Example: Ways to refer to the same file
- Path normalization
- Identify the path
- Handle legacy devices
- Apply the current directory
- Canonicalize separators
- Evaluate relative components
- Trim characters
- Skip normalization
- Case and the Windows file system
- Application Registration
- Finding an Application Executable
- Registering Applications
- Using the App Paths Subkey
- Using the Applications Subkey
- Examples
- Registering Verbs and Other File Association Information
- Registering a Perceived Type
File path formats on Windows systems
Members of many of the types in the System.IO namespace include a path parameter that lets you specify an absolute or relative path to a file system resource. This path is then passed to Windows file system APIs. This topic discusses the formats for file paths that you can use on Windows systems.
Traditional DOS paths
A standard DOS path can consist of three components:
- A volume or drive letter followed by the volume separator ( : ).
- A directory name. The directory separator character separates subdirectories within the nested directory hierarchy.
- An optional filename. The directory separator character separates the file path and the filename.
If all three components are present, the path is absolute. If no volume or drive letter is specified and the directory name begins with the directory separator character, the path is relative from the root of the current drive. Otherwise, the path is relative to the current directory. The following table shows some possible directory and file paths.
Path | Description |
---|---|
C:\Documents\Newsletters\Summer2018.pdf | An absolute file path from the root of drive C: . |
\Program Files\Custom Utilities\StringFinder.exe | An absolute path from the root of the current drive. |
2018\January.xlsx | A relative path to a file in a subdirectory of the current directory. |
..\Publications\TravelBrochure.pdf | A relative path to file in a directory that is a peer of the current directory. |
C:\Projects\apilibrary\apilibrary.sln | An absolute path to a file from the root of drive C: . |
C:Projects\apilibrary\apilibrary.sln | A relative path from the current directory of the C: drive. |
Note the difference between the last two paths. Both specify the optional volume specifier ( C: in both cases), but the first begins with the root of the specified volume, whereas the second does not. As result, the first is an absolute path from the root directory of drive C: , whereas the second is a relative path from the current directory of drive C: . Use of the second form when the first is intended is a common source of bugs that involve Windows file paths.
You can determine whether a file path is fully qualified (that is, it the path is independent of the current directory and does not change when the current directory changes) by calling the Path.IsPathFullyQualified method. Note that such a path can include relative directory segments ( . and .. ) and still be fully qualified if the resolved path always points to the same location.
The following example illustrates the difference between absolute and relative paths. It assumes that the directory D:\FY2018\ exists, and that you haven’t set any current directory for D:\ from the command prompt before running the example.
If you would like to see code comments translated to languages other than English, let us know in this GitHub discussion issue.
UNC paths
Universal naming convention (UNC) paths, which are used to access network resources, have the following format:
- A server or host name, which is prefaced by \\ . The server name can be a NetBIOS machine name or an IP/FQDN address (IPv4 as well as v6 are supported).
- A share name, which is separated from the host name by \ . Together, the server and share name make up the volume.
- A directory name. The directory separator character separates subdirectories within the nested directory hierarchy.
- An optional filename. The directory separator character separates the file path and the filename.
The following are some examples of UNC paths:
Path | Description |
---|---|
\\system07\C$\ | The root directory of the C: drive on system07 . |
\\Server2\Share\Test\Foo.txt | The Foo.txt file in the Test directory of the \\Server2\Share volume. |
UNC paths must always be fully qualified. They can include relative directory segments ( . and .. ), but these must be part of a fully qualified path. You can use relative paths only by mapping a UNC path to a drive letter.
DOS device paths
The Windows operating system has a unified object model that points to all resources, including files. These object paths are accessible from the console window and are exposed to the Win32 layer through a special folder of symbolic links that legacy DOS and UNC paths are mapped to. This special folder is accessed via the DOS device path syntax, which is one of:
In addition to identifying a drive by its drive letter, you can identify a volume by using its volume GUID. This takes the form:
DOS device path syntax is supported on .NET implementations running on Windows starting with .NET Core 1.1 and .NET Framework 4.6.2.
The DOS device path consists of the following components:
The device path specifier ( \\.\ or \\?\ ), which identifies the path as a DOS device path.
The \\?\ is supported in all versions of .NET Core and .NET 5+ and in .NET Framework starting with version 4.6.2.
A symbolic link to the «real» device object (C: in the case of a drive name, or Volume
The first segment of the DOS device path after the device path specifier identifies the volume or drive. (For example, \\?\C:\ and \\.\BootPartition\ .)
There is a specific link for UNCs that is called, not surprisingly, UNC . For example:
For device UNCs, the server/share portion forms the volume. For example, in \\?\server1\e:\utilities\\filecomparer\ , the server/share portion is server1\utilities . This is significant when calling a method such as Path.GetFullPath(String, String) with relative directory segments; it is never possible to navigate past the volume.
DOS device paths are fully qualified by definition. Relative directory segments ( . and .. ) are not allowed. Current directories never enter into their usage.
Example: Ways to refer to the same file
The following example illustrates some of the ways in which you can refer to a file when using the APIs in the System.IO namespace. The example instantiates a FileInfo object and uses its Name and Length properties to display the filename and the length of the file.
Path normalization
Almost all paths passed to Windows APIs are normalized. During normalization, Windows performs the following steps:
- Identifies the path.
- Applies the current directory to partially qualified (relative) paths.
- Canonicalizes component and directory separators.
- Evaluates relative directory components ( . for the current directory and .. for the parent directory).
- Trims certain characters.
This normalization happens implicitly, but you can do it explicitly by calling the Path.GetFullPath method, which wraps a call to the GetFullPathName() function. You can also call the Windows GetFullPathName() function directly using P/Invoke.
Identify the path
The first step in path normalization is identifying the type of path. Paths fall into one of a few categories:
- They are device paths; that is, they begin with two separators and a question mark or period ( \\? or \\. ).
- They are UNC paths; that is, they begin with two separators without a question mark or period.
- They are fully qualified DOS paths; that is, they begin with a drive letter, a volume separator, and a component separator ( C:\ ).
- They designate a legacy device ( CON , LPT1 ).
- They are relative to the root of the current drive; that is, they begin with a single component separator ( \ ).
- They are relative to the current directory of a specified drive; that is, they begin with a drive letter, a volume separator, and no component separator ( C: ).
- They are relative to the current directory; that is, they begin with anything else ( temp\testfile.txt ).
The type of the path determines whether or not a current directory is applied in some way. It also determines what the «root» of the path is.
Handle legacy devices
If the path is a legacy DOS device such as CON , COM1 , or LPT1 , it is converted into a device path by prepending \\.\ and returned.
A path that begins with a legacy device name is always interpreted as a legacy device by the Path.GetFullPath(String) method. For example, the DOS device path for CON.TXT is \\.\CON , and the DOS device path for COM1.TXT\file1.txt is \\.\COM1 .
Apply the current directory
If a path isn’t fully qualified, Windows applies the current directory to it. UNCs and device paths do not have the current directory applied. Neither does a full drive with separator C:\ .
If the path starts with a single component separator, the drive from the current directory is applied. For example, if the file path is \utilities and the current directory is C:\temp\ , normalization produces C:\utilities .
If the path starts with a drive letter, volume separator, and no component separator, the last current directory set from the command shell for the specified drive is applied. If the last current directory was not set, the drive alone is applied. For example, if the file path is D:sources , the current directory is C:\Documents\ , and the last current directory on drive D: was D:\sources\ , the result is D:\sources\sources . These «drive relative» paths are a common source of program and script logic errors. Assuming that a path beginning with a letter and a colon isn’t relative is obviously not correct.
If the path starts with something other than a separator, the current drive and current directory are applied. For example, if the path is filecompare and the current directory is C:\utilities\ , the result is C:\utilities\filecompare\ .
Relative paths are dangerous in multithreaded applications (that is, most applications) because the current directory is a per-process setting. Any thread can change the current directory at any time. Starting with .NET Core 2.1, you can call the Path.GetFullPath(String, String) method to get an absolute path from a relative path and the base path (the current directory) that you want to resolve it against.
Canonicalize separators
All forward slashes ( / ) are converted into the standard Windows separator, the back slash ( \ ). If they are present, a series of slashes that follow the first two slashes are collapsed into a single slash.
Evaluate relative components
As the path is processed, any components or segments that are composed of a single or a double period ( . or .. ) are evaluated:
For a single period, the current segment is removed, since it refers to the current directory.
For a double period, the current segment and the parent segment are removed, since the double period refers to the parent directory.
Parent directories are only removed if they aren’t past the root of the path. The root of the path depends on the type of path. It is the drive ( C:\ ) for DOS paths, the server/share for UNCs ( \\Server\Share ), and the device path prefix for device paths ( \\?\ or \\.\ ).
Trim characters
Along with the runs of separators and relative segments removed earlier, some additional characters are removed during normalization:
If a segment ends in a single period, that period is removed. (A segment of a single or double period is normalized in the previous step. A segment of three or more periods is not normalized and is actually a valid file/directory name.)
If the path doesn’t end in a separator, all trailing periods and spaces (U+0020) are removed. If the last segment is simply a single or double period, it falls under the relative components rule above.
This rule means that you can create a directory name with a trailing space by adding a trailing separator after the space.
You should never create a directory or filename with a trailing space. Trailing spaces can make it difficult or impossible to access a directory, and applications commonly fail when attempting to handle directories or files whose names include trailing spaces.
Skip normalization
Normally, any path passed to a Windows API is (effectively) passed to the GetFullPathName function and normalized. There is one important exception: a device path that begins with a question mark instead of a period. Unless the path starts exactly with \\?\ (note the use of the canonical backslash), it is normalized.
Why would you want to skip normalization? There are three major reasons:
To get access to paths that are normally unavailable but are legal. A file or directory called hidden. , for example, is impossible to access in any other way.
To improve performance by skipping normalization if you’ve already normalized.
On .NET Framework only, to skip the MAX_PATH check for path length to allow for paths that are greater than 259 characters. Most APIs allow this, with some exceptions.
.NET Core and .NET 5+ handles long paths implicitly and does not perform a MAX_PATH check. The MAX_PATH check applies only to .NET Framework.
Skipping normalization and max path checks is the only difference between the two device path syntaxes; they are otherwise identical. Be careful with skipping normalization, since you can easily create paths that are difficult for «normal» applications to deal with.
Paths that start with \\?\ are still normalized if you explicitly pass them to the GetFullPathName function.
You can pass paths of more than MAX_PATH characters to GetFullPathName without \\?\ . It supports arbitrary length paths up to the maximum string size that Windows can handle.
Case and the Windows file system
A peculiarity of the Windows file system that non-Windows users and developers find confusing is that path and directory names are case-insensitive. That is, directory and file names reflect the casing of the strings used when they are created. For example, the method call
Application Registration
This topic discusses how applications can expose information about themselves necessary to enable certain scenarios. This includes information needed to locate the application, the verbs that the application supports and the types of files that an application can handle.
This topic is organized as follows:
Applications can also be registered in the Set Program Access and Computer Defaults (SPAD) and Set Your Default Programs (SYDP) control panel applications. For information about SPAD and SYDP application registration, see Guidelines for File Associations and Default Programs, and Set Program Access and Computer Defaults (SPAD).
Finding an Application Executable
When the ShellExecuteEx function is called with the name of an executable file in its lpFile parameter, there are several places where the function looks for the file. We recommend registering your application in the App Paths registry subkey. Doing so avoids the need for applications to modify the system PATH environment variable.
The file is sought in the following locations:
- The current working directory.
- The Windows directory only (no subdirectories are searched).
- The Windows\System32 directory.
- Directories listed in the PATH environment variable.
- Recommended: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths
Registering Applications
Both the App Paths and Applications registry subkeys are used to register and control the behavior of the system on behalf of applications. The App Paths subkey is the preferred location.
Using the App Paths Subkey
In Windows 7 and later, we strongly recommend you install applications per user rather than per machine. An application that is installed for per user can be registered under HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\App Paths. An application that is installed for all users of the computer can be registered under HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\App Paths.
The entries found under App Paths are used primarily for the following purposes:
- To map an application’s executable file name to that file’s fully qualified path.
- To pre-pend information to the PATH environment variable on a per-application, per-process basis.
If the name of a subkey of App Paths matches the file name, the Shell performs two actions:
- The (Default) entry is used as the file’s fully qualified path.
- The Path entry for that subkey is pre-pended to the PATH environment variable of that process. If this is not required, the Path value can be omitted.
Potential issues to be aware of include:
- The Shell limits the length of a command line to MAX_PATH * 2 characters. If there are many files listed as registry entries or their paths are long, file names later in the list could be lost as the command line is truncated.
- Some applications do not accept multiple file names in a command line.
- Some applications that accept multiple file names do not recognize the format in which the Shell provides them. The Shell provides the parameter list as a quoted string, but some applications might require strings without quotes.
- Not all items that can be dragged are part of the file system; for example, printers. These items do not have a standard Win32 path, so there is no way to provide a meaningful lpParameters value to ShellExecuteEx.
Using the DropTarget entry avoids these potential issues by providing access to all of the clipboard formats, including CFSTR_SHELLIDLIST (for long file lists) and CFSTR_FILECONTENTS (for non-file-system objects).
To register and control the behavior of your applications with the App Paths subkey:
Add a subkey with the same name as your executable file to the App Paths subkey, as shown in the following registry entry.
See the following table for details of the App Paths subkey entries.
Registry entry | Details |
---|---|
(Default) | Is the fully qualified path to the application. The application name provided in the (Default) entry can be stated with or without its .exe extension. If necessary, the ShellExecuteEx function adds the extension when searching App Paths subkey. The entry is of the REG_SZ type. |
DontUseDesktopChangeRouter | Is mandatory for debugger applications to avoid file dialog deadlocks when debugging the Windows Explorer process. Setting the DontUseDesktopChangeRouter entry produces a slightly less efficient handling of the change notifications, however. The entry is of the REG_DWORD type and the value is 0x1. |
DropTarget | Is a class identifier (CLSID). The DropTarget entry contains the CLSID of an object (usually a local server rather than an in-process server) that implements IDropTarget. By default, when the drop target is an executable file, and no DropTarget value is provided, the Shell converts the list of dropped files into a command-line parameter and passes it to ShellExecuteEx through lpParameters. |
Path | Supplies a string (in the form of a semicolon-separated list of directories) to append to the PATH environment variable when an application is launched by calling ShellExecuteEx. It is the fully qualified path to the .exe. It is of REG_SZ. In Windows 7 and later, the type can be REG_EXPAND_SZ, and is commonly REG_EXPAND_SZ %ProgramFiles%.
|
SupportedProtocols | Creates a string that contains the URL protocol schemes for a given key. This can contain multiple registry values to indicate which schemes are supported. This string follows the format of scheme1:scheme2. If this list is not empty, file: will be added to the string. This protocol is implicitly supported when SupportedProtocols is defined. |
UseUrl | Indicates that your application can accept a URL (instead of a file name) on the command line. Applications that can open documents directly from the internet, like web browsers and media players, should set this entry. When the ShellExecuteEx function starts an application and the UseUrl=1 value is not set, ShellExecuteEx downloads the document to a local file and invokes the handler on the local copy. For example, if the application has this entry set and a user right-clicks on a file stored on a web server, the Open verb will be made available. If not, the user will have to download the file and open the local copy. The UseUrl entry is of REG_DWORD type, and the value is 0x1. In Windows Vista and earlier, this entry indicated that the URL should be passed to the application along with a local file name, when called via ShellExecuteEx. In Windows 7, it indicates that the application can understand any http or https url that is passed to it, without having to supply the cache file name as well. This registry key is associated with the SupportedProtocols key. |
Using the Applications Subkey
Through the inclusion of registry entries under the HKEY_CLASSES_ROOT\Applications\ApplicationName.exe subkey, applications can provide the application-specific information shown in the following table.
Registry entry | Description |
---|---|
shell\verb | Provides the verb method for calling the application from OpenWith. Without a verb definition specified here, the system assumes that the application supports CreateProcess, and passes the file name on the command line. This functionality applies to all the verb methods, including DropTarget, ExecuteCommand, and Dynamic Data Exchange (DDE). |
DefaultIcon | Enables an application to provide a specific icon to represent the application instead of the first icon stored in the .exe file. |
FriendlyAppName | Provides a way to get a localizable name to display for an application instead of just the version information appearing, which may not be localizable. The association query ASSOCSTR reads this registry entry value and falls back to use the FileDescription name in the version information. If that name is missing, the association query defaults to the display name of the file. Applications should use ASSOCSTR_FRIENDLYAPPNAME to retrieve this information to obtain the proper behavior. |
SupportedTypes | Lists the file types that the application supports. Doing so enables the application to be listed in the cascade menu of the Open with dialog box. |
NoOpenWith | Indicates that no application is specified for opening this file type. Be aware that if an OpenWithProgIDs subkey has been set for an application by file type, and the ProgID subkey itself does not also have a NoOpenWith entry, that application will appear in the list of recommended or available applications even if it has specified the NoOpenWith entry. For more information, see How to How to Include an Application in the Open With Dialog Box and How to exclude an Application from the Open with Dialog Box. |
IsHostApp | Indicates that the process is a host process, such as Rundll32.exe or Dllhost.exe, and should not be considered for Start menu pinning or inclusion in the Most Frequently Used (MFU) list. When launched with a shortcut that contains a non-null argument list or an explicit Application User Model IDs (AppUserModelIDs), the process can be pinned (as that shortcut). Such shortcuts are candidates for inclusion in the MFU list. |
NoStartPage | Indicates that the application executable and shortcuts should be excluded from the Start menu and from pinning or inclusion in the MFU list. This entry is typically used to exclude system tools, installers and uninstallers, and readme files. |
UseExecutableForTaskbarGroupIcon | Causes the taskbar to use the default icon of this executable if there is no pinnable shortcut for this application, and instead of the icon of the window that was first encountered. |
TaskbarGroupIcon | Specifies the icon used to override the taskbar icon. The window icon is normally used for the taskbar. Setting the TaskbarGroupIcon entry causes the system to use the icon from the .exe for the application instead. |
Examples
Some examples of application registrations through the HKEY_CLASSES_ROOT\Applications\ApplicationName.exe subkey are as follows. All registry entry values are of REG_SZ type, with the exception of DefaultIcon which is of REG_EXPAND_SZ type.
Registering Verbs and Other File Association Information
Subkeys registered under HKEY_CLASSES_ROOT\SystemFileAssociations enable the Shell to define the default behavior of attributes for file types and enable shared file associations. When users change the default application for a file type, the ProgID of the new default application has priority in providing verbs and other association information. This priority is due to it being the first entry in the association array. If the default program is changed, the information under the previous ProgID is no longer available.
To deal proactively with the consequences of a change to default programs, you can use HKEY_CLASSES_ROOT\SystemFileAssociations to register verbs and other association information. Due to their location after the ProgID in the association array, these registrations are lower priority. These SystemFileAssociationsregistrations are stable even when users change the default programs, and provide a location to register secondary verbs that will always be available for a particular file type. For a registry example, see Registering a Perceived Type later in this topic.
The following registry example shows what happens when the user runs the Default Programs item in Control Panel to change the default for .mp3 files to App2ProgID. After changing the default, Verb1 is no longer available, and Verb2 becomes the default.
Registering a Perceived Type
Registry values for perceived types are defined as subkeys of the HKEY_CLASSES_ROOT\SystemFileAssociations registry subkey. For example, the perceived type text is registered as follows:
A file type’s perceived type is indicated by including a PerceivedType value in the file type’s subkey. The PerceivedType value is set to the name of the perceived type registered under HKEY_CLASSES_ROOT\SystemFileAssociations registry subkey, as shown in the previous registry example. To declare .cpp files as being of perceived type «text», for example, add the following registry entry: