Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

The Value of Objects

Posted on July 5, 2022July 5, 2022

This is a reprint of an article published earlier this year in my premium PowerShell newsletter, Behind the PowerShell Pipeline. This is a sample of what my subscribers get 6-8 times a month.

I expect I will write several articles about PowerShell and its relationship with objects. I know that this is the biggest hurdle for PowerShell beginners to overcome. But once they grasp that PowerShell is about working with objects in the pipeline, they recognize the value and begin finding it easier to write PowerShell code and use it interactively at a console prompt.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!

In a Console A Long, Long Time Ago

Don't forget that I am approaching PowerShell from the role of an IT pro. Many of these people come to PowerShell with other automation and scripting skills. It might be old-fashioned batch files. It might be extensive experience with VBScript. That was my PowerShell journey. Like many of you, my initial PowerShell experiences were colored by my past. I kept trying to make PowerShell write text as I did with VBScript. Here's an old example.

Dim objSet, wshell
On Error Resume Next

Set wshell=CreateObject("Wscript.Shell")

If wscript.arguments.count=0 Then
 strSrv=InputBox("What server do you want to check?  You must have admin rights on it.  Do NOT use \\ " & _
 "before the servername.","Disk Check","SERVERNAME")
 If strSrv="" Then
   wshell.popup "Nothing entered or you cancelled",4,"Disk Check",0+48
   wscript.quit
 End If
Else
 strSrv=Trim(wscript.arguments(0))
End If

strQuery = "Select * from win32_logicaldisk where drivetype=3"
Set objSet=GetObject("winmgmts:\\" & strSrv).ExecQuery(strQuery)
if err.number<>0 then
 wshell.popup "Oops! Error connecting to " & UCase(strSrv) & vbCrlf & "make sure you are using valid " & _
 "credentials." & vbCrlf & "Error: " & err.number & " - " & err.description,5,"Disk Check Error",0+48
 wscript.quit
end if

For Each item In objSet
   PerFree=FormatPercent(item.FreeSpace/item.Size,2)
   o=o & item.DeviceID & "\" & VBTAB
   o=o & FormatNumber(item.Size/1048576,0) & Vbtab & FormatNumber(item.FreeSpace/1048576,0) & Vbtab & PerFree & Vbcrlf
next
WScript.Echo "Drive" & Vbtab & "Size (MB) Free (MB) %Free" & VbCrLf & o
set objSet=Nothing
set wshell=Nothing

wscript.quit

Don't panic. I'm not going to try and teach you VBScript. The script is getting disk information from WMI and displaying a summary. But to get the desired result, I spend a lot of time creating strings of text to display. In the For Each item section, I am building a string for each drive showing the size and free space. Each drive  gets concatenated to the variable o. At the end of the script, I write the string of information to the screen.

WScript.Echo "Drive" & Vbtab & "Size (MB) Free (MB) %Free" & VbCrLf & o

Here's what I end up with.

VBScript WMI output

Many people new to PowerShell are trying to force it to behave like this. They are used to a scripting language returning a string of text. This is especially true of people with a background in Linux bash scripting. Because Linux is a text-based operating system, its tools are based on the concept of parsing text. Someone new to PowerShell and still stuck in the text paradigm might write a PowerShell script like this.

$strSrv = Read-Host "Enter a computername"
$strQuery = "Select * from win32_logicaldisk where drivetype=3"
$objSet = Get-WmiObject -Query $strQuery -ComputerName $strSrv
Write-Host "Drive`t Size (MB)       Free (MB)       %Free"
foreach ($item in $objSet) {
$perFree = ($item.FreeSpace/$item.size)*100
$line = $item.DeviceID + "\   `t" + $item.Size/1048576 + "  " + $item.FreeSpace/1048576 + "  " + $perFree
Write-Host $line
}

Sadly, I have seen code like this in the real world. It will work. Sort of.

vbscript as powershell

But this is a lot of work. One reason people get hung up on text is that running a command in PowerShell almost always produces easy-to-read text output. Think of the result you get from Get-Service and Get-Process.

Objects Are Easy

The idea of an object shouldn't be that difficult. We deal with objects in the real world every day. The challenge is getting our heads wrapped around the concept of virtual objects. If you take the time to look at PowerShell, you'll see it is trying to help you. The command names imply objects. Get-Service is going to get service objects. Here's a situation where using the full cmdlet name instead of the gsv alias can help a beginner. You don't want to learn PowerShell thinking, "When I run the gsv command, I get a list of services." You want to be thinking, "When I run the Get-Service command, I am getting a collection of service objects that I can pass on to another command."

Let's revisit the WMI code. The code says, "Get me a WMI object that is a Win32_Logicialdisk that meets specific criteria on the specified remote computer." Once those objects have been returned, the code tells PowerShell what object properties to display. The results are displayed, and PowerShell automatically handles all of the formatting.

$computername  = Read-Host "Enter a computername"
$Query = "Select * from win32_logicaldisk where drivetype=3"
Get-WmiObject -Query $Query -ComputerName $computername | 
Select-Object -property DeviceID,Size,Freespace,
@{Name="PercentFree";Expression = {$_.freespace/$_.size}}
formatted results in PowerShell

