Understanding PowerShell Custom Properties with the Select-Object cmdlet

When I run a PowerShell training class, we naturally get around to talking about the Select-Object cmdlet. This is one of those all-purpose cmdlets that can be used in so many ways. Unfortunately, one of the ways often gives students more than a fair amount of trouble, so let’s see if I can’t demystify the process.

Let’s start with a basic PowerShell expression.

Get-CimInstance win32_logicaldisk -filter "deviceID='c:'"

Displaying disk usage (Image Credit: Jeff Hicks)
Displaying disk usage (Image Credit: Jeff Hicks)

This is a pretty common task, and the output is pretty straightforward. However, the PowerShell team decided what to display by default, which might not always meets your needs. This is where PowerShell’s Select-Object comes in to play. With it, you can tell PowerShell to take an object and select something from it. In this case, we want PowerShell to only select certain properties, which it will then display.

Get-CimInstance win32_logicaldisk -filter "deviceID='c:'" | Select -Property DeviceID,Size,Freespace

Selecting key properties (Image Credit: Jeff Hicks)
Selecting key properties (Image Credit: Jeff Hicks)

Now for the tricky part. In this scenario, although the output is useful, it could be better. For example, the size and freespace values are in bytes. I might like to see these values in GB or MB. I know how to convert a value to GB or MB.
Using PowerShell to convert to GB (Image Credit: Jeff Hicks)
Using PowerShell to convert to GB (Image Credit: Jeff Hicks)

That’s pretty close, but I can go a step further and tell PowerShell to treat the value as an integer, which will round the value.
Converting a value to an integer (Image Credit: Jeff Hicks)
Converting a value to an integer (Image Credit: Jeff Hicks)

That’s good, but what about the freespace value?
Converting the freespace value (Image Credit: Jeff Hicks)
Converting the freespace value (Image Credit: Jeff Hicks)

The initial result would work, but I’m inevitably asked how to limit the result to certain number of decimal places, so that’s what I did in the second command. I took the result of the division operating and rounded it to four decimal places.
With that said, I certainly don’t want to manually create these values. At this point, I have some simple code that will give me the results I want. Let’s hold onto this thought.
If you look at help for Select-Object, you’ll see that you can dynamically create a property with a specifically formatted hashtable. A hashtable is a way of expressing a key/value pair. The hashtables to use with Select-Object have two parts, a name and an expression. Those will be the keys. The first element in the hashtable will look like Name = ‘YourDefinedName’. So instead of displaying size, I want it to say SizeGB. I recommend not using spaces or funky characters.

Let’s start building the new expression. This isn’t complete, so don’t try to run it.

Get-CimInstance win32_logicaldisk -filter "deviceID='c:'" | Select -Property DeviceID,@{Name = "SizeGB"

The second element in the hashtable is the Expression. In a script or the PowerShell ISE, you could enter this on a new line:

@{Name = "SizeGB"
Expression =

But in this situation, it is just as easy to insert the end of line marker, the semi-colon.

Get-CimInstance win32_logicaldisk -filter "deviceID='c:'" | Select -Property DeviceID,@{Name = "SizeGB" ; Expression =

What is the value? The value will be the result of a scriptblock.

Get-CimInstance win32_logicaldisk -filter "deviceID='c:'" | Select -Property DeviceID,@{Name = "SizeGB" ; Expression = { } }

I also added the closing curly brace to hashtable.
A scriptblock is block of commands that PowerShell will execute. Whatever is written to the pipeline will be the value assigned to Expression. For the size, I’ve already figured out and tested the command. But I can’t simply paste in the actual value because that could change. Instead I can reference the property of the current object that is being processed using $_.

Get-CimInstance win32_logicaldisk -filter "deviceID='c:'" |
Select -Property DeviceID,@{Name = "SizeGB" ; Expression = { $_.Size/1GB –as [int] } }

The expression scriptblock will take the size property of the incoming object, divide it by 1GB, and treat it as an integer. If I were to pipe multiple objects from a list of servers, then the expression would be applied to each one. Let’s try just this much.

Testing the custom hashtable property (Image Credit: Jeff Hicks)
Testing the custom hashtable property (Image Credit: Jeff Hicks)

I’m intentionally keeping this simple so you can see how the different parts function. Given this, do you think you could figure out how to display a property called FreeGB?

Get-CimInstance win32_logicaldisk -filter "deviceID='c:'" |
Select -Property DeviceID,@{Name = "SizeGB" ; Expression = { $_.Size/1GB –as [int] } },
@{Name = "FreeGB" ; Expression = { [math]::Round($_.Freespace/1gb,4)}}

All I had to do was plug my command into the expression scriptblock and reference to correct property name.

Testing new property hashtables (Image Credit: Jeff Hicks)
Testing new property hashtables (Image Credit: Jeff Hicks)

The great thing is that you aren’t limited simply to objects coming in the pipeline. You can put as much code as you want in the expression scriptblock. This means you can get very creative. See if you can figure out what I’m doing here:

Get-CimInstance win32_logicaldisk -filter "deviceID='c:'" -computername $Computer |
Select -Property DeviceID,@{Name = "SizeGB" ; Expression = { $_.Size/1GB –as [int] } },
@{Name = "FreeGB" ; Expression = { [math]::Round($_.Freespace/1gb,4)}},
@{Name = "PctFree" ; Expression = { [math]::Round(($_.freespace/$_.size)*100,2)}},
@{Name = "OS" ; Expression = { (Get-CimInstance Win32_Operatingsystem -ComputerName $_.pscomputername).caption}},
@{Name = "AuditDate" ; Expression = { Get-Date }},
@{Name = "Computername" ; Expression = {$_.PSComputername}}

I modified the command to query a remote computer. Next, I created an entirely new property called OS that runs another Get-CimInstance command to get the operating system. Noticed how I passed it the computername value from the PSComputername property of the current object.

Customized output (Image Credit: Jeff Hicks)
Customized output (Image Credit: Jeff Hicks)


I now have a more complete and relevant object that I can export, save to a file, or turn into an HTML report. If you start small and simple, using property hashtables with Select-Object is not that complicated and opens up a world of possibilities.