How to dynamically build Calculated Properties with Select-Object?

69 Views Asked by At

I query a REST API which returns a JSON with many subproperties that I need to extract using a calculated property hashtable with Select-Object. The subproperty has always the same name (result), and I want to use a filter or a function to avoid rewriting the hashtable all along.

Example:

some json | ConvertFrom-Json | Select-Object @{ Name="name"; Expression={ $_."name".result}}, @{ Name="year"; Expression={ $_."year".result}}, etc.

I thought it will be more convenient (and easy) to build a filter that would return the hashtable:

filter Get-Result {
     param (
         [string]$field
     )
     @{N=$field; E={ $_."$field".result}}
}

But $field is not replaced in Expression block. I don't understand why

> Get-Result -field "year"

Name                           Value
----                           -----
N                              year
E                               $_."$field".result

How to make $_."$field".result be $_."year".result in Expression block?

1

There are 1 best solutions below

4
On BEST ANSWER

Two options: closure or source code generation

1. Create a closure

You can call GetNewClosure() on the scriptblock passed to the Expression entry - this will cause PowerShell's internal compiler to create a dynamic module that "remembers" the value bound to $fieldName (or any other variable references to the scope where GetNewClosure() is called):

# create some dummy objects for testing
$objects = 1..10|%{
  [pscustomobject]@{
    name = @{ result = "Name_${_}" }
    year = @{ result = Get-Random -Minimum 1900 -Maximum 2025 }
  }
}

# define filter to generate property expression tables
filter Get-Result {
   # to take advantage of pipeline input, use `$_`
   $fieldName = "$_"
   # return property table with closure attached
   @{N=$fieldName; E={ $_."$fieldName".result}.GetNewClosure()}
}

# this now works as expected
$objects |Select-Object -Property ('name','year' |Get-Result)

2. Construct scriptblock from source code

The alternative is to construct the expression block by generating its source code and then passing that off to [scriptblock]::Create():

filter Get-Result {
   # to take advantage of pipeline input, use `$_`
   $fieldName = "$_"

   # escape any literal `'`s
   $escapedName = [System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($fieldName)
   $expressionText = '$_.''{0}''.result' -f $escapedName
   $expression = [scriptblock]::Create($expressionText)

   # return property table with closure attached
   @{N=$fieldName; E=$expression}
}