Operators
Summary: in this tutorial, you will learn learn powershell arithmetic, comparison, and logical operators — the building blocks for expressions and conditions.
Operators
Operators are the fundamental building blocks for manipulating data and controlling program flow in PowerShell. Understanding operators is crucial because they're how you compare values, perform calculations, test conditions, and transform data in your scripts.
What Makes PowerShell Operators Different
If you've used other programming languages or shells, PowerShell's operator syntax might surprise you. Most languages use symbols like ==, !=, <, and > for comparisons. PowerShell takes a different approach — it uses dash-letter operators like -eq (equal), -ne (not equal), -lt (less than), and -gt (greater than).
Why this design choice? Because > and < are already reserved for redirection (sending output to files), a feature inherited from Unix shells. Rather than creating ambiguous syntax, PowerShell's designers chose explicit, readable operators. When you see -eq, there's no question what it means: "equals". This verbosity makes scripts more self-documenting.
Another crucial difference: PowerShell operators are type-aware. The same operator can behave differently depending on what types of data you're working with. The + operator adds numbers, but it concatenates strings. The -eq operator compares values but can also filter arrays. This polymorphic behavior is powerful but requires understanding how types interact with operators.
Arithmetic Operators: More Than Just Math
Arithmetic operators perform calculations, but in PowerShell they do much more than work with numbers.
Basic Arithmetic Operations
The standard arithmetic operators work as you'd expect with numbers:
5 + 3 # 8 (Addition)
10 - 4 # 6 (Subtraction)
6 * 7 # 42 (Multiplication)
20 / 3 # 6.666... (Division)
20 % 3 # 2 (Modulus / Remainder)
The modulus operator % is particularly useful for determining if a number is even or odd, or for implementing cyclic behavior (like wrapping array indices).
Type-Aware Arithmetic
Where PowerShell gets interesting is how these operators adapt to different types:
# String concatenation
"Hello" + " World" # "Hello World"
# String repetition
"Ha" * 3 # "HaHaHa"
# Array concatenation
@(1,2) + @(3,4) # @(1,2,3,4)
When you use + with strings, PowerShell concatenates them. When you use * with a string and a number, it repeats the string. With arrays, + combines them. This behavior makes sense once you internalize that PowerShell operators consider the type of the left operand to determine their behavior.
This type-awareness can occasionally lead to surprises:
"5" + 3 # "53" — string concatenation, not 8
5 + "3" # 8 — numeric addition (string converted to number)
The left operand's type determines the operation. If you start with a string, you get string concatenation. If you start with a number, PowerShell attempts to convert the right side to a number.
Increment and Decrement Operators
For variables, PowerShell supports increment (++) and decrement (--) operators:
$i = 0
$i++ # Post-increment: returns $i, then increments
++$i # Pre-increment: increments, then returns $i
$i-- # Post-decrement
--$i # Pre-decrement
The difference between pre and post variants matters when you use them in expressions:
$i = 5
$x = $i++ # $x is 5, $i becomes 6
$y = ++$i # $i becomes 7, $y is 7
Compound Assignment Operators
For convenience, PowerShell supports compound assignment operators that combine arithmetic with assignment:
$i = 10
$i += 5 # Same as $i = $i + 5 → $i is now 15
$i -= 3 # $i is now 12
$i *= 2 # $i is now 24
$i /= 4 # $i is now 6
$i %= 4 # $i is now 2
These are particularly useful in loops and accumulation operations:
$total = 0
1..100 | ForEach-Object { $total += $_ } # Sum of 1 to 100
Comparison Operators: Testing Relationships
Comparison operators test relationships between values. PowerShell's comparison operators are case-insensitive by default, but you can make them case-sensitive by prefixing with c or explicitly case-insensitive with i.
Equality Operators
Testing equality is one of the most common operations in programming:
5 -eq 5 # True
5 -ne 3 # True (not equal)
"hello" -eq "HELLO" # True (case-insensitive by default!)
"hello" -ceq "HELLO" # False (case-sensitive)
"hello" -ieq "HELLO" # True (explicitly case-insensitive)
The default case-insensitivity is designed for Windows environments where file names and system identifiers are typically case-insensitive. However, when comparing user input or working with case-sensitive systems (like Linux), you'll want -ceq.
Why does this matter? Imagine you're checking if a user entered "yes" to proceed:
if ($response -eq "yes") {
# Matches "yes", "Yes", "YES", "yEs", etc.
Start-Process $importantOperation
}
This flexibility reduces bugs from unexpected capitalization.
Relational Operators
These operators test ordering relationships:
10 -gt 5 # True (greater than)
5 -lt 10 # True (less than)
5 -ge 5 # True (greater than or equal)
5 -le 5 # True (less than or equal)
These work with numbers as expected, but also with strings using alphabetical comparison:
"banana" -gt "apple" # True (b comes after a)
"b" -lt "c" # True
This string comparison uses lexicographical ordering (dictionary order), which is useful for sorting file names or other text data.
Pattern Matching with -like and -match
PowerShell provides two powerful pattern matching operators with different capabilities.
Wildcard Matching with -like
The -like operator uses simple wildcards:
*matches zero or more characters?matches exactly one character[abc]matches any single character in the brackets
"PowerShell" -like "Power*" # True
"PowerShell" -like "*Shell" # True
"PowerShell" -like "P??erShell" # True (? matches 'o' and 'w')
"PowerShell" -notlike "Bash*" # True
Why use -like instead of -eq? Because it lets you match patterns without knowing the exact value:
$fileName = "report-2025-01-15.xlsx"
if ($fileName -like "report-*.xlsx") {
# Matches any report file with date in the name
Import-Excel $fileName
}
Regular Expression Matching with -match
For more complex patterns, -match uses regular expressions:
"PowerShell 7.4" -match "\d+\.\d+" # True — matches version number
$Matches[0] # "7.4"
"user@example.com" -match "^[\w.]+@[\w.]+$" # True — email pattern
"hello123" -notmatch "^\d+$" # True (not all digits)
When -match succeeds, PowerShell automatically populates the $Matches variable with captured groups. This is incredibly useful for extracting data from formatted text:
$logLine = "2025-01-15 14:32:45 ERROR Database connection failed"
if ($logLine -match "(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (\w+) (.+)") {
$date = $Matches[1] # "2025-01-15"
$time = $Matches[2] # "14:32:45"
$level = $Matches[3] # "ERROR"
$message = $Matches[4] # "Database connection failed"
}
When to use -like vs -match:
- Use
-likefor simple filename patterns, quick checks with wildcards - Use
-matchwhen you need to extract data, validate complex formats, or work with structured patterns
Containment Operators: Working with Collections
When you need to test if a value exists in a collection, containment operators provide an efficient way to do it:
# -contains: Does the array contain this value?
@(1,2,3) -contains 2 # True
@("apple","banana") -contains "cherry" # False
@(1,2,3) -notcontains 5 # True
# -in: Is this value in the array? (reversed order)
2 -in @(1,2,3) # True
"cherry" -notin @("apple","banana") # True
The difference between -contains and -in is just the order of operands. They're provided for readability — sometimes "if array contains value" reads better, sometimes "if value in array" is clearer.
Critical distinction: -contains vs -eq on arrays
When you use -eq with an array on the left side, it behaves as a filter and returns matching elements:
@(1,2,3,2,1) -eq 2 # Returns @(2, 2) — filters matching elements
@(1,2,3,2,1) -contains 2 # Returns True — boolean membership test
This filtering behavior of -eq is incredibly useful for extracting data:
$errors = Get-EventLog Application -Newest 100
$criticalErrors = $errors | Where-Object { $_.EntryType -eq 'Error' }
But for simple membership testing, use -contains or -in for clarity and efficiency.
Type Checking with -is and -isnot
PowerShell provides operators specifically for type checking, which is essential when writing robust scripts that handle various input types:
42 -is [int] # True
"hello" -is [string] # True
$null -is [string] # False
(Get-Date) -is [datetime] # True
42 -isnot [string] # True
Why is type checking important? Because it prevents runtime errors from type mismatches:
function Calculate-Area {
param([object]$width, [object]$height)
if ($width -isnot [double] -and $width -isnot [int]) {
throw "Width must be a number"
}
if ($height -isnot [double] -and $height -isnot [int]) {
throw "Height must be a number"
}
return $width * $height
}
You can also check for more general types:
@() -is [array] # True
@{} -is [hashtable] # True
(Get-Process)[0] -is [System.Diagnostics.Process] # True
Replacement with -replace
The -replace operator performs regular expression-based substitution:
"Hello World" -replace "World", "PowerShell" # "Hello PowerShell"
# Regex captures enable complex transformations
"2025-06-15" -replace "(\d{4})-(\d{2})-(\d{2})", '$3/$2/$1' # "15/06/2025"
# Multiple replacements in one pass
"abc123def456" -replace "\d+", "#" # "abc#def#"
# Case-sensitive replacement
"Hello hello HELLO" -creplace "hello", "hi" # "Hello hi HELLO"
The power of -replace comes from its regex support. You can use capture groups (parentheses) in the pattern and reference them in the replacement string with $1, $2, etc.
Common use cases:
- Sanitizing file names:
$filename -replace '[<>:"/\\|?*]', '_' - Extracting and reformatting data: Converting dates, phone numbers, URLs
- Cleaning text: Removing HTML tags, normalizing whitespace
Split and Join: String Decomposition and Reconstruction
The -split and -join operators are complementary — one breaks strings into arrays, the other combines arrays into strings.
Splitting Strings
# Basic split by delimiter
"one,two,three" -split "," # @("one", "two", "three")
# Split by regex (removes multiple spaces)
"one two three" -split "\s+" # @("one", "two", "three")
# Split by either / or \
"path/to/file" -split "[/\\]" # @("path", "to", "file")
# Limit number of splits
"a,b,c,d,e" -split ",", 3 # @("a", "b", "c,d,e")
The limit parameter is useful when you want to split on first occurrence only:
$emailHeader = "Subject: Meeting tomorrow at 3pm"
$parts = $emailHeader -split ": ", 2
$fieldName = $parts[0] # "Subject"
$fieldValue = $parts[1] # "Meeting tomorrow at 3pm"
Joining Arrays
# Join with specified separator
@("one","two","three") -join ", " # "one, two, three"
@(1,2,3) -join "-" # "1-2-3"
# Unary join (no separator)
-join @("P","o","w","e","r") # "Power"
Common pattern — splitting input and joining output:
# Convert CSV to TSV
$csvLine = "Name,Age,City"
$fields = $csvLine -split ","
$tsvLine = $fields -join "`t" # "Name Age City"
Logical Operators: Combining Conditions
Logical operators combine boolean conditions. They're essential for building complex decision logic.
Basic Logical Operations
# -and: Both must be true
$true -and $true # True
$true -and $false # False
# -or: At least one must be true
$true -or $false # True
$false -or $false # False
# -xor: Exactly one must be true (exclusive or)
$true -xor $false # True
$true -xor $true # False
# -not / !: Negation
-not $true # False
!$false # True
These operators let you express complex conditions clearly:
$age = 25
$hasLicense = $true
$hasInsurance = $true
if ($age -ge 18 -and $hasLicense -and $hasInsurance) {
"Eligible to rent a car"
}
$isWeekend = (Get-Date).DayOfWeek -in 'Saturday','Sunday'
if (-not $isWeekend) {
"Workday — time to get productive"
}
Short-Circuit Evaluation
PowerShell implements short-circuit evaluation for logical operators, which means:
-andstops evaluating if the left side is$false-orstops evaluating if the left side is$true
This behavior has two important implications:
1. Performance optimization:
# Expensive operations only run when needed
if ($quickCheck -and $expensiveCheck) {
# $expensiveCheck only runs if $quickCheck is true
}
2. Safe null checking:
if ($user -and $user.IsActive) {
# Won't error even if $user is $null
# because -and short-circuits when $user is falsy
}
You can observe short-circuit evaluation directly:
$false -and (Start-Process notepad) # notepad does NOT open
$true -or (Start-Process notepad) # notepad does NOT open
In neither case does notepad open because the right side is never evaluated.
Null-Handling Operators (PowerShell 7+)
PowerShell 7 introduced two operators specifically for handling null values elegantly: null-coalescing and null-coalescing assignment.
Null-Coalescing Operator (??)
The ?? operator returns the left operand if it's not null, otherwise returns the right operand:
$name = $null
$displayName = $name ?? "Anonymous" # "Anonymous"
$name = "Alice"
$displayName = $name ?? "Anonymous" # "Alice"
Why is this useful? Configuration defaults:
# Provide defaults for environment variables
$port = $env:PORT ?? 3000
$host = $env:HOST ?? "localhost"
$logLevel = $env:LOG_LEVEL ?? "Info"
"Starting server on ${host}:${port} with logging level: $logLevel"
Before PowerShell 7, you'd have to write:
$port = if ($env:PORT) { $env:PORT } else { 3000 }
The ?? operator makes this pattern concise and readable.
Null-Coalescing Assignment (??=)
The ??= operator assigns a value to a variable only if the variable is currently null:
$config = $null
$config ??= @{ Theme = "Dark"; Language = "en-US" }
# $config is now the hashtable
$config ??= @{ Theme = "Light" }
# $config unchanged — it's already not null
This is perfect for lazy initialization:
function Get-AppConfig {
# Only load config file if not already loaded
$script:config ??= Get-Content config.json | ConvertFrom-Json
return $script:config
}
The first call loads the config; subsequent calls reuse the cached value.
Written by the ShellRAG Team
The ShellRAG editorial team writes practical, beginner-friendly PowerShell tutorials with tested code examples and real-world use cases. Every article is technically reviewed for accuracy and updated regularly.
Learn more about us →