To complement Theo's helpful answer:
tl;dr:
# Note the use of -PipelineVariable obj and the use of $obj later.
PS> Write-Output -PipelineVariable obj (
[pscustomobject] @{ Brand = 'Volkswagen'; Model = 'Passat' },
[pscustomobject] @{ Brand = 'Ford'; Model = 'Mondeo' }
) |
Select-String -SimpleMatch wagen |
ForEach-Object { $obj.psobject.Properties.Value -join ', ' }
Volkswagen, Passat
The Write-Output
command is just a stand-in for the actual command that produces the input objects for Select-String
in your code, such as Import-Csv
.
Your Select-String
output implies that [pscustomobject]
instances served as its input, such as output by Import-Csv
, for instance.
- E.g., your output implies an input object such as
[pscustomobject] @{ Brand = 'Volkswagen'; Model = 'Passat' }
As Theo notes, Select-String
is designed to operate on strings, not on objects with properties.
When Select-String
receives such objects, it creates a string representation for them and searches that.
What makes this particularly unhelpful is that this string representation is not the same you would see in the terminal (console), where PowerShell's rich output formatting is used; instead, simple .ToString()
stringification is performed, which with [pscustomobject]
instances results in representations such as '@{Brand=Volkswagen; Model=Passat}'
(perhaps confusingly, this resembles - but is not the same as - a hashtable literal).
- GitHub issue #10726 proposes changing
Select-String
to instead operate on the rich display-formatting representations.
Additionally, if you do let Select-String
operate on such objects, its output (Microsoft.PowerShell.Commands.MatchInfo
instances) no longer contain the input objects, only their string representations, which means that in order to extract the values Volkswagen
and Passat
, you'd have to perform string parsing, which is both cumbersome and not robust.
To filter input objects with properties based on property values, Where-Object
is the better choice; e.g.:
PS> [pscustomobject] @{ Brand = 'Volkswagen'; Model = 'Passat' },
[pscustomobject] @{ Brand = 'Ford'; Model = 'Mondeo' } |
Where-Object Brand -eq Volkswagen
Brand Model
----- -----
Volkswagen Passat
That said, using Select-Object
can still be helpful if you don't know what properties the input objects have, and want to locate a value somewhere in the object, via its string representation:
PS> [pscustomobject] @{ Brand = 'Volkswagen'; Model = 'Passat' },
[pscustomobject] @{ Brand = 'Ford'; Model = 'Mondeo' } |
Select-String -SimpleMatch wagen
@{Brand=Volkswagen; Model=Passat}
The above is what you tried, but, as stated, this effectively outputs just the string representation of the entire object, with no (easy) ability to extract property values afterwards.
The solution is to use the common -PipelineVariable
parameter on the input-object-producing command, which allows access to the object at hand in a later pipeline segment's (ForEach-Object
) script block, which allows you to create the desired comma-separated list of property values easily (even without knowing the property names):
# Note the use of -PipelineVariable obj and the use of $obj later.
PS> Write-Output -PipelineVariable obj (
[pscustomobject] @{ Brand = 'Volkswagen'; Model = 'Passat' },
[pscustomobject] @{ Brand = 'Ford'; Model = 'Mondeo' }
) |
Select-String -SimpleMatch wagen |
ForEach-Object { $obj.psobject.Properties.Value -join ', ' }
Volkswagen, Passat
$obj.psobject.Properties.Value
uses the hidden, intrinsic .psobject
property that PowerShell provides for all objects, which is a rich source of reflection and in this case allows easy access to all property values, via member-access enumeration.