Regular Expressions & Advanced String/Text Processing
PowerShell Intermediate/Advanced
Chapter 2 · Regular Expressions & Advanced String/Text Processing
PowerShell Fundamentals 4 introduced -match as one comparison operator among several — enough to filter a pipeline, not enough to actually use regular expressions well. This chapter goes back to -match and gives it real depth: the automatic variable it quietly populates, capturing named pieces out of a match, regex-powered find-and-replace, and — the chapter's own central gotcha — exactly why -match alone can't find every match in a string, no matter how many times that string actually contains one.
-match & $Matches: A Comparison That Remembers What It Found
Unlike every other comparison operator from Fundamentals 4, a successful -match does something extra: it populates the $Matches automatic variable with the details of what actually matched.
Capture Groups: Numbered and Named
-replace: Regex-Based Replacement With Backreferences
-replace's replacement string reads the same capture groups -match would have populated into $Matches — just referenced as $1, $2, $3 (or ${name} for a named group) directly inside the replacement text itself, rather than through a separate variable.
Case Sensitivity: Insensitive by Default
Consistent with every comparison operator back in Fundamentals 4, -match and -replace are case-insensitive by default — a genuinely different default from most languages' own regex engines. A -c-prefixed variant exists for the rare case a real case-sensitive match is actually needed:
When -match Isn't Enough: [regex]::Matches()
-match against a single string only ever tells you two things: whether a match exists at all, and — if so — the details of exactly one match, in $Matches. It was never designed to enumerate every occurrence inside a longer string, no matter how many times that pattern genuinely appears. [regex]::Matches() — the real .NET method underneath, reached via the [regex] type accelerator — returns a full collection of every match found, each with its own .Value and its own .Groups. Reach for -match when you're testing a whole string against a pattern; reach for [regex]::Matches() the moment "how many times does this appear" or "find every instance" is the actual question.
$Matches isn't scoped to one expression — it's a shared automatic variable that any later -match call quietly replaces:
$Matches any time after a second -match has run, expecting the first result to still be sitting there, is a real, easy mistake — always assign what you need out of $Matches immediately after the -match that produced it.
-split With a Real Regex Pattern
Fundamentals 5 used -split with a plain literal separator. -split's right-hand side is always a regex, which becomes genuinely useful once the separator itself is a pattern rather than a fixed string:
[regex]::Matches() or a more complex pattern, try the simplified -match/$Matches form first on a single sample string — it's faster to write and read for the common "does this match, and what did it capture" case, and it's the same underlying regex engine either way.
Hands-On Exercises
Explain why "HELLO" -match "hello" returns $true, tying it back to Fundamentals 4's own case-insensitivity of -eq. What operator would you use instead for a genuinely case-sensitive match?
Given a string containing three email addresses, explain why -match and $Matches only ever surface one of them. What's the correct tool for finding all three, and how would you use it?
Write a single -replace expression that reformats a date string from "2024-08-05" (YYYY-MM-DD) to "08/05/2024" (MM/DD/YYYY), using regex capture groups and backreferences.
Chapter 2 Quick Reference
-match— regex comparison; populates$Matcheson success$Matches[0]/$Matches[N]/$Matches['name']— whole match / numbered group / named group-replace— regex-based replacement;$1/$2/${name}in the replacement string reference capture groups- Case-insensitive by default —
-cmatch/-creplace/-csplitfor case-sensitive variants [regex]::Matches($text, $pattern)— returns every match in a string;-matchalone only ever reports one$Matchesis shared and gets overwritten — assign what you need out of it immediately, before the next-matchruns-split— its right-hand side is always a regex, not just a literal separator- Next chapter: PowerShell Remoting (WinRM, Invoke-Command, PSSessions)