Skip to content

PowerShell Local Variables in Functions

How PowerShell functions handle local variables, scope shadowing, and explicit scope modifiers like $script: and $global: to control variable visibility and mutability.

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 .ps1 script 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:)            | | |
|   |   |                                           | | |
|   |   +-------------------------------------------+ | |
|   +-------------------------------------------------+ |
+-------------------------------------------------------+
scope-basics.ps1powershell
function Set-LocalVariable {
    $number = 10
    Write-Host "Inside function: $number"
}

Set-LocalVariable
Write-Host "Outside function: $number"

Where:

  • $number = 10 creates 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.
Consolepowershell
./scope-basics.ps1
Outputplaintext
Inside 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:

variable-shadowing.ps1powershell
$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-EnvironmentContext calls the function to execute its code block.
  • Write-Host "[Script Root]..." evaluates $environment in the script scope after the function runs, confirming its original value is intact.
Consolepowershell
./variable-shadowing.ps1
Outputplaintext
[Script Root] Environment before function: Production
[Function Start] Inherited environment: Production
[Function Body] Local environment set to: Staging
[Script Root] Environment after function: Production

A simpler demonstration:

shadowing-example.ps1powershell
$value = "Outer Value"

function Show-Shadowing {
    $value = "Inner Value"
    Write-Host "Inside function: $value"
}

Show-Shadowing
Write-Host "Outside function: $value"
Outputplaintext
Inside function: Inner Value
Outside function: Outer Value

Scope 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 .ps1 file.
  • $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-scope.ps1powershell
$script:counter = 0

function Add-Count {
    $script:counter++
    Write-Host "Counter is now: $script:counter"
}

Add-Count
Add-Count
Add-Count

Where:

  • $script:counter = 0 declares 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.
Outputplaintext
Counter is now: 1
Counter is now: 2
Counter is now: 3

The $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.

global-scope.ps1powershell
function Set-GlobalGreeting {
    $global:greeting = "Hello from the function!"
}

Set-GlobalGreeting
Write-Host $greeting

Where:

  • $global:greeting assigns a variable to the global scope, making it visible outside the script/function that created it.
  • Write-Host $greeting reads the global variable directly, without any prefix needed once it exists.
Outputplaintext
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:

scope-modifiers.ps1powershell
$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 = 0 creates $counter explicitly in the script scope.
  • $global:sessionAlert = "Nominal" creates $sessionAlert in the global environment.
  • $counter = 99 creates a local variable that exists only while Update-Counters executes.
  • $script:counter++ uses the increment operator (++) directly on the script-scoped variable, incrementing it from 0 to 1.
  • $global:sessionAlert = "Investigating" overwrites the global session state from within the local function.
Consolepowershell
./scope-modifiers.ps1
Outputplaintext
Before Function -> Script Counter: 0 | Global Alert: Nominal
Inside Function -> Local Counter: 99 | Script Counter: 1
After Function  -> Script Counter: 1 | Global Alert: Investigating

Forcing Access to an Outer Variable

To deliberately modify a script-scoped variable from within a function (bypassing shadowing), use the $script: prefix explicitly.

avoid-shadowing.ps1powershell
$script:value = "Outer Value"

function Update-Value {
    $script:value = "Updated by function"
}

Update-Value
Write-Host $value
Outputplaintext
Updated by function

Practical 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:

track-tasks.ps1powershell
$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-ExecutionSummary

Where:

  • [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.
  • $timestamp is a local variable created and destroyed on each invocation of Invoke-TaskStep.
  • $script:ExecutionLog.Add($entry) directly references the parent script's list object, ensuring all function calls append to the same memory buffer.
  • Show-ExecutionSummary is a secondary function that iterates over the shared $script:ExecutionLog without needing it passed as an argument.
Consolepowershell
./track-tasks.ps1
Outputplaintext

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