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.

powershell
$PSVersionTable | Format-List PSVersion, PSEdition, OS

Output:

yaml
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.

powershell
$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 (&&, ||).

powershell
# 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.

powershell
# 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().

powershell
$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:

text
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.

powershell
$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 ?.

powershell
# 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):

text
 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" }):

text
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 %.

powershell
# 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 }):

text
2
4
6
8
10

Output (Get-ChildItem *.log | ForEach-Object { … }):

text
app.log: 142.3 KB
error.log: 38.7 KB
access.log: 987.1 KB

Output (Measure-Command { 1..1000 | ForEach-Object { $_ * 2 } }):

text
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.

powershell
# 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):

text
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) } }):

text
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.

powershell
# 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):

text
 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]$_ }):

text
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.

powershell
# 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):

text
Count Name
----- ----
   18 svchost
    8 chrome
    4 RuntimeBroker
    3 conhost
    2 powershell
    1 explorer

Output (Get-ChildItem | Group-Object Extension):

text
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.

powershell
# 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):

text
Count    : 87
Average  :
Sum      :
Maximum  :
Minimum  :
Property :

Output (Get-Process | Measure-Object CPU -Sum -Average -Minimum -Maximum):

text
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):

text
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.

powershell
# 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):

text
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.

powershell
# 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):

text
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.

powershell
# 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):

text
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 *):

text
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):

text
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.

powershell
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.

powershell
# 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):

text
   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):

text
System.Diagnostics.Process

Output ([Math]::Sqrt(144)):

text
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.

powershell
# 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.

powershell
# 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.

powershell
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):

text
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.

powershell
# 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 }):

text
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.

powershell
# 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):

text
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.

powershell
# 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):

text
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):

text
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.

powershell
# 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):

text
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):

text
22631

Output (Test-Path "HKCU:\Software\MyApp"):

text
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.

powershell
# 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"):

text
    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):

text
[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):

text
Algorithm       Hash                                                                   Path
---------       ----                                                                   ----
SHA256          3A7BD3E2360A3D29EEA436FCFB7E44C735D117C42D1C1835420B6B9942DD4F1B   C:\file.exe

Output (Test-Path C:\file.txt):

text
True

Output (Join-Path "C:\logs" "app.log"):

text
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.

powershell
# 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):

text
{
  "Name": "Alice",
  "Age": 42,
  "Active": true
}

Output ($imported | Where-Object Score -gt 90):

text
Name  Score
----  -----
Alice 95

Output (Invoke-WebRequest "https://example.com" | Select-Object StatusCode, StatusDescription):

text
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.

powershell
$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"):

text
2026-04-24 15:30:45

Output (([datetime]"2026-12-31" - (Get-Date)).Days):

text
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.

powershell
# 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):

text
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):

text
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.

powershell
# 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):

text
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.

powershell
# 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):

text
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"):

text
Name                                           Type   TTL   Section    IPAddress
----                                           ----   ---   -------    ---------
example.com                                    A      3600  Answer     93.184.216.34

Output (Get-NetAdapter | Select-Object Name, Status, LinkSpeed, MacAddress):

text
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):

text
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.

powershell
# 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):

text
 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"):

text
 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):

text
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"):

text
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.

powershell
# 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):

text
   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.

powershell
# 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):

text
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.

powershell
# 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

powershell
# 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:

text
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
powershell
# All stopped services that should be automatic
Get-Service | Where-Object { $_.StartType -eq "Automatic" -and $_.Status -eq "Stopped" }

Output:

text
Status   Name               DisplayName
------   ----               -----------
Stopped  gupdate            Google Update Service (gupdate)
Stopped  RemoteRegistry     Remote Registry
Stopped  sppsvc             Software Protection
powershell
# 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:

text
Name FreeGB UsedGB
---- ------ ------
C     166.1  309.3
D     383.7  547.0
powershell
# Files modified in the last 24 hours
Get-ChildItem C:\logs -Recurse -File | Where-Object LastWriteTime -gt (Get-Date).AddDays(-1)

Output:

text
    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
powershell
# 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:

text
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
…
powershell
# Count lines in all log files
Get-ChildItem C:\logs -Filter *.log | ForEach-Object { @{File=$_.Name; Lines=(Get-Content $_).Count} }

Output:

text
Name                           Value
----                           -----
File                           app.log
Lines                          342
File                           error.log
Lines                          87
File                           access.log
Lines                          10482
powershell
# Test multiple ports on a host
80,443,8080,8443 | ForEach-Object { Test-NetConnection "example.com" -Port $_ -WarningAction SilentlyContinue } |
  Select-Object RemotePort,TcpTestSucceeded

Output:

text
RemotePort TcpTestSucceeded
---------- ----------------
        80             True
       443             True
      8080            False
      8443            False
powershell
# Find text in files
Select-String -Path "C:\logs\*.log" -Pattern "ERROR" -List | Select-Object Filename,LineNumber,Line

Output:

text
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
powershell
# Get local admins group members
Get-LocalGroupMember -Group "Administrators" | Select-Object Name,ObjectClass

Output:

text
Name                   ObjectClass
----                   -----------
DESKTOP-PC\alicedev    User
DESKTOP-PC\Administrator User
NT AUTHORITY\SYSTEM    WellKnownGroup
powershell
# 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:

text
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
…
powershell
# Get Windows version info
(Get-CimInstance Win32_OperatingSystem) | Select-Object Caption,Version,BuildNumber,OSArchitecture

Output:

text
Caption                     Version    BuildNumber OSArchitecture
-------                     -------    ----------- --------------
Microsoft Windows 11 Pro    10.0.22631 22631       64-bit
powershell
# Live tail a log file
Get-Content C:\logs\app.log -Wait -Tail 50

Output:

text
[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.