Reputation: 5715
Can someone please help me understand the difference between the below two pieces of code . Why are the results different for both . I each case i am selecting the same property (name) :
Code 1 :
$obj = Get-Service | Where-Object {$_.Status -eq "Running"} | foreach-object {$_.Name} | select -first 3
foreach ( $item in $obj ) { write-output "Name is : $item" }
Output :
Name is : AeLookupSvc
Name is : Appinfo
Name is : AudioEndpointBuilder
Code 2 :
$obj = Get-Service | Where-Object {$_.Status -eq "Running"} | select -first 3 name
foreach ( $item in $obj ) { write-output "Name is : $item" }
Output :
Name is : @{Name=AeLookupSvc}
Name is : @{Name=Appinfo}
Name is : @{Name=AudioEndpointBuilder}
Upvotes: 11
Views: 16751
Reputation: 24430
Select-Object returns an array of custom objects to the pipeline; in this case having only one property which happens to be a string.
As @walidtourni mentions, using expand
works around this issue. This is because expand
causes the output to be the property's value, instead of a custom object with a property with that value. The reason this is possible is expand
only takes one argument; i.e. there's no possibility of you attempting to return multiple values for the same "row".
The foreach-object on the other hand is simply spitting stuff out to the pipeline. If you tried to include a second property without manually wrapping both into a custom object, the output would create another row rather than two properties on the same row.
To demonstrate, run the following:
Clear
$x = Get-Service | Where-Object {$_.Status -eq "Running"} | select -first 3
$x | foreach-object {$_.Name} #returns 3 rows, single column; string
$x | foreach-object {$_.Name;$_.CanPauseAndContinue;} #returns 6 rows, single column; alternate string & boolean
$x | select Name #returns 3 rows, single column (string); custom object
$x | select Name, CanPauseAndContinue #returns 3 rows, two columns (string & boolean); custom property
$x | select -expand Name #returns 3 rows, single column; string; notice the lack of column header showing this is a string, not a string property of a custom object
$x | select -expand Name,CanPauseAndContinue #error; -expand can only take single valued arguments
$x | select -expand Name -expand CanPauseAndContinue #error; you can't try to define the same paramenter twice
Upvotes: 14
Reputation: 638
This is an old post but I thought I would expand on @JohnLBevan's answer.
Select-Object
's normal operation is to create a PSCustomObject
that copies over the properties from the original object.
Get-Process
Get-Process | Select-Object Name,CPU
If you only specify one property, then it creates an object with only one property (Get-Process | Select-Object Name
)--which is what you are seeing with the @{Name=AeLookupSvc}
, et cetera. Note that this is strictly different than the value of the property, which it has a parameter to support getting:
Get-Process | Select-Object -ExpandProperty Name
ForEach-Object
, on the other hand, acts more like a loop body executing against each pipeline object. This is easily used for retrieving the value of a property to be passed along, and even comes with a convenient shorthand for doing so.
Get-Process | ForEach-Object Name
So to really add on to the other answers, I did a bit of exploration as to the performance difference:
# Benchmark
1..100 | ForEach-Object {Measure-Command {Get-WinEvent -LogName System -ErrorAction SilentlyContinue} | Select-Object -ExpandProperty TotalMinutes} | Measure-Object -Average
# Average: 2.01325857505 Minutes
# Select-Object -ExpandProperty
1..100 | ForEach-Object {Measure-Command {Get-WinEvent -LogName System -ErrorAction SilentlyContinue | Select-Object -ExpandProperty LevelDisplayName -ErrorAction SilentlyContinue} | Select-Object -ExpandProperty TotalMinutes} | Measure-Object -Average
# Average: 2.87394915385 Minutes
# note the `Select-Object -ErrorAction SilentlyContinue` because not all pipeline records are accepted by Select-Object (strange, but I did not explore why)
# ForEach-Object
1..100 | ForEach-Object {Measure-Command {Get-WinEvent -LogName System -ErrorAction SilentlyContinue | ForEach-Object LevelDisplayName} | Select-Object -ExpandProperty TotalMinutes} | Measure-Object -Average
# Average: 3.10188571238333 Minutes
Note that I ran these in parallel (different PowerShell windows) on PowerShell 5.1.
Upvotes: 2
Reputation: 2272
For similar result you can change the second example like this:
select -first 3 -expand name
Select-object select properties object
Upvotes: 3