I'm trying to use a higher-order function to perform a certain task, but it seems that the scope isn't working as I expected. The code is as follows:
function DoSomething($scriptBlock, $message) {
# if not only running a check, run the given code
if ($shouldRunCheck) {
return $scriptBlock.Invoke()
}
Write-Host $message -foreground cyan
# write $message to file
}
This seems to work fine, but when I call it I don't seem to be able to save to variables outside of the script block.
$myArray = @()
$myArray += 'test 1'
DoSomething {
write-host $myArray # test 1
$myArray += 'test 2'
write-host $myArray # test 2
}
write-host $myArray # test 1
$myArray += 'test 3'
write-host $myArray # test 1 test 3
Essentially I'm need to add to the array variable from within the callback function but it just seems to over-write the variable as if the variable is read only?
+1 for @Theo explanation, I would although try to avoid to use the $Global: and the $Script: scopes as they might get confusing.
It is also possible to refer to the current scope of the caller by using ([Ref]$myArray).Value:
function DoSomething($scriptBlock) {
return $scriptBlock.Invoke()
}
$myArray = @('test 1')
$scriptBlock = {
([Ref]$myArray).Value += 'test 2'
}
DoSomething $scriptBlock
$myArray += 'test 3'
Write-Host $myArray
test 1 test 2 test 3
But the explanation from Theo also implies that the use += on a PowerShell array is a bad idea as you will recreate the array each time which is just slow.
In other words for a better performance, it is better to use an ArrayList and the Add method rather than assigning items:
function DoSomething($scriptBlock) {
return $scriptBlock.Invoke()
}
$myArray = New-Object System.Collections.ArrayList
$myArray.Add('test 1')
$scriptBlock = {
$myArray.Add('test 2')
}
DoSomething $scriptBlock
$myArray.Add('test 3')
Write-Host $myArray
test 1 test 2 test 3
You might also consider to use the mighty PowerShell pipeline for something like this:
$scriptBlock = {
'test 2'
}
$myArray = @(
'test 1'
DoSomething $scriptBlock
'test 3'
)
Write-Host $myArray
test 1 test 2 test 3
You are correct about the Scoping.
What happens is that inside the scriptblock, the variable $myArray is created new as type [String] when $myArray += 'test 2' is performed. This new variable does not exist outside the function, so the original $myArray is not altered at all.
To do that, you need to use scoping on the $myArray and declare it as $script:myArray = @('test 1') so it can be accessed throughout the entire script.
Then inside the scriptblock you add the new value to it using $script:myArray += 'test 2', like:
function DoSomething($scriptBlock) {
return $scriptBlock.Invoke()
}
$script:myArray = @('test 1') # declared in script-scope
# while inside the main script, you can simply access it as $myArray
Write-Host "MainScript: $myArray" # --> test 1
$scriptBlock = {
Write-Host "ScriptBlock: $script:myArray" # --> test 1
$script:myArray += 'test 2'
Write-Host "ScriptBlock: $script:myArray" # --> test 1 test 2
}
DoSomething $scriptBlock
Write-Host "MainScript: $myArray" # --> test 1 test 2
$myArray += 'test 3'
Write-Host "MainScript: $myArray" # --> test 1 test 2 test 3
Result:
MainScript: test 1 ScriptBlock: test 1 ScriptBlock: test 1 test 2 MainScript: test 1 test 2 MainScript: test 1 test 2 test 3
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With