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 -like for simple filename patterns, quick checks with wildcards
  • Use -match when 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:

  • -and stops evaluating if the left side is $false
  • -or stops 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.

Was this page helpful?
SR

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 →