38

I'm fairly new to powershell, and I'm just not getting how to modify a variable in a parent scope:

$val = 0
function foo()
{
    $val = 10
}
foo
write "The number is: $val"

When I run it I get:

The number is: 0

I would like it to be 10. But powershell is creating a new variable that hides the one in the parent scope.

I've tried these, with no success (as per the documentation):

$script:$val = 10
$global:$val = 10
$script:$val = 10

But these don't even 'compile' so to speak. What am I missing?

5 Answers 5

45

You don't need to use the global scope. A variable with the same name could have been already exist in the shell console and you may update it instead. Use the script scope modifier. When using a scope modifier you don't include the $ sign in the variable name.

$script:val=10 
Sign up to request clarification or add additional context in comments.

Comments

29

The parent scope can actually be modified directly with Set-Variable -Scope 1 without the need for Script or Global scope usage. Example:

$val = 0
function foo {
    Set-Variable -scope 1 -Name "Val" -Value "10"
}
foo
write "The number is: $val"

Returns:

The number is: 10

More information can be found in the Microsoft Docs article About Scopes. The critical excerpt from that doc:

Note: For the cmdlets that use the Scope parameter, you can also refer to scopes by number. The number describes the relative position of one scope to another. Scope 0 represents the current, or local, scope. Scope 1 indicates the immediate parent scope. Scope 2 indicates the parent of the parent scope, and so on. Numbered scopes are useful if you have created many recursive scopes.


Be aware that recursive functions require the scope to be adjusted accordingly:

$val = ,0
function foo {
    $b = $val.Count
    Set-Variable -Name 'val' -Value ($val + ,$b) -Scope $b
    if ($b -lt 10) {
        foo
    }
}

4 Comments

This is the answer closest to the question as precisely the variable in the immediate parent scope is accessed.
Does this still work? What does -scope 1 mean? It's not documented in the About_Scopes page anymore, but it seems interesting if you can specify a specific tier of scope, or is the 1 just an alias for script/global?
It does work. I didn't even know there was a -Scope 1 option... In case anyone is wondering it refers to the parent scope (0 being the current scope). Find your variable first using Get-Variable -Scope x -Name val.
@Blaisem It's possible it disappeared, but if so, they've put it back in. I had to search to page contents for "Scope 1" to find it, but the documentation says, "Scope 1 indicates the immediate parent scope. Scope 2 indicates the parent of the parent scope, and so on. Numbered scopes are useful if you have created many recursive scopes." I think that's critical enough information that I'm going to edit it into the answer.
11

Let me point out a third alternative, even though the answer has already been made. If you want to change a variable, don't be afraid to pass it by reference and work with it that way.

$val=1
function bar ($lcl) 
{
    write "In bar(), `$lcl.Value starts as $($lcl.Value)"
    $lcl.Value += 9
    write "In bar(), `$lcl.Value ends as $($lcl.Value)"
}
$val
bar([REF]$val)
$val

That returns:

1
In bar(), $lcl.Value starts as 1
In bar(), $lcl.Value ends as 10
10

2 Comments

I know this is super old but this is exactly the answer I was looking for. Very very much appreciated!
Better to be explicit about accepting a reference in the function signature with function bar ([ref]$lcl) instead of only accessing $lcl.Value in the function body.
8

If you want to use this then you could do something like this:

$global:val=0 
function foo()
{
    $global:val=10 
}
foo
write "The number is: $val"

3 Comments

And the first line can just be $val = 0 since it's implicitly global scope.
That doesn't work. I get: Unexpected token 'val' in expression or statement.
Make sure that you have $global:val and not $global:$val
3

Perhaps the easiest way is to dot source the function:

$val = 0
function foo()
{
    $val = 10
}
. foo
write "The number is: $val"

The difference here being that you call foo via . foo.

Dot sourcing runs the function as normal, but it runs inside the parent scope, so there is no child scope. This basically removes scoping. It's only an issue if you start setting or overwriting variables/definitions in the parent scope unintentionally. For small scripts this isn't usually the case, which makes dot sourcing really easy to work with.

If you only want a single variable, then you can use a return value, e.g.,

$val = 0
function foo()
{
    return 10
}
$val = foo
write "The number is: $val"

(Or without the return, as it's not necessary in a function)

You can also return multiple values to set multiple variables in this manner, e.g., $a, $b, $c = foo if foo returned 1,2,3.

The above approaches are my preferred way to handle cross-scope variables.

As a last alternative, you can also place the write in the function itself, so it's writing the variable in the same scope as it's being defined.

Of course, you can also use the scope namespace solutions, Set-Variable by scope, or pass it via [ref] as demonstrated by others here. There are many solutions in PowerShell.

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.