Reputation: 71
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
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
$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 valueConvertFrom-Csv
returns a PSCustomObject
, to retrieve all it's properties you use the hidden PSObject
property: .PSObject.Properties
.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@((($env:Path) | ConvertFrom-Csv -Delimiter ';' -Header (0..($env:Path -replace '[^;]').Length)).psobject.Properties.Value) -ne $null
Upvotes: 4
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), whereascmd.exe
does.
Note that POSIX-compatible shells such asbash
, 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 withUseShellExecute = $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 withUseShellExecute = $false
(the default) in .NET (Core) vs.ShellExecuteEx()
as used byStart-Process
by default and withUseShellExecute = $true
.Given that direct invocation in
cmd.exe
(as opposed to use of the internalstart
command) presumably also usesCreateProcess()
, there may be custom logic incmd.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