Home > Software design >  powershell find all objects with a nested array that matches given array list
powershell find all objects with a nested array that matches given array list

Time:08-14

I have an array of hashtables ($workObjectFromFile) that contain a single string 'id' and an array 'roles'. I want to return each row/object in the ht whose roles array matches an input array.

# generate workobject [System.Collections.Hashtable]
$workObject = @()
$workObject  = @{id = 'server1'; roles = 'role1'}
$workObject  = @{id = 'server2'; roles = 'role1','role2'}
$workObject  = @{id = 'server3'; roles = 'role4','role5','role2'}

$json_string = ConvertTo-Json $workObject -Depth 20 
$jsonFile = "C:\scratch\hostmap.json"
Set-Content $jsonFile -Value $json_string -Force
$json_fromFile = Get-Content $jsonFile -Raw
$workObjectFromFile = ConvertFrom-Json $json_fromFile

# find and return each server with any one of the given roles in the input filter
$inputFilter = 'role1','role3'
$return = @()
$workObjectFromFile | where {$_.roles -in $inputFilter} | foreach {
    $return  = [ordered]@{
        id = $_.id 
        roles = $_.roles
    } 
}
return $return 

Why does the where clause above only return server1? if i change this to

$workObjectFromFile.roles | where {$_ -in $inputFilter} 

It will return only the roles, but for the correctly matched objects

I need to return the parent object for these matched nested arrays

CodePudding user response:

server1.role is a string, so for that item your code is equivalent to this:

PS> 'role1' -in @('role1', 'role3')
True

whereas the roles for server2 and server3 are arrays, so your code is doing this for them:

PS> @('role1', 'role2') -in @('role1', 'role3')
False

i.e. does the entire array @('role1', 'role2') appear as a single item in the array @('role1', ‘role3')?

What you actually want to ask is:

  • are there any items in @('role1', 'role2') that also appear as an item in @('role1', 'role3')

which you can do as follows (adapted from https://stackoverflow.com/a/18845506/3156906):

PS> Compare-Object @('role1', 'role2') @('role1', 'role3') -PassThru -IncludeEqual -ExcludeDifferent
role1

Putting this back into your code gives:

PS> $workObjectFromFile | where { Compare-Object $inputFilter $_.roles -PassThru -IncludeEqual -ExcludeDifferent }

roles          id
-----          --
role1          server1
{role1, role2} server2

As an aside, if you want to make server1.roles an array containing a single item you can do one of these:

Comma operator

(see https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_operators?view=powershell-7.2#comma-operator-)

PS> $workObject  = @{id = 'server1'; roles = ,'role1'}
#                                            ^ comma operator
Array subexpression operator

(see https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_operators?view=powershell-7.2#array-subexpression-operator--)

PS> $workObject  = @{id = 'server1'; roles = @( 'role1' )}
#                                            ^^         ^ array subexpression operator

CodePudding user response:

First of all, I recommend you to use the PowerShell pipeline to accumulated you objects because it hash a better PowerShell syntax and because the increase assignment operator ( =) might perform very badly when the collection gets larger:

$workObject = 
    @{ id = 'server1'; roles = 'role1' },
    @{ id = 'server2'; roles = 'role1','role2' },
    @{ id = 'server3'; roles = 'role4','role5','role2' }

For what you trying to do, is actually issued here: #2132 The -Like and -NotLike comparison operators should allow for an array of values (-AnyIn). Unfortunately that doesn't exist (yet?) knowing that it does actually work that way for comparison operators that usually take a (simple) scalar at the righthand side, see: comparison operators / common features:

When the input of an operator is a scalar value, the operator returns a Boolean value. When the input is a collection, the operator returns the elements of the collection that match the right-hand value of the expression. If there are no matches in the collection, comparison operators return an empty array.

In other words, to resolve this, you will need to to iterate to the elements (as suggested in @Daniel's comment, but at the other hand might actually use the -eq operator for this:

$return = $workObject | Where-Object { $_.roles.where{ $inputFilter -eq $_ } }
  • Related