Using Add-Member in a Powershell One-Liner to Create Calculated Properties

This is one of the live demos I do when I teach class.  Its a slightly advanced concept for some of my students as it deals with object members.  It is actually fairly nice looking code and I love the fact that I can maintain my original object (instead of say changing to a PSCustom object) yet get data formatted the way I want.

In this example I am getting logical disk information from WMI:

PS C:\> Get-WmiObject win32_logicaldisk

DeviceID : C:
DriveType : 3
ProviderName :
FreeSpace : 7357739008
Size : 117761372160
VolumeName :

DeviceID : D:
DriveType : 3
ProviderName :
FreeSpace : 1470799872
Size : 2147479552
VolumeName : RECOVERY

DeviceID : E:
DriveType : 5
ProviderName :
FreeSpace : 0
Size : 7475329024
VolumeName : DVD_VIDEO

DeviceID : G:
DriveType : 2
ProviderName :
FreeSpace : 3652608000
Size : 8019488768
VolumeName : 8GB - ExpressCard

Now I am going to filter this down to drivetype 2 and 3 to keep only local and removable disks:

PS C:\> Get-WmiObject win32_logicaldisk -Filter "drivetype=2 or drivetype=3"
DeviceID : C:
DriveType : 3
ProviderName :
FreeSpace : 7357575168
Size : 117761372160
VolumeName :

DeviceID : D:
DriveType : 3
ProviderName :
FreeSpace : 1470799872
Size : 2147479552
VolumeName : RECOVERY

DeviceID : G:
DriveType : 2
ProviderName :
FreeSpace : 3652608000
Size : 8019488768
VolumeName : 8GB - ExpressCard

Alright now that I have this I want to turn it into a nice table showing the name, freespace, and size, but freespace and size are in bytes, which I just cant stand looking at.  Lets use Add-Member to create a couple of script properties and turn them into rounded GB.

PS C:\> Get-WmiObject win32_logicaldisk -Filter "drivetype=2 or drivetype=3" | Add-Member -MemberType ScriptProperty -Name FreeSpaceinGB -Value {[math]::Round(($this.freespace / 1GB),2)} -PassThru | Format-Table Name,FreespaceinGB -AutoSize
name FreeSpaceinGB
---- -------------
C: 6.85
D: 1.37
G: 3.4

I used add-member to add a scriptproperty to my objects.  In the code block for a script proprerty you must use $this instead of $_ which is a common mistake I see in classes.  Don't forget about using passthru.  Many action cmdlets like add-member dont put any output back into the pipeline once they do their work.  In this case using it in a pipeline, this is a bad thing.  Normally in a script you might use add-member in a pipe after a variable in which case the new member would be saved in the variable.  You wouldnt want the output.  Using it in a pipe like this though, we are not saving these object, they die once the line is done.  We need to use passthru so that Add-member does it work and then goes ahead and passes through the modified objects.  Once we get the new objects with the new property on them, we need to tell Powershell we want to view them since these new properties would not be displayed with the default view configured in Powershell's type file for this object type.  I use format-table and call the properties I want to see including my new property.

Lastly I am going to use another add-member cmdlet to add a second property and get closer to my desired result.  I still want to go another step, but first lets just look at the results with two properties added:

PS C:\> Get-WmiObject win32_logicaldisk -Filter "drivetype=2 or drivetype=3" | Add-Member -MemberType ScriptProperty -Name FreeSpaceinGB -Value {[math]::Round(($this.
freespace / 1GB),2)} -PassThru | Add-Member -MemberType ScriptProperty -Name SizeinGB -Value {[math]::Round(($this.size / 1GB),2)} -PassThru | Format-Table name,FreespaceinGB,SizeinGB -AutoSize

name FreeSpaceinGB SizeinGB
---- ------------- --------
C: 6.85 109.67
D: 1.37 2
G: 3.4 7.47

So this is a long one-liner but we have more to go.  I want to create another scriptproperty to contain the freespace percentage.  This poses an interesting challende that stumped me the first time I tried it.  I tried like this and didnt get an error, but it didnt work...clearly :)

PS C:\> Get-WmiObject win32_logicaldisk -Filter "drivetype=2 or drivetype=3" | Add-Member -MemberType ScriptProperty -Name FreeSpaceinGB -Value {[math]::Round(($this.freespace / 1GB),2)} -PassThru | Add-Member -MemberType ScriptProperty -Name SizeinGB -Value {[math]::Round(($this.size / 1GB),2)} -PassThru | Add-Member -MemberTypeScriptProperty -Name FreespacePercent -Value {[math]::Round(($this.freespace / $this.size * 100),2)} -PassThru | Format-Table Name,FreespaceinGB,SizeinGB,FreespacePercent