One of the best features of PowerShell is that you can also define new object properties. The WMI win32_logicaldisk object doesn't have a PercentFree property. But Select-Object can do that for me with the custom hashtable.

Once you start thinking about objects and not parsing text, you'll realize there is an entire world of possibilities. I tell beginning PowerShell scripters to say aloud what they want PowerShell to do.

Get all fixed logical disks from a remote computer. Select the computer name, drive letter, size in GB, free disk space, and percent free space and export to a CSV file.

Once you articulate what you want, you can find the PowerShell commands and parameters to make it happen.

Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" -ComputerName $computername |Select-Object -property DeviceID,@{Name="SizeGB";Expression = {$_.size/1GB -as [int32]}},Freespace,@{Name="PercentFree";Expression = {($_.freespace/$_.size)*100}},SystemName |Export-CSV c:\work\diskinfo.csv -Append

The biggest hurdle for beginners is discovering object properties with Get-Member and the tricks to format values, such as using the 1GB shortcut and the -As operator. I have found that the people who can visualize the concept of objects in the pipeline adopt PowerShell quicker and with less pain.

That's not to say we still don't need to mess with text or values. The percent free value I defined above is a  good example. You might be inclined to use the -f operator.

@{Name="PercentFree";Expression = {"{0:p2}" -f ($_.freespace/$_.size)}}

It sure looks nice.

formatted percentages

If all I want to do is look at this result, I might be ok. But even here, we're back to text. If I pipe this output to Get-Member I'll see that the PercentFree property is a System.String. The problem arises when I want to do something with the value, such as sorting. The value will sort as a string, not a number, and I might not get the expected results.

I find a better approach is to treat the value as a [System.Double] object using the [math] class to round the value to 2 decimal places.

@{Name="PercentFree";Expression = {[math]::round(($_.freespace/$_.size)*100,2)}}

With this, I get the formatted percentage I want and a value I can use.

I hope you can see the value here.

Functions Write Objects

The last part of the story I want to discuss briefly revolves around PowerShell functions. I have very strong opinions on how to write a PowerShell function properly and how it should behave. This is often at odds with people who come to PowerShell with a developer background or maybe used to writing VBScript subroutines.

While I recognize there are exceptions, a PowerShell function should write a series of objects, or maybe a single object, to the pipeline. It is not returning anything that looks like a string. Technically, there is nothing wrong with this simple PowerShell function.

Function Get-PercentFree {
    Param($Drive = "C:", $Computername = $env:COMPUTERNAME)

    $d = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DeviceID='$Drive'" -ComputerName $computername
    $p = [math]::round(($d.freespace / $d.size) * 100, 2)
    return $p
}

But it is of limited value. All I'm going to get is a result like 40.96. I would have written this type of code as a VBScript subroutine. But we're talking about PowerShell. I want an object in the pipeline.

Function Get-LogicalDisk {
    [cmdletbinding()]
    Param($Computername = $env:COMPUTERNAME)

    $disks = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" -ComputerName $computername
    $disks | Select-Object @{Name = "Computername"; Expression = { $_.SystemName } },
    DeviceID, Size, FreeSpace, @{Name = "PercentFree"; Expression = { [math]::round(($_.freespace / $_.size) * 100, 2) } },
    @{Name = "Audit"; Expression = { Get-Date } }
}

The function is written to make a point, not necessarily something ready for production. Running the function gives this kind of output.

get logical disk objects

If all I need is percent free, that is easy enough to retrieve.

getting drive usage with PowerShell

The function is flexible and re-usable because it writes a rich object to the pipeline.

If you noticed in the function, I also did not format the size and free space values. I left them in bytes. Getting or creating an object in PowerShell is separate from how it is formatted or presented. But that's a topic for another day.

If I need strings, say for a log file, I can still create them from objects.

$data = "prospero","thinkx1-jh","dom1","srv2" | 
Foreach-Object { Get-LogicalDisk -Computername $_ }
foreach ($item in $data) {
 $line = "[{0:d}] Drive {1} on {2} is {3}% free" -f $item.Audit,$item.DeviceID,$item.computername,$item.percentfree
 $line
 #$line | Out-File log.txt -append
}

Note that because the Audit property is a datetime object, I can format it using the d qualifier.

text from objects

Trying to accomplish this with a text-based paradigm is much more complicated and frustrating.

The more you think about the object you can get from PowerShell and how you can use it, the more you can accomplish, and most likely with better PowerShell code.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

4 thoughts on “The Value of Objects”

  1. Pingback: The Value of Objects - The Lonely Administrator - Syndicated Blogs - IDERA Community
  2. Pingback: Working with Objects in Powershell – Curated SQL
  3. Jim says:
    July 13, 2022 at 4:01 am

    Thank you. Coming from VBScript to Powershell I’ve done exactly this. However, I’ll be working hard from now on to follow your guidance on working with objects, rather than trying to leverage everything into strings! Very well written.

  4. Pingback: PowerShell SnippetRace 36 & 37-2022 | PowerShell Usergroup Austria

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d