Prof. Powershell

Math Matters

Hate math? Hate it no longer when you invoke the .NET Math class for this week's data formatting tip.

I'm sure many of you have either seen code like this if not run it yourself:

PS C:\> Get-WmiObject win32_logicaldisk -filter "drivetype=3" | Select DeviceId,Size,Freespace

DeviceId                   Size        Freespace
--------                   ----        ---------
C:                 201504845824      134099279872
D:                  32499560448        5596352512
E:                    209711104         135725056

You've probably also tried to reformat the number to something more meaningful, such as megabytes:

PS C:\> Get-WmiObject win32_logicaldisk -filter "drivetype=3" | Select DeviceID,@{Name="SizeMB";Expression={$_.Size/1MB}},@{Name="FreeMB";Expression={$_.Freespace/1MB}}

DeviceID                 SizeMB           FreeMB
--------                 ------           ------
C:              192169.99609375   127881.6796875
D:               30993.99609375    5337.09765625
E:                 199.99609375         129.4375

Of course very few people like all those decimal points. So you might try shortening them with this:

PS C:\> Get-WmiObject win32_logicaldisk -filter "drivetype=3" | Select DeviceID,@{Name="SizeMB";Expression={"{0:N2}" -f ($_.Size/1MB)}},
@{Name="FreeMB";Expression={"{0:N2}" -f ($_.Freespace/1MB)}}

DeviceID       SizeMB          FreeMB
--------       ------          ------
C:             192,170.00      127,887.63
D:             30,994.00       5,337.10
E:             200.00          129.44

This certainly looks better and if all you want is a simple formatted report, this is fine. But what you see is not what you get. Look at this:

PS C:\> Get-WmiObject win32_logicaldisk -filter "drivetype=3" |
Select DeviceID,@{Name="SizeMB";Expression={"{0:N2}" -f ($_.Size/1MB)}},
@{Name="FreeMB";Expression={"{0:N2}" -f ($_.Freespace/1MB)}} | Sort FreeMB -Descending

DeviceID       SizeMB          FreeMB
--------       ------          ------
D:             30,994.00       5,337.10
E:             200.00          129.44
C:             192,170.00      127,880.50

It's clearly not sorting in the expected order. That's because when you use the -f operator, it creates a string value. If you had a numeric value property from many machines you won't get the results you expect.

One solution is to convert this string back into a number:

PS C:\> Get-WmiObject win32_logicaldisk -filter "drivetype=3" |
Select DeviceID,@{Name="SizeMB";Expression={("{0:N2}" -f ($_.Size/1MB)) -as [double]}},@{Name="FreeMB";Expression={("{0:N2}" -f ($_.Freespace/1MB)) -as [double]}} | Sort FreeMB -Descending

DeviceID         SizeMB         FreeMB
--------         ------         ------
C:               192170      127881.47
D:                30994         5337.1
E:                  200         129.44

That's more like it, but it sure seems like a lot of work. Here's a better way using the .NET Math class.

There are no math cmdlets, but you can access the math library by using [Math]. You can't create a math object, but once you know the method, you can simply invoke it. Here's a one-liner to help show you the methods:

([math]).GetMethods() | sort name | Select Name -unique

Once you know the method, then you can run commands like this:

PS C:\> [math]::Sqrt(25)
5

PS C:\> [math]::Pow(2,10)
1024

PS C:\> [math]::Round(123.45678,2)
123.46

It's that last method that we're interested in. The method needs a number and number of decimal places. The best part is that it writes a numeric object, technically a double in this example, to the pipeline. Thus, we can modify our WMI query accordingly:

PS C:\> Get-WmiObject win32_logicaldisk -filter "drivetype=3" |
Select DeviceID,@{Name="SizeMB";Expression={[math]::Round($_.Size/1MB,2)}},
@{Name="FreeMB";Expression={[math]::Round($_.Freespace/1MB,2)}} | Sort FreeMB -Descending

DeviceID        SizeMB         FreeMB
--------        ------         ------
C:              192170      127884.68
D:               30994         5337.1
E:                 200         129.44

I haven't found a good and easy way to force PowerShell to include the trailing 0, but I can live with this. If you don't want any decimal points, simply eliminate the second parameter value in the Round() method. But now I have a way to get numeric data in the format I want without sacrificing anything.

About the Author

Jeffery Hicks is an IT veteran with over 25 years of experience, much of it spent as an IT infrastructure consultant specializing in Microsoft server technologies with an emphasis in automation and efficiency. He is a multi-year recipient of the Microsoft MVP Award in Windows PowerShell. He works today as an independent author, trainer and consultant. Jeff has written for numerous online sites and print publications, is a contributing editor at Petri.com, and a frequent speaker at technology conferences and user groups.

comments powered by Disqus
Most   Popular