The PowerShell reference for sysadmins and developers — cmdlets, pipeline tricks, scripting patterns, and real-world automation examples.
Quick reference
The 25 PowerShell patterns you use every week.
| Task | Command |
|---|---|
| List files | Get-ChildItem / ls / dir |
| Filter output | Where-Object { $_.Size -gt 1MB } |
| Select properties | Select-Object Name, Length |
| Sort | Sort-Object -Property LastWriteTime -Descending |
| Count items | (Get-ChildItem).Count |
| Get help | Get-Help Get-Process -Full |
| Run script | .\script.ps1 |
| Set execution policy | Set-ExecutionPolicy RemoteSigned -Scope CurrentUser |
| Get all processes | Get-Process |
| Kill process | Stop-Process -Name notepad |
| Get services | Get-Service |
| Start/stop service | Start-Service wuauserv |
| Get environment var | $env:PATH |
| Set env var | $env:MY_VAR = "value" |
| Read file | Get-Content file.txt |
| Write file | `"text" |
| Copy item | Copy-Item src.txt dst.txt |
| Move item | Move-Item old.txt new.txt |
| Remove item | Remove-Item file.txt |
| Test path | Test-Path C:\folder |
| Measure string length | "hello".Length |
| Format list | `Get-Process |
| Format table | `Get-Process |
| Export to CSV | `Get-Process |
| Measure execution time | Measure-Command { Get-ChildItem -Recurse } |
Variables and data types
# Variables — prefix $
$name = "Alice"
$count = 42
$price = 9.99
$active = $true
$nothing = $null
# Type casting
[int]$x = "123" # 123
[string]$s = 456 # "456"
[double]$d = "3.14" # 3.14
[bool]$b = 1 # $true
# Type check
$name.GetType().Name # String
$count -is [int] # True
# Arrays
$colors = @("red", "green", "blue")
$colors[0] # "red"
$colors[-1] # "blue" (last)
$colors.Count # 3
$colors += "yellow" # append
# Array slicing
$colors[1..2] # "green", "blue"
# Strongly typed array
[int[]]$nums = 1, 2, 3
# Hashtables (dictionaries)
$person = @{
Name = "Bob"
Age = 30
City = "Berlin"
}
$person.Name # "Bob"
$person["Age"] # 30
$person.Keys # Name, Age, City
$person.ContainsKey("City") # True
$person.Remove("City")
# Ordered hashtable
$ordered = [ordered]@{ a = 1; b = 2; c = 3 }
Strings
$s = "Hello, World!"
# Common string methods
$s.Length # 13
$s.ToUpper() # "HELLO, WORLD!"
$s.ToLower() # "hello, world!"
$s.Trim() # strip whitespace
$s.TrimStart("H") # "ello, World!"
$s.Replace("World", "PowerShell") # "Hello, PowerShell!"
$s.Split(", ") # @("Hello", "World!")
$s.StartsWith("Hello") # True
$s.EndsWith("!") # True
$s.Contains("World") # True
$s.IndexOf("W") # 7
$s.Substring(7, 5) # "World"
# String interpolation (double quotes expand variables)
$user = "Alice"
"Hello, $user!" # "Hello, Alice!"
"Count: $($arr.Count)" # expression in $()
# Multiline here-string
$text = @"
Line one
Line two
Line three
"@
# Single-quoted — no expansion
'Hello, $user' # "Hello, $user" (literal)
# String formatting
"Pi is {0:F2}" -f 3.14159 # "Pi is 3.14"
"{0,-10} {1,5}" -f "Name", "Score" # left/right align
# Join strings
$words = "one", "two", "three"
$words -join ", " # "one, two, three"
# Split
"a:b:c" -split ":" # @("a", "b", "c")
# Regex match
"hello123" -match "\d+" # True; $Matches[0] = "123"
"hello123" -replace "\d+", "XXX" # "helloXXX"
Control flow
# If / elseif / else
if ($x -gt 10) {
"big"
} elseif ($x -gt 5) {
"medium"
} else {
"small"
}
# Comparison operators
# -eq -ne -lt -le -gt -ge
# -like (wildcards: * ?)
# -match (regex)
# -contains (array contains value)
# -in (value in array)
# Case-insensitive by default; prefix i for explicit: -ieq, -ceq (case-sensitive)
$arr = 1, 2, 3
3 -in $arr # True
$arr -contains 2 # True
# Switch
switch ($day) {
"Mon" { "Start of week" }
"Fri" { "End of week" }
{ $_ -in "Sat","Sun" } { "Weekend" }
default { "Midweek" }
}
# Switch with regex
switch -Regex ($input) {
"^\d+$" { "Number" }
"^[a-z]+$" { "Lowercase word" }
}
# For loop
for ($i = 0; $i -lt 5; $i++) {
Write-Output $i
}
# ForEach-Object (pipeline)
1..5 | ForEach-Object { $_ * 2 }
# foreach statement (faster for collections)
foreach ($item in $collection) {
Write-Output $item
}
# While
$i = 0
while ($i -lt 5) {
$i++
}
# Do-while / do-until
do {
$input = Read-Host "Enter value"
} while ($input -ne "quit")
do {
$input = Read-Host "Enter value"
} until ($input -eq "quit")
# Break and continue
foreach ($n in 1..10) {
if ($n -eq 5) { break } # exit loop
if ($n % 2 -eq 0) { continue } # skip iteration
}
Functions
# Basic function
function Say-Hello {
param($Name)
"Hello, $Name!"
}
Say-Hello -Name "Alice"
# Advanced function with parameter attributes
function Get-Greeting {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Name,
[Parameter()]
[ValidateRange(1, 120)]
[int]$Age = 0,
[switch]$Formal
)
$greeting = if ($Formal) { "Good day" } else { "Hi" }
if ($Age -gt 0) {
"$greeting, $Name. You are $Age years old."
} else {
"$greeting, $Name!"
}
}
Get-Greeting -Name "Bob" -Age 30
Get-Greeting -Name "Dr. Smith" -Formal
# Return value (functions return everything written to pipeline)
function Add-Numbers {
param([int]$A, [int]$B)
return $A + $B # explicit return
}
$result = Add-Numbers -A 3 -B 7 # 10
# Function with pipeline input
function Double-Input {
process {
$_ * 2
}
}
1..5 | Double-Input # 2, 4, 6, 8, 10
# Splatting (pass hashtable as parameters)
$params = @{
Path = "C:\logs"
Recurse = $true
Filter = "*.log"
}
Get-ChildItem @params
Pipeline
The PowerShell pipeline passes objects, not text.
# Pipeline basics
Get-Process | Where-Object { $_.CPU -gt 10 } | Sort-Object CPU -Descending | Select-Object -First 5
# Aliases for common pipeline cmdlets
# Where-Object = where = ?
# ForEach-Object = foreach = %
# Select-Object = select
# Short form
Get-Process | ? CPU -gt 10 | sort CPU -Desc | select -First 5
# Calculated properties
Get-ChildItem | Select-Object Name, @{N="SizeMB"; E={ [math]::Round($_.Length / 1MB, 2) }}
# Group-Object
Get-Process | Group-Object Company | Sort-Object Count -Desc
# Measure-Object
Get-ChildItem -Recurse *.log | Measure-Object Length -Sum -Average -Maximum
# Tee-Object — branch pipeline
Get-Process | Tee-Object -FilePath procs.txt | Where-Object CPU -gt 5
# Out-* cmdlets
... | Out-File result.txt # save to file
... | Out-GridView # interactive grid (Windows)
... | Out-String # convert to string
... | Out-Null # discard output
File system
# Navigation
Set-Location C:\Users # cd
Push-Location C:\temp # cd + push current to stack
Pop-Location # cd back to pushed location
# Listing
Get-ChildItem # ls / dir
Get-ChildItem -Recurse # recursive
Get-ChildItem -Filter "*.ps1" # filter by name
Get-ChildItem -Recurse -Include "*.log" -Exclude "debug*"
# File info
$f = Get-Item myfile.txt
$f.Length # bytes
$f.LastWriteTime # DateTime
$f.Extension # ".txt"
$f.BaseName # "myfile"
# Read / write
Get-Content file.txt # read all lines (array)
Get-Content file.txt -Raw # read as single string
Set-Content file.txt "Hello" # overwrite
Add-Content file.txt "New line" # append
"Line" | Out-File file.txt -Append # append via pipeline
# Read large files efficiently
Get-Content bigfile.log | Where-Object { $_ -match "ERROR" }
# Create / copy / move / delete
New-Item -Path "folder" -ItemType Directory
New-Item -Path "file.txt" -ItemType File
Copy-Item source.txt dest.txt
Copy-Item folder -Destination backup -Recurse
Move-Item old.txt new.txt
Remove-Item file.txt
Remove-Item folder -Recurse -Force # delete folder tree
# Test existence
Test-Path "C:\file.txt" # True/False
Test-Path "C:\file.txt" -PathType Leaf # must be file
Test-Path "C:\folder" -PathType Container # must be directory
# Path manipulation
Split-Path "C:\folder\file.txt" -Leaf # "file.txt"
Split-Path "C:\folder\file.txt" -Parent # "C:\folder"
Join-Path "C:\folder" "sub" "file.txt" # "C:\folder\sub\file.txt"
[System.IO.Path]::GetExtension("file.txt") # ".txt"
# Temporary file/folder
$tmp = [System.IO.Path]::GetTempFileName()
$tmpDir = [System.IO.Path]::GetTempPath()
Working with CSV, JSON, XML
# CSV
$data = Import-Csv employees.csv
$data | Where-Object { $_.Department -eq "IT" } | Export-Csv it.csv -NoTypeInformation
# Create CSV from objects
$records = @(
[pscustomobject]@{ Name="Alice"; Score=95 }
[pscustomobject]@{ Name="Bob"; Score=87 }
)
$records | Export-Csv results.csv -NoTypeInformation
# JSON
$json = '{"name":"Alice","age":30}'
$obj = $json | ConvertFrom-Json
$obj.name # "Alice"
$hash = @{ Name = "Bob"; Age = 25 }
$hash | ConvertTo-Json # {"Name":"Bob","Age":25}
$hash | ConvertTo-Json -Depth 5 # deep objects
# Read/write JSON file
$config = Get-Content config.json | ConvertFrom-Json
$config | ConvertTo-Json | Set-Content config.json
# XML
[xml]$doc = Get-Content data.xml
$doc.root.item # navigate nodes
$doc.SelectNodes("//item[@id='1']") # XPath
Error handling
# Terminating vs non-terminating errors
# -ErrorAction: Continue (default), Stop, SilentlyContinue, Ignore
# Make all errors terminating globally
$ErrorActionPreference = "Stop"
# Per-command
Get-Item "missing.txt" -ErrorAction SilentlyContinue
# Try / Catch / Finally
try {
$result = 1 / 0
Get-Item "missing.txt" -ErrorAction Stop
}
catch [System.IO.FileNotFoundException] {
Write-Error "File not found: $_"
}
catch [System.DivideByZeroException] {
Write-Warning "Division by zero"
}
catch {
Write-Error "Unexpected error: $($_.Exception.Message)"
}
finally {
Write-Output "Always runs"
}
# $? — success of last command (True/False)
Get-Process notepad -ErrorAction SilentlyContinue
if (-not $?) { "Process not found" }
# $Error — array of recent errors
$Error[0] # most recent error
$Error[0].Exception.Message
# Trap (legacy; prefer try/catch)
trap {
Write-Error "Trapped: $_"
continue
}
Processes and services
# Processes
Get-Process # all processes
Get-Process -Name "chrome" # by name
Get-Process | Sort-Object WorkingSet -Desc | Select -First 10 # top 10 by RAM
Stop-Process -Name "notepad" # kill by name
Stop-Process -Id 1234 # kill by PID
Start-Process "notepad.exe"
Start-Process "cmd.exe" -ArgumentList "/c dir" -Wait -NoNewWindow
# Run and capture output
$out = & cmd.exe /c "ipconfig"
$out = Invoke-Expression "git status"
# Services
Get-Service # all services
Get-Service -Name "wuauserv" # Windows Update
Start-Service -Name "wuauserv"
Stop-Service -Name "wuauserv"
Restart-Service -Name "wuauserv"
Set-Service -Name "wuauserv" -StartupType Automatic
# Scheduled tasks
Get-ScheduledTask
Register-ScheduledTask -TaskName "MyTask" -Action (New-ScheduledTaskAction -Execute "powershell.exe" -Argument ".\script.ps1") -Trigger (New-ScheduledTaskTrigger -Daily -At "08:00")
Registry
# Navigate like a file system
Set-Location HKCU:\Software
Get-ChildItem HKLM:\SOFTWARE\Microsoft
# Read values
Get-ItemProperty -Path "HKCU:\Software\MyApp" -Name "Version"
(Get-ItemProperty "HKCU:\Software\MyApp").Version
# Write values
Set-ItemProperty -Path "HKCU:\Software\MyApp" -Name "Debug" -Value 1
New-Item -Path "HKCU:\Software\MyApp" -Force
New-ItemProperty -Path "HKCU:\Software\MyApp" -Name "Setting" -Value "on" -PropertyType String
# Delete
Remove-ItemProperty -Path "HKCU:\Software\MyApp" -Name "OldSetting"
Remove-Item -Path "HKCU:\Software\OldApp" -Recurse
Remoting and jobs
# Enable remoting (run as admin)
Enable-PSRemoting -Force
# Run command on remote machine
Invoke-Command -ComputerName server01 -ScriptBlock { Get-Process }
# Interactive session
Enter-PSSession -ComputerName server01
Exit-PSSession
# Multiple machines
Invoke-Command -ComputerName srv1, srv2, srv3 -ScriptBlock {
[pscustomobject]@{
Computer = $env:COMPUTERNAME
CPU = (Get-Process | Measure-Object CPU -Sum).Sum
}
}
# Background jobs
$job = Start-Job -ScriptBlock { Get-ChildItem C:\ -Recurse }
Get-Job # list jobs
Wait-Job -Id $job.Id # wait for completion
Receive-Job -Id $job.Id # get output
Remove-Job -Id $job.Id
# Parallel execution (PowerShell 7+)
1..10 | ForEach-Object -Parallel {
Start-Sleep -Seconds $_
"Done: $_"
} -ThrottleLimit 4
Modules and profiles
# Find modules
Find-Module -Name "PSReadLine"
Find-Module -Tag "Security"
# Install / import
Install-Module -Name "Az" -Scope CurrentUser
Import-Module Az
Get-Module # list imported
Get-Module -ListAvailable # all installed
# Profile — runs at startup
$PROFILE # path to profile script
Test-Path $PROFILE # check if exists
New-Item -Path $PROFILE -Force # create
notepad $PROFILE # edit
# Common profile customizations
Set-Alias ll Get-ChildItem
function prompt { "PS $($PWD.Path)> " }
$PSDefaultParameterValues['Out-File:Encoding'] = 'UTF8'
Useful one-liners
# Find large files
Get-ChildItem C:\ -Recurse -ErrorAction SilentlyContinue |
Where-Object { $_.Length -gt 100MB } |
Sort-Object Length -Desc |
Select-Object FullName, @{N="MB";E={[math]::Round($_.Length/1MB,1)}}
# Search file contents (like grep)
Select-String -Path "C:\logs\*.log" -Pattern "ERROR" -CaseSensitive
# Get disk usage by folder
Get-ChildItem C:\Users -Directory |
ForEach-Object { [pscustomobject]@{
Folder = $_.Name
SizeGB = [math]::Round((Get-ChildItem $_.FullName -Recurse -ErrorAction SilentlyContinue | Measure-Object Length -Sum).Sum / 1GB, 2)
}} | Sort-Object SizeGB -Desc
# List open ports
Get-NetTCPConnection -State Listen | Select LocalPort, @{N="Process";E={(Get-Process -Id $_.OwningProcess).Name}}
# Kill all instances of a process
Get-Process -Name "chrome" | Stop-Process -Force
# Bulk rename files
Get-ChildItem *.txt | Rename-Item -NewName { $_.Name -replace "old","new" }
# Download a file
Invoke-WebRequest "https://example.com/file.zip" -OutFile "file.zip"
# Get public IP
(Invoke-RestMethod "https://api.ipify.org?format=json").ip
# Check if admin
([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
# Elapsed time for a block
$sw = [System.Diagnostics.Stopwatch]::StartNew()
# ... do work ...
$sw.Elapsed.TotalSeconds
# Base64 encode/decode
[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("hello"))
[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String("aGVsbG8="))
Common mistakes
| Mistake | Wrong | Right |
|---|---|---|
| String comparison | $a == $b |
$a -eq $b |
| Null check | $x == $null |
$x -eq $null or $null -eq $x |
| Boolean values | $active = true |
$active = $true |
| Array empty check | if ($arr) |
if ($arr.Count -gt 0) |
| Last array element | $arr[$arr.Length-1] |
$arr[-1] |
| Suppress output | $x = Some-Cmdlet (might leak) |
$null = Some-Cmdlet |
| Force string conversion | [string]$obj (may be truncated) |
`$obj |
| Admin check | Running without elevation silently fails | Always check admin status for system changes |
PowerShell 5 vs PowerShell 7
| Feature | Windows PowerShell 5.1 | PowerShell 7+ |
|---|---|---|
| Cross-platform | Windows only | Windows, Linux, macOS |
| Parallel ForEach | No | ForEach-Object -Parallel |
| Null coalescing | No | $x ?? "default" |
| Null conditional | No | $obj?.Property |
| Ternary operator | No | $cond ? "yes" : "no" |
| Pipeline chain | No | cmd1 && cmd2 / cmd1 || cmd2 |
| Error view | Full | Concise (default) |
Select-String context |
Limited | -Context improved |
| .NET version | .NET Framework 4.x | .NET 8+ |
Get-Error |
No | Yes (detailed error info) |
Import-Excel etc. |
Same | Same (cross-platform modules) |
FAQ
Q: How do I run a PowerShell script?
Set execution policy first: Set-ExecutionPolicy RemoteSigned -Scope CurrentUser. Then run with .\script.ps1. The RemoteSigned policy allows local scripts and requires downloaded scripts to be signed.
Q: What's the difference between Write-Output and Write-Host?Write-Output sends objects to the pipeline — they can be captured, piped, or redirected. Write-Host writes directly to the console and bypasses the pipeline. Use Write-Output in scripts; use Write-Host only for console-only messages (progress, colour output) that should never be captured.
Q: How do I run PowerShell commands as administrator?
Right-click PowerShell and select "Run as administrator", or from code: Start-Process powershell -Verb RunAs -ArgumentList "-Command", "Your-Command". Check in script: ([Security.Principal.WindowsPrincipal]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator).
Q: How do I pass variables to Invoke-Command on a remote machine?
Use the $using: scope modifier: Invoke-Command -ComputerName srv1 -ScriptBlock { Get-Process $using:processName }. Alternatively, pass them via the -ArgumentList parameter and receive with param() inside the script block.
Q: PowerShell or Python for automation?
PowerShell is the right choice when you're managing Windows systems, Active Directory, Azure, or Microsoft 365 — it has deep native integration. Python is better for cross-platform tasks, data processing, or when you need a rich ecosystem of third-party libraries. Many sysadmins use both: PowerShell for Windows ops, Python for everything else.
Q: How do I find what cmdlet does something?Get-Command -Verb Get -Noun *Process* — search by verb/noun. Get-Help about_* — list conceptual help topics. Get-Command | Where-Object { $_.Definition -match "something" } — search by definition. The PowerShell Gallery and Find-Module are your friends for third-party modules.