PowerShell comparison operators look simple. -eq means equal, -ne means not equal, -gt means greater than, and so on.
The surprises start when the value on the left-hand side is a collection, when $null is involved, or when PowerShell has to decide what type the comparison should use.
These behaviours can produce perfectly valid PowerShell that returns something quite different from what an experienced administrator expected.
The important part is that the operator itself isn’t always the thing determining the result. The type and shape of the value on the left can change how the comparison behaves.
The comparison operators
The common comparison operators are:
| Operator | Purpose |
|---|---|
-eq | Equal to |
-ne | Not equal to |
-gt | Greater than |
-ge | Greater than or equal to |
-lt | Less than |
-le | Less than or equal to |
-like | Wildcard comparison |
-notlike | Negative wildcard comparison |
-match | Regular expression comparison |
-notmatch | Negative regular expression comparison |
-contains | Tests whether a collection contains a value |
-notcontains | Tests whether a collection does not contain a value |
-in | Tests whether a value exists in a collection |
-notin | Tests whether a value does not exist in a collection |
-is | Tests an object’s type |
-isnot | Tests that an object is not a particular type |
By default, string comparisons are case-insensitive. The c variants, such as -ceq, -clike and -cmatch, make them case-sensitive. Microsoft documents the full operator behaviour in about_Comparison_Operators.
The first thing to understand: the left-hand side matters
Consider:
"Server01" -eq "Server01"
That’s straightforward:
True
Now put an array on the left:
"Server01", "Server02", "Server03" -eq "Server02"
The result isn’t $true or $false.
It is:
Server02
That’s because when the left-hand side is a collection, PowerShell filters the collection and returns the elements that match.
For example:
$servers = "Server01", "Server02", "Server03"
$servers -eq "Server02"
returns:
Server02
And:
$servers -eq "Server99"
returns an empty array.
This matters when you write conditions around command output. A comparison that you expected to return a Boolean can instead return an object or an array.
Microsoft documents this behaviour explicitly: scalar input normally produces a Boolean result, while collection input returns the matching elements.
-eq doesn’t always mean “give me a Boolean”
This is one of the easiest behaviours to overlook.
For example:
$servers = @(
"Server01"
"Server02"
"Server03"
)
$result = $servers -eq "Server02"
$result.GetType().Name
The result is an array type rather than a Boolean.
This can matter when the result is passed to another part of a script, particularly when you assume the comparison itself has produced $true or $false.
If you specifically need to know whether something exists in a collection, containment operators are often clearer:
$servers -contains "Server02"
This returns:
True
-ne against a collection is worse than -eq
The filtering behaviour is easy enough to reason about with -eq. With -ne it produces a condition that is almost always true, which makes it a genuine source of bugs.
$servers = "Server01", "Server02", "Server03"
if ($servers -ne "Server02") {
"Server02 is not in the list"
}
That block runs.
Not because Server02 is missing, but because -ne returned the two elements that aren’t Server02, and a non-empty result is truthy.
The condition is effectively asking “does this collection contain anything other than Server02?”, which is a different question from the one the script appears to be asking.
The correct test is:
if ($servers -notcontains "Server02") {
"Server02 is not in the list"
}
or:
if ("Server02" -notin $servers) {
"Server02 is not in the list"
}
This one is worth watching for in code review, because the intent reads correctly and the behaviour doesn’t.
-contains is not a substring test
This one catches people who instinctively read -contains as “contains this text”.
It doesn’t work that way.
"SERVER01" -contains "SERVER"
returns:
False
PowerShell treats the scalar string on the left as a single-element collection. It then asks whether that collection contains an element equal to "SERVER".
It doesn’t, because the only element is "SERVER01".
But:
"SERVER01" -contains "SERVER01"
returns:
True
So -contains is about element membership, not whether one string contains another string.
For substring or pattern matching, use an appropriate string operator instead:
"SERVER01" -like "*SERVER*"
or:
"SERVER01" -match "SERVER"
The distinction becomes particularly useful when working with arrays of computer names, groups, ports, roles or other administrative data.
-contains and -in are opposites in how you read them
These two operators perform the same basic membership test from opposite directions.
$servers = "SERVER01", "SERVER02", "SERVER03"
$servers -contains "SERVER02"
returns:
True
You can write the same test the other way around:
"SERVER02" -in $servers
also returns:
True
The difference is mostly about which expression reads naturally in the script.
if ($servers -contains $computerName) {
# The collection contains this computer
}
versus:
if ($computerName -in $servers) {
# This computer is in the collection
}
Both are useful. Pick the form that makes the condition easiest to read.
-like uses wildcards, not regular expressions
-like uses wildcard patterns.
The two important wildcard characters are:
*matches zero or more characters?matches exactly one character
For example:
"SERVER01" -like "SERVER*"
returns:
True
The ? wildcard is more precise:
"SERVER01" -like "SERVER??" # True - two ? match "01"
"SERVER01" -like "SERVER?" # False - one ? can't match two characters
That distinction is easy to lose when writing patterns quickly.
-like versus -match
These two operators are often confused because both can be used for pattern matching.
-like uses wildcards:
"SERVER01" -like "SERVER*"
-match uses regular expressions:
"SERVER01" -match "^SERVER\d+$"
They can sometimes produce the same result, but they aren’t interchangeable.
Watch for characters that mean something in one syntax and not the other. A full stop is a literal character to -like and “any character” to -match:
"SERVER01" -like "SERVER.1" # False - looks for a literal full stop
"SERVER01" -match "SERVER.1" # True - . matches the 0
Use -like when a simple wildcard is enough. Use -match when you actually need regular-expression behaviour.
$Matches is not populated for collection input
-match has another behaviour worth knowing.
With scalar input:
"User: jsmith" -match "User: (?<User>\w+)"
returns True and populates $Matches.
You can then access:
$Matches.User
which contains:
jsmith
But $Matches is not populated when the left-hand side is a collection.
$users = @(
"User: jsmith"
"User: abrown"
)
$users -match "User: (?<User>\w+)"
returns the matching elements of the collection, but doesn’t populate $Matches with the captures from those matches.
That distinction is important if a script changes from processing one string to processing an array of strings.
$null belongs on the left
One of the most useful PowerShell comparison habits is putting $null on the left when testing for it.
Prefer:
if ($null -eq $value) {
# Value is null
}
rather than:
if ($value -eq $null) {
# Value is null
}
Here’s why that matters, with an actual result rather than a warning.
If $value is an empty array:
$value = @()
$value -eq $null
returns nothing. An empty result, which evaluates as false.
So this block never runs:
$value = @()
if ($value -eq $null) {
"Value is null" # never reached
}
The comparison filtered an empty collection and found no $null elements, so it returned an empty array. It never asked the question the script appeared to be asking.
Now consider an array that does contain a null:
$value = @("Server01", $null, "Server03")
if ($value -eq $null) {
"Value is null" # this DOES run
}
That block runs, because the comparison returned the $null element and a non-empty result is truthy. The variable itself is very much not null.
Putting $null on the left removes the collection behaviour entirely:
$null -eq $value
Now $null is the scalar on the left, so PowerShell performs a straightforward comparison and returns a Boolean in both cases.
This is one of those PowerShell conventions that looks like pedantry until a script starts handling command output that can contain zero, one or many objects.
Empty arrays and $null aren’t the same thing
PowerShell also distinguishes between no value and an empty collection.
$value = $null
and:
$value = @()
are not the same object.
A command that returns:
- no objects
- one object
- several objects
produces three different situations for the script to handle. In PowerShell 3.0 and later, a cmdlet returning nothing generally assigns $null rather than an empty array, but a variable you’ve built yourself can easily be either.
This is why defensive scripts need to be deliberate about whether they are testing for $null, testing membership, or checking .Count.
An array containing 0 is not the same as an empty array
PowerShell’s treatment of collections in Boolean contexts can also catch people out.
if (@()) {
"True"
}
doesn’t enter the block, because the array is empty.
But this is also worth knowing:
if (@(0)) {
"True"
}
doesn’t enter the block either.
A single-element array is evaluated on the Boolean value of that element, and 0 converts to $false.
Compare that with:
if (@(0, 0)) {
"True"
}
which is true, because a collection with more than one element is treated as non-empty regardless of what it contains.
And:
if (@(1)) {
"True"
}
is also true.
If your intention is to test the number of elements, do that explicitly:
if ($servers.Count -gt 0) {
"Servers found"
}
The left-hand type can change the comparison
This is another behaviour that can produce subtle bugs.
PowerShell uses the type of the left-hand operand to determine how the right-hand operand is converted for the comparison.
5 -eq "5"
returns True. So does:
"5" -eq 5
Equality isn’t where this bites. Ordering is.
"10" -lt "9" # True - string comparison, "1" sorts before "9"
10 -lt 9 # False - numeric comparison
This is a genuine source of bugs when values arrive as strings from CSV files, registry data, text-based configuration or web requests.
If a value represents a number, don’t assume PowerShell is treating it as one just because it contains digits.
Check the type:
$value.GetType().FullName
or convert it explicitly:
[int]$value
Sorting has the same problem:
"10", "9", "100" | Sort-Object
returns:
10
100
9
which is correct for strings and wrong for what you probably wanted.
-is tests the type, not the value
If you’re trying to determine what kind of object you have, -is is more appropriate than -eq.
$value -is [string]
asks whether $value is a string.
You can also test for other types:
$value -is [int]
$value -is [array]
$value -is [datetime]
This is fundamentally different from:
$value -eq "string"
The latter compares a value. The former tests its type.
Case sensitivity is explicit
PowerShell string comparisons are case-insensitive by default.
"server01" -eq "SERVER01"
returns:
True
Use the c form when case matters:
"server01" -ceq "SERVER01"
returns:
False
The same pattern applies to other comparison operators:
"SERVER01" -clike "server*"
"SERVER01" -cmatch "^server"
Worth knowing when you’re comparing values that come from case-sensitive systems: Linux paths, some LDAP attribute values, base64 strings, or anything sourced from an API.
A practical example with command output
Consider a script that gets a list of services:
$services = Get-Service
You could filter it with:
$services -eq "Spooler"
But that isn’t asking whether a service object’s name property equals "Spooler".
You’re comparing ServiceController objects with a string.
For object collections, a property-based filter is usually clearer:
$services | Where-Object Name -eq "Spooler"
Or:
$services | Where-Object {
$_.Name -eq "Spooler"
}
The comparison operator is still doing the work. The difference is that you’re applying it to the property you actually want to compare.
This matters particularly with:
Get-ADUser
Get-Process
Get-Service
Get-WinEvent
Get-CimInstance
Those return objects, not lines of text.
When a comparison surprises you, check the left
When troubleshooting an unexpected result, don’t just look at the operator.
$result = $something -eq "Enabled"
Before assuming $result is Boolean:
$result.GetType().FullName
If $something was a collection, $result may itself be a collection of matching elements, and that changes what happens when it’s passed into another expression.
A few patterns worth keeping
Scalar equality:
$value -eq "Enabled"
Case-sensitive equality:
$value -ceq "Enabled"
Collection membership:
$values -contains $value
Or the reverse:
$value -in $values
Negative membership, which is the one people get wrong:
$values -notcontains $value
Wildcard:
$value -like "SERVER*"
Regular expression:
$value -match "^SERVER\d+$"
Null check:
$null -eq $value
Type check:
$value -is [string]
Numeric comparison where the source is a string:
[int]$value -lt 10
Count check:
$values.Count -gt 0
The behaviour to remember
The comparison operator is only part of the expression.
When a comparison produces an unexpected result, check:
- What is on the left? A scalar, array or collection?
- What type is it? String, integer, object or something else?
- Are you comparing values or testing membership?
- Do you need
-likeor-match? - Are you deliberately testing
$null, and is it on the left? - Does case sensitivity matter?
- Could command output contain zero, one or many objects?
Most PowerShell comparison problems become easier to explain once you stop thinking of -eq, -ne and -match as simple Boolean operators.
The left-hand operand, its type and whether it is a collection can fundamentally change what PowerShell does with the expression.
References
- about_Comparison_Operators
- about_Booleans
- about_Automatic_Variables – covers
$Matches

From my early days on the helpdesk through roles as a service desk manager, systems administrator, and network engineer, I’ve spent more than 25 years in the IT world. As I transition into cyber security, my goal is to make tech a little less confusing by sharing what I’ve learned and helping others wherever I can.

