cheat sheet
PowerShell Essentials
Variables, operators, pipelines, Where-Object, ForEach-Object, Select-Object, sorting, grouping, remoting, jobs, CIM, registry, JSON, error handling, and common cmdlets.
PowerShell Essentials
What it is
PowerShell is Microsoft's cross-platform task automation shell and scripting language, built on .NET and maintained by Microsoft as an open-source project. Unlike traditional Unix shells that treat everything as text, PowerShell pipelines pass structured .NET objects between commands, making it far easier to filter, sort, and transform data without parsing. PowerShell 7+ runs on Windows, macOS, and Linux; reach for it when you need to script Windows administration tasks, work with APIs that return structured data, or automate anything in the Microsoft ecosystem.
Versions in 2026
PowerShell ships in two parallel tracks: Windows PowerShell 5.1 (Desktop edition, frozen at the .NET Framework 4.x runtime, in-box on every Windows install) and PowerShell 7.x (Core edition, built on .NET, cross-platform, downloaded separately and launched as pwsh). PowerShell 7.6 LTS reached GA on 2026-03-18 and is the current LTS branch — it is built on .NET 10 LTS and supported through November 2026. Use 5.1 only when a script must run on a stock Windows machine without any installer; reach for 7.6 for everything else, particularly anything cross-platform or anything that calls modern .NET APIs.
$PSVersionTable | Format-List PSVersion, PSEdition, OS
Output:
PSVersion : 7.6.2
PSEdition : Core
OS : Microsoft Windows 10.0.26100
Variables & types
Variables are prefixed with $ and are dynamically typed by default — PowerShell infers the type from the assigned value. You can enforce a type with a [type] cast (e.g. [int]$port = 8080), which causes assignment errors if the value can't be coerced. Automatic variables like $_, $?, and $LASTEXITCODE are set by the runtime and give you context about the current pipeline item, last command status, and native exit codes.
$name = "Alice"
$count = 42
$pi = 3.14159
$flag = $true
$nothing = $null
# Typed variables
[int]$port = 8080
[string]$label = "prod"
[bool]$enabled = $false
[datetime]$now = Get-Date
[string[]]$hosts = "web1","web2","web3"
[int[]]$ports = 80,443,8080
# Environment variables
$env:PATH
$env:USERPROFILE
$env:COMPUTERNAME
$env:USERNAME
# Automatic variables
$_ # current pipeline object
$PSItem # alias for $_
$args # arguments to a script/function
$MyInvocation # info about current script
$PSScriptRoot # directory of current script
$LASTEXITCODE # exit code of last native command
$? # $true if last command succeeded
$Error # array of recent errors; $Error[0] is the most recent
$null # explicit null value
$true/$false # Boolean literals
Operators
PowerShell uses named operators (-eq, -like, -match) instead of symbols like == or ~=, which makes operator intent explicit and sidesteps ambiguity with assignment. String comparisons are case-insensitive by default; prefix c (e.g. -ceq, -cmatch) for case-sensitive variants. PowerShell 7 adds null-coalescing (??), null-assignment (??=), ternary, and pipeline chain operators (&&, ||).
# Comparison
-eq -ne -lt -gt -le -ge
# Case-insensitive string
-eq "hello" # default is case-insensitive
-ceq "hello" # case-sensitive
-ieq "hello" # explicit case-insensitive
# Pattern matching
-like "node*" # wildcard (* and ?)
-notlike "*.log"
-clike "Node*" # case-sensitive wildcard
-match "^\d+" # regex match; populates $Matches
-notmatch "error"
-cmatch "^Error" # case-sensitive regex
# Containment
-contains # array contains value: @(1,2,3) -contains 2
-notcontains
-in # value in array: 2 -in @(1,2,3)
-notin
# Type
-is [string] # type test
-isnot [int]
-as [int] # type coercion (returns $null on failure)
# Logical
-and -or -not !
-xor
# Arithmetic
+ - * / % # modulo
[Math]::Pow(2,10) # 1024
[Math]::Round(3.567, 2)
# String
-f # format operator: "{0:N2}" -f 3.14159 → "3.14"
+ # string concatenation
* # repeat: "ab" * 3 → "ababab"
# Assignment
= += -= *= /= %=
++ -- # increment/decrement
# Null coalescing (PowerShell 7+)
$x ?? "default" # return $x unless null
$x ??= "default" # assign if null
# Ternary (PowerShell 7+)
$x -gt 0 ? "pos" : "neg"
# Pipeline chain (PowerShell 7+)
cmd1 && cmd2 # run cmd2 only if cmd1 succeeds
cmd1 || cmd2 # run cmd2 only if cmd1 fails
Strings
Double-quoted strings interpolate $variables and $(expressions); single-quoted strings are literal with no interpolation — use them for SQL, regex patterns, or anything where you want $ treated as a dollar sign. Here-strings (@"…"@ and @'…'@) preserve newlines and are the idiomatic way to embed multiline text or heredoc-style content in scripts.
# Interpolation (double quotes)
"Hello, $name — today is $(Get-Date -Format 'yyyy-MM-dd')"
"Pi is {0:F4}" -f [Math]::PI # "Pi is 3.1416"
# Literal string (single quotes — no interpolation)
'SELECT * FROM users WHERE name = ''$name''' # escape ' by doubling
# Here-strings
$body = @"
Line 1
Line 2 with $name interpolated
"@
$raw = @'
No $interpolation here
'@
# Common methods
"hello world".ToUpper() # "HELLO WORLD"
"hello world".ToLower()
" trim me ".Trim() # "trim me"
" trim me ".TrimStart()
" trim me ".TrimEnd()
"a,b,c".Split(",") # @("a","b","c")
"a,b,c".Split(",", 2) # @("a","b,c") — max 2 parts
[string]::Join("|", @("x","y","z")) # "x|y|z"
"hello".Replace("l","r") # "herro"
"hello".Contains("ell") # $true
"hello".StartsWith("hel")
"hello".EndsWith("llo")
"hello".IndexOf("l") # 2
"hello".Substring(1, 3) # "ell"
"hello".PadLeft(10) # " hello"
"hello".PadLeft(10, '-') # "-----hello"
"hello".PadRight(10, '.') # "hello....."
" ".IsNullOrWhiteSpace(" ") # use static method
[string]::IsNullOrEmpty($var)
[string]::IsNullOrWhiteSpace($var)
# Regex
$text = "Order 12345 placed"
$text -match 'Order (\d+)' # $Matches[1] = "12345"
$text -replace '\d+', 'XXXXX' # "Order XXXXX placed"
[regex]::Matches($text, '\d+') | ForEach-Object { $_.Value }
Arrays
Arrays are created with @() or by assigning a comma-separated list; they are fixed-size .NET arrays under the hood, so += is slow for large collections because it allocates a new array each time. Use a [System.Collections.Generic.List[T]] when you need to append frequently. Elements are accessed by zero-based index, and the -contains operator tests membership without needing .IndexOf().
$arr = @("a", "b", "c")
$arr = 1..10 # range
$arr += "d" # append (creates new array)
$arr[0] # first element
$arr[-1] # last element
$arr[1..3] # slice — elements 1, 2, 3
$arr.Count # length
$arr.Length
$arr.Contains("b") # $true
$arr.IndexOf("b") # 1
$arr -contains "b" # $true (operator form)
# Generic List (mutable, faster for many appends)
$list = [System.Collections.Generic.List[string]]::new()
$list.Add("item1")
$list.Add("item2")
$list.Remove("item1")
$list.Count
# Strongly typed array
[int[]]$nums = 1,2,3,4,5
# Array of custom objects
$people = @(
[pscustomobject]@{ Name = "Alice"; Age = 30 }
[pscustomobject]@{ Name = "Bob"; Age = 25 }
)
$people | Sort-Object Age
Output:
Name Age
---- ---
Bob 25
Alice 30
Hash tables
Hash tables are created with @{ Key = Value } and are PowerShell's primary key-value store, used for everything from structured data to splatting parameters. Keys are case-insensitive by default; use [ordered]@{} when insertion order matters. .GetEnumerator() is the reliable way to iterate key-value pairs in a pipeline.
$hash = @{
Name = "Alice"
Port = 8080
Active = $true
}
$hash["Name"] # access by key
$hash.Name # dot notation
$hash.Keys # all keys
$hash.Values # all values
$hash.ContainsKey("Port") # $true
$hash.ContainsValue(8080)
$hash.Count
$hash["NewKey"] = "value" # add/update
$hash.Remove("NewKey") # delete
# Ordered hash table (preserves insertion order)
$ordered = [ordered]@{
First = 1
Second = 2
Third = 3
}
# Enumerate
$hash.GetEnumerator() | ForEach-Object {
"$($_.Key) = $($_.Value)"
}
# Merge two hash tables
$merged = $hash + @{ Extra = "field" }
Where-Object — filtering
Where-Object filters objects flowing through the pipeline by evaluating a condition against each one — the pipeline equivalent of a filter or grep, but operating on structured objects rather than text. Use the script-block form ({ $_.Property -op value }) for complex or compound conditions, and the cleaner comparison form (Where-Object Property -op value) for simple single-property tests. The alias is ?.
# Script block form (full power)
Get-Process | Where-Object { $_.CPU -gt 100 }
Get-Process | Where-Object { $_.Name -like "node*" -and $_.WorkingSet -gt 100MB }
# Comparison form (PowerShell 3+, clean for simple cases)
Get-Process | Where-Object CPU -gt 100
Get-Process | Where-Object Name -like "node*"
Get-Process | Where-Object Name -eq "svchost"
Get-Process | Where-Object Name -match "^sql"
Get-Process | Where-Object Name -in @("chrome","firefox","edge")
Get-Process | Where-Object Name -notlike "idle*"
# Alias: ? or where
Get-Process | ? { $_.CPU -gt 10 }
# Filter on calculated / nested property
Get-Process | Where-Object { $_.MainWindowTitle -ne "" }
Get-Service | Where-Object { $_.Status -eq "Running" }
Get-ChildItem | Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-7) }
# Null / not-null check
Get-Process | Where-Object { $null -ne $_.Company }
Get-Process | Where-Object Company # truthy — excludes empty/null
# Multiple conditions
Get-Process | Where-Object {
$_.CPU -gt 10 -and
$_.WorkingSet -gt 50MB -and
$_.Name -notmatch "^idle"
}
# Filter on string content
Get-Content log.txt | Where-Object { $_ -match "ERROR" }
Get-Content log.txt | Where-Object { $_ -notmatch "^#" -and $_.Trim() -ne "" }
# All comparison operators work in Where-Object
# -eq -ne -lt -gt -le -ge -like -notlike -match -notmatch
# -contains -notcontains -in -notin -is -isnot
Output (Get-Process | Where-Object CPU -gt 100):
NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
52 312.44 298.17 487.23 3812 1 chrome
38 201.06 188.40 215.66 5104 1 node
21 88.32 94.71 132.09 7720 1 Code
Output (Get-Service | Where-Object { $_.Status -eq "Running" }):
Status Name DisplayName
------ ---- -----------
Running AdobeARMservice Adobe Acrobat Update Service
Running AudioEndpointBu... Windows Audio Endpoint Builder
Running Audiosrv Windows Audio
Running BFE Base Filtering Engine
Running BITS Background Intelligent Transf...
…
ForEach-Object — iterating the pipeline
ForEach-Object runs a script block once per object in the pipeline, with $_ (or $PSItem) representing the current object — it is the pipeline equivalent of a map or transform step. It supports -Begin / -Process / -End blocks for setup and teardown, and PowerShell 7 adds -Parallel for concurrent execution with -ThrottleLimit. For in-memory collections where pipeline overhead matters, prefer the foreach statement or the .ForEach() method. The alias is %.
# Basic — process each item
1..5 | ForEach-Object { $_ * 2 }
# Alias: %
Get-Service | % { $_.DisplayName }
# Named parameter form (-Process)
Get-Service | ForEach-Object -Process { Start-Service $_.Name }
# Begin / Process / End blocks
Get-Process | ForEach-Object `
-Begin { $total = 0 } `
-Process { $total += $_.CPU } `
-End { "Total CPU: $total" }
# Access the current object
Get-ChildItem *.log | ForEach-Object {
$size = $_.Length / 1KB
"$($_.Name): $([Math]::Round($size,1)) KB"
}
# Parallel execution (PowerShell 7+ only)
1..10 | ForEach-Object -Parallel {
Start-Sleep -Seconds 1
"Processed $_"
} -ThrottleLimit 5 # max 5 concurrent threads
# Parallel with shared variable (using $using:)
$multiplier = 3
1..5 | ForEach-Object -Parallel {
$_ * $using:multiplier
}
# ForEach statement (not pipeline — faster for in-memory collections)
foreach ($item in $arr) {
Write-Host $item
}
# ForEach method on collection (fastest — no pipeline overhead)
@("a","b","c").ForEach({ $_.ToUpper() })
$arr.ForEach({ Write-Host $_ })
# Measure execution time
Measure-Command { 1..1000 | ForEach-Object { $_ * 2 } }
Output (1..5 | ForEach-Object { $_ * 2 }):
2
4
6
8
10
Output (Get-ChildItem *.log | ForEach-Object { … }):
app.log: 142.3 KB
error.log: 38.7 KB
access.log: 987.1 KB
Output (Measure-Command { 1..1000 | ForEach-Object { $_ * 2 } }):
Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 47
Ticks : 471832
TotalDays : 5.46111111111111E-07
TotalHours : 1.31066666666667E-05
TotalMinutes : 0.000786400000000001
TotalSeconds : 0.0471832
TotalMilliseconds : 47.1832
Select-Object — shaping output
Select-Object projects specific properties from pipeline objects, add calculated properties, and controls how many items pass through — it is PowerShell's equivalent of a SQL SELECT. Use -ExpandProperty to unwrap a single property's value from its containing object (returning strings instead of objects), and calculated property hashtables (@{ N="..."; E={...} }) to rename or derive new fields. Unlike Format-*, Select-Object output can continue downstream in the pipeline.
# Pick specific properties
Get-Process | Select-Object Name, Id, CPU, WorkingSet
# Rename and calculate — calculated properties
Get-Process | Select-Object Name, @{ Name="MemMB"; Expression={ [Math]::Round($_.WorkingSet/1MB,1) } }
Get-Process | Select-Object Name, @{ N="CPU%"; E={ "{0:N1}" -f $_.CPU } }
# First / Last n items
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10
Get-Process | Sort-Object CPU | Select-Object -Last 5
# Skip n items
Get-Process | Select-Object -Skip 5
# Skip until condition is met (PowerShell 6+)
Get-Process | Select-Object -SkipUntil { $_.CPU -gt 10 }
# Unique values
Get-Process | Select-Object -Unique Name
# Expand a single property to its value (unwrap from object)
Get-Process | Select-Object -ExpandProperty Name # returns strings, not objects
(Get-Service).Name # equivalent dot access
# Select all properties plus add new ones
Get-Process | Select-Object *, @{ N="MemGB"; E={ $_.WorkingSet/1GB } }
# Exclude properties
Get-Process | Select-Object -ExcludeProperty __*
# Wildcard in property names
Get-Process | Select-Object *Memory*
# Nested property
Get-Process | Select-Object Name, @{ N="Module"; E={ $_.Modules[0].ModuleName } }
# Index
Get-Process | Select-Object -Index 0, 2, 4 # elements at these positions
Output (Get-Process | Select-Object Name, Id, CPU, WorkingSet):
Name Id CPU WorkingSet
---- -- --- ----------
ApplicationFrameHost 5680 0 8740864
chrome 3812 487.23 313049600
Code 7720 132.09 99151872
conhost 9104 0.45 7897088
explorer 4236 12.01 79544320
…
Output (Get-Process | Select-Object Name, @{ Name="MemMB"; Expression={ [Math]::Round($_.WorkingSet/1MB,1) } }):
Name MemMB
---- -----
ApplicationFrameHost 8.3
chrome 298.5
Code 94.6
conhost 7.5
explorer 75.9
…
Sort-Object
Sort-Object sorts pipeline objects by one or more property values, ascending by default; add -Descending to reverse. You can sort on a calculated expression (a script block), mix sort directions per key using hashtable form, and deduplicate with -Unique. Unlike bash sort, it operates on .NET object properties directly — no field-splitting required.
# Ascending (default)
Get-Process | Sort-Object CPU
# Descending
Get-Process | Sort-Object CPU -Descending
# Multiple sort keys
Get-Process | Sort-Object Name, CPU -Descending
# Case-sensitive sort
Get-ChildItem | Sort-Object Name -CaseSensitive
# Unique values during sort
Get-Process | Sort-Object Name -Unique
# Sort on calculated expression
Get-ChildItem | Sort-Object { $_.LastWriteTime }
Get-Process | Sort-Object { $_.WorkingSet / $_.CPU }
# Descending on one key, ascending on another
Get-Process | Sort-Object @{ E="Name"; Asc=$true }, @{ E="CPU"; Desc=$true }
# Sort strings as numbers
"10","9","20","1" | Sort-Object { [int]$_ }
Output (Get-Process | Sort-Object CPU -Descending | Select-Object -First 5):
NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
52 312.44 298.17 487.23 3812 1 chrome
38 201.06 188.40 215.66 5104 1 node
21 88.32 94.71 132.09 7720 1 Code
14 45.11 48.22 67.44 2948 1 powershell
9 22.08 19.88 31.12 6372 1 SearchIndexer
Output ("10","9","20","1" | Sort-Object { [int]$_ }):
1
9
10
20
Group-Object
Group-Object collects pipeline objects into groups by a shared property value, returning objects with Name (the group key), Count, and Group (the matching objects). It is the pipeline equivalent of GROUP BY in SQL. Use -NoElement to get just counts without retaining the grouped objects, saving memory on large collections.
# Group processes by name
Get-Process | Group-Object Name
# Output: Name (group key), Count, Group (the objects)
Get-Process | Group-Object Name | Sort-Object Count -Descending
# Group files by extension
Get-ChildItem | Group-Object Extension
# Group with No-Element (just counts, saves memory)
Get-Process | Group-Object Name -NoElement | Sort-Object Count -Descending
# Access groups programmatically
$groups = Get-Process | Group-Object Name
foreach ($g in $groups) {
"$($g.Name): $($g.Count) instance(s)"
}
# Group then select from each group
Get-Process | Group-Object Name | ForEach-Object {
$_.Group | Sort-Object CPU -Descending | Select-Object -First 1
}
# Group on calculated property
Get-ChildItem | Group-Object { $_.LastWriteTime.Date }
Get-Process | Group-Object { [Math]::Round($_.CPU / 10) * 10 } # bucket by 10s
Output (Get-Process | Group-Object Name -NoElement | Sort-Object Count -Descending | Select-Object -First 6):
Count Name
----- ----
18 svchost
8 chrome
4 RuntimeBroker
3 conhost
2 powershell
1 explorer
Output (Get-ChildItem | Group-Object Extension):
Count Name Group
----- ---- -----
4 .log {app.log, error.log, access.log, debug.log}
3 .txt {readme.txt, notes.txt, todo.txt}
2 .ps1 {deploy.ps1, backup.ps1}
1 .json {config.json}
1 .csv {report.csv}
Measure-Object
Measure-Object aggregates numeric pipeline values — count, sum, average, minimum, and maximum — against a named property. With text input it can also count lines, words, and characters. It is the pipeline equivalent of wc combined with basic stats, and composes naturally after Where-Object or Select-Object to measure a filtered or projected set.
# Count
Get-Process | Measure-Object
# Sum, average, min, max
Get-Process | Measure-Object CPU -Sum -Average -Minimum -Maximum
# File sizes
Get-ChildItem C:\Windows -Recurse -File | Measure-Object Length -Sum -Average
# Word/line/character count on text
Get-Content log.txt | Measure-Object -Word -Line -Character
# Sum on calculated property (pipe through Select-Object first)
Get-Process | Select-Object @{ N="MemMB"; E={ $_.WorkingSet/1MB } } |
Measure-Object MemMB -Sum -Average
Output (Get-Process | Measure-Object):
Count : 87
Average :
Sum :
Maximum :
Minimum :
Property :
Output (Get-Process | Measure-Object CPU -Sum -Average -Minimum -Maximum):
Count : 87
Average : 14.2731034482759
Sum : 1241.76
Maximum : 487.23
Minimum : 0
Property : CPU
Output (Get-Content log.txt | Measure-Object -Word -Line -Character):
Lines Words Characters Property
----- ----- ---------- --------
342 2891 18423
Compare-Object
Compare-Object diffs two collections (the reference set and the difference set) and annotates each item with a SideIndicator: <= means the item exists only in the reference, => only in the difference set. Use -IncludeEqual to also emit matching items. It is useful for auditing configuration drift, comparing file lists across machines, or finding what changed between two snapshots.
# Compare two sets of objects
$ref = Get-Content reference.txt
$diff = Get-Content current.txt
Compare-Object $ref $diff
# Output: InputObject, SideIndicator (<= in ref only, => in diff only)
Compare-Object $ref $diff -IncludeEqual # also show matching lines
# Compare processes on two machines
$local = Get-Process | Select-Object -ExpandProperty Name | Sort-Object -Unique
$remote = Invoke-Command server01 { Get-Process | Select-Object -Expand Name | Sort-Object -Unique }
Compare-Object $local $remote
# Suppress equal — show only differences (default behavior)
Compare-Object $ref $diff | Where-Object SideIndicator -eq "=>"
Output (Compare-Object $ref $diff):
InputObject SideIndicator
----------- -------------
new config line =>
old removed line <=
Tee-Object — split the pipeline
Tee-Object is a T-junction in the pipeline: it forwards every object to the next stage unchanged while simultaneously capturing them into a variable or writing them to a file. This lets you inspect intermediate pipeline data without breaking the chain — the PowerShell equivalent of Unix tee.
# Send to variable AND continue pipeline
Get-Process | Tee-Object -Variable procs | Measure-Object
# Send to file AND continue pipeline
Get-Process | Tee-Object -FilePath C:\procs.txt | Select-Object Name, CPU
# Append to file
Get-Process | Tee-Object -FilePath C:\procs.txt -Append | Measure-Object
Output (Get-Process | Tee-Object -Variable procs | Measure-Object):
Count : 87
Average :
Sum :
Maximum :
Minimum :
Property :
Format-* cmdlets — controlling display
Format-Table, Format-List, and Format-Wide control how objects are rendered in the terminal — they convert objects into formatting instructions that the output engine turns into text. They must always be the last step in a pipeline; piping their output to other cmdlets (like Export-Csv or Select-Object) discards the actual data. Use Select-Object instead when you need to shape data for downstream processing.
# Table
Get-Process | Format-Table Name, CPU, WorkingSet
Get-Process | Format-Table -AutoSize # auto-fit column widths
Get-Process | Format-Table -Wrap # wrap long values
Get-Process | Format-Table Name, @{ N="MemMB"; E={ [Math]::Round($_.WorkingSet/1MB,1) }; Width=10 }
Get-Process | Format-Table -GroupBy Name
# List
Get-Process | Format-List * # show all properties
Get-Service wuauserv | Format-List *
Get-Process | Format-List Name, CPU, @{ N="MemMB"; E={ $_.WorkingSet/1MB } }
# Wide (one-column display)
Get-Process | Format-Wide Name -Column 4 # 4 columns of names
# Custom
Get-Process | Format-Custom -Depth 2
# IMPORTANT: Format-* is for display only — never pipe Format-* output to other cmdlets
# Use Select-Object for downstream processing instead
Output (Get-Process | Format-Table Name, CPU, WorkingSet -AutoSize | Select-Object -First 6):
Name CPU WorkingSet
---- --- ----------
ApplicationFrameHost 0 8740864
chrome 487.23 313049600
Code 132.09 99151872
conhost 0.45 7897088
explorer 12.01 79544320
…
Output (Get-Service wuauserv | Format-List *):
Name : wuauserv
RequiredServices : {rpcss}
CanPauseAndContinue : False
CanShutdown : True
CanStop : True
DisplayName : Windows Update
DependentServices : {}
MachineName : .
ServiceName : wuauserv
ServicesDependedOn : {rpcss}
ServiceHandle :
Status : Running
ServiceType : Win32ShareProcess
StartType : Manual
Site :
Container :
Output (Get-Process | Format-Wide Name -Column 4 | Select-Object -First 3):
ApplicationFrameHost chrome Code
conhost explorer MsMpEng
notepad powershell SearchIndexer
…
Out-* cmdlets
Out-* cmdlets control where formatted output is sent: the console, a file, a string buffer, a grid UI, or a printer. Unlike Format-*, some Out-* cmdlets (notably Out-String) produce usable string output for logging or further processing. Out-Null is the idiomatic way to discard pipeline output without assigning to a variable.
Out-Default # default output (implied at end of pipeline)
Out-Host # write to console (bypass variable capture)
Out-Null # discard output
Out-String # convert objects to string (useful for logs)
Out-File C:\out.txt # write to file
Out-File C:\out.txt -Append
Out-File C:\out.txt -Encoding UTF8
Out-GridView # interactive grid (Windows PowerShell / PowerShell with GUI)
Out-GridView -PassThru # select rows interactively, return selected objects
Out-Printer # send to default printer
# Redirect streams
Get-Process 2>&1 | Out-File all.log # capture both stdout and stderr
Get-Member — discover object structure
Get-Member inspects the .NET type of objects flowing through the pipeline and lists their properties, methods, and script members. It is the primary tool for discovering what you can do with an unfamiliar object — run it after any cmdlet to see all available properties before writing a Select-Object or Where-Object expression. Pipe a type literal (e.g. [Math] | Get-Member -Static) to explore static members.
# Show all properties and methods of objects in the pipeline
Get-Process | Get-Member
Get-Process | Get-Member -MemberType Property # properties only
Get-Process | Get-Member -MemberType Method # methods only
Get-Process | Get-Member -MemberType ScriptProperty
# Find what type an object is
(Get-Process)[0].GetType().FullName
(Get-Date).GetType()
# Static members of a type
[Math] | Get-Member -Static
[string] | Get-Member -Static
[DateTime]::Now
[Math]::Sqrt(144)
Output (Get-Process | Get-Member -MemberType Property | Select-Object -First 8):
TypeName: System.Diagnostics.Process
Name MemberType Definition
---- ---------- ----------
BasePriority Property int BasePriority {get;}
Container Property System.ComponentModel.IContainer Container {get;}
EnableRaisingEvents Property bool EnableRaisingEvents {get;set;}
ExitCode Property int ExitCode {get;}
ExitTime Property datetime ExitTime {get;}
Handle Property System.IntPtr Handle {get;}
HandleCount Property int HandleCount {get;}
HasExited Property bool HasExited {get;}
…
Output ((Get-Process)[0].GetType().FullName):
System.Diagnostics.Process
Output ([Math]::Sqrt(144)):
12
Splatting — cleaner parameter passing
Splatting lets you store a cmdlet's parameters in a hashtable (or array) and pass them all at once with @varName instead of listing them inline. This keeps long cmdlet calls readable and makes it easy to build parameters conditionally before invoking a command. The @ sigil (vs $) tells PowerShell to expand the collection as named arguments rather than pass the hashtable as a single value.
# Instead of long parameter lines:
$params = @{
Path = "C:\output.csv"
Delimiter = ","
NoTypeInformation = $true
Encoding = "UTF8"
}
Export-Csv @params
# Splatting with positional args (array splatting)
$args = @("server01", "-Port", 5985)
Test-NetConnection @args
# Combine splatting with additional parameters
$common = @{ ErrorAction = "Stop"; Verbose = $true }
Get-Service @common -Name "wuauserv"
Error handling
PowerShell distinguishes between terminating errors (caught by try/catch) and non-terminating errors (which write to the error stream but let the script continue). Most cmdlets emit non-terminating errors by default; add -ErrorAction Stop to promote them to terminating so try/catch can handle them. $ErrorActionPreference = "Stop" applies that behavior globally for a scope, and $Error[0] always holds the most recent error object.
# Terminating errors — caught by try/catch
try {
$content = Get-Content "C:\nonexistent.txt" -ErrorAction Stop
} catch [System.IO.FileNotFoundException] {
Write-Error "File not found: $_"
} catch [System.UnauthorizedAccessException] {
Write-Error "Access denied: $_"
} catch {
Write-Error "Unexpected: $($_.Exception.Message)"
} finally {
Write-Host "Always runs"
}
# Non-terminating — check $?
Get-Item "C:\maybe\exists.txt" -ErrorAction SilentlyContinue
if (-not $?) { Write-Warning "Not found" }
# Suppress errors entirely
Get-Item "C:\missing" -ErrorAction SilentlyContinue
# Treat all errors as terminating in this scope
$ErrorActionPreference = "Stop"
# Per-command error capture
Get-Item "C:\missing" -ErrorVariable myErr -ErrorAction SilentlyContinue
if ($myErr) { Write-Warning $myErr[0].Message }
# $Error automatic variable — array of all recent errors
$Error[0] # most recent error
$Error[0].InvocationInfo # where the error came from
$Error[0].Exception.Message # exception message
$Error.Clear() # clear the error log
# Check native command exit codes
git status
if ($LASTEXITCODE -ne 0) { throw "git failed with exit code $LASTEXITCODE" }
Functions and advanced parameters
Adding [CmdletBinding()] to a function turns it into an "advanced function" that behaves like a compiled cmdlet — it gains -Verbose, -WhatIf, -Confirm, and -ErrorAction support automatically. Parameters are declared in a param() block with [Parameter()] attributes that control whether they are mandatory, accept pipeline input, or have validation constraints ([ValidateSet()], [ValidateRange()], [ValidateScript()]). This is the idiomatic way to write reusable, production-grade PowerShell tools.
function Invoke-Deploy {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact='High')]
param(
[Parameter(Mandatory, Position=0)]
[string]$Environment,
[Parameter(Mandatory, ValueFromPipeline)]
[string[]]$Service,
[Parameter()]
[ValidateSet("blue","green","canary")]
[string]$Strategy = "blue",
[Parameter()]
[ValidateRange(1,100)]
[int]$Replicas = 2,
[Parameter()]
[ValidateScript({ Test-Path $_ })]
[string]$ConfigPath,
[switch]$Force
)
begin {
Write-Verbose "Starting deployment to $Environment"
}
process {
foreach ($svc in $Service) {
if ($PSCmdlet.ShouldProcess("$svc in $Environment", "Deploy")) {
Write-Verbose "Deploying $svc with strategy $Strategy"
[pscustomobject]@{
Service = $svc
Environment = $Environment
Strategy = $Strategy
Replicas = $Replicas
Timestamp = Get-Date
Status = "Deployed"
}
}
}
}
}
# Call it
"api","web" | Invoke-Deploy -Environment "prod" -Strategy "canary" -WhatIf
Output ("api","web" | Invoke-Deploy -Environment "prod" -Strategy "canary" -WhatIf):
What if: Performing the operation "Deploy" on target "api in prod".
What if: Performing the operation "Deploy" on target "web in prod".
Remoting — Invoke-Command and sessions
PowerShell Remoting runs script blocks on one or more remote machines over WinRM (HTTP/HTTPS), serializing .NET objects across the wire. Invoke-Command is stateless and one-shot; create a PSSession with New-PSSession when you need to issue multiple commands against the same machine without reconnecting each time. Pass local variables into remote script blocks with $using:varName.
# Run a command on a remote machine (one-off)
Invoke-Command -ComputerName server01 -ScriptBlock { Get-Service | Where-Object Status -eq Running }
# Run on multiple machines in parallel
Invoke-Command -ComputerName server01,server02,server03 -ScriptBlock { hostname }
# Pass variables with $using:
$serviceName = "wuauserv"
Invoke-Command -ComputerName server01 -ScriptBlock { Get-Service $using:serviceName }
# Reusable persistent session (faster for multiple commands)
$session = New-PSSession -ComputerName server01
Invoke-Command -Session $session -ScriptBlock { Get-Process }
Invoke-Command -Session $session -ScriptBlock { Stop-Process -Name "notepad" -Force }
Remove-PSSession $session
# Interactive remote shell
Enter-PSSession -ComputerName server01
# ... run commands ...
Exit-PSSession
# Run a script file remotely
Invoke-Command -ComputerName server01 -FilePath C:\scripts\deploy.ps1
# Copy file to remote (using session)
$s = New-PSSession server01
Copy-Item C:\local\file.txt -Destination C:\remote\ -ToSession $s
Copy-Item C:\remote\file.txt -Destination C:\local\ -FromSession $s
Remove-PSSession $s
# Disconnect / reconnect sessions (PowerShell remoting over WinRM)
$s = New-PSSession server01 -Name "LongJob"
Invoke-Command -Session $s -ScriptBlock { Start-Sleep 60 } -AsJob
Disconnect-PSSession $s
# ... later ...
$s = Get-PSSession -ComputerName server01 | Connect-PSSession
Output (Invoke-Command -ComputerName server01,server02,server03 -ScriptBlock { hostname }):
server01
server02
server03
Jobs — background execution
Background jobs run script blocks in a child process, freeing the interactive session for other work. Use Start-Job / Get-Job / Receive-Job to manage them; output accumulates in a buffer until you call Receive-Job. In PowerShell 7, prefer Start-ThreadJob (from the ThreadJob module) for lighter-weight concurrency that runs in the same process without the serialization overhead of cross-process jobs.
# Start a background job
$job = Start-Job -ScriptBlock { Get-Process | Sort-Object CPU -Descending | Select-Object -First 10 }
# Check job status
Get-Job
Get-Job -Id $job.Id
# Wait for job to finish
Wait-Job $job
Wait-Job $job -Timeout 30 # timeout in seconds
# Retrieve results
Receive-Job $job # get output (removes from buffer)
Receive-Job $job -Keep # get output without removing
# Clean up
Remove-Job $job
# Wait for all jobs and get results
$jobs = 1..5 | ForEach-Object { Start-Job -ScriptBlock { Start-Sleep 2; $using:_ * 10 } }
$results = $jobs | Wait-Job | Receive-Job
$jobs | Remove-Job
# Thread jobs (PowerShell 7+ — faster, in-process)
$job = Start-ThreadJob -ScriptBlock { Get-Process }
Receive-Job $job -Wait -AutoRemoveJob
# Scheduled jobs (persistent, survives logoff)
Register-ScheduledJob -Name "NightlyReport" -ScriptBlock {
Get-Process | Export-Csv C:\reports\procs.csv -NoTypeInformation
} -Trigger (New-JobTrigger -Daily -At "02:00")
Output (Get-Job):
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
1 Job1 BackgroundJob Completed True localhost Get-Process | Sort-…
CIM / WMI
CIM (Common Information Model) cmdlets (Get-CimInstance) are the modern replacement for the older Get-WmiObject cmdlets — they use WS-Man (WinRM) instead of DCOM, which is firewall-friendlier and works reliably for remote queries. Use CIM classes like Win32_OperatingSystem, Win32_LogicalDisk, and Win32_Process to query hardware, OS, and process information without external tools.
# Get CIM instance (preferred — uses WS-Man; works remote without DCOM)
Get-CimInstance -ClassName Win32_OperatingSystem
Get-CimInstance -ClassName Win32_ComputerSystem
Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType = 3"
# Query on remote machine
Get-CimInstance -ClassName Win32_Process -ComputerName server01
# Create a CIM session for multiple queries
$cs = New-CimSession -ComputerName server01,server02
Get-CimInstance -ClassName Win32_Service -CimSession $cs -Filter "State = 'Running'"
Remove-CimSession $cs
# Common CIM classes
Get-CimInstance Win32_BIOS
Get-CimInstance Win32_Processor
Get-CimInstance Win32_PhysicalMemory | Measure-Object Capacity -Sum
Get-CimInstance Win32_NetworkAdapterConfiguration -Filter "IPEnabled = True"
Get-CimInstance Win32_UserAccount
Get-CimInstance Win32_Product | Sort-Object Name # installed software (slow)
Get-CimInstance MSCluster_Cluster -Namespace root/MSCluster # failover cluster
# WMI query (older — uses DCOM)
Get-WmiObject -Class Win32_Service -Filter "State='Running'" | Select-Object Name,State
Output (Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object Caption, Version, BuildNumber, OSArchitecture):
Caption Version BuildNumber OSArchitecture
------- ------- ----------- --------------
Microsoft Windows 11 Pro 10.0.22631 22631 64-bit
Output (Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType = 3" | Select-Object DeviceID, Size, FreeSpace):
DeviceID Size FreeSpace
-------- ---- ---------
C: 511604834304 178345771008
D: 999653638144 412088786944
Registry
PowerShell exposes the Windows Registry as a filesystem-like provider, so HKLM: and HKCU: are drives you can navigate with Set-Location, Get-ChildItem, Get-Item, and Test-Path. Get-ItemProperty reads value data; Set-ItemProperty writes it. This unified provider model means registry operations share the same cmdlet vocabulary as file system operations.
# Navigate registry like a filesystem
Set-Location HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion
Get-ChildItem HKLM:\SOFTWARE
# Read a value
Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ProductName
(Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").CurrentBuild
# Set a value
Set-ItemProperty -Path "HKCU:\Software\MyApp" -Name "Theme" -Value "Dark"
# Create a key
New-Item -Path "HKCU:\Software\MyApp" -Force
# Create a value
New-ItemProperty -Path "HKCU:\Software\MyApp" -Name "Version" -Value "1.0" -PropertyType String
# Delete a value
Remove-ItemProperty -Path "HKCU:\Software\MyApp" -Name "OldValue"
# Delete a key (recursive)
Remove-Item -Path "HKCU:\Software\MyApp" -Recurse
# Test if key exists
Test-Path "HKCU:\Software\MyApp"
Output (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ProductName):
ProductName : Windows 11 Pro
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT
PSChildName : CurrentVersion
PSDrive : HKLM
PSProvider : Microsoft.PowerShell.Core\Registry
Output ((Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").CurrentBuild):
22631
Output (Test-Path "HKCU:\Software\MyApp"):
True
File system operations
PowerShell's file system cmdlets (Get-ChildItem, Copy-Item, Move-Item, Remove-Item) work uniformly across the local filesystem, network shares, and other providers. Get-ChildItem replaces both ls and find — use -Filter for glob patterns and -Recurse to walk directory trees. Get-Content -Wait provides a live tail equivalent to tail -f.
# List files
Get-ChildItem C:\logs
Get-ChildItem C:\logs -Filter "*.log"
Get-ChildItem C:\logs -Recurse -Filter "*.log"
Get-ChildItem C:\logs -Recurse -File # files only
Get-ChildItem C:\logs -Recurse -Directory # directories only
Get-ChildItem C:\logs -Include "*.log","*.txt" -Recurse
Get-ChildItem C:\logs -Exclude "*.bak" -Recurse
Get-ChildItem C:\logs | Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-1) }
# Read / write
Get-Content C:\file.txt
Get-Content C:\file.txt -Raw # single string, not array of lines
Get-Content C:\file.txt -Tail 100 # last 100 lines
Get-Content C:\file.txt -Wait # live tail
Set-Content C:\file.txt "content"
Add-Content C:\file.txt "append this"
Clear-Content C:\file.txt
# Copy, move, rename, delete
Copy-Item C:\src\file.txt C:\dst\
Copy-Item C:\src\ C:\dst\ -Recurse
Move-Item C:\old.txt C:\new.txt
Rename-Item C:\old.txt "new.txt"
Remove-Item C:\file.txt
Remove-Item C:\folder -Recurse -Force
# Create directories
New-Item -ItemType Directory C:\newfolder
New-Item -ItemType Directory C:\a\b\c -Force # create parents as needed
[System.IO.Directory]::CreateDirectory("C:\a\b\c")
# Paths
Join-Path "C:\logs" "app.log"
Split-Path "C:\logs\app.log" # parent: "C:\logs"
Split-Path "C:\logs\app.log" -Leaf # filename: "app.log"
[System.IO.Path]::GetExtension("app.log") # ".log"
[System.IO.Path]::GetFileNameWithoutExtension("app.log") # "app"
Resolve-Path .\relative\path # absolute path
Test-Path C:\file.txt
(Get-Item C:\file.txt).Length / 1MB # file size in MB
# Hash a file
Get-FileHash C:\file.exe -Algorithm SHA256
Output (Get-ChildItem C:\logs -Filter "*.log"):
Directory: C:\logs
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 4/24/2026 10:15 AM 145832 app.log
-a--- 4/24/2026 9:02 AM 39680 error.log
-a--- 4/23/2026 11:47 PM 1010688 access.log
-a--- 4/22/2026 3:30 PM 12288 debug.log
Output (Get-Content C:\file.txt — sample 4-line file):
[2026-04-24 10:00:01] INFO Server started on port 8080
[2026-04-24 10:00:03] INFO Connected to database
[2026-04-24 10:01:22] WARN High memory usage detected
[2026-04-24 10:02:45] ERROR Connection reset by peer
Output (Get-FileHash C:\file.exe -Algorithm SHA256):
Algorithm Hash Path
--------- ---- ----
SHA256 3A7BD3E2360A3D29EEA436FCFB7E44C735D117C42D1C1835420B6B9942DD4F1B C:\file.exe
Output (Test-Path C:\file.txt):
True
Output (Join-Path "C:\logs" "app.log"):
C:\logs\app.log
JSON, CSV, and XML
PowerShell's ConvertTo-Json / ConvertFrom-Json and Export-Csv / Import-Csv round-trip structured objects to and from text formats without manual parsing. JSON depth defaults to 2 — increase it with -Depth 10 for nested objects. CSV import returns typed PSCustomObject rows you can immediately pipe to Where-Object or Sort-Object.
# JSON
$obj = [pscustomobject]@{ Name="Alice"; Age=42; Active=$true }
$json = $obj | ConvertTo-Json
$json | Out-File data.json
$back = Get-Content data.json -Raw | ConvertFrom-Json
$back.Name # "Alice"
# Nested / deep JSON
$obj | ConvertTo-Json -Depth 10 # default depth is 2
# Invoke REST API
$response = Invoke-RestMethod "https://api.example.com/data"
$response.items | Select-Object Name, Id
# CSV
$data = @(
[pscustomobject]@{ Name="Alice"; Score=95 }
[pscustomobject]@{ Name="Bob"; Score=87 }
)
$data | Export-Csv output.csv -NoTypeInformation -Encoding UTF8
$imported = Import-Csv output.csv
$imported | Where-Object Score -gt 90
# CSV with custom delimiter
$data | Export-Csv output.csv -Delimiter ";" -NoTypeInformation
Import-Csv output.csv -Delimiter ";"
# XML
[xml]$xml = Get-Content config.xml
$xml.root.element # navigate nodes
$xml.SelectNodes("//item") # XPath
$xml.Save("config.xml") # save changes
Output ($obj | ConvertTo-Json):
{
"Name": "Alice",
"Age": 42,
"Active": true
}
Output ($imported | Where-Object Score -gt 90):
Name Score
---- -----
Alice 95
Output (Invoke-WebRequest "https://example.com" | Select-Object StatusCode, StatusDescription):
StatusCode StatusDescription
---------- -----------------
200 OK
Date and time
Get-Date returns a .NET [datetime] object with full arithmetic support — add or subtract time spans with .AddDays(), .AddHours(), etc. Use the -Format parameter for output formatting and [datetime]::ParseExact() when you need to parse a non-standard date string. Date subtraction returns a TimeSpan object, making it easy to compute durations and thresholds.
$now = Get-Date
$now.ToString("yyyy-MM-dd HH:mm:ss")
$now.ToString("yyyyMMdd")
Get-Date -Format "yyyy-MM-dd"
Get-Date -Format "HH:mm"
Get-Date -UFormat "%Y-%m-%d" # Unix format string
# Arithmetic
$yesterday = (Get-Date).AddDays(-1)
$nextWeek = (Get-Date).AddDays(7)
$in1hr = (Get-Date).AddHours(1)
(Get-Date).AddMinutes(-30)
(Get-Date).AddMonths(1)
# Compare dates
(Get-Date) -gt [datetime]"2026-01-01"
([datetime]"2026-12-31" - (Get-Date)).Days # days until
# Parse a string
[datetime]::Parse("2026-04-24")
[datetime]::ParseExact("24/04/2026", "dd/MM/yyyy", $null)
# Timestamp for filenames
$stamp = Get-Date -Format "yyyyMMdd_HHmmss" # 20260424_153045
Output (Get-Date -Format "yyyy-MM-dd HH:mm:ss"):
2026-04-24 15:30:45
Output (([datetime]"2026-12-31" - (Get-Date)).Days):
250
Modules
Modules are the packaging unit for PowerShell cmdlets, functions, and providers. Since PowerShell 7.4, the recommended client is Microsoft.PowerShell.PSResourceGet (cmdlets such as Install-PSResource, Find-PSResource) — Install-Module from the old PowerShellGet 2.x still works but is in maintenance mode and queued for eventual removal. PowerShell 7.6 ships PSResourceGet 1.2.0 with improved NuGet-style v3 dependency resolution. Modules installed with -Scope CurrentUser go under $HOME\Documents\PowerShell\Modules and don't require admin rights. Get-Command -Module <name> is a fast way to discover what a module exports without reading its docs.
# Modern client (PSResourceGet — preferred on 7.4+)
Find-PSResource -Name '*Azure*' -Repository PSGallery
Install-PSResource -Name Az -Scope CurrentUser
Update-PSResource Az
Get-InstalledPSResource Az
# Legacy client (still works for compatibility)
Find-Module SqlServer
Install-Module SqlServer -AllowClobber
# Import is the same either way
Import-Module Az.Accounts
Import-Module SqlServer
# List installed
Get-Module -ListAvailable
Get-Module -ListAvailable *Az*
# List loaded
Get-Module
# Remove from session
Remove-Module SqlServer
# Module commands
Get-Command -Module Az.Compute
Get-Command -Module SqlServer | Select-Object Name, CommandType
Output (Get-Module):
ModuleType Version PreRelease Name ExportedCommands
---------- ------- ---------- ---- ----------------
Script 2.1.0 PSReadLine {Get-PSReadLineKeyHandler, …}
Manifest 7.0.0.0 Microsoft.PowerShell.Management {Add-Content, Clear-Content, …}
Manifest 7.0.0.0 Microsoft.PowerShell.Utility {Add-Member, Add-Type, …}
Output (Get-Command -Module SqlServer | Select-Object Name, CommandType | Select-Object -First 6):
Name CommandType
---- -----------
Add-SqlAvailabilityDatabase Cmdlet
Add-SqlAvailabilityGroupListenerStaticIp Cmdlet
Add-SqlFirewallRule Cmdlet
Backup-SqlDatabase Cmdlet
Convert-UrnToPath Cmdlet
Disable-SqlAlwaysOn Cmdlet
…
Profile — persistent customization
The PowerShell profile is a script that runs automatically when a new session starts, making it the right place for aliases, custom functions, environment tweaks, and module imports. $PROFILE resolves to the current-user/current-host profile path — the most common one to edit. Multiple profile files exist (per-user vs all-users, per-host vs all-hosts) and load in order from broadest to narrowest scope.
# View profile paths
$PROFILE # current user, current host
$PROFILE.CurrentUserAllHosts # current user, all hosts
$PROFILE.AllUsersCurrentHost # all users, current host
$PROFILE.AllUsersAllHosts # all users, all hosts
# Create/edit profile
notepad $PROFILE
code $PROFILE # VS Code
# Example profile content
Set-Alias g git
Set-Alias k kubectl
function prompt { "PS $($executionContext.SessionState.Path.CurrentLocation)> " }
$env:PATH += ";C:\tools"
Import-Module PSReadLine
Set-PSReadLineOption -PredictionSource History
Output ($PROFILE):
C:\Users\alice\Documents\PowerShell\Microsoft.PowerShell_profile.ps1
Network cmdlets
Test-NetConnection replaces ping and telnet for connectivity checks, returning a structured object with TCP test results. Invoke-RestMethod is the go-to for consuming JSON APIs — it automatically parses JSON responses into PowerShell objects, so you can immediately pipe results through Where-Object and Select-Object. Invoke-WebRequest gives lower-level access to headers, status codes, and raw response bodies.
# TCP connectivity test
Test-NetConnection "github.com" -Port 443
Test-NetConnection "10.0.0.1" -Port 22 -InformationLevel Detailed
# DNS lookup
Resolve-DnsName "example.com"
Resolve-DnsName "example.com" -Type MX
Resolve-DnsName "example.com" -Server 8.8.8.8
# Listening ports
Get-NetTCPConnection -State Listen | Select-Object LocalPort,OwningProcess | Sort-Object LocalPort
Get-NetTCPConnection | Where-Object State -eq Established
# Network adapters
Get-NetAdapter | Select-Object Name, Status, LinkSpeed, MacAddress
Get-NetIPAddress | Where-Object AddressFamily -eq IPv4
# ARP table
Get-NetNeighbor -State Reachable
# Routes
Get-NetRoute -AddressFamily IPv4
# Firewall rules
Get-NetFirewallRule -Enabled True | Select-Object DisplayName, Direction, Action
New-NetFirewallRule -DisplayName "Allow SSH" -Direction Inbound -Protocol TCP -LocalPort 22 -Action Allow
# Download
Invoke-WebRequest "https://example.com/file.zip" -OutFile "$env:TEMP\file.zip"
Invoke-WebRequest "https://api.example.com/data" -Method GET -Headers @{ Authorization="Bearer $token" }
# POST with JSON body
$body = @{ user="alice"; pass="secret" } | ConvertTo-Json
Invoke-RestMethod "https://api.example.com/login" -Method POST -Body $body -ContentType "application/json"
Output (Test-NetConnection "github.com" -Port 443):
ComputerName : github.com
RemoteAddress : 140.82.114.4
RemotePort : 443
InterfaceAlias : Wi-Fi
SourceAddress : 192.168.1.42
TcpTestSucceeded : True
Output (Resolve-DnsName "example.com"):
Name Type TTL Section IPAddress
---- ---- --- ------- ---------
example.com A 3600 Answer 93.184.216.34
Output (Get-NetAdapter | Select-Object Name, Status, LinkSpeed, MacAddress):
Name Status LinkSpeed MacAddress
---- ------ --------- ----------
Ethernet Up 1 Gbps A8-66-7F-1B-3E-01
Wi-Fi Up 866.7 Mbps A8-66-7F-1B-3E-02
Output (Get-NetTCPConnection -State Listen | Select-Object LocalPort,OwningProcess | Sort-Object LocalPort | Select-Object -First 8):
LocalPort OwningProcess
--------- -------------
80 812
135 964
443 812
445 4
5985 4
7680 5428
8080 9344
49664 736
Process and service management
Get-Process and Get-Service return live .NET objects you can filter, sort, and act on in the pipeline — unlike tasklist or sc query, no text parsing needed. Stop-Process and Start-Service act on those objects directly. Use Set-Service to change startup type (Automatic/Manual/Disabled) persistently without touching the registry directly.
# Processes
Get-Process
Get-Process -Name "chrome"
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10
Stop-Process -Name "notepad" -Force
Stop-Process -Id 1234
Start-Process "notepad.exe"
Start-Process "msiexec.exe" -ArgumentList "/i setup.msi /quiet" -Wait -NoNewWindow
Wait-Process -Name "setup" -Timeout 120
# Services
Get-Service
Get-Service -Name "wuauserv"
Get-Service | Where-Object Status -eq Stopped
Start-Service "wuauserv"
Stop-Service "wuauserv" -Force
Restart-Service "wuauserv"
Set-Service "wuauserv" -StartupType Automatic
New-Service -Name "MySvc" -BinaryPathName "C:\svc\myservice.exe" -DisplayName "My Service" -StartupType Automatic
Output (Get-Process | Sort-Object CPU -Descending | Select-Object -First 5):
NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
52 312.44 298.17 487.23 3812 1 chrome
38 201.06 188.40 215.66 5104 1 node
21 88.32 94.71 132.09 7720 1 Code
14 45.11 48.22 67.44 2948 1 powershell
9 22.08 19.88 31.12 6372 1 SearchIndexer
Output (Get-Process -Name "chrome"):
NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
52 312.44 298.17 487.23 3812 1 chrome
18 92.11 84.30 43.17 4208 1 chrome
9 48.22 41.88 12.05 7104 1 chrome
Output (Get-Service | Where-Object Status -eq Stopped | Select-Object -First 5):
Status Name DisplayName
------ ---- -----------
Stopped AJRouter AllJoyn Router Service
Stopped ALG Application Layer Gateway Service
Stopped AppReadiness App Readiness
Stopped AppVClient Microsoft App-V Client
Stopped AppXSvc AppX Deployment Service (AppXSVC)
…
Output (Get-Service -Name "wuauserv"):
Status Name DisplayName
------ ---- -----------
Running wuauserv Windows Update
Event logs
Get-WinEvent is the modern cmdlet for querying Windows event logs — prefer it over the older Get-EventLog. Use -FilterHashtable for efficient server-side filtering by log name, event ID, level, and time range, which is much faster than piping all events through Where-Object. Get-EventLog only covers classic logs; Get-WinEvent covers both classic and ETW (modern) logs.
# Classic event log (Windows EventLog)
Get-EventLog -LogName Application -Newest 50
Get-EventLog -LogName System -EntryType Error -Newest 20
Get-EventLog -LogName Application -Source "MSSQLSERVER" -Newest 100
Get-EventLog -LogName Application -After (Get-Date).AddHours(-1)
# Modern event log (Get-WinEvent — preferred)
Get-WinEvent -LogName Application -MaxEvents 50
Get-WinEvent -LogName System -FilterHashtable @{ Level=2; StartTime=(Get-Date).AddDays(-1) }
Get-WinEvent -FilterHashtable @{
LogName = "Application"
Id = 1000,1001
StartTime = (Get-Date).AddDays(-7)
}
# Search event message text
Get-WinEvent -LogName Application -MaxEvents 1000 |
Where-Object { $_.Message -match "failed" }
# Write to event log
New-EventLog -LogName Application -Source "MyScript"
Write-EventLog -LogName Application -Source "MyScript" -EventId 1001 -EntryType Information -Message "Script started"
Output (Get-WinEvent -LogName System -FilterHashtable @{ Level=2; StartTime=(Get-Date).AddDays(-1) } | Select-Object -First 4):
ProviderName: Service Control Manager
TimeCreated Id LevelDisplayName Message
----------- -- ---------------- -------
4/24/2026 9:05:12 AM 7034 Error The Print Spooler service terminated unexpectedly.
4/24/2026 8:47:33 AM 7031 Error The Windows Search service terminated unexpectedly.
4/23/2026 11:22:10 PM 7034 Error The Print Spooler service terminated unexpectedly.
4/23/2026 10:15:44 PM 7023 Error The Interactive Services Detection service terminated…
Scheduled tasks
PowerShell's Task Scheduler cmdlets (Get-ScheduledTask, Register-ScheduledTask) wrap the Windows Task Scheduler COM API, letting you create, modify, and manage tasks without the GUI. Build a task by assembling action, trigger, and settings objects separately, then pass them to Register-ScheduledTask. Tasks can run as SYSTEM or as a specific user, with or without an interactive session.
# List tasks
Get-ScheduledTask
Get-ScheduledTask -TaskPath "\MyApp\"
Get-ScheduledTask | Where-Object State -eq Running
# Create a task
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-File C:\scripts\daily.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At "06:00"
$setting = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Hours 1)
Register-ScheduledTask -TaskName "DailyReport" -TaskPath "\MyApp\" `
-Action $action -Trigger $trigger -Settings $setting -RunLevel Highest
# Run / stop / disable
Start-ScheduledTask -TaskName "DailyReport" -TaskPath "\MyApp\"
Stop-ScheduledTask -TaskName "DailyReport" -TaskPath "\MyApp\"
Disable-ScheduledTask -TaskName "DailyReport" -TaskPath "\MyApp\"
Enable-ScheduledTask -TaskName "DailyReport" -TaskPath "\MyApp\"
Unregister-ScheduledTask -TaskName "DailyReport" -Confirm:$false
Output (Get-ScheduledTask | Where-Object State -eq Running | Select-Object TaskName, TaskPath, State | Select-Object -First 5):
TaskName TaskPath State
-------- -------- -----
MicrosoftEdgeUpdateTaskMachine \ Running
OneDrive Standalone Update Task v2 \ Running
GoogleUpdateTaskMachineCore \Google\ Running
NvTmRep_CrashReport1_{…} \NvTmRepOnLogon_{…}\ Running
ScanForUpdates \Microsoft\Windows\W… Running
Credentials
Get-Credential prompts for a username and password and returns a [PSCredential] object that many cmdlets accept via -Credential. Passwords are stored as [SecureString] — never as plain text in memory. To persist credentials across sessions, serialize the SecureString with ConvertFrom-SecureString (encrypted with the current user's key) and reload it with ConvertTo-SecureString.
# Prompt user for credential
$cred = Get-Credential
$cred = Get-Credential -UserName "DOMAIN\user" -Message "Enter admin password"
# Use credential
Invoke-Command -ComputerName server01 -Credential $cred -ScriptBlock { whoami }
# Store credential securely (current user only)
$cred.Password | ConvertFrom-SecureString | Set-Content C:\cred.txt
# Reload credential
$user = "DOMAIN\user"
$pass = Get-Content C:\cred.txt | ConvertTo-SecureString
$cred = New-Object PSCredential($user, $pass)
# Plain text to SecureString (avoid in production)
$ss = ConvertTo-SecureString "plaintext" -AsPlainText -Force
Useful one-liners
# Top 10 processes by CPU
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10 Name,Id,CPU,@{N="MemMB";E={[Math]::Round($_.WorkingSet/1MB,1)}}
Output:
Name Id CPU MemMB
---- -- --- -----
chrome 3812 487.23 298.2
node 5104 215.66 188.4
Code 7720 132.09 94.7
powershell 2948 67.44 48.2
SearchIndexer 6372 31.12 19.9
MsMpEng 3120 28.77 312.5
explorer 4236 12.01 75.9
svchost 1844 9.33 42.1
Teams 8816 7.88 220.4
OneDrive 9024 5.22 88.6
# All stopped services that should be automatic
Get-Service | Where-Object { $_.StartType -eq "Automatic" -and $_.Status -eq "Stopped" }
Output:
Status Name DisplayName
------ ---- -----------
Stopped gupdate Google Update Service (gupdate)
Stopped RemoteRegistry Remote Registry
Stopped sppsvc Software Protection
# Disk space on all drives
Get-PSDrive -PSProvider FileSystem | Select-Object Name,@{N="FreeGB";E={[Math]::Round($_.Free/1GB,2)}},@{N="UsedGB";E={[Math]::Round($_.Used/1GB,2)}}
Output:
Name FreeGB UsedGB
---- ------ ------
C 166.1 309.3
D 383.7 547.0
# Files modified in the last 24 hours
Get-ChildItem C:\logs -Recurse -File | Where-Object LastWriteTime -gt (Get-Date).AddDays(-1)
Output:
Directory: C:\logs
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 4/24/2026 10:15 AM 145832 app.log
-a--- 4/24/2026 9:02 AM 39680 error.log
# Largest files in a directory
Get-ChildItem C:\Windows -Recurse -File -ErrorAction SilentlyContinue |
Sort-Object Length -Descending | Select-Object -First 20 FullName,@{N="MB";E={[Math]::Round($_.Length/1MB,1)}}
Output:
FullName MB
-------- --
C:\Windows\Installer\3b7c6a.msi 812.4
C:\Windows\SoftwareDistribution\Download\abc123\windows11.cab 634.1
C:\Windows\System32\MRT.exe 143.2
C:\Windows\System32\drivers\ntoskrnl.exe 14.7
…
# Count lines in all log files
Get-ChildItem C:\logs -Filter *.log | ForEach-Object { @{File=$_.Name; Lines=(Get-Content $_).Count} }
Output:
Name Value
---- -----
File app.log
Lines 342
File error.log
Lines 87
File access.log
Lines 10482
# Test multiple ports on a host
80,443,8080,8443 | ForEach-Object { Test-NetConnection "example.com" -Port $_ -WarningAction SilentlyContinue } |
Select-Object RemotePort,TcpTestSucceeded
Output:
RemotePort TcpTestSucceeded
---------- ----------------
80 True
443 True
8080 False
8443 False
# Find text in files
Select-String -Path "C:\logs\*.log" -Pattern "ERROR" -List | Select-Object Filename,LineNumber,Line
Output:
Filename LineNumber Line
-------- ---------- ----
app.log 87 [2026-04-24 10:02:45] ERROR Connection reset by peer
error.log 1 [2026-04-24 08:11:03] ERROR Disk quota exceeded
error.log 34 [2026-04-24 09:47:22] ERROR Null reference exception in ProcessOrder
# Get local admins group members
Get-LocalGroupMember -Group "Administrators" | Select-Object Name,ObjectClass
Output:
Name ObjectClass
---- -----------
DESKTOP-PC\alicedev User
DESKTOP-PC\Administrator User
NT AUTHORITY\SYSTEM WellKnownGroup
# Count unique IP addresses in an IIS log
Get-Content C:\inetpub\logs\*.log | Where-Object { $_ -notmatch "^#" } |
ForEach-Object { ($_ -split " ")[2] } | Group-Object -NoElement | Sort-Object Count -Descending
Output:
Count Name
----- ----
842 203.0.113.47
317 198.51.100.22
204 192.0.2.100
89 10.0.0.5
41 172.16.0.30
…
# Get Windows version info
(Get-CimInstance Win32_OperatingSystem) | Select-Object Caption,Version,BuildNumber,OSArchitecture
Output:
Caption Version BuildNumber OSArchitecture
------- ------- ----------- --------------
Microsoft Windows 11 Pro 10.0.22631 22631 64-bit
# Live tail a log file
Get-Content C:\logs\app.log -Wait -Tail 50
Output:
[2026-04-24 15:28:01] INFO Request GET /api/health 200 OK (12ms)
[2026-04-24 15:28:07] INFO Request POST /api/orders 201 Created (88ms)
[2026-04-24 15:28:15] WARN Slow query detected: 2341ms
[2026-04-24 15:28:22] INFO Request GET /api/users 200 OK (9ms)
…
Sources
- Microsoft Learn — What's New in PowerShell 7.6 — LTS feature, cmdlet, and breaking-change list.
- PowerShell Team Blog — Announcing PowerShell 7.6 (LTS) GA — release announcement (2026-03-18) and support lifecycle.
- Microsoft Learn — Install-PSResource — recommended successor to
Install-Module. - Microsoft Learn — PowerShell documentation hub — top-level index for current cmdlet and language reference.