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



Solution 1:[1]

You can use the .NET Path class:

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

Solution 2:[2]

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.

Solution 3:[3]

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!

Solution 4:[4]

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.

Solution 5:[5]

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"

Solution 6:[6]

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

Solution 7:[7]

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

Solution 8:[8]

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

Solution 9:[9]

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.

Solution 10:[10]

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"
}