6

I am new to powershell scripting. Apologies if I am missing something simple.

Let's say I have an object called object that has a field called field. Now let there be a list of these objects.

How would I get the list of fields in the same order?

In python it would be:

list_of_objects = [o1, o2, o3]
list_of_fields = [i.field for i in list_of_object]
mklement0
  • 382,024
  • 64
  • 607
  • 775
Kaiwen Chen
  • 192
  • 1
  • 1
  • 12
  • 2
    Literally, `$list_of_objects.field`. In v3 & beyond, powershell implements something called automatic member enumeration which does this for you. The same works for method calls. If on v2, you can use the `ForEach-Object` cmdlet: `$list_of_objects | % field`. See: `Get-Help ForEach-Object` – Maximilian Burszley Mar 29 '19 at 18:23
  • almost every object in PoSh has a hidden property named `.PSObject` & that has a prop named `.Properties` & each of those has a prop named `.Name`. that seems likely to be what you want. [*grin*] – Lee_Dailey Mar 29 '19 at 18:24
  • @Lee_Dailey That is way too low-level for someone brand new to the language. `.psobject` is a hidden member for a reason :) – Maximilian Burszley Mar 29 '19 at 18:25
  • @TheIncorrigible1 - yep, it is hidden ... but it's the most direct way to get the list of props. otherwise you need to use something like `($PSVersionTable | Get-Member -MemberType Properties).Name` to get the names. it think that makes `$PSVersionTable.PSObject.Properties.Name` seem somewhat more appropriate. [*grin*] – Lee_Dailey Mar 29 '19 at 18:31
  • Hey guys, I tried to implement it in the fashion that you mentioned, but it isn't working, and is returning empty. This is the specific example: ```PS C:\Users\ME> $parents.item.registration.parentCompoundNumber #no return PS C:\Users\ME> $parents[0].item.registration.parentCompoundNumber #returns something``` – Kaiwen Chen Mar 29 '19 at 18:56
  • `parents` are of type `System.array` but when I get down to `item` it no longer is. – Kaiwen Chen Mar 29 '19 at 19:03

2 Answers2

6

is nice, and not so nice, because it unwraps collections for you, and sometimes this can hide that it is masking the elements' members. When you're using $parents.item, you're accessing the array's method, and trying to access its members (which there aren't any, so is giving you $null):

Item           ParameterizedProperty System.Object IList.Item(int index) {get;set;}

You can overcome this by using the method I shared in the comments to iterate over each member and avoid this masking:

$list = $parents | ForEach-Object -MemberName item
$list.registration.parentCompoundNumber

Alternatively, a syntax more people are familiar with:

$list = $parents | Select-Object -ExpandProperty item

or unrolling it yourself:

# you could directly assign the outputs of a `foreach` loop to a variable by
# removing these comments (<##>)
<# $items = #> 
  foreach ($parent in $parents) {
    $parent.item.registration.parentCompoundNumber
  }

To see when this masking is happening, consider this example which uses the unary array operator:

, @('a', 'b', 'c') | Get-Member

This will let you observe the wrapping array or collection's members.

mklement0
  • 382,024
  • 64
  • 607
  • 775
Maximilian Burszley
  • 18,243
  • 4
  • 34
  • 63
  • Thank you, the syntax I ended up finding the most readable is ```$ans = $parents | % item | % registration | % parentcompoundNumber``` – Kaiwen Chen Mar 29 '19 at 19:40
  • 2
    @KaiwenChen Glad to help. A word of caution: using the pipeline (`$obj | cmdlet`) has inherent overhead so over a sufficiently large dataset, it's very slow. – Maximilian Burszley Mar 29 '19 at 19:42
  • Yes, I figured this might be the case since it's looping over the list every iteration, but this script only picks up at most 20 results at a time. Thanks again, I appreciate it. – Kaiwen Chen Mar 29 '19 at 19:44
  • 1
    Thanks, @TheIncorrigible1; I hope my edit makes sense. As an aside: as far as I know, PowerShell doesn't (yet) support _extension_ methods. – mklement0 Mar 29 '19 at 20:55
  • 1
    @mklement0 Unfortunately correct. You still have to call the extension class on the object, which doesn't really accomplish anything. – Maximilian Burszley Mar 29 '19 at 21:11
3

To complement Maximilian Burszley's helpful answer:

The linked answer contains viable workarounds for the member-name collisions; let me add a PSv4+ alternative that is both more concise and faster than a pipeline-based approach:

$parent.ForEach('item').registration.parentCompoundNumber

Using the .ForEach() array method with a property name ('item') unambiguously targets the elements' members.


To offer a slight reframing of the explanation for why a workaround is needed:

  • PowerShell's member-access enumeration essentially treats $someCollection.someProp as if you had written foreach ($element in $someCollection) { $element.someProp }; that is, the elements of $someCollection are enumerated, and the the elements' .someProp property values are returned as an array.

    • Note: As in the pipeline, if the collection happens to have just one element, that element's property value is returned as-is, not as a single-element array.
  • However, if the collection type itself happens to have a member named someProp, it is used, and no enumeration takes place; that is, collection-level members shadow (take precedence over) element-level members of the same name - and that is what happened with .Item in your case.

    • When in doubt, output $someCollection.someProp interactively / during debugging to see what it evaluates to.
mklement0
  • 382,024
  • 64
  • 607
  • 775