Powershell Variable Scoping

Confusion caused by variable scoping is a constant source of errors in Powershell. Most importantly, when is a variable visible, but any changes aren’t visible outside of the current scope. This is especially common with inner functions.

Here is an example, declaring a variable in an outer function makes it visible to the inner function. But it is passed by copy, so updating it in the inner function does not make changes visible to the outer function.

function Outer
{
    $var1 = 1;
    
    function Inner
    {
        Write-Output "Inner: $var1"

        # This change will not be visible in the outer function
        $var1++;
        Write-Output "Inner: $var1"
    }

    Write-Output "Outer: $var1"
    Inner
    Write-Output "Outer: $var1"
}
Outer

# Output: 
# Outer: 1
# Inner: 1
# Inner: 2
# Outer: 1

One way to fix that, is to declare the variable either “AllScope”, or in the global namespace. “AllScope” would be more advisable, since AllScope variables are available in all child scopes, as opposed to everywhere.

function Outer
{
    $var1 = 1
    Set-Variable -Name allscope -Value 1 -Option AllScope
    $global:var3 = 1
    
    function Inner
    {
        Write-Output "Inner: var1:$var1 allscope:$allscope var3:$global:var3"

        # This change will not be visible in the outer function
        $var1++;
        $allscope++;
        $global:var3++;
        Write-Output "Inner: var1:$var1 allscope:$allscope var3:$global:var3"
    }

    Write-Output "Outer: var1:$var1 allscope:$allscope var3:$global:var3"
    Inner
    Write-Output "Outer: var:$var1 allscope:$allscope var3:$global:var3"
}
Outer

# Outputs:
# Outer: var1:1 allscope:1 var3:1
# Inner: var1:1 allscope:1 var3:1
# Inner: var1:2 allscope:2 var3:2
# Outer: var:1 allscope:2 var3:2

Here is a program which executes to show a bunch of other behaviors when it commends to appending values to an array.

$Global:GlobalLevelArray = @("Script!");
$ScriptLevelArray = @("Script!");

function OuterFunc
{
    param
    (
        $OuterFuncArrayArg
    )

    process
    {
        # Changes to global variables will always be visible
        $Global:GlobalLevelArray = $Global:GlobalLevelArray + "OuterFunc";

        # Notice that this will update the array at this scope,
        # but not outside of this function.
        $ScriptLevelArray = $ScriptLevelArray + "OuterFunc";

        # This will update the array at this scope,
        # but changes will not be accessible to the caller.
        $OuterFuncArrayArg = $OuterFuncArrayArg + "OuterFunc";

        # This variable is visible at this level, and to child functions.
        $OuterFuncArray = @("OuterFunc");

        # A variable at the local scope is the current scope, but will not be available to the child scope.
        $local:OuterFuncLocalArray = @("OuterFunc");

        # This variable is available an modifieable to all child functions
        # Changes by the child will be reflected in here.
        Set-Variable -Name OuterFuncAllScopes -Value @("OuterFunc") -Option AllScope

        function NestedFunc
        {
            param
            (
            )
            process
            {
                $Global:GlobalLevelArray = $Global:GlobalLevelArray + "NestedFunc";
                $ScriptLevelArray = $ScriptLevelArray + "NestedFunc";
                $OuterFuncArrayArg = $OuterFuncArrayArg + "NestedFunc";
                $OuterFuncArray = $OuterFuncArray + "NestedFunc";
                $local:OuterFuncLocalArray = $local:OuterFuncLocalArray + "NestedFunc";
                $OuterFuncAllScopes = $OuterFuncAllScopes + "NestedFunc";

                # This, even though its declared all the way down in this nested func, will be available everywhere.
                $global:NestedFuncGlobalArray = @("NestedFunc")

                Write-Output "`t`tNestedFunc:GlobalLevelArray -> $($Global:GlobalLevelArray -join ',')"
                Write-Output "`t`tNestedFunc:ScriptLevelArray -> $($ScriptLevelArray -join ',')"
                Write-Output "`t`tNestedFunc:OuterFuncArrayArg -> $($OuterFuncArrayArg -join ',')"
                Write-Output "`t`tNestedFunc:OuterFuncArray -> $($OuterFuncArray -join ',')"
                Write-Output "`t`tNestedFunc:OuterFuncLocalArray -> $($local:OuterFuncLocalArray -join ',')"
                Write-Output "`t`tNestedFunc:OuterFuncAllScopes -> $($OuterFuncAllScopes -join ',')"
                Write-Output "`t`tNestedFunc:NestedFuncGlobalArray -> $($global:NestedFuncGlobalArray -join ',')"
            }
        }

        NestedFunc

        $global:NestedFuncGlobalArray = $global:NestedFuncGlobalArray + "OuterFunc";

        Write-Output "`tOuterFunc:ScriptLevelArray -> $($ScriptLevelArray -join ',')"
        Write-Output "`tOuterFunc:OuterFuncArrayArg -> $($OuterFuncArrayArg -join ',')"
        Write-Output "`tOuterFunc:GlobalLevelArray -> $($Global:GlobalLevelArray -join ',')"
        Write-Output "`tOuterFunc:OuterFuncArray -> $($OuterFuncArray -join ',')"
        Write-Output "`tOuterFunc:OuterFuncLocalArray -> $($local:OuterFuncArray -join ',')"
        Write-Output "`tOuterFunc:OuterFuncAllScopes -> $($OuterFuncAllScopes -join ',')"
        Write-Output "`tOuterFunc:NestedFuncGlobalArray -> $($global:NestedFuncGlobalArray -join ',')"
    }
}