Name FreeSpaceinGB SizeinGB FreespacePercent
---- ------------- -------- ----------------
C: 6.85 109.67
D: 1.37 2 68.49
G: 3.4 7.47

As you can see, only the D drive has its freespace percentage showing.  No errors whatsoever.  This boggle me a bit the first time I saw it.  Actually the first time, none of my examples worked so it wasnt this inconsistent :)

In order to try some process of elimination I took the add-member out of the picture and tried this again using a straight expression.  This turned out to be a wise move as it would appear that errors that occur in the code of a scriptproperty arent shown, but there are in this test line:

PS C:\> Get-WmiObject win32_logicaldisk -Filter "drivetype=2 or drivetype=3" | ForEach-Object {$_.Name; Write-Host $_.size $_.size.GetType().FullName; Write-Host $_.freespace $_.freespace.GetType().FullName; $_.freespace / $_.size; ""} C:
117761372160 System.UInt64
7355174912 System.UInt64
The operation '[System.UInt64] / [System.UInt64]' is not defined.
At line:1 char:215
+ Get-WmiObject win32_logicaldisk -Filter "drivetype=2 or drivetype=3" | ForEach-O
bject {$_.Name; Write-Host $_.size $_.size.GetType().FullName; Write-Host $_.frees
pace $_.freespace.GetType().FullName; $_.freespace / <<<< $_.size; ""}

D:
2147479552 System.UInt64
1470799872 System.UInt64
0.684895868102776

G:
8019488768 System.UInt64
3652608000 System.UInt64
The operation '[System.UInt64] / [System.UInt64]' is not defined.
At line:1 char:215
+ Get-WmiObject win32_logicaldisk -Filter "drivetype=2 or drivetype=3" | ForEach-O
bject {$_.Name; Write-Host $_.size $_.size.GetType().FullName; Write-Host $_.frees
pace $_.freespace.GetType().FullName; $_.freespace / <<<< $_.size; ""}

Now I can see the error.  On the C and G drives I am getting this error doring the divide operation: "The operation '[System.UInt64] / [System.UInt64]' is not defined."  Honestly, I dont know enough about data types like unsigned 64-bit integers to know why this wont work, and why it did on the G drive.  Like I always tell my students, you dont have to know nearly all the answers to be great with Powershell.  I decided to just try and convert these to regular [int64] numbers and whalla...it works now.  Check out this test code and then we can look at our new add-member code:

PS C:\> Get-WmiObject win32_logicaldisk -Filter "drivetype=2 or drivetype=3" | ForEach-Object {$_.Name; Write-Host $_.size $_.size.GetType().FullName; Write-Host $_.freespace $_.freespace.GetType().FullName; [int64]$_.freespace / [int64]$_.size; ""}

C:
117761372160 System.UInt64
7355006976 System.UInt64
0.0624568722416626

D:
2147479552 System.UInt64
1470799872 System.UInt64
0.684895868102776

G:
8019488768 System.UInt64
3652608000 System.UInt64
0.455466440027315

Suhweeet!.  Now that my divison works, lets modify our last attempt at calculating the freespace percentage with this new knowledge and let 'er rip!:

PS C:\> Get-WmiObject win32_logicaldisk -Filter "drivetype=2 or drivetype=3" | Add-Member -MemberType ScriptProperty -Name FreeSpaceinGB -Value {[math]::Round(($this.freespace / 1GB),2)} -PassThru | Add-Member -MemberType ScriptProperty -Name SizeinGB -Value {[math]::Round(($this.size / 1GB),2)} -PassThru | Add-Member -MemberTypeScriptProperty -Name FreespacePercent -Value {[math]::Round(([int64]$this.freespace / [int64]$this.size * 100),2)} -PassThru | Format-Table Name,FreespaceinGB,SizeinGB,FreespacePercent

Name FreeSpaceinGB SizeinGB FreespacePercent
---- ------------- -------- ----------------
C: 6.85 109.67 6.24
D: 1.37 2 68.49
G: 3.4 7.47 45.55

So the end of this very long one-liner, we have a very nice looking output table that has a our calculated data that is considerably more human digestable in my opinion.  There is a quite a bit going on in this post, but I hope this helps give a real world example of using add-member to simply create additional properties to look the way you want them to.  To close out this post, I will show you that my objects are still the original .Net WMI objects:

PS C:\> Get-WmiObject win32_logicaldisk -Filter "drivetype=2 or drivetype=3" | Add-Member -MemberType ScriptProperty -Name FreeSpaceinGB -Value {[math]::Round(($this.freespace / 1GB),2)} -PassThru | Add-Member -MemberType ScriptProperty -Name SizeinGB -Value {[math]::Round(($this.size / 1GB),2)} -PassThru | Add-Member -MemberTypeScriptProperty -Name FreespacePercent -Value {[math]::Round(([int64]$this.freespace / [int64]$this.size * 100),2)} -PassThru | Get-Member

   TypeName: System.Management.ManagementObject#root\cimv2\Win32_LogicalDisk

Name MemberType Definition
---- ---------- ----------
Chkdsk Method System.Management.ManagementBaseObj...
Reset Method System.Management.ManagementBaseObj...
SetPowerState Method System.Management.ManagementBaseObj...
Access Property System.UInt16 Access {get;set;}
Availability Property System.UInt16 Availability {get;set;}
BlockSize Property System.UInt64 BlockSize {get;set;}
Caption Property System.String Caption {get;set;}
Compressed Property System.Boolean Compressed {get;set;}
ConfigManagerErrorCode Property System.UInt32 ConfigManagerErrorCod...
ConfigManagerUserConfig Property System.Boolean ConfigManagerUserCon...
CreationClassName Property System.String CreationClassName {ge...
Description Property System.String Description {get;set;}
DeviceID Property System.String DeviceID {get;set;}
DriveType Property System.UInt32 DriveType {get;set;}
ErrorCleared Property System.Boolean ErrorCleared {get;set;}
ErrorDescription Property System.String ErrorDescription {get...
ErrorMethodology Property System.String ErrorMethodology {get...
FileSystem Property System.String FileSystem {get;set;}
FreeSpace Property System.UInt64 FreeSpace {get;set;}
InstallDate Property System.String InstallDate {get;set;}
LastErrorCode Property System.UInt32 LastErrorCode {get;set;}
MaximumComponentLength Property System.UInt32 MaximumComponentLengt...
MediaType Property System.UInt32 MediaType {get;set;}
Name Property System.String Name {get;set;}
NumberOfBlocks Property System.UInt64 NumberOfBlocks {get;s...
PNPDeviceID Property System.String PNPDeviceID {get;set;}
PowerManagementCapabilities Property System.UInt16[] PowerManagementCapa...
PowerManagementSupported Property System.Boolean PowerManagementSuppo...
ProviderName Property System.String ProviderName {get;set;}
Purpose Property System.String Purpose {get;set;}
QuotasDisabled Property System.Boolean QuotasDisabled {get;...
QuotasIncomplete Property System.Boolean QuotasIncomplete {ge...
QuotasRebuilding Property System.Boolean QuotasRebuilding {ge...
Size Property System.UInt64 Size {get;set;}
Status Property System.String Status {get;set;}
StatusInfo Property System.UInt16 StatusInfo {get;set;}
SupportsDiskQuotas Property System.Boolean SupportsDiskQuotas {...
SupportsFileBasedCompression Property System.Boolean SupportsFileBasedCom...
SystemCreationClassName Property System.String SystemCreationClassNa...
SystemName Property System.String SystemName {get;set;}
VolumeDirty Property System.Boolean VolumeDirty {get;set;}
VolumeName Property System.String VolumeName {get;set;}
VolumeSerialNumber Property System.String VolumeSerialNumber {g...
__CLASS Property System.String __CLASS {get;set;}
__DERIVATION Property System.String[] __DERIVATION {get;s...
__DYNASTY Property System.String __DYNASTY {get;set;}
__GENUS Property System.Int32 __GENUS {get;set;}
__NAMESPACE Property System.String __NAMESPACE {get;set;}
__PATH Property System.String __PATH {get;set;}
__PROPERTY_COUNT Property System.Int32 __PROPERTY_COUNT {get;...
__RELPATH Property System.String __RELPATH {get;set;}
__SERVER Property System.String __SERVER {get;set;}
__SUPERCLASS Property System.String __SUPERCLASS {get;set;}
PSStatus PropertySet PSStatus {Status, Availability, Dev...
ConvertFromDateTime ScriptMethod System.Object ConvertFromDateTime();
ConvertToDateTime ScriptMethod System.Object ConvertToDateTime();
Delete ScriptMethod System.Object Delete();
GetType ScriptMethod System.Object GetType();
Put ScriptMethod System.Object Put();
FreeSpaceinGB ScriptProperty System.Object FreeSpaceinGB {get=[m...
FreespacePercent ScriptProperty System.Object FreespacePercent {get...
SizeinGB ScriptProperty System.Object SizeinGB {get=[math]:...

 

 

This posting is provided "AS IS" with no warranties, and confers no rights. Use of included script samples are subject to the terms specified at https://www.microsoft.com/info/cpyright.htm.