Use PowerShell 7 (pwsh) for new work — it runs on macOS, Linux, and Windows, and ships
ForEach-Object -Parallel, ?? /
?. operators, ternary a ? b : c, and pipeline chains
(&& / ||).
Windows PowerShell 5.1 is the in-box Windows version — still supported, no new features.
Modules: PSReadLine (interactive UX), Pester (tests),
PSScriptAnalyzer (lint).
Install · profileSetup
powershell
# Install PowerShell 7 (cross-platform)
brew install --cask powershell # macOS
winget install --id Microsoft.PowerShell # Windows
sudo apt install -y powershell # Debian/Ubuntu (via packages.microsoft.com)
# Versions
pwsh -v # PowerShell 7.x (cross-platform "Core")
powershell -v # Windows PowerShell 5.1 (Windows only, legacy)
# Profile (per-host)
notepad $PROFILE # opens $HOME/.config/PowerShell/Microsoft.PowerShell_profile.ps1
. $PROFILE # reload without restarting
# Run a script
pwsh ./build.ps1
pwsh -File ./build.ps1 -ConfigPath ./prod.json
# Daily commands
Get-Help Get-ChildItem -Examples # docs + examples
Get-Command -Module Az # list cmdlets in a module
Update-Help # refresh help locally
Verb-Noun conventionCmdlets
Every command is a verb-noun pair. Approved verbs are stable; nouns are singular. Aliases (ls, cat, cd) exist for muscle memory — never use them in scripts.
Fan out GitHub API calls with ForEach-Object -Parallel, build typed objects, group + aggregate, persist to JSON.
Same shape as a SQL pipeline but every stage is a cmdlet.
Use Get-Member when stuck.
Pipe anything into Get-Member and you see every property and method available. It’s how you discover the shape of an opaque cmdlet’s output without reading docs.
Never use aliases in scripts.ls, %, ? are great interactively but obscure intent. PSScriptAnalyzer flags them — treat that as a hard error in CI.
foreach statement is faster than the cmdlet for in-memory data.ForEach-Object processes one item per pipeline step; foreach (..) iterates a materialized array. Reach for the statement when you already have the array.
Common trapsWatch out for
Single-result cmdlets unwrap arrays.$x = Get-ChildItem | Where-Object … can give you a single object OR an array. Wrap with @() when you expect a collection.
Format-* kills downstream cmdlets.Get-Process | Format-Table | Export-Csv exports formatting metadata, not the objects. Keep Format-* at the very end.
Comparison operators are case-insensitive by default.'A' -eq 'a' is true. Use the -c prefix (-ceq, -cmatch) when case matters — especially when comparing hashes or tokens.
PowerShell 7 is the open-source, cross-platform successor to Windows PowerShell 5.1. It runs on Windows, macOS, and Linux, ships with a modernised standard library, supports parallel pipelines with ForEach-Object -Parallel, and adds null-coalescing operators and pipeline chain operators. PowerShell 7 is the recommended version for all new scripting and automation work.
What are cmdlets in PowerShell?
Cmdlets (pronounced command-lets) are compiled .NET commands that follow a Verb-Noun naming convention such as Get-Process, Set-Item, or Remove-Item. Each cmdlet accepts named parameters and outputs .NET objects rather than plain text, making it easy to pipe structured data between commands. Use Get-Command -Verb Get to list all cmdlets with a given verb.
How does the PowerShell pipeline work?
The PowerShell pipeline passes .NET objects between cmdlets, not text strings. Get-Process | Where-Object CPU -gt 10 | Sort-Object CPU -Descending pipes process objects, filters by CPU usage, and sorts — all without any string parsing. Use Select-Object to pick properties and Export-Csv or ConvertTo-Json to serialise the final objects to a file or string.
How do hashtables work in PowerShell?
A hashtable is a key-value collection defined with @{key = value}. Access values with $ht['key'] or $ht.key. Hashtables are commonly used as named parameter splats (@params instead of individual -Param arguments) and to build custom objects with [PSCustomObject]@{Name='x'; Value=1}. Use [ordered]@{} to preserve insertion order in PowerShell 3+.
How do I handle errors in PowerShell?
Set $ErrorActionPreference = 'Stop' to make all errors terminating, then wrap code in try/catch/finally blocks. The catch block receives the exception in $_ or $PSItem; access the original error record via $_.Exception. Use -ErrorAction Stop on individual cmdlets to override the preference just for that call. Write-Error emits a non-terminating error; throw generates a terminating one.