Chapter 07Lesson 02~125 minutes

Paths, Literal Paths, Wildcards, Test-Path, and Robust Path Construction

Build cross-platform paths deliberately, distinguish pattern paths from literal filenames, validate existing items, and anchor automation to the correct root instead of the caller’s current directory.

Learning objectives

  • Explain relative, absolute, provider, home, and temporary path concepts.
  • Build and decompose paths with Join-Path and Split-Path instead of manual separator concatenation.
  • Use Test-Path, Resolve-Path, and Convert-Path according to whether the item exists and what information is needed.
  • Distinguish -Path wildcard interpretation from -LiteralPath exact filename handling.
  • Preview the role of $PSScriptRoot for script-relative resources without duplicating Chapter 08.
  • Safely handle filenames containing spaces, brackets, dollar signs, wildcard characters, and Unicode.

1. A path is an address, and robust automation builds addresses deliberately

Filesystem automation fails surprisingly often because code treats paths as ordinary text. A path is text syntactically, but its separators, root, wildcard characters, provider, current location, and existence all affect how PowerShell interprets it. The safest habit is to build and validate paths with path-aware commands instead of concatenating fragments manually.

$workspace = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath 'ps-path-lab'
$config = Join-Path -Path $workspace -ChildPath 'config.json'

$workspace
$config

Join-Path delegates separator handling to PowerShell/provider logic, which is more portable than embedding \ or / throughout the script.

2. Relative paths depend on location; absolute paths identify a root

A relative path is interpreted from the current PowerShell location. An absolute path includes the relevant root or drive. Relative paths are convenient interactively; absolute paths are usually safer for automation that can be launched from different working directories.

Get-Location
Resolve-Path .
$HOME

# The exact filesystem root is platform-specific.
Get-PSDrive -PSProvider FileSystem |
    Select-Object Name, Root

The tilde (~) is commonly used as a home-location shorthand in PowerShell paths, while $HOME is an explicit automatic variable containing the user home path. For stored configuration and function parameters, an explicit resolved path is often easier to log and test.

3. Join paths to construct; split paths to reason about components

Join-Path combines parent and child path components. Split-Path extracts parent, leaf, qualifier, or related components. These commands communicate intent more clearly than string slicing.

$root = Join-Path ([System.IO.Path]::GetTempPath()) 'academy path lab'
$file = Join-Path $root 'report [final] ü.txt'

Split-Path -Path $file -Parent
Split-Path -Path $file -Leaf
Split-Path -Path $file -LeafBase
Split-Path -Path $file -Extension

The path intentionally contains spaces, brackets, and Unicode. If you keep the complete path as one string object, spaces do not split it into separate PowerShell arguments.

4. Test existence, resolve provider paths, and convert only when the target exists

Test-Path asks whether a path resolves according to the provider. Resolve-Path returns resolved path objects and expands wildcard matches. Convert-Path converts a PowerShell path to the provider’s underlying path representation. Resolution commands generally require the target to exist, so do not use them to construct a destination that you have not created yet.

$tempRoot = [System.IO.Path]::GetTempPath()

Test-Path -Path $tempRoot
Resolve-Path -Path $tempRoot
Convert-Path -Path $tempRoot

$future = Join-Path $tempRoot 'not-created-yet.txt'
Test-Path -LiteralPath $future

A common design pattern is: construct a destination with Join-Path, then use Test-Path or creation logic. Use Resolve-Path when you specifically need to canonicalize an existing path or expand wildcard matches.

5. -Path interprets wildcards; -LiteralPath treats characters literally

PowerShell wildcard characters include *, ?, and bracket expressions such as [abc]. Many provider cmdlets accept -Path, which can interpret those characters as patterns. If the filename itself contains wildcard characters, use -LiteralPath.

$root = Join-Path ([System.IO.Path]::GetTempPath()) 'ps-literal-lab'
New-Item -ItemType Directory -Path $root -Force | Out-Null

$literal = Join-Path $root 'report[1].txt'
Set-Content -LiteralPath $literal -Value 'literal brackets'

# Pattern interpretation can match differently than intended:
Get-Item -Path $literal -ErrorAction SilentlyContinue

# LiteralPath means exactly this filename:
Get-Item -LiteralPath $literal

This distinction is security-relevant as well as convenient. When a path comes from data and is supposed to name exactly one item, -LiteralPath prevents wildcard expansion from widening the operation.

