Share via

Windows PowerShell: Think Commands, Not Scripts

Don’t be intimidated by the term “scripting,” because you can do a lot with Windows PowerShell using simple commands.

Don Jones

Perception has been one of the biggest struggles Windows PowerShell has had in terms of administrator acceptance. There’s a lingering perception that the shell is a “scripting language,” akin to VBScript. While a lot of admins love what they can do with a scripting language, plenty more are turned off by the perception of complexity and a steep learning curve.

It’s a shame. The shell supports a robust script-based approach, but it does equally well at a simpler, command-oriented approach. The real beauty of the shell is that you can use either approach to accomplish many of the same tasks.

Just a Script

The following function will accept computer names from the command line, either as strings or in the “ComputerName” property of an input object; it will also retrieve the BIOS and OS information from each computer using Windows Management Instrumentation (WMI):

function Get-Inventory
       [string] $computername
   Process {
      $os = gwmi win32_operatingsystem -computername $computername
      $bios = gwmi win32_bios -computername $computername
      $obj = new-object psobject
      $obj | add-member noteproperty ComputerName $computername
      $obj | add-member noteproperty OSBuild ($os.buildnumber)
      $obj | add-member noteproperty SPVersion ($os.servicepackmajorversion)
      $obj | add-member noteproperty BIOSSerial ($bios.serialnumber)
      Write-output $obj

Note that the parentheses force the shell to execute expressions—such as getting the BuildNumber property from the object in the $os variable—and return the result of that expression as the third parameter value of Add-Member.

I can also run this function by piping in static computer names:

'localhost','server2' | Get-Inventory

Or by sending the contents of a text file containing one computer name per line:

Get-Content names.txt | Get-Inventory

Or even by retrieving computer objects from Active Directory, changing the Name property to ComputerName, and piping those:

Import-Module ActiveDirectory
Get-ADComputer –filter * | Select-Object @{Label='ComputerName';Expression={$_.Name}} | Get-Inventory

By the way, I use the braces to encapsulate executable code. The $_ placeholder represents the object piped into the Select-Object cmdlet. The results of any of these are a neatly formatted four-column table. I can easily redirect that output to a file, printer or grid, or even filter and sort the results before displaying them. For example:

Get-Content names.txt | Get-Inventory | Where { $_.BuildNumber –eq 7600 } | Sort ComputerName

Once again, the braces encapsulate an executable block of code—the expression I want to filter—and the $_ placeholder represents the piped-in object.

Command Performance

There’s nothing wrong with a script like that, but it’s working pretty hard. It’s a scripter- or programmer-style approach that plenty of admins find intimidating. You could do the same thing in a single, somewhat complex command. Brace yourself:

Get-WmiObject Win32_OperatingSystem -computername (get-content names.txt) | 
Select-object @{Label="ComputerName";Expression={$_.__SERVER}},
             @{Label="BIOSSerial";Expression={(gwmi win32_bios -comp $_.__server).serialnumber}}

There’s a lot going on there. Here’s a breakdown:

  1. First, run Get-WmiObject to retrieve the Win32_OperatingSystem object from the specified computer names. If you’re a regular reader of this column, you may know that objects returned by Get-WmiObject will always include a __SERVER property, which contains the computer name that the WMI objects came from.
  2. The WMI objects are piped to Select-Object. I use four hashtables to define four properties: ComputerName, OSBuild, SPVersion and BIOSSerial. Each hashtable specifies a label—which will later be used as the column header for the output—and an expression. The hashtable is constructed by using the @ array operator, followed by the Label/Expression definition inside braces. Those braces enclose the executable code that defines the expression portion of the hashtable.
  3. For the first three columns, the expression is simply referring to an existing property of the object; I’m basically just changing the property name.
  4. For the fourth column, my expression is actually executing a second WMI query against the same server. It’s pulling the computer name from the __SERVER property. See how the entire WMI call is enclosed in parentheses? Those force the expression to be executed first. Whatever object results from that expression will be inserted in place of the parenthetical portion. The period following the closing parentheses lets me access properties of that resulting object, so I’m accessing its SerialNumber property for my fourth column.

In some ways, this syntax is more difficult to read than the script with which I started. It’s compact and uses a lot of punctuation. This is the type of thing you can use as a template and modify it to suit your other needs. If you can’t figure out why it’s not working, post a question on my blog at and I’ll help you figure it out.

Don’t Call It a Script

The point is that Windows PowerShell doesn’t have to be used like a scripting language. The command I demonstrated might be complex, but it’s no more complex than some of the far-out commands I’ve seen administrators write for the old Cmd.exe shell. Once you get used to the syntax—which admittedly takes some practice—the command is a lot less complicated than writing a full-on script or function.

So don’t let the term “scripting language” turn you away from the shell. It’s only as “scripty” as you choose.

Don Jones

Don Jones* is a founder of Concentrated Technology, and answers questions about Windows PowerShell and other technologies at He’s also an author for, which makes many of his books available as free electronic editions through his web site.*