1

The following object is a toy example to illustrate my question:

$food = @(
    [PSCustomObject]@{Category = "Fruits"; Items = @("Mango", "Pineapple", "Orange", "Tangerine")},
    [PSCustomObject]@{Category = "Cities"; Items = @("Abidjan", "Douala")},
    [PSCustomObject]@{Category = "Animals"; Items = @("Dog", "Bird", "Cat")}
)

$food

Category Items
-------- -----
Fruits   {Mango, Pineapple, Orange, Tangerine}
Cities   {Abidjan, Douala}
Animals  {Dog, Bird, Cat}

I would like to access all items of the "Fruits" category, for example. I tried to first determine the index of "Fruits" in the Category property then use it in the "Items" property. I thought that the entire array of fruit names would be returned; however, it did not work as expected:

$i = $food.Category.IndexOf("Fruits")
$food[$i]
$food.Items[$i]

Mango

How should I go about it?

Thank you.

2 Answers 2

1

You need to apply index $i to the $food array itself, not to .Items:

$food[$i].Items # -> @('Mango', 'Pineapple', 'Orange', 'Tangerine')

However, a more PowerShell-idiomatic solution, using the Where-Object cmdlet to perform the category filtering (with simplified syntax), would be:

($food | Where-Object Category -eq Fruits).Items

Note:

  • The above could potentially return multiple elements from the $food array, namely if more than one element has a .Category property value 'Fruits'.

  • To limit output to the first element, you could append a Select-Object -First 1 pipeline segment, but there's a simpler alternative: the intrinsic .Where() method has an overload that allows you to stop at the first match:[1]

    $food.Where({ $_.Category -eq 'Fruits' }, 'First').Item
    
    • It is somewhat unfortunate that the Where-Object cmdlet doesn't have an analogous feature; GitHub issue #13834 suggests enhancing the Where-Object cmdlet to ensure feature parity with its method counterpart, .Where().

As for what you tried:

$food.Items[$i]

Accessing .Items on the array stored in $food performs member-access enumeration.

That is, the .Items property on each object stored in the array is accessed, and the resulting values are returned as a single, flat array of all such property values.

  • To spell it out: $food.Items returns the following array:

    @('Mango', 'Pineapple', 'Orange', 'Tangerine', 'Abidjan', 'Douala', 'Dog', 'Bird', 'Cat')
    
  • As an aside: Your index-finding code, $food.Category.IndexOf("Fruits"), also makes use of member-access enumeration, relying on returning the .Category property values across all elements of the $food array.

Indexing into the resulting array (with [$i], with $i being 0) then only extracts its first element, i.e. 'Mango'.


[1] Using the .Where() method has additional implications: (a) the input object(s) must be collected in full, up front, and (b) what is output is invariably a collection, namely of type [System.Collections.ObjectModel.Collection[psobject]] - see this answer for details.

Sign up to request clarification or add additional context in comments.

2 Comments

Your very detailed answer is very much appreciated. Being able to understand the inner workings of PowerShell in this context definitely helps a lot.
I'm glad to hear the answer was helpful, @SavedByJESUS; my pleasure.
1

You need to loop over each element in the $food array to find the object having a property with Category equal to Fruits then expand the Items property. The 3 most commonly used filtering techniques in PowerShell are:

  1. Where-Object:

    $food | Where-Object Category -EQ Fruits |
       ForEach-Object Items
    
  2. .Where Method:

    $food.Where({ $_.Category -eq 'Fruits' }).Items
    
  3. A classic foreach loop with an if condition:

    foreach ($item in $food) {
        if ($item.Category -eq 'Fruits') {
            $item.Items
        }
    }
    

1 Comment

Thank you very much for showing me all these alternative solutions. I will study them.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.