6. Manual path concatenation is fragile across separators, trailing delimiters, and providers

$base = [System.IO.Path]::GetTempPath()

# Fragile style:
$bad = $base + '/academy/config.json'

# Path-aware construction:
$good = Join-Path $base 'academy'
$good = Join-Path $good 'config.json'

$bad
$good

The fragile example may happen to work on a particular platform, but it pushes separator and trailing-slash responsibilities into your string logic. Join-Path expresses that these fragments are path components rather than arbitrary text.

7. $PSScriptRoot anchors resources to a script instead of the launch directory

When code lives in a .ps1 file, $PSScriptRoot contains the directory of that script. This is useful for locating templates, fixtures, or sibling configuration files regardless of the caller’s current directory. Chapter 08 will cover script entry points and parameters in depth; here the important idea is path anchoring.

# Inside a .ps1 file:
# $configPath = Join-Path $PSScriptRoot 'config/app.json'
# $templatePath = Join-Path $PSScriptRoot 'templates/service.conf'

Avoid replacing one global assumption with another: a script-relative file should be anchored to $PSScriptRoot; a user-supplied file should come from a parameter; a temporary file should go under a temporary location.

8. Use the temporary directory intentionally and normalize only when you need canonical identity

PowerShell 7 includes a Temp: FileSystem PSDrive mapped to the user’s temporary directory. The .NET method [System.IO.Path]::GetTempPath() is also cross-platform. Temporary paths are ideal for disposable labs and staging intermediate files.

Get-PSDrive Temp -ErrorAction SilentlyContinue
[System.IO.Path]::GetTempPath()

$labRoot = Join-Path ([System.IO.Path]::GetTempPath()) 'powershell-academy'

Do not normalize a path merely for cosmetic reasons. Normalization can require the item to exist and can resolve provider-specific details. Normalize when you need to compare existing identities, log a canonical location, or pass a concrete provider path to another API.

9. Lab: safely handle spaces, brackets, wildcard characters, and Unicode

The lab creates only a disposable directory under your user temporary location. It demonstrates literal path handling and cleans up afterward.

$labRoot = Join-Path ([System.IO.Path]::GetTempPath()) 'ps-path-special-lab'
New-Item -ItemType Directory -Path $labRoot -Force | Out-Null

$names = @(
    'normal.txt'
    'has spaces.txt'
    'report[1].txt'
    'cost$2026.txt'
    'résumé-测试.txt'
)

foreach ($name in $names) {
    $path = Join-Path $labRoot $name
    Set-Content -LiteralPath $path -Value "Name=$name"
}

Get-ChildItem -LiteralPath $labRoot |
    Select-Object Name, FullName, Length

foreach ($name in $names) {
    $path = Join-Path $labRoot $name
    if (-not (Test-Path -LiteralPath $path -PathType Leaf)) {
        throw "Missing expected file: $path"
    }
}

Remove-Item -LiteralPath $labRoot -Recurse -Force

Verification checklist

10. Common path mistakes

Concatenating with hard-coded separators. Prefer path-aware composition.

Using -Path for an untrusted literal filename containing brackets or *. Use -LiteralPath when no pattern matching is intended.

Calling Resolve-Path for a destination that does not exist yet. Construct first; resolve after creation if canonicalization is needed.

Assuming current directory equals script directory. They are separate concepts; $PSScriptRoot exists for script-relative resources.

11. Knowledge check

Question 1. Why is Join-Path preferable to manual separator concatenation?

Question 2. When should you choose -LiteralPath?

Question 3. Why can Resolve-Path fail for a valid destination path?

Question 4. What problem does $PSScriptRoot solve?

Question 5. Why is a path with spaces safe when passed as one PowerShell string value?

12. Summary

Treat paths as addresses with provider semantics, not arbitrary strings. Use Join-Path and Split-Path to construct and decompose, Test-Path to ask existence/type questions, and resolution commands for existing paths. Choose -LiteralPath whenever wildcard interpretation is not intended, anchor script resources with $PSScriptRoot, and use temporary locations for disposable operational work.

13. Further reading

Next lesson

Turn robust paths into safe create, copy, move, rename, and remove workflows

Lesson 03 builds a disposable workspace, introduces idempotent filesystem mutation, and uses -WhatIf before destructive operations.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.