Variable changes inside Functions do not persist

by arnoldus at 2012-08-26 09:44:43

I don’t understand how you control what a function is allowed to do with the variables.

In my functions (that are on the same script file as the main script), changes to variables inside a function only persist until the function ends, then the vars return to their old values.
How can I modify this behaviour?

I don’t use functions as a black box, I use them as a subroutine, a fast way to do something repetitive. But changes made must then persist.
by poshoholic at 2012-08-26 19:01:06
If you want to persist variable changes from a child scope (e.g. from inside of a function or a script block) into a parent scope, then you need to specifically identify the scope.

For example, look at this script which demonstrates the difference between assigning a new local variable inside a function (even if it has the same name as a variable outside of a function) and deliberately assigning a value to a variable in a parent scope:

[script=powershell]$TestVariable = 'Original value'

function Test-Persist {
if ($UseScope) {
# This sets the value of $TestVariable in the parent (1) scope. 0 = current scope, 1 = parent, 2 = grandparent, etc.
Set-Variable -Name TestVariable -Scope 1 -Value 'Changed in Test-Persist'
} else {
# This creates a new $TestVariable variable in the child scope that has nothing to do with the $TestVariable variable in the parent scope.
$TestVariable = 'Changed in Test-Persist'

"Value before first test: $TestVariable"
"Value after simple assignment from inside Test-Persist: $TestVariable"
Test-Persist -UseScope
"Value after deliberate assignment with scope from inside Test-Persist: $TestVariable"[/script]

I think you should be able to extract what you need to do from that example. If not, please let me know.
by arnoldus at 2012-08-27 01:41:28
That does not really help AFAIS, you can not do arithmetic with the previous value, only set a value (yeah, you could import all variables as params and do changes on those and then set them before exitting, but then it’s just simpler doing everything in the main body).

I guess I need to pull the functions back into the main body of code.

EDIT: dot source!
Dot space function: ". myfunction", and I got what I need.
by JeffH at 2012-08-27 04:20:23
Scope is a topic that trips up many people. If you haven’t already, read the about topic on Scope.
by DexterPOSH at 2012-08-27 07:53:14
Hi Arnoldus,
I think Kirk explained it very well,
Child is able to see the variables in Parent Scope but to modify it you need to explicitly tell that you are modifying variable of the Parent Scope.
That’s what line 9 in the code is doing.
Set-Variable -Name TestVariable -Scope 1 -Value ‘Changed in Test-Persist’
by mjolinor at 2012-08-30 16:07:35
Variables created within a scope only persist as long as the scope. Operations that only update a property or element of an existing object do not create a new object. Many times the kind of scope problems you describe can be solved quite simply by switching to using a hash table to hold your values, rather than discrete variables:

$MyVar = @{
Counter = 0
Sum = 0
Names = @()
LastRun = $nul

function update {
$MyVar.Counter ++
$MyVar.Sum += 10
$MyVar.Names += ‘Don’,‘Jeff’,‘Kirk’
$MyVar.LastRun = (get-date)

by DonJ at 2012-08-30 16:18:52
I’d strongly suggest reading the about_scope help topic in the shell.

It isn’t a question of the "variables returning to their original values." When you assign a variable inside a function, the shell creates a new variable and assigns the value. If that variable name was already in use outside the function, then the in-function variable essentially "masks" the out-of-function version. When the function ends, everything created inside the function goes away. It’s a bit more subtle in terms of behavior than you might realize.

As a general practice, functions should avoid modifying variables outside themselves. There are some reasons to do so, but in general a function should pass values out as via the pipeline. In other words, rather than this:

$x = 5
function mine {
set-variable -name x -scope 1 -value ($x + 1)

You should do this:

$x = 5
function mine {
$in_x = $in_x + 1
write-output $x_in
$result = mine $x

In other words, all values the function needs to work with should be explicitly passed in via parameters, not assumed from the parent scope. Anything the calling scope needs from the function should be output by the function, either using the Return keyword or the Write-Output command. Again, there are times when you have to make an exception to that general rule - but it’s a good rule to try and follow as it makes the functions better-encapsulated and more standalone. It’s also, frankly, less confusing.