Originally, this question was prompted while investigating an answer to question 27373126 about selecting items from an array of hash tables. (This was about shorter ways to get the properties than using Foreach-Object or Select-Object with hash table arguments.)
Having found that I could get items using script-block arguments to Select-Object
as follows:
$hash = @{title="title1";detail="detail1"},@{title="title2";detail="detail2"}
$hash | select-object {$_.title},{$_.detail}
and that I could tidy up the output objects (produced with properties named "$_.title" and "$_.detail") by defining functions as follows:
function title{$_.title};function detail{$_.detail}
$hash | select {title},{detail}
I then tried to automate producing output with properties defined by a list of property names (I know these are wrong, this is just how I found the issue I am questioning):
'title','detail' | foreach { $hash | select $_ }
title
-----
# 4 blank lines
I know why there are 4 blank lines. There is no "title" member in $hash
, it is a "title" item. The surprising part was no column for "detail". Tried this:
'title','detail' | foreach { $hash | select $_ } | gm
TypeName: Selected.System.Collections.Hashtable
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
title NoteProperty object title=null
Maybe something strange with Foreach-Object providing arguments to Select-Object
. Tried:
$hash | select title; $hash|select detail
title
-----
# 4 blank lines
Which command is generating the output?:
'start';$hash|select title;'in';$hash|select detail;'done'
start
title
-----
in
done
So 2 blank lines from each section but no "detail" member. Time to get a bit more specific:
'start';$hash|select title|gm;'in';$hash|select detail|gm;'done'
start
TypeName: Selected.System.Management.Automation.Internal.Host.InternalHost
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
title NoteProperty object title=null
in
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
detail NoteProperty object detail=null
done
So the second select IS generating a "detail" member but it is getting lost somewhere. How about this:
&{$hash|select title;$hash|select detail}|gm
TypeName: Selected.System.Management.Automation.Internal.Host.InternalHost
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
title NoteProperty object title=null
No "detail" again. After lots of fruitless testing, some useful research uncovered a partial answer. The full description is in How PowerShell Formatting and Outputting REALLY works but the upshot is that the final display was being controlled by the properties of the first object through the pipeline. This object had a "title" property but no "detail" property so the output was formatted to match. That explains why the displayed output had no "detail" column but why did the last Get-Member
not show it? Get-Member
should have received 2 objects with a "title" followed by 2 objects with a "detail". I know this because a manual Get-Member
does show them:
&{$hash|select title;$hash|select detail}|foreach{[componentmodel.typedescriptor]::getproperties($_)[0].name}
title
title
detail
detail
# Can't use gettype() since doesn't know about added properties
So it would seem that not just Format-Table
decides its output based on the first object down the pipeline. Apparently, Get-Member
also exhibits this behaviour, though in this case it is the first object of each type. All the objects were PSCustomObjects
but the first had only a "title" added property so Get-Member
assumed that all PSCustomObjects
looked the same.
And finally, THE QUESTION. Which other cmdlets, if any, exhibit this "first object down the pipeline silently defines subsequent behaviour" functionality?