PowerShell · Basics

Regex in PowerShell

A practical guide to pattern matching in PowerShell with -match, -replace, -split, and Select-String — and the handful of regex gotchas that catch even experienced scripters.

Quick idea: PowerShell has four regex-aware tools — -match/-notmatch to test a pattern, -replace to substitute it, -split to break a string apart on it, and Select-String to search it across lines and files — and all four use the same .NET regular expression engine underneath.
-match / -notmatch

Tests a string against a pattern and populates $Matches with the result.

-replace

Finds text matching a pattern and substitutes it, including captured groups.

Select-String

Searches lines of text or files for a pattern, similar to grep.

What Is Regex in PowerShell?

A regular expression, or regex, is a pattern that describes text rather than spelling it out literally. Instead of checking whether a string equals "Server-01", a regex can check whether it matches the shape “Server-” followed by two digits, which also matches Server-02, Server-47, and every other server built the same way.

Think of regex as a much more precise version of the wildcards you already know from dir *.log. A wildcard like * only knows “anything, any length.” Regex can say “exactly two digits,” “one or more word characters,” or “this text, but only at the start of the line” — distinctions a wildcard simply cannot express.

PowerShell does not implement its own regex engine. Every regex-aware operator and cmdlet — -match, -replace, -split, and Select-String — hands the pattern to the same underlying .NET regular expression engine, so a pattern that works in one of them works in all of them.

Important: PowerShell’s regex-aware operators are case-insensitive by default. Use the c-prefixed variants — -cmatch, -creplace, -csplit — when case must matter, or -CaseSensitive on Select-String.

Why Regex Instead of Wildcards?

Wildcard matching with -like is fine for filenames and simple containment checks. Regex earns its place the moment the shape of the text matters more than a literal substring — validating an input format, pulling a value out of the middle of a line, or reshaping text on the fly.

Structure, Not Just Text

Match “three digits, a dash, three digits, a dash, four digits” instead of a fixed string.

Capture Groups

Pull the domain and username out of CONTOSO\jsmith in a single expression.

Reusable Rules

One pattern validates or reshapes every input that fits the same real-world format.

-like and -notlike still have their place — they are faster to read and to run for simple cases. Reach for regex once you need to describe a pattern rather than a literal, or once you need the matched text back out, not just a yes/no answer.

Why Enterprise Scripts Depend on Regex

Most of what an infrastructure script actually does is take messy, human- or log-shaped text and turn it into something structured. Regex is usually how that happens.

A script parsing the Security event log for a failed logon needs to pull the account name and domain out of a multi-line message. A script auditing usernames needs to convert DOMAIN\user into user@domain.com before it can be handed to another system. A log-tailing script needs to find every line containing ERROR or 0x8007 across a folder of rotated log files without opening each one by hand. All three are regex problems, not string-equality problems.

In Active Directory: Parsing DOMAIN\username strings out of logon events, service account names, or CSV imports is one of the most common regex uses in an AD-facing script — the named-capture example later in this post is exactly that pattern.

Core Regex Syntax

A regex pattern is built from literal characters plus a small set of constructs that control how much and where something matches. The table below covers the constructs used in the commands further down.

Construct Meaning
\dAny decimal digit. \D is any non-digit.
\wAny word character ([a-zA-Z_0-9]). \W is any non-word character.
\sAny whitespace character. \S is any non-whitespace character.
.Any single character except a newline.
[abc]Any one character from the set. [^abc] matches any character not in the set.
[a-z]Any one character in the range.
* + ?Previous element zero-or-more, one-or-more, or zero-or-one times.
{n,m}Previous element between n and m times. {n} is exactly n, {n,} is at least n.
^ $Start and end of the string (or of each line, in Multiline mode).
(...)A capture group. Numbered left to right; (?<name>...) gives it a name instead.
Practical note: Because $ means “end of string” in a regex and also triggers variable expansion inside a PowerShell double-quoted string, always wrap a regex pattern in single quotes. 'fish$' is a regex anchor; "fish$" is PowerShell trying to expand a variable called $.

Commands

The block below runs through matching, capturing, replacing, and searching in the order you would actually reach for them: test first, capture what you need, replace or split it, then scale the same pattern up to Select-String for files.

# Basic match test: does the string contain the pattern anywhere?
'PowerShell' -match '^Power\w+'
# Output: True

# -notmatch is the inverse: True when the pattern does NOT match
'bag' -notmatch 'b[iou]g'
# Output: True

# Capture groups: pull structured values out of a matched string
$string = 'The last logged on user was CONTOSO\jsmith'
$string -match 'was (?<domain>.+)\\(?<user>.+)'
# Output: True

