12

I was given this extensive PowerShell script from a major/reputable corporation which was supposed to work flawlessly. Well, it didn't.
The script is composed of numerous nested functions with lots of variables passed to the main parent function and then to all of its children. Children who use and modify all these variables.

Why is it that all these variables do not contain the correct data?
Here's the structure I'm talking about:

f1 {
     f2 {
          v #prints 0
          v = 1
          f3
     }
     f3 {
          v #prints 1
          v = 2
     }
     v = 0
     f2
     v #should print 2 but prints 0
}

3 Answers 3

23

Note:

This was tested with PowerShell 3.0 and 4.0.

Within nested functions, all child functions have access to all of the parent functions' variables. Any changes to the variables are visible within the local scope of the current function and all the nested child functions called afterwards. When the child function has finished execution, the variables will return to the original values before the child function was called.

In order to apply the variable changes throughout all the nested functions scopes, the variable scope type needs to be changed to AllScope:

Set-Variable -Name varName -Option AllScope

This way, no matter at what level within the nested functions the variable gets modified, the change is persistent even after the child function terminates and the parent will see the new updated value.


Normal behavior of variable scopes within Nested Functions:

function f1 ($f1v1 , $f1v2 )
{
        function f2 ()
       {
               $f2v = 2
               $f1v1 = $f2v #local modification visible within this scope and to all its children
               f3
               "f2 -> f1v2 -- " + $f1v2 #f3's change is not visible here
       }
        function f3 ()
       {
               "f3 -> f1v1 -- " + $f1v1 #value reflects the change from f2
               $f3v = 3
               $f1v2 = $f3v #local assignment will not be visible to f2
               "f3 -> f1v2 -- " + $f1v2
       }
       
        f2
        "f1 -> f1v1 -- " + $f1v1 #the changes from f2 are not visible
        "f1 -> f1v2 -- " + $f1v2 #the changes from f3 are not visible
}

f1 1 0

Printout:

f3 -> f1v1 -- 2
f3 -> f1v2 -- 3
f2 -> f1v2 -- 0
f1 -> f1v1 -- 1
f1 -> f1v2 -- 0

Nested Functions with AllScope variables:

function f1($f1v1, $f1v2)
{
    Set-Variable -Name f1v1,f1v2 -Option AllScope
    function f2()
    {
        $f2v = 2
        $f1v1 = $f2v #modification visible throughout all nested functions
        f3
        "f2 -> f1v2 -- " + $f1v2 #f3's change is visible here
    }
    function f3()
    {
        "f3 -> f1v1 -- " + $f1v1 #value reflects the change from f2
        $f3v = 3
        $f1v2 = $f3v #assignment visible throughout all nested functions
        "f3 -> f1v2 -- " + $f1v2 
    }
    
    f2
    "f1 -> f1v1 -- " + $f1v1 #reflects the changes from f2 
    "f1 -> f1v2 -- " + $f1v2 #reflects the changes from f3 
}

f1 1 0

Printout:

f3 -> f1v1 -- 2
f3 -> f1v2 -- 3
f2 -> f1v2 -- 3
f1 -> f1v1 -- 2
f1 -> f1v2 -- 3
Sign up to request clarification or add additional context in comments.

4 Comments

I find your answer marvelous and am very grateful for it. But, about your statement, "When the child function has finished execution, the variables will return to the original values before the child function was called", I have some doubts. I thought variables inside nested scope will be newly generated locally only with the same values as the variables with the same name in the parent scope. Is it the true mechanism that variables inside the parent scope are manipulated inside the nested scope and get back to the initial values? I think it is a wasteful process.
This does not work. Your second example using Allscope returns 1,3,0,1,0
@ChrisA What version have you tried this with? This was written originally for PowerShell 3 or 4. I haven't followed the version changes ever since, but I know there have been some big changes between 5 and 7. Including the removal of AllScope.
See @Darin answer for PS v5+.
1

Alternative Solution for Later Versions of Powershell:

The accepted answer fails in PowerShell 7.3.9, and probably fails in all versions of 6.x.x and 7.x.x. The following code uses Set-Variable instead, but it is important to note that the value for the -Scope parameter must reflect the call depth of the nested function.

In the question's example, setting -Scope to 2 is required for function f3 to properly modify the variable v within its grandparent scope, i.e., the scope of function f1, which is two levels up. To clarify, f3 is called by f2, which in turn is called by f1. Using -Scope 1 in f3 would mistakenly access f2's scope, which would then create a new variable v in f2 instead of modifying the existing variable v in f1.

function f1 {
     function f2 {
          $v #prints 0
          Set-Variable -Name 'v' -Value 1 -Scope 1
          f3
     }
     function f3 {
          $v #prints 1
          Set-Variable -Name 'v' -Value 2 -Scope 2
     }
     $v = 0
     f2
     $v #prints 2
}

f1

Results:

0
1
2

1 Comment

The accepted answer did not work for me in PS v5 either, only this method did.
0

Alternative Solution - Executing Child Functions in Parent Scope

In another SO question mklement0 commented "functions and script files are just named script blocks". This statement appears to be 100% true. This means that functions can be dot sourced, which in turn means a function can dot source its child functions, and thus cause the child functions to operate in the parent function's scope.

function f1 {
     function f2 {
          $v #prints 0
          $v = 1
          . f3
     }
     function f3 {
          $v #prints 1
          $v = 2
     }
     $v = 0
     . f2
     $v #prints 2
}

f1

Results:

0
1
2

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.