TB

MoppleIT Tech Blog

Welcome to my personal blog where I share thoughts, ideas, and experiences.

Avoid Wildcard Pitfalls in PowerShell: Use -LiteralPath for Safe, Predictable File Operations

Square brackets, asterisks, and question marks look harmless in file names—until PowerShell treats them as wildcards. If you copy a file named report[final].csv with -Path, you might accidentally match multiple files or none at all. The fix is simple and reliable: default to -LiteralPath for any operation that targets an exact file system path.

In this post, you'll learn why wildcards sneak in, how -LiteralPath prevents surprising matches, and a safe pattern for validation, logging, and error handling. You'll walk away with copy-and-paste snippets you can drop into automation scripts and CI/CD pipelines today.

Why [] and other characters bite—and how -LiteralPath saves you

Know your wildcards

PowerShell (and underlying providers) interpret certain characters in -Path as wildcards:

  • * matches zero or more characters
  • ? matches a single character
  • [] defines a character class (e.g., [0-9])

That means a path like C:\drop\report[final].csv can be interpreted as a pattern rather than a literal file name, potentially matching other files in the directory or failing unexpectedly.

Prefer -LiteralPath for exact matches

When you use -LiteralPath, PowerShell bypasses wildcard expansion completely. The string you pass is treated as an exact path—even if it contains [, ], *, or ?. This is the safest and most predictable way to target specific files and directories in scripts, automation tasks, and production runbooks.

Example: safe copy with literal paths

$src    = 'C:\drop\report[final].csv'
$dstDir = 'C:\safe\backup'
$dst    = Join-Path -Path $dstDir -ChildPath 'report[final].csv'

New-Item -ItemType Directory -Path $dstDir -Force | Out-Null

if (-not (Test-Path -LiteralPath $src)) {
  Write-Warning ('Missing file: {0}' -f $src)
  return
}

Copy-Item -LiteralPath $src -Destination $dst -Force -ErrorAction Stop
$full = Resolve-Path -LiteralPath $dst
Write-Host ('Copied -> {0}' -f $full)
# Using -Path here would treat [] as wildcards

This pattern ensures PowerShell never treats the name as a wildcard, avoids accidental multi-file operations, and gives you a clear, validated flow.

A safe, repeatable copy pattern

Make the following steps a habit whenever you touch the file system:

  1. Build paths safely with Join-Path instead of concatenation.
  2. Validate existence with Test-Path -LiteralPath before acting.
  3. Log the action you are about to take (where, what, why) for auditability.
  4. Stop on errors so you don’t continue in a broken state (-ErrorAction Stop and try/catch).
  5. Prefer literal operations for all file manipulations (Copy-Item, Remove-Item, Move-Item, Get-Item).

Hardened function with validation, logging, and WhatIf

Here’s a reusable function that demonstrates a safe pattern. It validates inputs, logs intent, supports dry runs with -WhatIf, and uses -LiteralPath everywhere.

function Copy-LiteralSafe {
  [CmdletBinding(SupportsShouldProcess)]
  param(
    [Parameter(Mandatory)]
    [string]$Source,

    [Parameter(Mandatory)]
    [string]$DestinationDir,

    [string]$LogFile,

    [switch]$VerboseLogging
  )

  $ErrorActionPreference = 'Stop'

  if (-not (Test-Path -LiteralPath $Source)) {
    throw "Source not found: $Source"
  }

  if (-not (Test-Path -LiteralPath $DestinationDir)) {
    New-Item -ItemType Directory -Path $DestinationDir -Force | Out-Null
  }

  $leaf = Split-Path -Leaf -Path $Source
  $dest = Join-Path -Path $DestinationDir -ChildPath $leaf

  $msg = "Copy $Source -> $dest"
  if ($VerboseLogging) { Write-Host $msg }
  if ($LogFile) { Add-Content -LiteralPath $LogFile -Value ("{0:u} {1}" -f (Get-Date), $msg) }

  if ($PSCmdlet.ShouldProcess($dest, "Copy from literal source")) {
    Copy-Item -LiteralPath $Source -Destination $dest -Force
    $resolved = Resolve-Path -LiteralPath $dest
    Write-Verbose ("Copied to {0}" -f $resolved)
    return $resolved
  }
}