OuterFunc -OuterFuncArray $ScriptLevelArray

$global:NestedFuncGlobalArray = $global:NestedFuncGlobalArray + "Script!";


Write-Output "Script:ScriptLevelArray -> $($ScriptLevelArray -join ',')"
Write-Output "Script:GlobalLevelArray -> $($GlobalLevelArray -join ',')"

Write-Output "Script:OuterFuncLocalArray -> $($local:OuterFuncArray -join ',')"

# An AllScopes variable in a child scope will not be visible in the parent scope
Write-Output "Script:OuterFuncAllScopes -> $($OuterFuncAllScopes -join ',')"
Write-Output "Script:NestedFuncGlobalArray -> $($global:NestedFuncGlobalArray -join ',')"

The output of this progrom:

		NestedFunc:GlobalLevelArray -> Script!,OuterFunc,NestedFunc
		NestedFunc:ScriptLevelArray -> Script!,OuterFunc,NestedFunc
		NestedFunc:OuterFuncArrayArg -> Script!,OuterFunc,NestedFunc
		NestedFunc:OuterFuncArray -> OuterFunc,NestedFunc
		NestedFunc:OuterFuncLocalArray -> NestedFunc
		NestedFunc:OuterFuncAllScopes -> OuterFunc,NestedFunc
		NestedFunc:NestedFuncGlobalArray -> NestedFunc
	OuterFunc:ScriptLevelArray -> Script!,OuterFunc
	OuterFunc:OuterFuncArrayArg -> Script!,OuterFunc
	OuterFunc:GlobalLevelArray -> Script!,OuterFunc,NestedFunc
	OuterFunc:OuterFuncArray -> OuterFunc
	OuterFunc:OuterFuncLocalArray -> OuterFunc
	OuterFunc:OuterFuncAllScopes -> OuterFunc,NestedFunc
	OuterFunc:NestedFuncGlobalArray -> NestedFunc,OuterFunc
Script:ScriptLevelArray -> Script!
Script:GlobalLevelArray -> Script!,OuterFunc,NestedFunc
Script:OuterFuncLocalArray -> 
Script:OuterFuncAllScopes -> 
Script:NestedFuncGlobalArray -> NestedFunc,OuterFunc,Script!

Leave a Reply

Your email address will not be published. Required fields are marked *