I have an array of hashmaps as follows, and I would like to compute the sum of the x
property for each item in the list:
$Items = @(
@{x = 110; y = 108 },
@{x = 102; y = 100 },
@{x = 116; y = 114 }
)
The following function does so correctly:
function Get-SumXFunction {
Param($Items)
$Measured = $Items | Measure-Object -Sum x
return $Measured.Sum
}
But I ultimately want to use this function as a parameter in another function, and I read on this site that the correct way to do this in PowerShell is using a script block. So, I rewrote the function using a script block as follows:
$GetSumXScriptBlock = {
Param($Items)
$Measured = $Items | Measure-Object -Sum x
Write-Output $Measured.Sum
}
(Note that I have changed return
to Write-Output
, as my understanding is that this is necessary to be able to assign the output of the script block to a variable later on.)
But the script block, when called using .Invoke($Items)
, doesn't do the sum at all! It just returns the x
-value of the first item:
Write-Host "Get-SumXFunction:" (Get-SumXFunction $Items)
Write-Host "Get-SumXScriptBlock:" ($GetSumXScriptBlock.Invoke($Items))
Get-SumXFunction: 328
Get-SumXScriptBlock: 110
Why does the script block give different results than the function? How can I write my function as a script block that produces correct results?
The immediate fix is to use an aux. single-item array to wrap your array of hashtables:
$GetSumXScriptBlock.invoke((, $Items))
Note:
It is the signature of the .Invoke()
method that makes this necessary: its only parameter is of type: params object[]
, which means that passing an array causes its elements to be considered individual arguments to pass to the script block, so that only the first hashtable in your array is passed to the - first and only - parameter of your script block, $Items
.
Using the unary form of ,
the array constructor operator, wraps your array of hashtables in an outer, single-element array, therefore causing its only element - the array of hashtables - to be passed as a whole as the first positional script-block argument.
The preferable fix is to invoke your script block with &
, the call operator rather than via the .Invoke()
method, which allows you to use the usual argument-mode syntax:
& $GetSumXScriptBlock $Items
Note:
.Invoke()
method to execute a script block in PowerShell code (as opposed to when using the PowerShell SDK, typically from C#) is best avoided in general, not just to for syntax reasons, but also because it changes the semantics of the call in several respects. See this answer for more information.As for return
vs. Write-Output
in your code:
I have changed
return
toWrite-Output
, as my understanding is that this is necessary to be able to assign the output of the script block to a variable later on.
Write-Output
is not necessary in your script block, and neither is return
- both are equivalent in your case, and can be simplified:
In essence, a function is also a script block, only a named one (as is a script file).
In PowerShell code in general, you can use implicit output, and you need return
only for explicit flow control.
Thus, just $Measured.Sum
by itself as the last statement in both script blocks is sufficient.
For more information about:
PowerShell's implicit output behavior, see this answer.
return
, see this answer.