Zeek Joseph
Zeek Joseph

Reputation: 71

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?

Upvotes: 4

Views: 130

Answers (2)

iRon
iRon

Reputation: 23603

"Powershell does not work with quoted values in the PATH variable, as it treats the quotes as ordinary characters:"

Afaik, PowerShell doesn't have any native command (or automated variable) at all that returns a list of paths from the environment path for you. Meaning, you have to script this yourself.

If you take the easy way and just split the environment path like $env:Path -split ';' you might indeed end up with issues where you have a literal path that contains a semicolumn. This means you have to come up with a smarter way to split you path or use an existing converter/parser.
Knowing that the ConvertFrom-Csv cmdlet is very close to what you want to do, you might consider to use that:

($Env:Path | ConvertFrom-Csv -Delimiter ';' -Header (0..99)).PSObject.Properties.Value
C:\Program Files (x86)
C:\;My Folder
C:\Program Files

Explanation

  • $Env:Path contains a single string with all the environment path joined with a semicolumn (;)
  • ConvertFrom-Csv -Delimiter ';' splits the environment path using the semicolumn as a delimiter and also takes care about quoted values and spaces around the delimiter.
    • -Header (0..99) defines a numeric header (from 0 up to 99[1]) for each value
  • ConvertFrom-Csv returns a PSCustomObject, to retrieve all it's properties you use the hidden PSObject property: .PSObject.Properties
    • Although there will be a lot of headers with an empty values using the PowerShell member access enumeration by adding the .Value property will simply return all the values that are not $Null. Note that this is build on a ConvertFrom-Csv inconsistency where the last empty cells in the last row of csv file are considered $Null (rather than an empty string), see: #17702
  1. This is a hardcoded value for the maximum number of paths expected in the environment string which is in fact unknown. To limit the number of headers to cover at least all paths, you might use the helpful command suggested by mklement0 instead:
@((($env:Path) | ConvertFrom-Csv -Delimiter ';' -Header (0..($env:Path -replace '[^;]').Length)).psobject.Properties.Value) -ne $null

Upvotes: 4

mklement0
mklement0

Reputation: 437042

iRon's helpful answer shows you how you can parse the $env:PATH value manually into its constituent directory paths in a manner that respects double-quoted entries.[1]


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.


[1] However, I suggest the following, improved version:
@(($env:Path | ConvertFrom-Csv -Delimiter ';' -Header (0..($env:Path -replace '[^;]').Length)).psobject.Properties.Value) -ne $null
This creates only 1 extra header per "..."-enclosed entry that contains ; while also ensuring that all entries are returned, even if their number exceeds 99. Any resulting extra field values are then eliminated with the -ne $null filter.

Upvotes: 4

Related Questions