Extending Objects in Windows PowerShell, Part 1

If you have followed my work for a while you know that I am constantly talking about objects in PowerShell. During my training classes, I stress the importance of thinking about objects in the pipeline. When I look at problems in forums, I look to see if the poster is thinking about objects or still working with text. Once I have the data I want to work with as a collection of objects, then there’s practically no limit to what I can do with it.
Extending Objects in PowerShell Article Series:

With that in mind, probably one of the most compelling features in PowerShell is how easy it is to extend it. You’re not limited to whatever a cmdlet writes to the pipeline. You can easily extend the objects to meet your business needs, perhaps in ways you have never even considered before. Here’s what I am talking about.
We can start with a simple command.

$dc1 = Get-CimInstance win32_operatingsystem -ComputerName chi-dc01

The value of $dc1 is an object that contains WMI information from the win32_operatingsystem class.

The $dc1 object in PowerShell. (Image Credit: Jeff Hicks)
The $dc1 object in PowerShell. (Image Credit: Jeff Hicks)

Remember that you can pipe to Select-Object to see all of the properties.

$dc1 | select *

Let’s say you want to know how long the computer has been running. It’s important to note before we get too far that even though I’m running a command for a single computer object, nothing would change if I had 100 objects. You might use Select-Object to create a new property on the fly.

$dc1 | Select PSComputername,LastBootUpTime,@{Name="Uptime";Expression={(Get-Date) - $_.lastbootuptime}}
Using Select-Object in Windows PowerShell. (Image Credit: Jeff Hicks)
Using Select-Object in Windows PowerShell. (Image Credit: Jeff Hicks)

Although that’s useful, our new property only exists for the duration of the command. Imagine that I would like my uptime property to be persistent because I want to do several things with the $dc1 variable. This is where the Add-Member cmdlet comes into play.
In PowerShell, an object’s properties and methods are referred to as its members, which is why we have a Get-Member cmdlet. With Add-Member, we can dynamically add new properties and methods. Here’s a command to add a custom property to $dc1.

$dc1 | Add-Member -MemberType ScriptProperty -Name Uptime -Value {(Get-Date) - $this.lastbootuptime}

There are three parts to PowerShell’s Add-Member cmdlet. First, you need to specify what type of member to add. Although the help content indicates a number different types, several of them don’t seem to work in PowerShell. You’ll most likely be using member types of Noteproperty, ScriptProperty, AliasProperty and possibly ScriptMethod.
In this situation, I’m using a ScriptProperty, which means that I’m going to use a piece of PowerShell script to create a new property value. My new property will have a name of uptime, and the value will be whatever result I get from executing the scriptblock. The code in your scriptblock can be as long or complex as you need it to be. My script block is taking the result of Get-Date and subtracting the LastBootUpTime value from the current object in the pipeline. In scriptblocks for Where-Object or ForEach-Object, we use $_ to reference the current object in the pipeline. With this type of operation, we use $this.
When you run this command, nothing will be written to the pipeline by default, unless you use the –Passthru parameter. Although, you probably still won’t see the custom property, because PowerShell has a predefined set of rules on how to display a CIM Win32_OperatingSystem object and those rules know nothing about a property called Uptime. With that said, the property is there, and you can reference it.

Using the Get-Member cmdlet in Windows PowerShell. (Image Credit: Jeff Hicks)
Using the Get-Member cmdlet in Windows PowerShell. (Image Credit: Jeff Hicks)

That is probably not the result for the Uptime property that you were expecting, but it is correct as subtracting two dates creates a TimeSpan object. But PowerShell is quite helpful and will format the result the way it thinks you need it.

Formatting our result. (Image Credit: Jeff Hicks)
Formatting our result. (Image Credit: Jeff Hicks)

I don’t know about you, but that looks pretty helpful to me. However, for the sake of demonstration, let’s say you always want the uptime value to be a string

Converting the uptime value to a string. (Image Credit: Jeff Hicks)
Converting the uptime value to a string. (Image Credit: Jeff Hicks)

We can re-run the Add-Member command.

$dc1 | Add-Member -MemberType ScriptProperty -Name Uptime -Value {((Get-Date) - $this.lastbootuptime).ToString()} -Force

I want to make sure you notice the –Force parameter I added. If you try to define a member that already exists, PowerShell will complain and refuse to add it, unless you specify –Force. Now that I have, the uptime property is formatted the way I want it.

Newly formatted dc1.uptime property. (Image Credit: Jeff Hicks)
Newly formatted dc1.uptime property. (Image Credit: Jeff Hicks)

This property will exist for the duration of my PowerShell session or until I remove the $dc1 variable. If I always wanted to have this property, I could create a custom type extension using Update-TypeData. But that’s a topic for a different article.
To wrap up, let me quickly demonstrate that this technique works for multiple objects as well. I have a group of CIMSessions to a few servers. I’m going to get Win32_OperatingSystem instances and add my custom property.

$data = get-cimsession | get-cimInstance win32_operatingsystem | Add-Member -MemberType ScriptProperty -Name Uptime -Value {(Get-Date) - $this.lastBootuptime} -PassThru

When you use a command like this, be sure to include the –Passthru parameter, otherwise you won’t see anything.
As I mentioned earlier, the default display doesn’t know anything about your custom property.
071215 1919 ImAllAboutT7
But you do:
071215 1919 ImAllAboutT8

Next time we’ll look at what else we might add to our PowerShell expressions to make them meet our needs.