Why isn't the function [System.IO.Path]::Combine
taking my first parameter?
PS C:\WINDOWS\system32> $g_basePath
F:\Dev\OneClickTools
PS C:\WINDOWS\system32> [Tests.Utils]::CUSTOMASSEMBLY_TEST
CustomLogic.dll
PS C:\WINDOWS\system32> [System.IO.Path]::Combine($g_basePath, "\bin\debug", [Tests.Utils]::CUSTOMASSEMBLY_TEST)
\bin\debug\CustomLogic.dll
The third command shows that only the second and third parameters were concatenated or is it using an empty string as $g_basePath
..?
To complement Martin Brandl's helpful answer:
Specifically, the - counter-intuitive - logic applied by [System.IO.Path]::Combine
is: "If path2 or path3 is also an absolute path, the combine operation discards all previously combined paths and resets to that absolute path." (this applies analogously to the other methods overloads too).
In other words: the last path argument specified that starts with \
(including \\
) or <letter>:
(a drive spec.) causes all previous paths to be ignored.
PS> [IO.Path]::Combine('\foo', '\bar', '\baz')
\baz # !! '\foo' and '\bar' were ignored
For the expected behavior - where an initial \
should be treated as an optional part of each path component other than the first - ensure that every component but the first doesn't start with \
- Join-Path
handles that as one would expect.
A simple function can do that:
# Combines array of path components $a to a single path.
function combine([string[]] $a) {
[IO.Path]::Combine([string[]] (@($a[0]) + $a[1..$($a.count-1)] -replace '^\\', ''))
}
Note that casting to [string[]]
is crucial for this to work.
Sample call:
PS> combine '\foo', '\bar', '\baz'
\foo\bar\baz # as expected
Using Join-Path
, which only supports pairs of components as of Windows PowerShell v5.1[1]
, an alternative to using a pipeline to chain multiple calls is to nest multiple calls with (...)
:
PS> Join-Path \foo (Join-Path \bar \baz)
\foo\bar\baz
Note that even though the nesting is somewhat awkward, you do NOT need to worry about subordinate components that start with \
.
As for the logic of PowerShell's own Join-Path
cmdlet:
While it's understandable to expect the -ChildPath
parameter to support an array of paths (which it doesn't), it's important to understand that Join-Path
's design differs fundamentally from that of [System.IO.Path]::Combine()
, as of Windows PowerShell v5.1[1]
:
Join-Path
uses multiple input paths to generate multiple output paths rather than interpreting the input paths as components of a single output path.
Input paths are pair(s) of parent and child paths, with each pair getting joined and resulting in its own output path:
PS> Join-Path \foo \bar \foo\bar
Join-Path -Path \foo -ChildPath \bar
[System.IO.Path]::Combine()
, handles an optional leading \
in the child path as you would expect.While the parent-path parameter (-Path
) does support an array of parent paths, the child-path parameter (-ChildPath
) does not, although when combined with the -Resolve
parameter and wildcards, it can effectively result in multiple child paths.
PS> Join-Path -Path \foo, \baz -ChildPath \bar \foo\bar \baz\bar
Unlike [System.IO.Path]::Combine()
, Join-Path
optionally supports resolving wildcard paths with -Resolve
:
PS> Join-Path -Resolve C:\Win* Sys* C:\Windows\System C:\Windows\System32 C:\Windows\SystemApps C:\Windows\SystemResources C:\Windows\SysWOW64 C:\Windows\system.ini
Finally it's worth noting that Join-Path
not only works with filesystem paths, but with the paths of any PowerShell [hierarchical data store] provider, applying the provider-appropriate path separators.
[1]
The cross-platform PowerShell edition, PowerShell Core, as of v6.0.0, already does support an arbitrary number of child components, enabling calls such as Join-Path a b c
to yield a\b\c
(Windows) or a/b/c
(Unix), whereas the Windows-native Windows PowerShell edition as of v5.1 still doesn't support it.
This new behavior is not yet documented (and not even reflected syntax-wise in Join-Path -?
), but it may make its way into Windows PowerShell (and thereby all editions) eventually.
Just omit the leading backslash on your second path:
[System.IO.Path]::Combine($g_basePath, "bin\debug", [Tests.Utils]::CUSTOMASSEMBLY_TEST)
A pure PowerShell attempt would be to use the Join-path cmdlet twice:
Join-Path $g_basePath 'bin\debug' | Join-path -ChildPath [Tests.Utils]::CUSTOMASSEMBLY_TEST
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With