PowerShell — Reference
Source: https://learn.microsoft.com/en-us/powershell/
PowerShell
- Created: 2006 (Windows PowerShell 1.0) by Microsoft (Jeffrey Snover et al.); open-sourced and rebuilt cross-platform on .NET Core as PowerShell 6.0 in 2018
- Latest stable: PowerShell 7.4 LTS is built on .NET 8 (latest patch 7.4.15, ~2026); 7.5/7.6/7.7 are in active release; Windows PowerShell 5.1 is the legacy in-box Windows-only edition (no further feature work). (What’s new in 7.4)
- Paradigms: Object-oriented shell (objects flow through pipelines, not text); imperative; functional sprinkles via script blocks; class-based OO since 5.0
- Typing: Dynamic; optional static types via
[type]$varcasts and[CmdletBinding()]parameter attributes; .NET type system underneath - Memory: .NET-managed (CLR GC)
- Compilation: Parsed to AST → bytecode-compiled to dynamic methods at runtime (similar to a JIT script lang); .NET assembly under the hood
- Primary domains: Windows administration, Microsoft 365 / Azure / Active Directory automation, CI/CD on Windows, infrastructure scripting cross-platform (Linux/macOS), DevOps glue, REST API consumption, package and configuration management
- Notable runtimes: PowerShell (cross-platform, .NET, current), Windows PowerShell 5.1 (Windows-only, .NET Framework, legacy)
- Official docs: https://learn.microsoft.com/en-us/powershell/
At a glance
PowerShell’s defining innovation is the object pipeline: instead of streaming text between commands like Unix shells, cmdlets emit and consume .NET objects with typed properties and methods. Get-Process | Where-Object CPU -gt 100 | Sort-Object CPU -Desc | Select-Object -First 5 filters, sorts, and projects without parsing a single line. Cmdlets follow the Verb-Noun convention (Get-, Set-, New-, Remove-, Invoke-, Test-) with a published verb list, which makes discovery (Get-Command *-Process) and tab completion uniform across modules. Two editions coexist: Windows PowerShell 5.1 (in-box on every Windows since 2016, .NET Framework, frozen) and PowerShell 7+ (cross-platform on .NET 8/9, the actively developed line). Many on-box Windows modules still ship only for 5.1 — the Windows Compatibility Module (WindowsPSModulePath / Import-Module -UseWindowsPowerShell) bridges them into 7. (PS docs hub)
Getting started
Install:
- Windows: preinstalled 5.1; install 7 via
winget install --id Microsoft.PowerShellor MSI from https://github.com/PowerShell/PowerShell/releases. - macOS:
brew install --cask powershell. - Linux:
apt,dnf,snapper https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-linux. - Multiple versions can coexist;
pwshlaunches 7+,powershell.exelaunches 5.1.
Hello world:
# hello.ps1
Write-Output "Hello, world!"Run: pwsh ./hello.ps1. (Windows: by default scripts are blocked by execution policy — Set-ExecutionPolicy -Scope CurrentUser RemoteSigned.)
Project layout: A module:
MyModule/
MyModule.psd1 # manifest (metadata, exports, version)
MyModule.psm1 # script module (or .dll for binary)
Public/Get-Thing.ps1 # one cmdlet per file (convention)
Private/Helper.ps1
Tests/Get-Thing.Tests.ps1 # Pester tests
Package/build tool: PSResourceGet (replacement for legacy PowerShellGet v2; ships with 7.4) — Install-PSResource Pester, Find-PSResource, Publish-PSResource. PowerShell Gallery (https://www.powershellgallery.com/) is the public registry. Module manifests use .psd1 (a restricted PowerShell hashtable). For multi-file modules, Build-Module (from ModuleBuilder) or psake / Invoke-Build for build orchestration.
REPL: pwsh (or powershell.exe for 5.1). With PSReadLine (bundled, 2.3.6+ in 7.4) you get syntax highlighting, history search (Ctrl+R), prediction (Set-PSReadLineOption -PredictionSource HistoryAndPlugin), and ViMode. Windows Terminal is the modern host. VS Code with the PowerShell extension is the IDE.
Basics
Types & literals. Numbers: 42, 0xFF, 1.5, 1.5e3, 1kb/1mb/1gb/1tb/1pb (size suffixes — multipliers, evaluate to [long]), 100ms/1s/1m are NOT supported (use [timespan]::FromSeconds(1)). Strings: single-quoted 'literal' (no expansion), double-quoted "hello $name $($obj.Prop)" (variable + $() subexpression), here-strings @"…multi-linetrue,null. Arrays:@(1, 2, 3)or1, 2, 3. Hashtables:@{a=1; b=2}. Ordered:[ordered]@{…}`.
Variables & scoping.
$name = "alice" # Untyped
[int]$count = 0 # Typed (cast enforced on subsequent assigns)
[ValidateSet('a','b')] $x = 'a' # validation attribute
$global:g = 1
$script:moduleVar = 2 # script/module scope
$private:hidden = 3Scopes: Global, Script, Local, Private. Functions are by-default Local. Modules have an isolated Script scope.
Control flow.
if ($x -gt 0) { … } elseif ($x -lt 0) { … } else { … }
switch ($val) {
'a' { 'apple' }
{$_ -is [int]} { 'number' }
default { 'other' }
}
foreach ($item in $list) { … }
$list | ForEach-Object { … } # pipeline form (slower per-item, lazier)
for ($i=0; $i -lt 10; $i++) { … }
while (cond) { … }
do { … } while (cond)
do { … } until (cond)Operators are -eq, -ne, -lt, -gt, -le, -ge, -like (wildcard), -match (regex), -contains, -in, -notin, -band, -bor. Case-sensitive variants: -ceq, -cmatch, etc. (Default is case-insensitive.) == doesn’t exist.
Functions / advanced functions.
function Get-Greeting {
[CmdletBinding()] # makes it an "advanced function" → free -Verbose, -ErrorAction, -WhatIf if [SupportsShouldProcess]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[string]$Name,
[string]$Greeting = 'Hello'
)
process { "$Greeting, $Name!" }
}
'Alice','Bob' | Get-Greetingbegin/process/end blocks for streaming pipeline input (process runs once per input). Without them, all pipeline input collects into $input.
Strings. -f operator for format: '{0,-10} {1:N2}' -f 'pi', 3.14159. .Replace(), .Split(), .Substring(), .PadLeft(). Regex via -match / -replace / -split. Format with Format-Table, Format-List, ConvertTo-Json.
Collections. Arrays via @(…); resize is expensive (allocates new). Use [System.Collections.Generic.List[T]]::new() for repeated Add(). Hashtable: @{}. PSCustomObject (the lightweight record): [PSCustomObject]@{Name='x'; Value=1} — has property access syntax and works in Format-Table automatically.
Intermediate
Type system depth. Full .NET type system: [int], [string], [datetime], [guid], [regex], [scriptblock], [hashtable], [type]. Cast with [type]$x or $x -as [type] (returns $null on failure). Generics: [System.Collections.Generic.Dictionary[string,int]]::new(). Custom enums and classes since 5.0. The PSObject wrapping system lets you attach NoteProperty, ScriptProperty, ScriptMethod to any object at runtime.
Modules. Three flavors:
- Script module (
.psm1) — PowerShell source. - Binary module (
.dll) — compiled C# implementingCmdlet/PSCmdlet. - Manifest module (
.psd1) — points to the others; defines version, dependencies, exports, compatible editions. Auto-loading scans$env:PSModulePath.Export-ModuleMember -Function Get-Thingcontrols what’s public.
Error handling. Two kinds: terminating (caught by try/catch, fires trap) and non-terminating (collected in $Error[0], doesn’t stop). Cmdlets pick which to emit. Force terminating: -ErrorAction Stop per call, $ErrorActionPreference = 'Stop' globally, or try { … } catch [System.IO.IOException] { … } finally { … }. throw 'msg' raises a terminating error. Write-Error writes a non-terminating one.
Concurrency.
ForEach-Object -Parallel { … } -ThrottleLimit 5(PS7+) — runspace-per-item, true parallel.Start-Job— out-of-process job (heavyweight, fork-equivalent);Wait-Job,Receive-Job,Remove-Job.Start-ThreadJob(in-process, runspace-based — much lighter thanStart-Job).- Runspaces (the underlying primitive):
[runspacefactory]::CreateRunspacePool()for true parallelism with shared variable injection. Foundation of-Parallel.
I/O. Get-Content, Set-Content, Add-Content, Out-File. Encoding default in PS7+ is UTF-8 no BOM; in 5.1 it varies (and historically defaulted to UTF-16 LE with BOM for Out-File). -Encoding utf8, utf8BOM, utf8NoBOM, ascii, unicode. [System.IO.File]::ReadAllText($p) for raw .NET access.
Stdlib highlights. Invoke-WebRequest / Invoke-RestMethod for HTTP (vastly improved in 7.4 — Brotli, persistent connections, custom timeouts). ConvertTo-Json/ConvertFrom-Json. Test-Path, Resolve-Path, Join-Path, Split-Path. Get-ChildItem (ls/dir). Select-String (grep-equivalent). Compare-Object. Group-Object. Measure-Object. Get-Random. Test-Json (now using JsonSchema.NET as of 7.4 — Draft 4 schemas no longer supported, breaking change). (7.4 breaking changes)
Advanced
Memory / GC. .NET CLR GC (server or workstation depending on host). Long-running pwsh instances accumulate variables in $global: scope; clean up with Remove-Variable or use Start-ThreadJob for isolation. Large pipelines stream — they don’t materialize unless you @() or Sort-Object (which buffers).
Concurrency deep dive. Runspaces are the foundation. [powershell]::Create().AddScript($sb).BeginInvoke() queues async work; Wait then EndInvoke to collect. Runspace pools ([runspacefactory]::CreateRunspacePool(1, $maxThreads)) reuse threads — used by ForEach-Object -Parallel. Variables can be injected via $using:varName inside the script block (essential pattern). 7+ added using namespace/module and improved using assembly for binary deps.
FFI. Native interop is just .NET interop:
Add-Type -TypeDefinition $csharpSource— compile inline C# at runtime, get a usable .NET type.[DllImport]via Add-Type for raw P/Invoke to native libraries (Win32 APIs, libc).[System.Runtime.InteropServices.Marshal]for buffer/struct marshaling.- Windows-only:
New-Object -ComObject Excel.Applicationfor COM automation.
Reflection.
Get-Member— properties and methods of any object.$obj | Get-Member.Get-Command Get-Process— discovers what command does what.(Get-Command Get-Process).Parameters— full parameter metadata.[type]::GetMethods(),.GetProperties(),.GetFields()— full .NET reflection.Get-PSCallStack— execution stack inspection.
Performance tools.
Measure-Command { … }— wall-clock for a script block.Trace-Command— internal tracing of parameter binding, type conversion, etc.- PowerShell Profiler: https://github.com/nohwnd/Profiler — sampling profiler for PS scripts.
Set-PSDebug -Trace 2— line-by-line trace.- Slow patterns to avoid: array
+=in a loop (O(n²) — use[List[T]]),ForEach-Objectoverforeachfor large in-memory collections,Select-Objectwhen not projecting (use direct property access).
God mode
-
AST manipulation:
[System.Management.Automation.Language.Parser]::ParseInput($script, [ref]$tokens, [ref]$errors)returns a full AST. Walk it with$ast.FindAll({ $args[0] -is [CommandAst] }, $true). Foundation of PSScriptAnalyzer, formatters, and the language server. -
Dynamic modules /
[scriptblock]::Create(): Compile arbitrary text into a callable script block at runtime:& ([scriptblock]::Create('Get-Date')). Use sparingly — sandboxing is viaConstrained Language Mode(set with$ExecutionContext.SessionState.LanguageMode). -
Classes (PS5+): Real CLR types from PowerShell:
class Animal { [string]$Name Animal([string]$n) { $this.Name = $n } [string] Speak() { return 'generic noise' } } class Dog : Animal { Dog([string]$n) : base($n) {} [string] Speak() { return 'woof' } }Attributes work (
[ValidateSet],[ValidateRange]). DSC resources are class-based since DSC 2. -
DSC (Desired State Configuration): Declarative config-as-code; PS5.1 ships
PSDesiredStateConfigurationv1; DSC 3 (current) is platform-independent and pulls config fromdsc resource/dsc config get. Replaces a lot of Puppet/Chef use cases on Windows. (DSC docs) -
CIM/WMI:
Get-CimInstance Win32_Processis the modern way (DCOM/WSMan, fast).Get-WmiObjectis deprecated. -
.NET reflection from PowerShell: Anything you can do in C# you can prototype in PS —
[System.Net.ServicePointManager]::SecurityProtocol,[Reflection.Assembly]::LoadFrom($p), etc. -
Splatting (
@parameters): Pass a hashtable as named parameters:$p = @{Path='c:\x'; Recurse=$true}; Get-ChildItem @p. The single biggest readability win for cmdlets with many params. Splatting an array passes positional args. -
Advanced functions (
[CmdletBinding()]+ parameter sets):[Parameter(ParameterSetName='ByName')]and[Parameter(ParameterSetName='ById')]mark params as mutually exclusive sets; PowerShell picks the active set from supplied params. Combine with[ValidateScript({})],[ArgumentCompleter({…})],[ValidatePattern],[ValidateRange]. -
Runspaces + runspace pools: raw API behind every parallel feature. Useful when you need shared state, cancellation tokens, or to inject .NET assemblies into the worker.
-
usingstatements (top-of-file only):using namespace System.Collections.Generic— alias for shorter type names.using assembly /path/to/lib.dll— load a DLL.using module MyModule— import at parse time (necessary if you reference the module’s classes —Import-Moduledoesn’t expose them).
-
$PSDefaultParameterValues: session-wide defaults:$PSDefaultParameterValues['Get-ChildItem:Force'] = $true. Keys can use wildcards. -
Custom formatters via
.ps1xml: define how your custom types render inFormat-Table/Format-List.Update-FormatData -PrependPath my.format.ps1xml. -
Custom type extensions (
.ps1xml): add NoteProperty/ScriptProperty/ScriptMethod to existing types — e.g., add a.ToTable()to every PSCustomObject. -
JEA (Just Enough Administration): constrained PowerShell endpoints exposing only specific cmdlets/parameters via
New-PSSessionConfigurationFile -SessionType RestrictedRemoteServer. Lets help-desk users run sanctioned admin commands without local admin. (JEA docs) -
PowerShell 7 internals: built on .NET 8 (LTS).
pwshis a native AOT executable + a managed assembly. The pipeline is implemented asCmdlet.ProcessRecordinvocations chained through aPipelineProcessor; objects flow throughPipe.AddInput/Retrieve. Source at https://github.com/PowerShell/PowerShell.
Idioms & style
-
Naming:
Verb-Noun, both PascalCase. Use approved verbs (Get-Verblists them);New-/Add-/Set-have specific meanings. Variables PascalCase or camelCase per personal taste; param names always PascalCase. -
One pipeline expression per line for readability:
Get-Process | Where-Object CPU -gt 100 | Sort-Object CPU -Descending | Select-Object -First 5 -
Use
[CmdletBinding()]even for trivial functions — gets you common parameters for free. -
Always use full cmdlet names in scripts. Aliases (
?for Where-Object,%for ForEach-Object,gcifor Get-ChildItem) are interactive-only. -
Prefer
foreachoverForEach-Objectfor in-memory collections (no per-item pipeline overhead). -
Avoid
$null -eq $x/$x -eq $nullconfusion — always put$nullon the left:if ($null -eq $x). Right-side$nulltriggers collection comparison semantics. -
Splat for >3 params.
-
Use
.WriteLine($string)over+=on arrays in tight loops. -
Linter: PSScriptAnalyzer is the standard (
Invoke-ScriptAnalyzer .). Editor extensions surface its rules live. -
Formatter:
Invoke-Formatter(also from PSScriptAnalyzer); VS Code’s PowerShell extension uses it. -
Style guide: community-maintained at https://poshcode.gitbook.io/powershell-practice-and-style/ — adopted widely.
Ecosystem
- Testing: Pester — BDD framework (
Describe,Context,It,Should -Be). Mocking withMock Get-Foo { … }. Comes preinstalled in 7+. - Linting/formatting: PSScriptAnalyzer.
- Docs: PlatyPS (now Microsoft.PowerShell.PlatyPS, v2) generates Markdown help and external help XML from comment-based help.
- Packaging: PSResourceGet → PowerShell Gallery.
- Build orchestration:
Invoke-Build,psake. - Cross-platform shells:
pwshruns on Linux/macOS for CI portability (GitHub Actions hasshell: pwshavailable). - Notable users: Microsoft (entire Azure CLI shell experience, Microsoft 365 Admin via Exchange Online / Teams / SharePoint Online modules, Intune, Defender), Stack Overflow (deployment), GitHub (Windows-side internal tooling), every Windows shop running Active Directory / Exchange / SCCM / SCOM, OneDrive sync diagnostics scripts, Cloud platform tooling (
Az.*modules with hundreds of submodules).
Gotchas
- The “two PowerShells” problem. Windows PowerShell 5.1 (
powershell.exe, .NET Framework, frozen) vs PowerShell 7+ (pwsh, .NET, current). Many built-in Windows modules are 5.1-only — useImport-Module -UseWindowsPowerShellto bridge from 7. The two have different default encodings, different built-in cmdlets, different$PSVersionTable.PSEdition. - Default encoding differences. 5.1
Out-Filedefaults to UTF-16 LE with BOM. 7+ defaults to UTF-8 without BOM. Cross-version scripts must always specify-Encoding utf8. ==doesn’t exist. Use-eq. Comparison operators are-prefixed;=is assignment only.- Comparison is case-insensitive by default. Use
-ceq,-cmatch,-clikefor case-sensitive. $nullon the right in-eqagainst an array yields a filter, not a boolean —@(1,2,$null) -eq $nullreturns@($null). Alwaysif ($null -eq $x).- Array
+=is O(n²). Each+=allocates a new array. Use[List[object]]::new(). functionreturns all uncaptured output.function f { 1; 2; 'x' }returns@(1, 2, 'x'). Capture or pipe toOut-Nullto suppress.return $xis just shorthand for output-then-return — it doesn’t restrict the function’s output to that value if other expressions also output.- Pipeline parameter binding can match unintended params.
[Parameter(ValueFromPipelineByPropertyName)]matches pipeline objects’ properties to params by name; can surprise. Inspect withTrace-Command -Name ParameterBinding. Test-Jsonin 7.4 dropped Draft 4 schema support. Migrate to Draft 7+. (breaking changes 7.4)- Execution policy is a Windows-only nag, not security. It blocks scripts by default but anyone can
Set-ExecutionPolicy -Scope Process Bypassor pipe into stdin. Start-Process -Waitblocks but doesn’t capture output — useStart-Process -RedirectStandardOutput tmp.txt -Waitor call the executable directly (& foo.exe argsdoes what you want and captures).- Native command stderr.
& foo.exe 2>&1works but the items in the merged stream areErrorRecordobjects, not strings. Wrap with2>&1 | ForEach-Object { "$_" }. - Method invocation needs parens, no spaces.
$obj.Foo($a, $b), not$obj.Foo $a $b— that’s parsed asFooreturned then two strings. if ($var)truthiness treats0, empty string,$null, empty array as false;'False'as true (it’s a non-empty string).- PowerShell on Linux/macOS: aliases
ls/cat/mvetc. are removed so they don’t shadow native binaries —Get-ChildItemworks;lscalls/bin/ls. - Tilde expansion bug pre-7.4 — fixed in 7.4 to expand
~to$HOMEon Windows for native commands. (7.4 release notes) - Heredocs (here-strings) on PowerShell 5.1: the closing
"@MUST be at column 0; even one space of indent is a parse error. (5.1 doesn’t have the indent-tolerant version.)
Citations
- PowerShell documentation hub — https://learn.microsoft.com/en-us/powershell/
- What’s new in PowerShell 7.4 — https://learn.microsoft.com/en-us/powershell/scripting/whats-new/what-s-new-in-powershell-74
- Install PowerShell — https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell
- About operators — https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_operators
- About scopes — https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_scopes
- About splatting — https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_splatting
- About classes — https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_classes
- About foreach-parallel — https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_foreach-parallel
- DSC overview — https://learn.microsoft.com/en-us/powershell/dsc/overview
- JEA overview — https://learn.microsoft.com/en-us/powershell/scripting/learn/remoting/jea/overview
- Pester — https://pester.dev/
- PSScriptAnalyzer — https://github.com/PowerShell/PSScriptAnalyzer
- PowerShell Practice & Style guide — https://poshcode.gitbook.io/powershell-practice-and-style/
- PowerShell GitHub source — https://github.com/PowerShell/PowerShell