PowerShell Built-in Variables — Automatic & Preference Variables#
What it is#
PowerShell ships with two categories of built-in variables that are always in scope without any Import-Module or declaration: automatic variables set and maintained by the runtime (e.g. $_, $Error, $MyInvocation), and preference variables that let you tune runtime behaviour (e.g. $ErrorActionPreference, $VerbosePreference). Unlike environment variables ($env:*), these live in PowerShell’s variable scope tree and reset or persist according to documented rules. The alternative — environment variables — are process-level strings that survive process boundaries but carry no type information.
Core automatic variables#
Automatic variables are created and updated by PowerShell itself; assigning to most of them is either silently ignored or raises an error. They are the primary way to access pipeline context, command results, and session state without invoking a cmdlet.
$_ # Current pipeline object (alias: $PSItem)
$null # Null value — assigning to $null discards output
$true # Boolean true
$false # Boolean false
$args # Array of arguments passed to a script/function without param()
$input # Enumerator of piped input to a function or script block
$OFS # Output field separator (default: single space)
$$ # PID of the last executed native command's process
$^ # First token of the last executed command line
$? # Boolean success/failure status of the last command
Output: (none — variable reference table; evaluate interactively to see values)
# $_ in a pipeline
1..5 | Where-Object { $_ -gt 3 }
Output:
4
5
# $args in a simple function without param()
function Greet { "Hello, $($args[0])!" }
Greet "Alice Dev"
Output:
Hello, Alice Dev!
# $? immediately after a command
Get-Item "C:\Windows"
$?
Output:
True
Error and exit-code variables#
$Error is a fixed-size circular ring buffer (default 256 entries) that PowerShell appends to after every non-terminating error; $Error[0] is always the most recent. $LASTEXITCODE holds the numeric exit code of the most recently invoked native executable and is distinct from $?, which reflects PowerShell cmdlet success. Use both together when wrapping native tools.
$Error # ArrayList — all errors this session, newest first
$Error[0] # Most recent ErrorRecord
$Error[0].Exception # Underlying .NET exception object
$Error[0].InvocationInfo.Line # Source line that caused it
$Error[0].FullyQualifiedErrorId # Stable identifier for the error kind
$Error.Count # Number of stored errors
$Error.Clear() # Flush the error buffer
Output: (none — property access; values depend on session state)
# Trigger an error and inspect it
Get-Item "C:\nonexistent" -ErrorAction SilentlyContinue
$Error[0].Exception.Message
Output:
Cannot find path 'C:\nonexistent' because it does not exist.
# Native exit code vs PowerShell $?
cmd /c "exit 42"
$LASTEXITCODE
$?
Output:
42
True
Version and host variables#
$PSVersionTable is a read-only hashtable exposing the PowerShell build metadata; it is the canonical way to gate version-specific code paths. $Host exposes the hosting application object (terminal or ISE), including its UI.RawUI sub-object for controlling window title, colors, and buffer size.
$PSVersionTable # Hashtable: PSVersion, PSEdition, OS, Platform, …
$PSVersionTable.PSVersion # [System.Version] — compare with -ge/-lt
$PSVersionTable.PSVersion.Major # Integer major version (7 for PS 7.x)
$PSVersionTable.PSEdition # "Core" (PS 7+) or "Desktop" (Windows PS 5.1)
$PSVersionTable.OS # OS description string
$PSVersionTable.Platform # "Win32NT" | "Unix" | "MacOSX"
Output: (none — property reference; run $PSVersionTable at the prompt to see values)
$PSVersionTable
Output:
Name Value
---- -----
PSVersion 7.4.2
PSEdition Core
GitCommitId 7.4.2
OS Microsoft Windows 10.0.22631
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
$Host # PowerShell host application object
$Host.Name # e.g. "ConsoleHost" or "Windows PowerShell ISE Host"
$Host.Version # Host application version (not PS engine version)
$Host.UI.RawUI.WindowTitle # Get/set terminal window title
$Host.UI.RawUI.ForegroundColor # Current foreground color
$Host.UI.RawUI.BackgroundColor # Current background color
$Host.UI.RawUI.BufferSize # Console buffer dimensions
$Host.Runspace # The current runspace
Output: (none — property access; assignments have no console output)
# Change terminal title from a script
$Host.UI.RawUI.WindowTitle = "Deploy — myhost"
Output: (none — sets the title bar text silently)
$PSCulture # Current input culture, e.g. "en-US"
$PSUICulture # Current UI culture for localized messages
$PID # PID of the current PowerShell process
$IsWindows # $true on Windows (PS 6+)
$IsLinux # $true on Linux
$IsMacOS # $true on macOS
$IsCoreCLR # $true on .NET Core / .NET 5+
Output: (none — read-only boolean/string properties)
Script and path introspection#
These variables are set fresh each time a script or function executes, making them the safe way to build relative paths inside a script regardless of the caller’s working directory.
$PSScriptRoot # Absolute path of the directory containing the current script
$PSCommandPath # Full path of the current script file
$MyInvocation # Rich object: how, where, and by whom this code was invoked
Output: (none — set by the runtime when a script runs; empty in the interactive prompt)
# Inside C:\scripts\deploy.ps1
"Script dir : $PSScriptRoot"
"Script path: $PSCommandPath"
Output:
Script dir : C:\scripts
Script path: C:\scripts\deploy.ps1
# $MyInvocation members
$MyInvocation.MyCommand.Name # Name of the running command, function, or script
$MyInvocation.MyCommand.Path # Full path if it is a .ps1 file
$MyInvocation.BoundParameters # [hashtable] explicitly passed parameter values
$MyInvocation.UnboundArguments # Extra positional arguments
$MyInvocation.InvocationName # How the command was typed (alias vs full name)
$MyInvocation.Line # Full command line string that triggered this code
$MyInvocation.ScriptLineNumber # Line number in the source file
$MyInvocation.OffsetInLine # Column offset on that line
Output: (none — property reference; values depend on calling context)
Advanced function variables#
$PSCmdlet is only available inside [CmdletBinding()] advanced functions and grants access to ShouldProcess, ShouldContinue, WriteVerbose, and other API entry points. $PSBoundParameters is a dictionary of parameter-name → value pairs for only the parameters the caller explicitly supplied; parameters with defaults that were not passed are absent, making it safe to splat onto child commands.
function Remove-Widget {
[CmdletBinding(SupportsShouldProcess)]
param(
[string]$Name,
[string]$Reason = "no reason given"
)
# ShouldProcess respects -WhatIf and -Confirm
if ($PSCmdlet.ShouldProcess($Name, "Remove widget")) {
"Removing $Name because $Reason"
}
# Splat only what the caller supplied
$PSBoundParameters.Remove("Reason") | Out-Null
# Set-Widget @PSBoundParameters # forward without extra keys
}
Output: (none — function definition; call with -WhatIf to preview)
Remove-Widget -Name "gadget-7" -WhatIf
Output:
What if: Performing the operation "Remove widget" on target "gadget-7".
# $PSBoundParameters only contains explicitly passed params
function Show-Params {
[CmdletBinding()]
param([string]$A, [int]$B = 99)
$PSBoundParameters
}
Show-Params -A "hello"
Output:
Key Value
--- -----
A hello
$PSCmdlet.ParameterSetName # Active parameter set name
$PSCmdlet.MyInvocation # Same data as $MyInvocation
$PSCmdlet.WriteVerbose("…") # Emit a VERBOSE stream message
$PSCmdlet.WriteDebug("…") # Emit a DEBUG stream message
$PSCmdlet.WriteWarning("…") # Emit a WARNING stream message
$PSCmdlet.WriteError(…) # Emit a non-terminating error
$PSCmdlet.ThrowTerminatingError(…) # Emit a terminating error
Output: (none — method reference)
Execution context ($ExecutionContext)#
$ExecutionContext exposes the host’s EngineIntrinsics object, which gives access to session state, the invocation API, and the path resolver from within a script. It is rarely needed in everyday scripting but is essential when writing host applications, module initializers, or dynamic argument completers.
$ExecutionContext # EngineIntrinsics object
$ExecutionContext.Host # Same object as $Host
$ExecutionContext.SessionState # SessionState — variables, functions, aliases
$ExecutionContext.SessionState.Path # PathIntrinsics (Resolve-Path equivalent)
$ExecutionContext.InvokeCommand # CommandIntrinsics — invoke arbitrary commands
$ExecutionContext.InvokeProvider # ProviderIntrinsics — filesystem, registry, env
Output: (none — property reference; inspect interactively)
# Resolve a path relative to the current location
$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath(".\logs")
Output:
C:\Users\Alice Dev\logs
# List all variables in the current scope via SessionState
$ExecutionContext.SessionState.PSVariable.GetAll() | Select-Object Name | Sort-Object Name
Output:
Name
----
ConfirmPreference
DebugPreference
Error
ErrorActionPreference
…
Preference variables#
Preference variables control how PowerShell output streams — verbose, debug, warning, information, error, and progress — are displayed or suppressed. Accepted values are Continue (show), SilentlyContinue (suppress, no record), Stop (make terminating), Inquire (prompt user), and Ignore (suppress, no $Error entry). Set them locally inside a function to avoid leaking changes into the caller’s scope.
$ErrorActionPreference # Default: "Continue" — non-terminating errors
$WarningPreference # Default: "Continue"
$VerbosePreference # Default: "SilentlyContinue"
$DebugPreference # Default: "SilentlyContinue"
$InformationPreference # Default: "SilentlyContinue" — PS 5+
$ProgressPreference # Default: "Continue" — progress bars
$ConfirmPreference # Default: "High" — ShouldProcess threshold
$WhatIfPreference # Default: $false — enables -WhatIf globally
Output: (none — variable reference)
# Suppress progress bars (speeds up Invoke-WebRequest in scripts)
$ProgressPreference = "SilentlyContinue"
Invoke-WebRequest "https://example.com" -OutFile result.html
$ProgressPreference = "Continue"
Output: (none — file written silently, no progress bar drawn)
# Elevate non-terminating errors to terminating
$ErrorActionPreference = "Stop"
try {
Get-Item "C:\nonexistent"
} catch {
"Caught: $($_.Exception.Message)"
} finally {
$ErrorActionPreference = "Continue"
}
Output:
Caught: Cannot find path 'C:\nonexistent' because it does not exist.
# Enable verbose output without the -Verbose flag
$VerbosePreference = "Continue"
Write-Verbose "Connecting to myhost…"
$VerbosePreference = "SilentlyContinue"
Output:
VERBOSE: Connecting to myhost…
PowerShell 7.x preference and style variables#
PowerShell 7.x introduces several additional preference variables that do not exist in Windows PowerShell 5.1. The most consequential is $PSNativeCommandUseErrorActionPreference (7.3+, on by default in 7.4+) which finally bridges the gap between native-tool exit codes and PowerShell’s $ErrorActionPreference — when $true, any native command that exits non-zero raises an error subject to the usual preference rules. $PSStyle (7.2+) is the typed replacement for raw ANSI escape sequences and lets you change rendering of error text, formatting tables, table headers, and progress bars at runtime.
# Make a failing native command terminate the script
$PSNativeCommandUseErrorActionPreference = $true
$ErrorActionPreference = 'Stop'
try {
robocopy C:\src C:\dst /MIR # robocopy uses non-zero "success" codes — caution!
} catch {
"Native command failed with exit code $LASTEXITCODE"
}
$PSNativeCommandUseErrorActionPreference = $false
Output:
Native command failed with exit code 16
# Inspect and modify $PSStyle (PS 7.2+)
$PSStyle.OutputRendering # Host | PlainText | Ansi
$PSStyle.Formatting.Error # ANSI escape string used for error text
$PSStyle.Formatting.TableHeader # Used in Format-Table headers
$PSStyle.Progress.View # Classic | Minimal (7.2+)
Output:
Host
[31;1m
[32;1m
Minimal
# Force plain-text output (useful when piping to a file or non-ANSI consumer)
$PSStyle.OutputRendering = 'PlainText'
Get-ChildItem | Out-File listing.txt
$PSStyle.OutputRendering = 'Host'
Output: (none — file written without escape codes)
| Variable | Available since | Purpose |
|---|---|---|
$PSStyle | 7.2 | Typed ANSI style controller for errors, formatting, progress |
$PSNativeCommandUseErrorActionPreference | 7.3 (default $true in 7.4+) | Make non-zero native exits respect $ErrorActionPreference |
$PSNativeCommandArgumentPassing | 7.3 | Legacy / Standard / Windows — controls native argv quoting |
$ErrorView | 5.1 (NormalView default), 7.2+ ConciseView default | Switch between NormalView, ConciseView, CategoryView, DetailedView |
$MaximumHistoryCount | 1.0 | Lines retained in Get-History (default 4096) |
Output and formatting variables#
These variables adjust how PowerShell formats collections and joins array items when they are cast to strings. They are infrequently modified but can produce surprising output when inherited from a parent scope.
$OFS # Output field separator; default " " (space)
$FormatEnumerationLimit # Max items in format views before "…"; default 4
$MaximumHistoryCount # Session history buffer size; default 4096
$OutputEncoding # Encoding for native command stdout (System.Text.Encoding)
$PSModuleAutoLoadingPreference # "All" | "None" | "ModuleQualified"
$PSDefaultParameterValues # [hashtable] cmdlet:param → default value
Output: (none — variable reference)
# $OFS changes how arrays render when cast to string
$OFS = ", "
"$( 1..5 )"
$OFS = " " # restore
Output:
1, 2, 3, 4, 5
# $PSDefaultParameterValues — set defaults for any cmdlet
$PSDefaultParameterValues = @{
"Format-Table:AutoSize" = $true
"Invoke-WebRequest:UseBasicParsing" = $true
"Get-ChildItem:Force" = $true
}
# Now every Get-ChildItem call behaves as if you passed -Force
Get-ChildItem C:\Users\Alice Dev\
Output:
(hidden files and folders are now visible)
Common pitfalls#
$nullequality order — always write$null -eq $result, not$result -eq $null; arrays on the left short-circuit incorrectly with the reversed form.$?resets after every statement — capture it on the very next line; any subsequent expression overwrites it.$LASTEXITCODEvs$?for native tools —$?is$trueeven when a native tool returns non-zero; check$LASTEXITCODE -ne 0for executables likegit,python,curl.$PSBoundParametersomits defaults — a parameter with a default value is absent from$PSBoundParametersunless the caller explicitly passed it; never check it for optional-with-default parameters.$Errorpersists session-wide —$Error[0]can be stale from an earlier unrelated command; combine-ErrorVariable myErrwith-ErrorAction SilentlyContinueto capture errors from a specific call.$ProgressPreferencein scripts — leaving it atContinueinside scripts causesInvoke-WebRequestandCopy-Itemto draw TUI progress bars that drastically slow output redirection; always set it toSilentlyContinuein non-interactive scripts.- Modifying
$?accidentally — any expression between your command and the$?check — even a comment-freeif— will reset$?.
Real-world recipes#
Version gate at script start#
#Requires -Version 7
if ($PSVersionTable.PSVersion.Major -lt 7) {
throw "Requires PowerShell 7+. Installed: $($PSVersionTable.PSVersion)"
}
if (-not $IsWindows) { throw "Windows-only script." }
Output: (none — throws on version mismatch)
Script-relative config loading#
Load a JSON config from the same directory as the running script, regardless of where the caller’s working directory is.
$configPath = Join-Path $PSScriptRoot "config.json"
$config = Get-Content $configPath -Raw | ConvertFrom-Json
Write-Host "Loaded config from $configPath"
Output:
Loaded config from C:\scripts\config.json
Splat only caller-supplied parameters#
Forward a subset of the caller’s parameters to a child cmdlet without accidentally including defaults or internal switches.
function Invoke-Deploy {
[CmdletBinding()]
param([string]$Environment, [string]$Version, [switch]$DryRun)
$passThrough = $PSBoundParameters.Clone()
$passThrough.Remove("DryRun") | Out-Null
if ($DryRun) { $passThrough["WhatIf"] = $true }
Start-Deployment @passThrough
}
Output: (none — function definition)
Silent parallel web downloads#
Suppress progress bars and stop on first error across multiple downloads.
$ProgressPreference = "SilentlyContinue"
$ErrorActionPreference = "Stop"
$urls = @(
"https://example.com/file1.csv",
"https://example.com/file2.csv"
)
try {
$urls | ForEach-Object -Parallel {
Invoke-WebRequest $_ -OutFile (Split-Path $_ -Leaf)
"Downloaded $_"
} -ThrottleLimit 4
} catch {
"Failed: $($_.Exception.Message)"
} finally {
$ProgressPreference = "Continue"
$ErrorActionPreference = "Continue"
}
Output:
Downloaded https://example.com/file1.csv
Downloaded https://example.com/file2.csv
Sources#
- Microsoft Learn — about_Automatic_Variables (7.6) — authoritative list of every automatic variable in the 7.6 LTS branch.
- Microsoft Learn — about_Preference_Variables (7.6) — covers
$PSNativeCommandUseErrorActionPreference,$PSStyle, and the default-changes from earlier versions. - Microsoft Learn — about_ANSI_Terminals — full surface of the
$PSStyletyped controller. - Microsoft Learn — What’s New in PowerShell 7.6 — release notes for the LTS branch backing these defaults.