arrayspowershellparameter-passingscriptblock

PowerShell unrolling arrays differently in function vs. script block


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?


Solution

  • The immediate fix is to use an aux. single-item array to wrap your array of hashtables:

    $GetSumXScriptBlock.invoke((, $Items))
    

    Note:


    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:


    As for return vs. Write-Output in your code:

    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.

    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: