skip to content

PowerShell Built-in Variables — Automatic & Preference Variables

Complete reference for every automatic and preference variable built into PowerShell — $_, $Error, $MyInvocation, $PSCmdlet, $Host, $PSVersionTable, $ExecutionContext, and all preference variables that tune runtime behaviour.

12 min read 55 snippets deep dive

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)

VariableAvailable sincePurpose
$PSStyle7.2Typed ANSI style controller for errors, formatting, progress
$PSNativeCommandUseErrorActionPreference7.3 (default $true in 7.4+)Make non-zero native exits respect $ErrorActionPreference
$PSNativeCommandArgumentPassing7.3Legacy / Standard / Windows — controls native argv quoting
$ErrorView5.1 (NormalView default), 7.2+ ConciseView defaultSwitch between NormalView, ConciseView, CategoryView, DetailedView
$MaximumHistoryCount1.0Lines 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#

  1. $null equality order — always write $null -eq $result, not $result -eq $null; arrays on the left short-circuit incorrectly with the reversed form.
  2. $? resets after every statement — capture it on the very next line; any subsequent expression overwrites it.
  3. $LASTEXITCODE vs $? for native tools$? is $true even when a native tool returns non-zero; check $LASTEXITCODE -ne 0 for executables like git, python, curl.
  4. $PSBoundParameters omits defaults — a parameter with a default value is absent from $PSBoundParameters unless the caller explicitly passed it; never check it for optional-with-default parameters.
  5. $Error persists session-wide$Error[0] can be stale from an earlier unrelated command; combine -ErrorVariable myErr with -ErrorAction SilentlyContinue to capture errors from a specific call.
  6. $ProgressPreference in scripts — leaving it at Continue inside scripts causes Invoke-WebRequest and Copy-Item to draw TUI progress bars that drastically slow output redirection; always set it to SilentlyContinue in non-interactive scripts.
  7. Modifying $? accidentally — any expression between your command and the $? check — even a comment-free if — 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#