Search code examples
windowspowershellsplitpathenvironment-variables

Windows Semicolons in Path


I'm working on a Windows program that adds values to the PATH environment variable. The PATH variable is semi-colon (;) delimited, and any valid folder paths can be included. However, ; is valid in folder paths, so paths containing semicolons must be surrounded in double quotes when being added to path:

C:\Program Files (x86);"C:\;My Folder";C:\Program Files

To avoid this issue with semicolons, my program wraps all paths in double quotes. However, someone recently informed me that Powershell does not work with quoted values in the PATH variable, as it treats the quotes as ordinary characters:

I looked at how PowerShell handles the Path variable (based on this code: PowerShell/PowerShell@16176ef/src/System.Management.Automation/engine/CommandDiscovery.cs#L1187-L1240). From what I can tell, PowerShell doesn't remove double quotes - it just splits paths wherever it sees a semicolon.

So, the PATH value C:\Program Files (x86);"C:\;My Folder";C:\Program Files is wrongly interpreted like this (if I correctly understand what's going on):

C:\Program Files (x86)
"C:\
My Folder"
C:\Program Files

Here's a video demonstrating the behavior (this only occurs with Powershell, not with cmd): https://www.mediafire.com/file/aih2ky9fz07x5w0/powershellpathwithsemicolonsbug.mp4/file

What is the best way for my program to deal with this? And, more pressingly, why does Powershell not work with PATH values that have quotes (even though CMD does)? Editing the path value to include semicolons using the manual way - Edit the system environment variables -> Path -> Edit - causes Windows to automatically wrap the required paths in quotes, breaking Powershell. So this isn't an issue with my program or situation - it's an inherent problem with how Powershell processes the PATH.

Is this behavior intentional in Powershell? And is there a workaround, or will I have to compromise by either A. Wrapping paths that contain semicolons in quotes and allowing this buggy behavior to occur or B. not allowing semicolons in PATH?


Solution

  • iRon's helpful answer shows you how you can parse the $env:PATH value manually in a manner that respects double-quoted entries.


    As for your questions:

    Is this behavior intentional in PowerShell?

    I don't think so, and it is arguably a bug, as discussed in GitHub issue #24002; quoting from this comment (of mine):

    Indeed, PowerShell doesn't support "..."-enclosed entries in $env:PATH in direct invocation (see below), whereas cmd.exe does.
    Note that POSIX-compatible shells such as bash, like PowerShell, also do not support "..."-enclosed entries.

    Arguably, PowerShell and POSIX-compatible shells either should support "..."-enclosed entries or support a way to escape the entry-separator character (which is ; on Windows, and : on Unix-like platforms), but they do not.

    By contrast, when you use Start-Process, as well as the [System.Diagnostics.Process] API with UseShellExecute = $true, which is equivalent, "..."-enclosed entries ARE recognized.

    This may come down to differences between two WinAPI functions:

    • CreateProcess() as used in direct invocation and with UseShellExecute = $false (the default) in .NET (Core) vs.
    • ShellExecuteEx() as used by Start-Process by default and with UseShellExecute = $true.

    Given that direct invocation in cmd.exe (as opposed to use of the internal start command) presumably also uses CreateProcess(), there may be custom logic in cmd.exe for interpreting %PATH%.


    Is there a workaround?

    Assuming that creation of short (8.3) file names is turned on (it is by default), you can take advantage of the fact that a file name containing ; always has a short name generated for it, even if it otherwise has 8 or fewer chars. in the base name and an extension with 3 or fewer chars and that the resulting short name is guaranteed not to contain ; or spaces; e.g.:

    # -> e.g., 'C:\M_YFOL~1'
    $shortFolderPath = 
      (New-Object -ComObject Scripting.FileSystemObject).GetFolder("C:\;My Folder").ShortPath
    

    In other words:

    • Replace those $env:PATH entries that require "..." enclosure for disambiguation due to embedded ; with their short (8.3) versions...

    • ... and remove the double quotes around them.

    Note that the specific 8.3 names can vary depending on the presence of other folders with similar long names, so they should be determined on each machine.