Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

[System.IO.Path]::Combine not taking my powershell variable as a parameter?

Tags:

powershell

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..?

like image 351
Bruno Avatar asked Jun 12 '16 17:06

Bruno


2 Answers

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
      • Short for: Join-Path -Path \foo -ChildPath \bar
      • Note how PS, unlike [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
    • Note how 2 output paths were generated, by pairing each parent path with the one child path.
  • 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.

like image 114
mklement0 Avatar answered Nov 29 '22 09:11

mklement0


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
like image 37
Martin Brandl Avatar answered Nov 29 '22 08:11

Martin Brandl