Under the hood, all PowerShell objects are PSObject objects
These
PSObjects might be stored in a
variable (to name them) or flow (anonymously) through a
pipeline.
In order to demonstrate of wrapping objects into a PSObject, I create a few objects …
$forty_two = 42
$now = new-object System.DateTime
$com_obj = new-object -comObject shell.application
$a_process =(get-process)[0]
$void_obj = new-object psCustomObject
… and assign them to an
array so that I can iterate over each element of them:
$all_objects = $forty_two, $now, $com_obj, $a_process, $void_obj
First, I have the object's types printed. I also print the type of the an object's special member psObject
which directly accesses the psObject object that wraps the underlying object:
$all_objects | foreach-object {
"$($_.psObject.GetType().FullName): $($_.GetType().FullName)"
}
Which prints
System.Management.Automation.PSObject: System.Int32
System.Management.Automation.PSObject: System.DateTime
System.Management.Automation.PSObject: System.__ComObject
System.Management.Automation.PSObject: System.Diagnostics.Process
System.Management.Automation.PSObject: System.Management.Automation.PSCustomObject
This dichthomy of an object being both, a PSObject object and a .NET (or COM) type that is pointed at by the PSObject, allows for some interesting features in PowerShell, for example adding members to an object which is demonstrated below.
Adding (extended) members to a PowerShell object
$obj = new-object System.Int64
With
add-member
, it's possible to add user defined members to
$obj
:
$obj | add-member -memberType noteProperty -name theNumber -value 42
$obj | add-member -memberType noteProperty -name someText -value 'foo, bar, baz'
write-output "num = $($obj.theNumber), text = $($obj.someText)"
#
# num = 42, text = foo, bar, baz
How is it possible to add members to a .NET class?
Answer: members can be added because of the
Extended Type System (ETS): the members are not added to the .NET object but to the PSObject that wraps the .NET object.
Extended members that were added to a object can be displayed with get-member -view extended
:
$obj | get-member -view extended
#
# TypeName: System.Int64
#
# Name MemberType Definition
# ---- ---------- ----------
# someText NoteProperty string someText=foo, bar, baz
# theNumber NoteProperty int theNumber=42
$psObj = $obj.psObject
$psObj.GetType().FullName
#
# System.Management.Automation.PSObject
The PSObject
object has a member Properties
which stores the extended values:
$psObj.properties
#
# MemberType : NoteProperty
# IsSettable : True
# IsGettable : True
# Value : 42
# TypeNameOfValue : System.Int32
# Name : theNumber
# IsInstance : True
#
# MemberType : NoteProperty
# IsSettable : True
# IsGettable : True
# Value : foo, bar, baz
# TypeNameOfValue : System.String
# Name : someText
# IsInstance : True
Finally, there is also the BaseObject
member which refers to the System.Int64
that we created at the beginning:
$psObj.baseObject.GetType().FullName
#
# System.Int64
Adding a ToString() method
The following example adds a
ToString()
method to a PsObject instance. Because all objects already have a
ToString()
method,
add-member
cmdLet requires the
-force
option.
$psObj_1 = @{ val_1 = 'one' ; val_2 = 'two' ; val_3 = 'three' }
$psObj_2 = @{ val_1 = 'foo' ; val_2 = 'bar' ; val_3 = 'baz' }
$psObj_2 | add-member `
-memberType scriptMethod `
-name ToString {
"val 1: $($this.val_1), " +
"val 2: $($this.val_2), " +
"val 3: $($this.val_3)"
} `
-force
$psObj_1.ToString()
#
# System.Collections.Hashtable
$psObj_2.ToString()
#
# val 1: foo, val 2: bar, val 3: baz
#
# Unfortunately, this version of ToString is
# not used when not called explicitetly, as
# for example when strings are interpolated:
#
write-host "psObj_1: $psObj_1"
#
# psObj_1: System.Collections.Hashtable
write-host "psObj_2: $psObj_2"
#
# psObj_2: System.Collections.Hashtable