Every variable in PowerShell exists within an execution scope, a boundary that dictates its visibility, lifetime, and mutability across functions, scripts, and sessions. By default, functions inherit visibility of parent variables, but assigning a value inside a function creates a local variable that hides outer definitions, a behavior known as variable shadowing. Using explicit scope modifiers like $script: and $global: allows scripts to read and modify parent states safely and intentionally. Understanding scope is essential when writing functions, since variables created inside a function are normally isolated from the rest of the script.
PowerShell Scope Fundamentals
PowerShell uses a nested, hierarchical scoping model. When a command references a variable, PowerShell searches for that variable starting in the current scope and traverses upward through parent scopes until it finds a match.
The three primary scopes used in scripting are:
- Global Scope (
$global:): The baseline session scope. Variables defined in the global scope are accessible anywhere within the current PowerShell session, including inside any functions, modules, or child scripts. - Script Scope (
$script:): Created automatically when a.ps1script file executes. Variables created at the root level of a script file belong to this scope and are discarded when the script finishes execution. They are accessible across functions defined within the same script file. - Local Scope (
$local:): The current running context. Inside a function, the local scope contains variables defined within that function's body. Child scopes cannot modify parent variables by default.
+-------------------------------------------------------+
| Global Scope ($global:) |
| +-------------------------------------------------+ |
| | Script Scope ($script:) | |
| | +-------------------------------------------+ | |
| | | Local Function Scope ($local:) | | |
| | | | | |
| | +-------------------------------------------+ | |
| +-------------------------------------------------+ |
+-------------------------------------------------------+function Set-LocalVariable {
$number = 10
Write-Host "Inside function: $number"
}
Set-LocalVariable
Write-Host "Outside function: $number"Where:
$number = 10creates a variable in the function's local scope.Write-Host "Outside function: $number"attempts to access the variable outside the function, where it doesn't exist.
./scope-basics.ps1Inside function: 10
Outside function:Variable Shadowing in Functions
Variable shadowing occurs when a variable declared inside a function has the same name as one in an outer scope. When you read a variable inside a function without defining it locally, PowerShell reads the value from the nearest parent scope. However, if you assign a value to that variable name inside the function, PowerShell does not update the parent variable; instead, it creates a new local variable that masks or shadows the parent variable for the remainder of that function's execution. The local variable temporarily "shadows" the outer one, so modifications inside the function don't affect the original unless explicitly scoped.
Observing Variable Shadowing
The following script illustrates how local assignment protects parent variables from accidental modification:
$environment = "Production"
function Set-EnvironmentContext {
Write-Host "[Function Start] Inherited environment: $environment"
$environment = "Staging"
Write-Host "[Function Body] Local environment set to: $environment"
}
Write-Host "[Script Root] Environment before function: $environment"
Set-EnvironmentContext
Write-Host "[Script Root] Environment after function: $environment"Where:
$environment = "Production"declares a variable in the script scope.Write-Host "[Function Start]..."accesses$environment. Because no local variable has been created inside the function yet, PowerShell looks up to the parent script scope and retrieves"Production".$environment = "Staging"creates a new variable inside the function's local scope with the name$environment. It does not modify the outer variable.Set-EnvironmentContextcalls the function to execute its code block.Write-Host "[Script Root]..."evaluates$environmentin the script scope after the function runs, confirming its original value is intact.
./variable-shadowing.ps1[Script Root] Environment before function: Production
[Function Start] Inherited environment: Production
[Function Body] Local environment set to: Staging
[Script Root] Environment after function: ProductionA simpler demonstration:
$value = "Outer Value"
function Show-Shadowing {
$value = "Inner Value"
Write-Host "Inside function: $value"
}
Show-Shadowing
Write-Host "Outside function: $value"Inside function: Inner Value
Outside function: Outer ValueScope Modifiers ($script: and $global:)
To bypass variable shadowing and interact directly with a specific tier in the scope hierarchy, prepend a scope modifier to the variable name.
Common scope prefixes include:
$local:variableName: Explicitly references or creates the variable in the current local scope.$script:variableName: References or creates the variable in the parent script file scope, shared across all functions within that.ps1file.$global:variableName: References or creates the variable in the engine-level session scope, accessible by any script, module, or CLI command.
The $script: Prefix
The $script: prefix allows a variable to be shared across all functions within the same script file, without needing to pass it as a parameter each time.
$script:counter = 0
function Add-Count {
$script:counter++
Write-Host "Counter is now: $script:counter"
}
Add-Count
Add-Count
Add-CountWhere:
$script:counter = 0declares a variable in the script scope, accessible by any function in the same file.$script:counter++modifies the script-scoped variable from within the function.
Counter is now: 1
Counter is now: 2
Counter is now: 3The $global: Prefix
The $global: prefix makes a variable accessible across the entire PowerShell session, including from the console, other scripts you dot-source, and any function.
function Set-GlobalGreeting {
$global:greeting = "Hello from the function!"
}
Set-GlobalGreeting
Write-Host $greetingWhere:
$global:greetingassigns a variable to the global scope, making it visible outside the script/function that created it.Write-Host $greetingreads the global variable directly, without any prefix needed once it exists.
Hello from the function!Writing to Script and Global Scopes Together
The following script shows using both $script: and $global: modifiers to manipulate variables across scope boundaries:
$script:counter = 0
$global:sessionAlert = "Nominal"
function Update-Counters {
$counter = 99
$script:counter++
$global:sessionAlert = "Investigating"
Write-Host "Inside Function -> Local Counter: $counter | Script Counter: $script:counter"
}
Write-Host "Before Function -> Script Counter: $script:counter | Global Alert: $global:sessionAlert"
Update-Counters
Write-Host "After Function -> Script Counter: $script:counter | Global Alert: $global:sessionAlert"Where:
$script:counter = 0creates$counterexplicitly in the script scope.$global:sessionAlert = "Nominal"creates$sessionAlertin the global environment.$counter = 99creates a local variable that exists only whileUpdate-Countersexecutes.$script:counter++uses the increment operator (++) directly on the script-scoped variable, incrementing it from0to1.$global:sessionAlert = "Investigating"overwrites the global session state from within the local function.
./scope-modifiers.ps1Before Function -> Script Counter: 0 | Global Alert: Nominal
Inside Function -> Local Counter: 99 | Script Counter: 1
After Function -> Script Counter: 1 | Global Alert: InvestigatingForcing Access to an Outer Variable
To deliberately modify a script-scoped variable from within a function (bypassing shadowing), use the $script: prefix explicitly.
$script:value = "Outer Value"
function Update-Value {
$script:value = "Updated by function"
}
Update-Value
Write-Host $valueUpdated by functionPractical Usage: Managing Internal Function State
A standard administrative pattern is using script-level variables to track shared state across multiple utility functions within an automation tool without leaking variables into the user's global session.
Maintaining Execution Logs Across Multiple Invocations
The following script uses $script: scope to maintain a central execution history across multiple function calls:
$script:ExecutionLog = [System.Collections.Generic.List[string]]::new()
function Invoke-TaskStep {
param (
[string]$TaskName,
[switch]$Failed
)
$timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
if ($Failed) {
$entry = "[$timestamp] FAILURE: Task '$TaskName' encountered an error."
} else {
$entry = "[$timestamp] SUCCESS: Task '$TaskName' completed successfully."
}
$script:ExecutionLog.Add($entry)
}
function Show-ExecutionSummary {
Write-Host "`n--- Script Execution History ---" -ForegroundColor Cyan
foreach ($logEntry in $script:ExecutionLog) {
Write-Host $logEntry
}
}
Invoke-TaskStep -TaskName "Verify Network Share"
Invoke-TaskStep -TaskName "Download Source Archive"
Invoke-TaskStep -TaskName "Extract Archive" -Failed
Invoke-TaskStep -TaskName "Notify Administrator"
Show-ExecutionSummaryWhere:
[System.Collections.Generic.List[string]]::new()instantiates a strongly-typed collection in the script scope to store log strings.param ([string]$TaskName, [switch]$Failed)defines input parameters for the step function.$timestampis a local variable created and destroyed on each invocation ofInvoke-TaskStep.$script:ExecutionLog.Add($entry)directly references the parent script's list object, ensuring all function calls append to the same memory buffer.Show-ExecutionSummaryis a secondary function that iterates over the shared$script:ExecutionLogwithout needing it passed as an argument.
./track-tasks.ps1
--- Script Execution History ---
[2026-09-28 00:03:06] SUCCESS: Task 'Verify Network Share' completed successfully.
[2026-09-28 00:03:06] SUCCESS: Task 'Download Source Archive' completed successfully.
[2026-09-28 00:03:06] FAILURE: Task 'Extract Archive' encountered an error.
[2026-09-28 00:03:06] SUCCESS: Task 'Notify Administrator' completed successfully.Choosing the Right Scope
| Scenario | Recommended Scope |
|---|---|
| Temporary calculation inside a function | Local (default) |
| Shared counter/state across functions in one script | $script: |
| Configuration value needed session-wide | $global: |
| Passing data into a function | Parameters (param()) |
| Returning data from a function | return or implicit output |
Quick Reference
| Concept | Syntax | Behavior |
|---|---|---|
| Local scope | $var = "value" |
Exists only within the current function; discarded when function exits. |
| Script scope | $script:var = "value" |
Shared across all functions in the same .ps1 file; discarded when script finishes. |
| Global scope | $global:var = "value" |
Available throughout the entire PowerShell session. |
| Explicit local | $local:var = "value" |
Explicitly references or creates a variable in the current local scope. |
| Variable shadowing | $var = "value" inside function |
Creates a new local variable that hides the parent variable. |
| Read parent variable | $var (no prefix) |
Reads the nearest parent scope's variable if no local exists. |
| Modify parent variable | $script:var or $global:var |
Bypasses shadowing and modifies the specific scope directly. |