ChatGPT解决这个技术问题 Extra ChatGPT

How do I use Join-Path to combine more than two strings into a file path?

If I want to combine two strings into a file path, I use Join-Path like this:

$path = Join-Path C: "Program Files"
Write-Host $path

That prints "C:\Program Files". If I want to do this for more than two strings though:

$path = Join-Path C: "Program Files" "Microsoft Office"
Write-Host $path

PowerShell throws an error:

Join-Path : A positional parameter cannot be found that accepts argument 'Microsoft Office'. At D:\users\ma\my_script.ps1:1 char:18 + $path = join-path <<<< C: "Program Files" "Microsoft Office" + CategoryInfo : InvalidArgument: (:) [Join-Path], ParameterBindingException + FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell .Commands.JoinPathCommand

I tried using a string array:

[string[]] $pieces = "C:", "Program Files", "Microsoft Office"
$path = Join-Path $pieces
Write-Host $path

But PowerShell prompts me to enter the childpath (since I didn't specify the -childpath argument), e.g. "somepath", and then creates three files paths,

C:\somepath
Program Files\somepath
Microsoft Office\somepath

which isn't right either.

Note that since PowerShell 6 your intuitive first attempt now works as expected and correctly handles trailing/leading path separators in parts of the path 🎉

P
Peter Mortensen

You can use the .NET Path class:

[IO.Path]::Combine('C:\', 'Foo', 'Bar')

Certainly the most concise form, and handles path separators and trailing/leading slashes on path fragments properly, which the current accepted answer (basic string concatenation) does not do.
For executing above command in my powershell ise getting this error -Cannot find an overload for "Combine" and the argument count: "3". At line:1 char:19 + [io.path]::combine <<<< ('c:\', 'foo', 'bar') + CategoryInfo : NotSpecified: (:) [], MethodException + FullyQualifiedErrorId : MethodCountCouldNotFindBest
@Aamol What CLR version you're running ($PSVersionTable)? Does [io.path]::combine([string[]]('c:\','foo','bar')) work?
Seems the parameter limit is 3, after 3 the first parameter is ignored. (here at least, ps 5.1, clr 4.0)
@DavidKeaveny "handles path separators and trailing/leading slashes on path fragments properly" -- Not really. join-path does what you expect, join-path "C:\" "\foo" outputs C:\foo, Path.Combine however ignores the first argument whenever the second argument contains a leading separator: [io.path]::combine('c:\', '\foo') annoyingly outputs \foo.
P
Peter Mortensen

Since Join-Path can be piped a path value, you can pipe multiple Join-Path statements together:

Join-Path "C:" -ChildPath "Windows" | Join-Path -ChildPath "system32" | Join-Path -ChildPath "drivers"

It's not as terse as you would probably like it to be, but it's fully PowerShell and is relatively easy to read.


+1 since it will work across all powershell 2,3,4 , the problem with the [io.path]::Combine API is its different for .net framework 3,4
M
Marcus Mangelsdorf

Since PowerShell 6.0, Join-Path has a new parameter called -AdditionalChildPath and can combine multiple parts of a path out-of-the-box. Either by providing the extra parameter or by just supplying a list of elements.

Example from the documentation:

Join-Path a b c d e f g
a\b\c\d\e\f\g

So in PowerShell 6.0 and above your variant

$path = Join-Path C: "Program Files" "Microsoft Office"

works as expected!


M
Matt

Join-Path is not exactly what you are looking for. It has multiple uses but not the one you are looking for. An example from Partying with Join-Path:

Join-Path C:\hello,d:\goodbye,e:\hola,f:\adios world
C:\hello\world
d:\goodbye\world
e:\hola\world
f:\adios\world

You see that it accepts an array of strings, and it concatenates the child string to each creating full paths. In your example, $path = join-path C: "Program Files" "Microsoft Office". You are getting the error since you are passing three positional arguments and join-path only accepts two. What you are looking for is a -join, and I could see this being a misunderstanding. Consider instead this with your example:

"C:","Program Files","Microsoft Office" -join "\"

-Join takes the array of items and concatenates them with \ into a single string.

C:\Program Files\Microsoft Office

Minor attempt at a salvage

Yes, I will agree that this answer is better, but mine could still work. Comments suggest there could be an issue with slashes, so to keep with my concatenation approach you could do this as well.

"C:","\\Program Files\","Microsoft Office\" -join "\" -replace "(?!^\\)\\{2,}","\"

So if there are issues with extra slashes it could be handled as long as they are not in the beginning of the string (allows UNC paths). [io.path]::combine('c:\', 'foo', '\bar\') would not work as expected and mine would account for that. Both require proper strings for input as you cannot account for all scenarios. Consider both approaches, but, yes, the other higher-rated answer is more terse, and I didn't even know it existed.

Also, would like to point out, my answer explains how what the OP doing was wrong on top of providing a suggestion to address the core problem.


This is wrong because even though multiple consecutive \ in path will work, it is ugly and can cause problems potentially.
@MikhailOrlov Can you describe a potential problem as supposed to just suggesting it could happen? Do you have another suggestion? I'm asking as I don't see a problem. If something is wrong I would like to address it.
I've been handling lots of low quality code recently, people compare paths by String.Equals and parse paths with String.Split('\\') without removing empty strings. I can't think of anything more hazardous in consequences, mostly I'm just being paranoid. Thank you for your edit.
Including the path separator explicitly can cause problems with cross-platform portability. While PowerShell currently only runs on Windows, that's likely to change in the not too distant future, and it's a good idea to develop good habits as early as possible. Not to mention that these habits can transfer over to other languages.
Your partying link is broken. Here's the updated link: devblogs.microsoft.com/powershell/partying-with-join-path
P
Peter Mortensen

If you are still using .NET 2.0, then [IO.Path]::Combine won't have the params string[] overload which you need to join more than two parts, and you'll see the error Cannot find an overload for "Combine" and the argument count: "3".

Slightly less elegant, but a pure PowerShell solution is to manually aggregate path parts:

Join-Path C: (Join-Path  "Program Files" "Microsoft Office")

or

Join-Path  (Join-Path  C: "Program Files") "Microsoft Office"

P
Peter Mortensen

Here are two more ways to write a pure PowerShell function to join an arbitrary number of components into a path.

This first function uses a single array to store all of the components and then a foreach loop to combine them:

function Join-Paths {
    Param(
        [Parameter(mandatory)]
        [String[]]
        $Paths
    )
    $output = $Paths[0]
    foreach($path in $Paths[1..$Paths.Count]) {
        $output = Join-Path $output -ChildPath $path
    }
    $output
}

Because the path components are elements in an array and all part of a single argument, they must be separated by commas. Usage is as follows:

PS C:\> Join-Paths 'C:', 'Program Files', 'Microsoft Office'
C:\Program Files\Microsoft Office

A more minimalist way to write this function is to use the built-in $args variable, and then collapse the foreach loop into a single line using Mike Fair's method.

function Join-Paths2 {
    $path = $args[0]
    $args[1..$args.Count] | %{ $path = Join-Path $path $_ }
    $path
}

Unlike the previous version of the function, each path component is a separate argument, so only a space is necessary to separate the arguments:

PS C:\> Join-Paths2 'C:' 'Program Files' 'Microsoft Office'
C:\Program Files\Microsoft Office

M
Mike Fair

Here's something that will do what you'd want when using a string array for the ChildPath.

$path = "C:"
@( "Program Files", "Microsoft Office" ) | %{ $path = Join-Path $path $_ }
Write-Host $path

Which outputs

C:\Program Files\Microsoft Office

The only caveat I found is that the initial value for $path must have a value (cannot be null or empty).


P
Peter Mortensen

The following approach is more concise than piping Join-Path statements:

$p = "a"; "b", "c", "d" | ForEach-Object -Process { $p = Join-Path $p $_ }

$p then holds the concatenated path 'a\b\c\d'.

(I just noticed that this is the exact same approach as Mike Fair's, sorry.)


F
Francesco

You can use it this way:

$root = 'C:'
$folder1 = 'Program Files (x86)'
$folder2 = 'Microsoft.NET'

if (-Not(Test-Path $(Join-Path $root -ChildPath $folder1 | Join-Path -ChildPath $folder2)))
{
   "Folder does not exist"
}
else 
{
   "Folder exist"
}

K
Kevin

Or you could write your own function for it (which is what I ended up doing).

function Join-Path-Recursively($PathParts) {
    $NumberOfPathParts = $PathParts.Length;

    if ($NumberOfPathParts -eq 0) {
        return $null
    } elseif ($NumberOfPathParts -eq 1) {
        return $PathParts[0]
    } else {
        return Join-Path -Path $PathParts[0] -ChildPath $(Join-Path-Recursively -PathParts $PathParts[1..($NumberOfPathParts-1)])
    }
}

You could then call the function like this:

Join-Path-Recursively -PathParts  @("C:", "Program Files", "Microsoft Office")
Join-Path-Recursively  @("C:", "Program Files", "Microsoft Office")

This has the advantage of having the exact same behaviour as the normal Join-Path function and not depending on the .NET Framework.


关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now