Extending Objects with PowerShell, Part 2

Let’s continue our exploration of expanding the objects we get from running a PowerShell cmdlet. In the previous article, I demonstrated how to use Add-Member to define a new property that’s calculated every time you get it. In this article, we’ll continue looking at how we can use objects with PowerShell, this time by learning how to create alias with objects, as well as utilizing methods with objects.
Extending Objects in PowerShell Article Series:

$dc1 = Get-CimInstance win32_operatingsystem -ComputerName chi-dc01
$dc1 | Add-Member -MemberType ScriptProperty -Name Uptime -Value {(Get-Date) - $this.lastbootuptime}

You might also want to create alias property, which happens frequently in PowerShell. In the case of $DC1, it might be easier to use a property called OperatingSystem, which is much more meaningful than the original Caption property.

$dc1 | Add-Member -MemberType AliasProperty -Name OperatingSystem -value Caption -force

The OperatingSystem property in Windows PowerShell. (Image Credit: Jeff Hicks)
The OperatingSystem property in Windows PowerShell. (Image Credit: Jeff Hicks)

For the value parameter, simply specify the name of the original property.
The other type of custom property that you can define is referred to as a Noteproperty. This is a static value that’s defined when you define the property. You can use a simple string:

$dc1 | Add-Member -MemberType NoteProperty -Name Location -value "Chicago"

You can also use the result of a PowerShell expression.

$psversion = Invoke-Command {  $PSVersionTable.PSVersion } -computername $dc1.PSComputerName
$dc1 | Add-Member -MemberType NoteProperty -Name PSVersion -value $psversion

The value of $PSVersion is only calculated once since it’s unlikely to change. Now after a few lines of PowerShell, I’ve extended the original WMI object with information that’s meaningful to me and perhaps necessary for other internal IT processes.
071215 1921 AllAboutTha2
There’s really no limit to what you can add or how many additional properties. The only limit is your imagination.
But as they say on late night television, “But wait, there’s more!”
You can also add methods to an object like $DC1. Generally the methods you add are doing something using one or more properties of the original object. How about adding a Ping method so that I can test if the computer is still reachable? We will accomplish this by defining a ScriptMethod.

$dc1 | Add-Member -MemberType ScriptMethod -Name Ping -value {Test-Connection -ComputerName $this.pscomputername -quiet} -force

As we did with ScriptProperty, the value is a PowerShell scriptblock. Use $this to reference the current object. In my example I am going to run the Test-Connection cmdlet, specifying the computername from the $DC1 object. I’m also telling Test-Connection to only return true or false. I can invoke this method like any other.
071215 1921 AllAboutTha3
I can even define methods that take parameters. Remember, your scriptblock can be as complex as you need it to be and scriptblocks can take parameters.
To make this easier, I’ll break the process down into a few steps. First, I’ll define the parameterized scriptblock.

$getdisk = {
Get-CimInstance -classname Win32_logicalDisk -filter "DeviceID='$DeviceID'" -ComputerName $this.pscomputername

This scriptblock is designed to get disk information from the computer, defaulting to the C: drive. Now I will add another ScriptMethod.

$dc1 | Add-Member -MemberType ScriptMethod -Name GetDisk -value $getdisk -force

Using the –Force parameter will tell PowerShell to go ahead and overwrite any existing properties with the same name. You’ll find this very handy when developing your scriptblocks, because if you are like me, you won’t get everything right the first time. What does this look like?
071215 1921 AllAboutTha4
By omitting the (), PowerShell didn’t try to execute the method. This is the best way I have found to determine any method parameters. Once I know, I can invoke the method.

Invoking the getdisk() method. (Image Credit: Jeff Hicks)
Invoking the getdisk() method. (Image Credit: Jeff Hicks)

You’ll notice that the scriptblock for GetDisk() gives me a complete object. Yes, I could have written the scriptblock to give me a single value like Size, but that limits me. I always like to have options.
Let’s put this all together.

if ($dc1.ping()) {
$dc1 | Select PSComputername,Location,OperatingSystem,ServicePackMajorVersion,Uptime,
$c = $_.GetDisk()
else {
Write-Warning "$($dc1.PSComputerName) appears to be offline"

071215 1921 AllAboutTha6
Finally, let’s put this all together for several computers using some existing CIM sessions.

$data = Get-CimSession | Get-CimInstance -ClassName Win32_OperatingSystem | foreach {
#add custom members
$_ | Add-Member -MemberType ScriptProperty -Name Uptime -Value {(Get-Date) - $this.lastbootuptime}
$_ | Add-Member -MemberType AliasProperty -Name OperatingSystem -value Caption
$_ | Add-Member -MemberType AliasProperty -Name ServicePack -value ServicePackMajorVersion
$_ | Add-Member -MemberType NoteProperty -Name Location -value "Chicago"
$psversion = Invoke-Command {$PSVersionTable.PSVersion } -computername $_.PSComputerName
$_ | Add-Member -MemberType NoteProperty -Name PSVersion -value $psversion
#use -passthru on the last step to write something to the pipeline
$_ | Add-Member -MemberType ScriptMethod -Name GetDisk -value {
Get-CimInstance -classname Win32_logicalDisk -filter "DeviceID='$DeviceID'" -ComputerName $this.pscomputername
} -PassThru

With these members in place, I can use them with the $data object however I want.
071215 1921 AllAboutTha7
Or process everything.

$data | Select PSComputername,Location,OperatingSystem,ServicePack,InstallDate,Uptime,
   $c = $_.GetDisk()
PSVersion | Out-Gridview -Title "Server Data"

071215 1921 AllAboutTha8

This is certainly a different way of managing with PowerShell and is something I want to continue to explore in the next article. I hope you’ll let me know what you think about all of this.