Time to read: ~ 3 minutes
Words: 571
Update: Learning from my mistakes aka Failing Up
Update: Reliably informed that `-AdditionalChildPath` was added after 5.1
Join Me For a Moment
There’s a multitude of scripts out in the wild with a chain of Join-Path
commands. Initially, when I wanted to create a path safely, a Join-Path
cmdlets chain was also my go-to. However, after reading up on the documentation, I realised another way: I only need a singular instance of the Join-Path
command.
Target Location
My PowerShell console is open in my home
folder, and I’ve a test file: /home/soneill/PowerShell/pester-5-groupings/00-run-tests.ps1
.
If I wanted to create a variable that goes to the location of that file, one of the safe ways of doing that is to use Join-Path
.
Long Form
I mean, I could create the variable myself by concatenating strings, but then I’d have to take the path separator into account depending if I’m on Windows or not.
Apparently not…
$var = ".\PowerShell\pester-5-groupings\00-run-tests.ps1"
[PSCustomObject] @{
Type = 'Long Form'
Separator = 'Manual entry: \'
Variable = $var
Path = try {Get-ChildItem -Path $var -ErrorAction Stop} catch {'Error!'}
}

I thought this wouldn’t work but, when running the code samples, it appears that PowerShell doesn’t mind me using a forward-slash (/
) or a back-slash (\
); it’ll take care of the proper separator for me.
UPDATE: This way works fine from a file but run the script from a PowerShell terminal and it’s a no-go.

UPDATED UPDATE: Thanks for Cory Knox (twitter) and Steven Judd (twitter) for pointing out that this fails because it’s using /bin/ls
instead of the Get-ChildItem
alias:

Manual Creation
A more explicit, cross-platform method would be to use the [IO.Path]::DirectorySeparatorChar
.
$sep = [IO.Path]::DirectorySeparatorChar
$var = ".${sep}PowerShell${sep}pester-5-groupings${sep}00-run-tests.ps1"
[PSCustomObject] @{
Type = 'Manual Creation'
Separator = "[IO.Path]::DirectorySepartorChar: $sep"
Variable = $var
Path = try {Get-ChildItem -Path $var -ErrorAction Stop} catch {'Error!'}
}

This method works fine but creating the path can get very long if I don’t use a variable. Even using a variable, I have to wrap the name in curly braces because of the string expansion method I used. That’s not something that I would expect someone picking up PowerShell for the first time to know.
-f Strings
In case you’re wondering, another string expansion method here would be to use -f
strings.
$sep = [IO.Path]::DirectorySeparatorChar
$varf = '.{0}PowerShell{0}pester-5-groupings{0}00-run-tests.ps1' -f $sep
[PSCustomObject] @{
Type = 'F String'
Separator = "[IO.Path]::DirectorySepartorChar: $sep"
Variable = $varf
Path = try {Get-ChildItem -Path $varf -ErrorAction Stop} catch {'Error!'}
}

Many Join-Path Commands
Better yet would be if I didn’t have to account for the separator at all. Here’s where the multiple Join-Path
cmdlets come into play.
$var2 = Join-Path -Path . -ChildPath PowerShell | Join-Path -ChildPath pester-5-groupings | Join-Path -ChildPath 00-run-tests.ps1
[PSCustomObject] @{
Type = 'Many join paths'
Separator = 'Taken care of: Join-Path'
Variable = $var2
Path = try {Get-ChildItem -Path $var2 -ErrorAction Stop} catch {'Error!'}
}

Multiple Join-Path
commands work fine. No real issue with people using this way, but there is another!
Only One Join-Path Needed
Join-Path
has a parameter called -AdditionalChildPath
that takes the remaining arguments from the command line and uses them in much the same way as a Join-Path
command chain would.
$var3 = Join-Path -Path . -ChildPath PowerShell -AdditionalChildPath 'pester-5-groupings', '00-run-tests.ps1'
[PSCustomObject] @{
Type = 'AdditionalChildPaths'
Separator = 'Taken care of: Join-Path'
Variable = $var3
Path = try {Get-ChildItem -Path $var3 -ErrorAction Stop} catch {'Error!'}
}

More Output than Put Out
So there you go—more than one way to join a path. Use whichever ones work for you. It’s good to know your options, though.