I am setting the variable 'a' like this:
$a=dir -recurse c:\temp
If I now examine this object with 'get-member' like this:
$a|get-member
I get back the type, but also all the methods and other properties like this:
TypeName: System.IO.FileInfo
Name MemberType Definition
---- ---------- ----------
Mode CodeProperty System.String Mode{get=Mode;}
AppendText Method System.IO.StreamWriter AppendText()
CopyTo Method System.IO.FileInfo CopyTo(string destFileName), System.IO.FileInfo CopyTo(string destFileName, bool...
[...]
Which is nice; but sometimes I just want to get hold of the type (I'll look up whatever it does afterwards).
So I tried this:
$a|get-member|select-object -Property typename
The output suprised me initially: because what you get back is the typename for each individual item in the collection- and the types (although clearly related) are not identical:
TypeName
--------
System.IO.DirectoryInfo
System.IO.DirectoryInfo
System.IO.DirectoryInfo
[...]
System.IO.FileInfo
[...]
Then I thought about this; and it sort of made sense - this is a collection of Objects that I'm piping through the Object-Pipeline; but then it made me think:
What was 'Get-Member' actually telling me previously ? When it said the type was 'System.IO.FileInfo' - but actually the collection contains a mixture of object types ?
Whatever it is 'Get-Member' is displaying - how do I get at that exact thing ?
I can almost (sort-of, but actually its just wrong) get what I thought I was initially after with this:
$a|get-member|select-object -Property typename -first 1
But this just peeks at the 'first' object; and in fact gives me a different answer to what 'Get-Member' output for me.
So what is the 'TypeName' that 'Get-Member' is showing- and where is that stored ?
Is the value of 'dir' (Get-ChildItem against a filepath) simply a collection of objects, or is it a parent object (with its own set of 'scalar' properties) and a single property referencing a collection of associated objects ?
That's a lot of questions in one, let's see if we can make some sense of this.
The TypeName
that Get-Member
displays for each distinct type of object (we'll get back to that), comes from a hidden property that all objects in PowerShell carry, called pstypenames
:
PS C:\> $something = 1
PS C:\> $something.pstypenames
System.Int32
System.ValueType
System.Object
PS C:\>
So, pstypenames
is an ordered list of all the types in that objects type hierarchy. If we change the value of pstypenames
, you'll see that reflected in the output from Get-Member
:
PS C:\> $something.pstypenames.Insert(0,"MonoJohnny.CustomTypeName")
PS C:\> Get-Member -InputObject $something
TypeName: MonoJohnny.CustomTypeName
Name MemberType Definition
---- ---------- ----------
...
So, if you want the TypeName
for an object, as displayed by Get-Member
, you can always do:
$something.pstypenames[0]
As shown above, this value can be manipulated, so if you want the true type of an object at runtime, use the GetType()
method:
$something.GetType().FullName
The reason that Get-Member
only shows you the entire list of properties for a System.IO.FileInfo
object once is that it (rightly so) assumes that all other objects of type System.IO.FileInfo
will have the exact same members - no need to duplicate that output.
I you have multiple distinct types in a collection and pipe those to Get-Member
, it'll only show you the members for the first object it encounters with a unique type name (remember, the value of pstypenames[0]
). This is the case when you pipe Get-ChildItem
to Get-Member
, since Get-ChildItem
on the filesystem provider only returns two different types of objects - FileInfo
objects and DirectoryInfo
objects.
For builtin types this is totally fine, but with custom objects that you create in PowerShell, this can be quite annoying.
Consider the following example:
PS C:\> $Object1 = New-Object psobject -Property @{ Prop1 = 1 }
PS C:\> $Object2 = New-Object psobject -Property @{ Prop2 = 2 }
Now, $Object1
and $Object2
are clearly 2 different kinds of objects - they have different property names. But what happens when we pipe them to Get-Member
in the same pipeline:
PS C:\> $Object1,$Object2 |Get-Member
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Prop1 NoteProperty System.Int32 Prop1=1
Since the underlying type of both objects are System.Management.Automation.PSObject
, the value of pstypenames
is also the same for both, and Get-Member
can't distinguish between the two.
Now, all of a sudden, the ability to manipulate pstypenames
without actually fiddling with the type system comes in handy:
PS C:\> $Object1.pstypenames.Insert(0,"ObjectType1")
PS C:\> $Object2.pstypenames.Insert(0,"ObjectType2")
PS C:\> $Object1,$Object2 |Get-Member
TypeName: ObjectType1
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Prop1 NoteProperty System.Int32 Prop1=1
TypeName: ObjectType2
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Prop2 NoteProperty System.Int32 Prop2=2
Sweet!
You can also inject a custom type name during object creation by specifying PSTypeName
as a string property:
PS C:\> $Object1 = New-Object psobject -Property @{ Prop1 = 1; PSTypeName = 'ObjectType1' }
PS C:\> $Object2 = New-Object psobject -Property @{ Prop2 = 2; PSTypeName = 'ObjectType2' }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With