Question:
I want to generate a string for a file path inside a powershell script. I want this to work in both in windows and mac.
At the moment the code is hardcoded to windows like paths (“\” -> windows, “/” -> unix):
$templatep="$CoreRoot\templates\$serviceName"
I changed this to:
$templatep= Join-Path $CoreRoot "templates" $serviceName
And it works in mac with Powershell 6.0. BUT it doesn’t work in my windows server with Powershell 4. I have to do something like this:
$templatep= Join-Path $CoreRoot -ChildPath "templates" | Join-Path -ChildPath $serviceName
Any idea why this is just working in my mac? Is this a new feature in powershell 5 or 6?
I dont’t like the having to pipe multiple Join-Paths. Is there a better way to do this?
Thanks!
Answer:
First, a workaround using the .NET framework:
1 2 |
[IO.Path]::Combine('a', 'b', 'c') |
This yields a/b/c
on Unix, and a\b\c
on Windows, and conveniently supports any number of path components.
Note:
- This workaround is only for filesystem paths, whereas
Join-Path
is designed to work with any PowerShell drive provider’s paths. - Make sure that no component other than the first starts with
\
(Windows) or/
(Unix), because any preceding component is then ignored; e.g., on Windows:
[IO.Path]::Combine('\a', '\b', 'c') # -> '\b\c' - '\a' is ignored(!)
Note thatJoin-Path
does not exhibit this behavior; see this answer for details.
As an alternative to sequencing Join-Path
calls with a pipeline you can simply use (...)
(a subexpression):
1 2 |
Join-Path a (Join-Path b c) # -> 'a\b\c' (on Windows) |
The syntax displayed by Join-Path -?
as of Windows PowerShell v5.1.14393.693
(incidental parameters omitted):
1 2 |
Join-Path [-Path] |
This syntax implies that invocation Join-Path a b c
results in a syntax error in Windows PowerShell, because there is no parameter to bind the c
argument to.
By contrast, the syntax displayed in PowerShell (Core) v6+ reveals an additional parameter:
1 2 |
Join-Path [-Path] |
It is the additional -AdditionalChildPath
parameter, which is declared in a manner that collects all remaining positional arguments that (ValueFromRemainingArguments
), that makes specifying an arbitrary number of child components work, so that Join-Path a b c
indeed works, for instance.
Unfortunately, this enhancement won’t be back-ported to Windows PowerShell.
Note that even though [-Path] <String[]>
is an array parameter, its purpose is not to accept multiple child path components of a single output path, but to allow joining of multiple parent-child path pairs; e.g.:
1 2 3 4 |
$ Join-Path a,b c # same as: Join-Path -Path a,b -ChildPath c a\c b\c |
Finally, even you can typically get away with hard-coding /
as the path separator on both platforms, because many Windows API functions as well as PowerShell’s own cmdlets accept \
and /
interchangeably.
However, not all utilities may behave this way, so it’s generally safer to use the platform-appropriate separator.
For instance, the following works just fine on Windows:
1 2 |
Get-Item c:/windows/system32 # same as: Get-Item c:\windows\system32 |