You wanted only the .log files. You wrote Where-Object Name -match 'log', and back came catalog.txt, backup.log.zip, and error-log.txt too. No error, just too many results.
The bug is mixing up two operators that look alike. -like uses wildcards and has to match the whole string. -match uses regular expressions and is happy to match any part of the string. Once that clicks, most "my filter matches too much" problems go away.
Everything below was run in PowerShell 7.4 (7.4.6), and the output shown is the real output. The same rules apply in Windows PowerShell 5.1.
The shortest honest comparison
-like |
-match |
|
|---|---|---|
| Pattern type | Wildcard (*, ?, [ ]) |
Regular expression (regex) |
| How much must match | The whole string | Any part of the string |
. means |
A literal dot | Any single character |
| Anchors needed | No | Yes, ^ and $ if you want start/end |
Sets $Matches
|
No | Yes (for a single string) |
| Case-sensitive version | -clike |
-cmatch |
| Negated version | -notlike |
-notmatch |
The trap in three lines
'catalog.txt' -like 'log'
'catalog.txt' -like '*log*'
'catalog.txt' -match 'log'
False
True
True
-like 'log' asks "is the whole string exactly log?" No. To get a "contains" check with -like, you need stars on both sides. -match 'log' asks "does log appear anywhere?" Yes, inside catalog.
Why -match '.log' matches almost everything
Many people translate *.log into regex by just dropping the star. Here is what happens:
$names = 'app.log', 'catalog.txt', 'backup.log.zip', 'error-log.txt', 'applog'
$names -like '*.log'
'---'
$names -match '.log'
app.log
---
app.log
catalog.txt
backup.log.zip
error-log.txt
applog
-like '*.log' gave exactly one file. -match '.log' matched all five, because in regex:
-
.is not a dot. It means "any one character", soalogincatalog.txtandploginapplogboth count. - There is no anchor, so the match can sit anywhere, like the middle of
backup.log.zip.
The regex that means the same thing as *.log escapes the dot and anchors the end:
$names = 'app.log', 'catalog.txt', 'backup.log.zip', 'error-log.txt', 'applog'
$names -match '\.log$'
app.log
\. is a literal dot, and $ means "end of string". If you also want "must start with", add ^ at the front, for example '^app\.log$'.
Wildcards cheat sheet for -like
Wildcards only have three special characters:
-
*is zero or more characters -
?is exactly one character -
[ ]is one character from a set or range, like[abc]or[0-5]
'report1.csv' -like 'report?.csv'
'report10.csv' -like 'report?.csv'
'report7.csv' -like 'report[0-5].csv'
'server-b' -like 'server-[abc]'
True
False
False
True
report10.csv fails because ? is exactly one character, and 10 is two. report7.csv fails because 7 isn't in [0-5].
-match gives you $Matches for free
When -match runs against a single string and finds something, it fills the $Matches automatic variable. Index 0 is the whole match, and each pair of parentheses becomes a numbered group:
'srv-web-01' -match 'web-(\d+)'
$Matches
'---'
$Matches[1]
True
Name Value
---- -----
1 01
0 web-01
---
01
Named groups are easier to read in scripts:
'user=alice; role=admin' -match 'user=(?<user>\w+).*role=(?<role>\w+)'
"$($Matches.user) is $($Matches.role)"
True
alice is admin
-like never touches $Matches. It only tells you True or False.
On arrays, both operators filter (and $Matches lies)
Put an array on the left and both operators stop returning True/False. They return the items that match. That's handy, but there's a catch: -match on an array does not update $Matches.
$null = 'warm-up' -match 'warm' # sets $Matches
$servers = 'srv-web-01', 'srv-db-02', 'srv-web-03'
$servers -match 'web'
'---'
$Matches[0] # still the old value
srv-web-01
srv-web-03
---
warm
The filtering worked, but $Matches[0] is still warm from the earlier single-string match. If you need capture groups for each item, loop over the items and run -match on one string at a time, or use Select-String.
Case-insensitive by default
Both operators ignore case unless you ask for the c version:
'ERROR.LOG' -like '*.log'
'ERROR.LOG' -clike '*.log'
'ERROR: disk full' -match 'error'
'ERROR: disk full' -cmatch 'error'
True
False
True
False
There are also -ilike and -imatch, which spell out the default case-insensitive behavior.
-notlike and -notmatch
The negated versions follow the same rules, so the same anchoring advice applies:
$names = 'app.log', 'catalog.txt', 'backup.log.zip'
$names -notlike '*.log'
'---'
$names -notmatch '\.log$'
catalog.txt
backup.log.zip
---
catalog.txt
backup.log.zip
Without the anchor, -notmatch 'log' would throw away catalog.txt too, which is the same trap in reverse.
Escaping user input: [WildcardPattern]::Escape and [regex]::Escape
If the pattern comes from a variable (a file name, a search box, a CSV column), special characters will bite you. Square brackets are wildcards for -like. Dots, dollar signs, and parentheses are regex for -match. Escape the text first:
'file[1].txt' -like 'file[1].txt'
'file[1].txt' -like ([WildcardPattern]::Escape('file[1].txt'))
[WildcardPattern]::Escape('file[1].txt')
'---'
'price: $5.00 (USD)' -match '$5.00 (USD)'
'price: $5.00 (USD)' -match [regex]::Escape('$5.00 (USD)')
[regex]::Escape('$5.00 (USD)')
False
True
file`[1`].txt
---
False
True
\$5\.00\ \(USD\)
file[1].txt treats [1] as "one character from the set 1", so it doesn't match the literal brackets. '$5.00 (USD)' as a regex means "end of string, then 5, any character, 00, space, a group containing USD", which can never match. After escaping, both behave like plain text.
Real use: filtering Get-ChildItem with Where-Object
Here is the original bug and two correct fixes. The demo creates its own temp folder and cleans up after itself:
$dir = Join-Path ([IO.Path]::GetTempPath()) 'like-vs-match-demo'
New-Item -ItemType Directory -Path $dir -Force | Out-Null
'app.log', 'catalog.txt', 'backup.log.zip', 'error-log.txt' |
ForEach-Object { New-Item -Path $dir -Name $_ -ItemType File -Force | Out-Null }
# Too much: regex 'log' matches anywhere in the name
Get-ChildItem $dir | Where-Object Name -match 'log' | Select-Object -ExpandProperty Name
'---'
# Exactly .log files: wildcard...
Get-ChildItem $dir | Where-Object Name -like '*.log' | Select-Object -ExpandProperty Name
# ...or anchored regex
Get-ChildItem $dir | Where-Object Name -match '\.log$' | Select-Object -ExpandProperty Name
Remove-Item $dir -Recurse -Force
app.log
backup.log.zip
catalog.txt
error-log.txt
---
app.log
app.log
Both fixes return only app.log. For simple file name filters, -like is usually the clearer choice. Reach for -match when you actually need regex features like \d+, alternatives (error|warn), or capture groups.
If you want to try these patterns without setting up anything, CMD Master's free interactive PowerShell course in the browser gives you a real terminal to experiment in.
Quick checklist
- Matching file names like
*.logorreport-2026-*.csv: use-like. - Need digits, alternatives, or pieces of the text: use
-matchand read$Matches. - Using
-matchfor a whole value: anchor it with^and$. - Remember the regex
.is any character. Write\.for a real dot. - Running
-matchon an array: you get filtered items, not a fresh$Matches. - Need case to matter:
-clike/-cmatch. - Pattern comes from a variable:
[WildcardPattern]::Escape()for-like,[regex]::Escape()for-match.
Practice in a real terminal
Wildcards and regex are much easier to learn by typing them than by reading tables. CMD Master is a free, interactive browser terminal for learning CMD, PowerShell, and Bash, with instant feedback on every command and nothing to install.
Sources: Microsoft Learn: about_Comparison_Operators, about_Wildcards, about_Regular_Expressions, about_Automatic_Variables ($Matches), Where-Object, WildcardPattern.Escape, Regex.Escape.
Quick quiz
Without running it: what does this line output in PowerShell 7.4?
'app.log' -match '*.log'
Is it True, False, or something else? Drop your answer (and why) in the comments.
Top comments (0)