Select the values of one property on all objects of an array in PowerShell

只愿长相守 提交于 2019-11-26 11:19:45
Scott Saad

I think you might be able to use the ExpandProperty parameter of Select-Object.

For example, to get the list of the current directory and just have the Name property displayed, one would do the following:

ls | select -Property Name

This is still returning DirectoryInfo or FileInfo objects. You can always inspect the type coming through the pipeline by piping to Get-Member (alias gm).

ls | select -Property Name | gm

So, to expand the object to be that of the type of property you're looking at, you can do the following:

ls | select -ExpandProperty Name

In your case, you can just do the following to have a variable be an array of strings, where the strings are the Name property:

$objects = ls | select -ExpandProperty Name

As an even easier solution, you could just use:

$results = $objects.Name

Which should fill $results with an array of all the 'Name' property values of the elements in $objects.

To complement the preexisting, helpful answers with guidance of when to use which approach and a performance comparison.

  • Outside of a pipeline, use:

    $objects.Name
    (PSv3+), as demonstrated in rageandqq's answer, which is both syntactically simpler and much faster.

    • Accessing a property at the collection level to get its members' values as an array is called member enumeration and is a PSv3+ feature.
    • Alternatively, in PSv2, use the foreach statement, whose output you can also assign directly to a variable:
      $results = foreach ($obj in $objects) { $obj.Name }
    • Tradeoffs:
      • Both the input collection and output array must fit into memory as a whole.
      • If the input collection is itself the result of a command (pipeline) (e.g., (Get-ChildItem).Name), that command must first run to completion before the resulting array's elements can be accessed.
  • In a pipeline where the result must be processed further or the results don't fit into memory as a whole, use:

    $objects | Select-Object -ExpandProperty Name

    • The need for -ExpandProperty is explained in Scott Saad's answer.
    • You get the usual pipeline benefits of one-by-one processing, which typically produces output right away and keeps memory use constant (unless you ultimately collect the results in memory anyway).
    • Tradeoff:
      • Use of the pipeline is comparatively slow.

For small input collections (arrays), you probably won't notice the difference, and, especially on the command line, sometimes being able to type the command easily is more important.


Here is an easy-to-type alternative, which, however is the slowest approach; it uses simplified ForEach-Object syntax called an operation statement (again, PSv3+): ; e.g., the following PSv3+ solution is easy to append to an existing command:

$objects | % Name      # short for: $objects | ForEach-Object -Process { $_.Name }

For the sake of completeness: The little-known PSv4+ .ForEach() collection method is yet another alternative:

# By property name (string):
$objects.ForEach('Name')

# By script block (much slower):
$objects.ForEach({ $_.Name })
  • This approach is similar to member enumeration, with the same tradeoffs, except that pipeline logic is not applied; it is marginally slower, though still noticeably faster than the pipeline.

  • For extracting a single property value by name (string argument), this solution is on par with member enumeration (though the latter is syntactically simpler).

  • The script-block variant, although much slower, allows arbitrary transformations; it is a faster - all-in-memory-at-once - alternative to the pipeline-based ForEach-Object cmdlet.


Comparing the performance of the various approaches

Here are sample timings for the various approaches, based on an input collection of 100,000 objects, averaged across 100 runs; the absolute numbers aren't important and vary based on many factors, but it should give you a sense of relative performance:

Command                                         FriendlySecs (100-run avg.) Factor
-------                                         --------------------------- ------
$objects.ForEach('Number')                      0.078                       1.00
$objects.Number                                 0.079                       1.02
foreach($o in $objects) { $o.Number }           0.188                       2.42
$objects | Select-Object -ExpandProperty Number 0.881                       11.36
$objects.ForEach({ $_.Number })                 0.925                       11.93
$objects | % { $_.Number }                      1.564                       20.16
$objects | % Number                             2.974                       38.35
  • The member-enumeration / property-name-based collection-method solution are faster by a factor of 10+ than the fastest pipeline-based solution.

  • The foreach statement solution is about 2.5 slower, but still about 4-5 times as fast as the fastest pipeline solution.

  • Use of a script block with the collection-method solution (.ForEach({ ... }) slows things down dramatically, so that it is virtually on par with the fastest pipeline-based solution (Select-Object -ExpandProperty).

  • % Number (ForEach-Object Number), curiously, performs worst, even though % Number is the conceptual equivalent of % { $_.Number }).


Source code for the tests:

Note: Download function Time-Command from this Gist to run these tests.

$count = 1e5 # input-object count (100,000)
$runs  = 100  # number of runs to average 

# Create sample input objects.
$objects = 1..$count | % { [pscustomobject] @{ Number = $_ } }

# An array of script blocks with the various approaches.
$approaches = { $objects | Select-Object -ExpandProperty Number },
              { $objects | % Number },
              { $objects | % { $_.Number } },
              { $objects.ForEach('Number') },
              { $objects.ForEach({ $_.Number }) },
              { $objects.Number },
              { foreach($o in $objects) { $o.Number } }

# Time the approaches and sort them by execution time (fastest first):
Time-Command $approaches -Count $runs | Select Command, FriendlySecs*, Factor
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!