PetSerAl, as he routinely does, has provided the crucial pointer in a comment on the question:
Member-access enumeration - the ability to access a member (a property or a method) on a collection and have it implicitly applied to each of its elements, with the results getting collected in an array, was introduced in PSv3.[1]
Member-access enumeration is not only expressive and convenient, it is also faster than alternative approaches.
A simplified example:
PS> ((Get-Item /), (Get-Item $HOME)).Mode
d--hs- # The value of (Get-Item /).Mode
d----- # The value of (Get-Item $HOME).Mode
Applying .Mode
to the collection that the (...)
-enclosed command outputs causes the .Mode
property to be accessed on each item in the collection, with the resulting values returned as an array (a regular PowerShell array, of type[System.Object[]]
).
Caveats: Member-access enumeration handles the resulting array like the pipeline does, which means:
If the array has only a single element, that element's property value is returned directly, not inside a single-element array:
PS> @([pscustomobject] @{foo=1}).foo.GetType().Name
Int32 # 1 was returned as a scalar, not as a single-element array.
If the property values being collected are themselves arrays, a flat array of values is returned:
PS> @([pscustomobject] @{foo=1,2}, [pscustomobject] @{foo=3,4}).foo.Count
4 # a single, flat array was returned: 1, 2, 3, 4
Also, member-access enumeration only works for getting (reading) property values, not for setting (writing) them.
This asymmetry is by design, to avoid potentially unwanted bulk modification; in PSv4+, use .ForEach('<property-name', <new-value>)
as the quickest workaround (see below).
This convenient feature is NOT available, however:
- if you're running on PSv2 (categorically)
- if the collection itself has a member by the specified name, in which case the collection-level member is applied.
For instance, even in PSv3+ the following does NOT perform member-access enumeration:
PS> ('abc', 'cdefg').Length # Try to report the string lengths
2 # !! The *array's* .Length property value (item count) is reported, not the items'
In such cases - and in PSv2 in general - a different approach is needed:
- Fastest alternative, using the
foreach
statement, assuming that the entire collection fits into memory as a whole (which is implied when using member-access enumeration).
PS> foreach ($s in 'abc', 'cdefg') { $s.Length }
3
5
- PSv4+ alternative, using collection method
.ForEach()
, also operating on the collection as a whole:
PS> ('abc', 'cdefg').ForEach('Length')
3
5
Note: If applicable to the input collection, you can also set property values with .ForEach('<prop-name>', <new-value>)
, which is the fastest workaround to not being able to use .<prop-name> = <new-value>
, i.e. the inability to set property values with member-access enumeration.
- Slowest, but memory-efficient approaches, using the pipeline:
Note: Use of the pipeline is only memory-efficient if you process the items one by one, in isolation, without collecting the results in memory as well.
Using the ForEach-Object
cmdlet, as in Burt Harris' helpful answer:
PS> 'abc', 'cdefg' | ForEach-Object { $_.Length }
3
5
For properties only (as opposed to methods), Select-Object -ExpandProperty
is an option; it is conceptually clear and simple, and virtually on par with the ForEach-Object
approach in terms of performance (for a performance comparison, see the last section of this answer):
PS> 'abc', 'cdefg' | Select-Object -ExpandProperty Length
3
5
[1] Previously, the feature was semi-officially known as just member enumeration, introduced in this 2012 blog post along with the feature itself. A decision to formally introduce the term member-access enumeration was made in early 2022.
($Diff | Where-Object {$_.SideIndicator -eq '<='}).InputObject
will only work when$Diff | Where-Object {$_.SideIndicator -eq '<='}
produce exactly one item. – Yogh