# Dry-run example
Copy-LiteralSafe -Source 'C:\drop\report[final].csv' -DestinationDir 'C:\safe\backup' -VerboseLogging -WhatIf

# Commit example
Copy-LiteralSafe -Source 'C:\drop\report[final].csv' -DestinationDir 'C:\safe\backup' -LogFile 'C:\logs\file-ops.log' -VerboseLogging

Notice the pattern: validate with Test-Path -LiteralPath, build destinations with Join-Path, then commit with Copy-Item -LiteralPath. Logging captures intent and result, while -WhatIf gives you a safe dry run.

Safer deletes, moves, and checks

  • Delete: Remove-Item -LiteralPath 'C:\temp\weird[file].tmp' -Force
  • Move: Move-Item -LiteralPath 'C:\in\report[final].csv' -Destination 'C:\archive\report[final].csv' -Force
  • Existence check: Test-Path -LiteralPath 'C:\in\report[final].csv'
  • Inspect: Get-Item -LiteralPath 'C:\in\report[final].csv'

Operational tips: validation, logging, and dry runs

1) Validate with Test-Path and fail fast

Checking existence before acting protects you from surprises and unclear error messages. When you do act, set -ErrorAction Stop so exceptions bubble up to a try/catch block:

try {
  if (-not (Test-Path -LiteralPath $src)) {
    throw "Missing file: $src"
  }
  Copy-Item -LiteralPath $src -Destination $dst -Force -ErrorAction Stop
}
catch {
  Write-Error ("Copy failed: {0}" -f $_)
  # Optionally: exit 1 for CI/CD
}

2) Log the action before you commit

Log intent first, then execute. This makes reviews and audits clearer:

$intent = "Copy $src -> $dst"
Write-Information $intent
Add-Content -LiteralPath 'C:\logs\ops.log' -Value ("{0:u} {1}" -f (Get-Date), $intent)
Copy-Item -LiteralPath $src -Destination $dst -Force -ErrorAction Stop

Prefer Write-Information for structured, controllable output (tied to $InformationPreference) instead of Write-Host for logs.

3) Use -WhatIf and -Confirm via ShouldProcess

For scripts and modules, opt into SupportsShouldProcess. This lets callers use -WhatIf and -Confirm without you changing logic:

  • My-Command -WhatIf prints intended changes.
  • My-Command -Confirm prompts before destructive operations.

4) When you must use -Path (and wildcards are intended)

Sometimes you really do want pattern matching. Use -Path, but escape special characters in literal segments with the backtick:

$src = 'C:\drop\report`[final`].csv' # Escapes [ and ]
Copy-Item -Path $src -Destination 'C:\safe\backup\report`[final`].csv' -Force

As soon as you need exact matching again, switch back to -LiteralPath.

5) CI/CD and production pipelines

  • Artifacts and releases: Treat build artifacts as literal paths; fail fast if missing; log SHA256 and file sizes.
  • Backups and rotations: Move/Copy with -LiteralPath to avoid wildcard expansions that could sweep whole directories.
  • Permissions and security: Combine -LiteralPath with explicit ACL updates to prevent accidental broad changes.

Performance and reliability notes

  • Provider-level filtering: If you need filtering in large directories, use -Filter where supported for performance, but keep literal segments literal (e.g., literal parent dir + filter).
  • Avoid string concatenation: Use Join-Path to handle separators and readability.
  • Consistency: Make -LiteralPath your default mental model; reach for -Path only when you truly need wildcards.

Security best practices

  • Principle of least surprise: Literal paths reduce accidental data exposure or destructive operations.
  • Auditability: Log intent before commit; include user, timestamp, and working directory.
  • Guard deletes and overwrites: Pair -LiteralPath with -WhatIf and/or -Confirm for high-risk commands like Remove-Item or robocopy mirroring.

Quick checklist

  • Use -LiteralPath for all direct file references.
  • Validate with Test-Path -LiteralPath before action.
  • Use -ErrorAction Stop and try/catch to fail fast.
  • Log the action before you commit it.
  • Enable -WhatIf/-Confirm where possible.

What you get:

  • Fewer surprises
  • Safer copies and deletes
  • Predictable, testable results

Make exact path handling a habit. 📘 Read the PowerShell Advanced CookBook → https://www.amazon.com/PowerShell-Advanced-Cookbook-scripting-advanced-ebook/dp/B0D5CPP2CQ/

← All Posts Home →