PowerShell 7 Pipeline Chain Operators: && and || Explained

PowerShell 7 added && and || — the powershell 7 pipeline chain operators borrowed from POSIX shells. They let you write conditional command chains without wrapping everything in try/catch blocks. But they behave differently from Bash equivalents, especially around non-terminating errors, and they do not exist at all in Windows PowerShell 5.1. This guide explains exactly when to use them, how they interact with $?, and where they fall short.
Syntax and Basic Usage of && and ||
&& runs the right-hand command only if the left-hand command succeeded. || runs the right-hand command only if the left-hand command failed. Chaining them is the PowerShell equivalent of shell one-liners.
# Run the second command only if the first succeeds
New-Item -ItemType Directory -Path 'C:\Deploy\Output' -Force &&
Copy-Item -Path 'C:\Build\app.zip' -Destination 'C:\Deploy\Output\'
# Run the fallback only if the primary fails
Get-Process -Name 'myapp' -ErrorAction SilentlyContinue ||
Start-Process -FilePath 'C:\Apps\myapp.exe'
# Chain multiple commands
Install-Module Pester -Force &&
Import-Module Pester &&
Write-Host "Pester ready."
These operators require PowerShell 7.0 or later. Attempting to use them in Windows PowerShell 5.1 throws a parse error — they are not valid syntax in that runtime.
How $? (Last Exit Code) Drives Chain Decisions
$? is a Boolean that reflects whether the most recent command completed without an error. The chain operators evaluate $? after each command to decide whether to proceed. Understanding when PowerShell sets $? to $false is the key to using these operators reliably.
# $? is $true after a successful cmdlet
Get-Date
Write-Host "`$? after Get-Date: $?" # True
# $? is $false after a native executable returns non-zero exit code
cmd.exe /c "exit 1"
Write-Host "`$? after cmd exit 1: $?" # False
Write-Host "LASTEXITCODE: $LASTEXITCODE" # 1
# Chain operator respects $?
cmd.exe /c "exit 0" && Write-Host "Success path" # prints
cmd.exe /c "exit 1" && Write-Host "This won't run" # skipped
cmd.exe /c "exit 1" || Write-Host "Failure path" # prints
For native executables, $? directly mirrors the exit code. For PowerShell cmdlets, the picture is more nuanced — see the limitations section.
Differences from Bash: Native Commands vs Cmdlets
In Bash, && and || work uniformly on all command types. In PowerShell, the operators behave differently depending on whether the left-hand command is a native executable or a cmdlet.
# Native executables — $? maps to exit code, chain works as expected
git pull && Write-Host "Pull succeeded"
git checkout -b feature/test || Write-Host "Branch may already exist"
# PowerShell cmdlets — $? is $true even after non-terminating errors
Get-Item 'C:\DoesNotExist' -ErrorAction SilentlyContinue || Write-Host "Not found"
# Output: the || branch does NOT run because Get-Item set $? = $true
# even though it wrote an error record to the error stream
# To make || trigger on a cmdlet error, use -ErrorAction Stop + try/catch instead
# OR force the cmdlet to write an exit code — not practical in most cases
This is the most important difference from Bash. Cmdlets that emit non-terminating errors still set $? to $true when -ErrorAction SilentlyContinue suppresses the error display.
Combining Chain Operators with Pipeline Segments
Chain operators bind more loosely than the pipeline | operator. A pipeline segment on either side runs as a unit before the chain evaluates.
# The entire pipeline segment runs as the left-hand side
Get-Service | Where-Object Status -eq 'Stopped' | Export-Csv 'C:\stopped.csv' -NoTypeInformation &&
Write-Host "Stopped services exported."
# Parentheses clarify grouping when mixing operators
(Get-Content 'C:\config.json' -Raw | ConvertFrom-Json) &&
Write-Host "Config parsed successfully."
# Use parentheses when the pipeline produces output you need on both sides
$result = (robocopy C:\Source C:\Dest /MIR) && { Write-Host "Robocopy succeeded." }
Real-World Pattern: Install-Module && Import-Module
The most practical use of chain operators in admin scripts is conditional module setup — install if not present, then import, then run.
# Pattern: ensure a module is available before using it
function Ensure-Module {
param([string]$Name)
if (-not (Get-Module $Name -ListAvailable)) {
Write-Host "Installing $Name..."
Install-Module $Name -Scope CurrentUser -Force -ErrorAction Stop
}
}
# Using chain operators for readable conditional flow
Ensure-Module 'Az.Accounts' &&
Import-Module Az.Accounts &&
Connect-AzAccount &&
Write-Host "Azure session ready." ||
Write-Warning "Azure setup failed — check prerequisites."
Note: the final || here catches a failure at any point in the chain because each operator evaluates the cumulative $? state from the previous step.
Limitations: Non-Terminating Errors Do Not Trigger ||
This bears repeating because it catches almost everyone coming from Bash. PowerShell distinguishes terminating errors (which stop execution) from non-terminating errors (which write to the error stream but continue). Chain operators only respond to $?, and $? stays $true after most cmdlet non-terminating errors.
# TRAP: non-terminating error, || does NOT fire
Get-Item 'C:\Missing' || Write-Host "This line does NOT run"
# FIX option 1: use -ErrorAction Stop to promote to terminating
# (then you need try/catch, making chain operators moot)
# FIX option 2: test $? explicitly
Get-Item 'C:\Missing' -ErrorAction SilentlyContinue
if (-not $?) { Write-Host "Get-Item failed — handling error" }
# FIX option 3: use a wrapper that sets $? via exit code
function Invoke-SafeGet { param($Path); if (-not (Test-Path $Path)) { exit 1 } Get-Item $Path }
Invoke-SafeGet 'C:\Missing' || Write-Host "Caught via wrapper"
Common Errors and Fixes
Non-terminating cmdlet errors do not trigger the || branch. As shown above, only terminating errors set $? to $false for cmdlets. For reliable error branching on cmdlets, use try { ... } catch { ... } with -ErrorAction Stop. Reserve && / || for native executables and pipelines that produce meaningful exit codes.
Chain operators are unavailable in Windows PowerShell 5.1. The operators cause a parse error in 5.1: “Unexpected token ‘&&'”. If your script must run on both 5.1 and 7+, gate the chain operator code behind if ($PSVersionTable.PSVersion.Major -ge 7) or rewrite the logic using if ($?) { ... } which works on all versions.
Related Cmdlets / See Also
- PowerShell Ternary Operator Explained
- PowerShell Error Handling Best Practices
- PowerShell Pipeline Explained
Wrapping Up
&& and || are genuinely useful for chaining native executables and keeping one-liner admin commands readable. Understand their limitation — non-terminating cmdlet errors are invisible to them — and you’ll know exactly when to reach for them versus a proper try/catch block. Use them liberally with git, dotnet, robocopy, and other native tools; use try/catch for cmdlet-heavy workflows.