# $Matches is a hashtable populated by the match above.
# Key 0 is always the full match; named groups become their own keys.
$Matches.domain   # Output: CONTOSO
$Matches.user     # Output: jsmith

# -replace uses the same capture groups in its substitution string,
# referenced with $1, $2, or ${name} for named groups
$SearchExp = '^(?<DomainName>[\w-.]+)\\(?<Username>[\w-.]+)$'
$ReplaceExp = '${Username}@${DomainName}'
'Contoso.local\John.Doe' -replace $SearchExp, $ReplaceExp
# Output: John.Doe@Contoso.local

# -replace is case-insensitive by default; -creplace forces case sensitivity
'book' -ireplace 'B', 'C'   # Output: Cook
'book' -creplace 'B', 'C'   # Output: book (no match, nothing replaced)

# -split breaks a string into an array on a regex delimiter
"Lastname:FirstName:Address" -split ':'
# Output: Lastname / FirstName / Address (three separate strings)

# Select-String searches lines of text or files for a pattern, like grep
Select-String -Path "C:\Logs\*.log" -Pattern 'ERROR|0x8007' -Context 2,2

# -CaseSensitive and -SimpleMatch narrow how Select-String interprets Pattern
Select-String -Path "C:\Logs\app.log" -Pattern 'Timeout' -CaseSensitive

# -Quiet returns a Boolean instead of full MatchInfo objects — useful in an if()
if (Select-String -Path "C:\Logs\app.log" -Pattern 'FATAL' -Quiet) {
    Write-Warning 'Fatal error found in app.log'
}
Key rule: $Matches is only overwritten on a scalar -match that returns True (or a -notmatch that returns False). A failed match, or a match against a collection instead of a single string, leaves $Matches holding whatever it held before — check the Boolean result before trusting its contents.

Quantifiers and Anchors Cheat Sheet

Pattern Meaning Example
\d{3}-\d{4}Three digits, a dash, four digits.Matches 555-1234
[A-Z]+-\d\dOne or more uppercase letters, a dash, two digits.Matches DC-01
^Power\w+Starts with “Power”, then one or more word characters.Matches PowerShell
^fish$The entire string is exactly “fish”.Does not match fishing
3\.\d{2,}Literal dot (escaped), then two or more digits.Matches 3.141

Troubleshooting Cheat Sheet

Symptom Likely Cause Fix
-match works in the console but a script variable is empty The previous -match call returned False, or ran against a collection, so $Matches still holds an older value. Check the Boolean result of -match in an if before reading $Matches.
-replace substitution outputs literal $1 or an empty value The replacement string was double-quoted, so PowerShell tried to expand $1 as a variable before -replace ever saw it. Use single quotes for the substitution string, e.g. '$1 Universe', or escape the dollar sign with a backtick in a double-quoted string.
Pattern meant to be case-sensitive still matches mixed case PowerShell’s regex operators are case-insensitive by default. Use -cmatch, -creplace, or -csplit, or add -CaseSensitive to Select-String.
^ and $ only seem to match the whole file, not each line By default, ^ and $ anchor to the start and end of the entire input string, not each line. Pass the Multiline option to -split, or use (?m) inline in the pattern, to anchor per line instead.
A regex match hangs or times out on certain input A pattern with nested quantifiers (like ^(a+)+$) can force the .NET engine into catastrophic backtracking on crafted or unusual input. Build a [regex] object with an explicit match timeout instead of a bare string pattern: [regex]::new($pattern, 'None', [timespan]::FromSeconds(2)).

Final Thoughts

Regex in PowerShell is not a separate skill bolted onto the shell — it is the same .NET regex engine reached through four different doors: a Boolean test, a substitution, a splitter, and a line-oriented search tool. Once the syntax is familiar, the choice of which door to use comes down to what you need back: true/false, replaced text, an array of pieces, or matching lines from a file.

Most of the friction reported against PowerShell regex is not the regex itself — it is PowerShell’s own quoting rules colliding with regex’s use of $ and its case-insensitive defaults. Once those two habits are internalised, regex becomes one of the most reused tools in a PowerShell script.

Those same quoting rules are worth knowing well beyond regex — see PowerShell String Formatting — Here-Strings, -f, and Interpolation Gotchas for the full set of interpolation and here-string pitfalls.

Key takeaway: Single-quote your patterns and substitution strings, check the Boolean result before trusting $Matches, and reach for -cmatch/-creplace/-csplit the moment case actually matters.
Next in this series

Next, we can cover PowerShell string formatting: here-strings, the -f operator, and the interpolation gotchas that trip up scripts moving between single- and double-quoted strings.