164

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Michael A
  • 4,391
  • 8
  • 34
  • 61
  • 5
    Note that [since PowerShell 6](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/join-path?view=powershell-6#example-7--combine-an-indefinite-number-of-paths) your intuitive first attempt now [works as expected](https://stackoverflow.com/a/53745818/2822719) _and_ correctly handles trailing/leading path separators in parts of the path – Marcus Mangelsdorf Aug 25 '20 at 10:50
  • This was my issue. I was using an old version of powershell that only accepted 2 arguments. – Darren Hoehna Sep 22 '22 at 19:58

10 Answers10

224

You can use the .NET Path class:

[IO.Path]::Combine('C:\', 'Foo', 'Bar')
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mark Toman
  • 3,090
  • 2
  • 17
  • 18
  • 4
    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. – David Keaveny Feb 10 '15 at 00:19
  • 3
    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 Sep 28 '16 at 00:20
  • @Aamol What CLR version you're running (`$PSVersionTable`)? Does `[io.path]::combine([string[]]('c:\','foo','bar'))` work? – Mark Toman Sep 28 '16 at 16:24
  • I have CLRVersion 2.0.50727.5420 and your mentioned command also do not work on my machine. Which give this exception - Cannot find an overload for "Combine" and the argument count: "1". At line:1 char:19 + [io.path]::combine <<<< ([string[]]('c:\','foo','bar')) + CategoryInfo : NotSpecified: (:) [], MethodException + FullyQualifiedErrorId : MethodCountCouldNotFindBest – Aamol Sep 29 '16 at 01:52
  • @Aamol Combine seems to have only 2 parameters in .NET 2.0: http://stackoverflow.com/questions/760612/how-can-path-combine-be-used-with-more-than-2-arguments See if you can upgrade and also check the supportedRuntime version in \Windows\System32\WindowsPowerShell\v1.0\powershell_ise.exe.config, set it to `v4.0`. – Mark Toman Sep 29 '16 at 09:43
  • 1
    Seems the parameter limit is 3, after 3 the first parameter is ignored. (here at least, ps 5.1, clr 4.0) – ehiller Nov 19 '17 at 16:33
  • It even works on Linux with PS Core. Also, I don't experience the limit @ehiller mentions. – beruic Oct 22 '18 at 11:10
  • 6
    @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`. – Quantic Dec 04 '18 at 16:03
  • In PowerShell 6.0 you can [finally use Join-Path](https://stackoverflow.com/a/53745818/2822719) to accomplish exactly that behavior! :) – Marcus Mangelsdorf Dec 12 '18 at 15:02
  • @Quantic: If `path2` parameter in `IO.Path::Combine` includes a root, `path2` is returned. In general, Path is rooted if it starts with drive specification _or_ directory separator char (see [Path.IsPathRooted](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.ispathrooted)). – tibx Apr 27 '21 at 07:39
129

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
David Keaveny
  • 3,904
  • 2
  • 38
  • 52
  • 9
    +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 – Ram Nov 06 '17 at 14:03
55

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 intuitive guess

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

works as expected!

Marcus Mangelsdorf
  • 2,852
  • 1
  • 30
  • 40
17

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.

Matt
  • 45,022
  • 8
  • 78
  • 119
  • 3
    This is wrong because even though multiple consecutive \ in path will work, it is ugly and can cause problems potentially. – Mikhail Orlov Mar 06 '15 at 15:55
  • @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. – Matt Mar 06 '15 at 15:58
  • 3
    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. – Mikhail Orlov Mar 07 '15 at 16:39
  • 3
    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. – bshacklett Nov 30 '15 at 18:59
  • 1
    Your partying link is broken. Here's the updated link: https://devblogs.microsoft.com/powershell/partying-with-join-path/ – John Dyer Apr 27 '22 at 17:10
17

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"
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Konstantin Spirin
  • 20,609
  • 15
  • 72
  • 90
7

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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jon
  • 356
  • 3
  • 8
6

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

Mike Fair
  • 153
  • 2
  • 6
2

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

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Daniel
  • 321
  • 3
  • 10
1

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"
}
Francesco
  • 23
  • 4
1

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.

Kevin
  • 2,760
  • 3
  • 15
  • 30