Fancy Octopus

I was grocery shopping the other day and had to take a picture of this.  What exactly differentiates a "fancy" octopus.  I know.  Do you?  It's PowerShell.  Fancy octopi always use PowerShell.  I'm glad it's in an "easy open can".

Today we're going to open up a can of PowerShell for the helpdesk.

Helpdesk Automation

How many minutes does it take over the phone with a user to walk them through giving you the data you need to resolve their issue?  We've all been there...

"Click on the Start button."
"What's that?
"The square in the bottom left corner of the screen."
"Is it the one that says 'Start' on it?"
"Yes, that one."
"I don't see it."

Now most people don't plan to spend their entire career on the help desk.  It is a starting point for bigger things in IT. PowerShell can be your career LAUNCH PAD.  Seriously.  PowerShell skills will differentiate you from your peers and slingshot you to the front of the pack.

Now back to closing tickets... PowerShell style.  What if you could get all of the data you need in seconds?  As long as the machine is on the wire we're good.

Alternatives that work, but with issues:

  • Most folks would try to remote control the machine, but scripting is much faster.
  • You may be able to get some of the data from SCCM or an enterprise asset system, but that takes time and the data may be stale.

Here is an outline of example data collection scripting techniques:

  • Command line automation (wrapped in PowerShell)
    • SYSTEMINFO /S computername /FO csv | ConvertFrom-CSV
  • WMI (PowerShell or command line)
    • Get-WMIObject Win32_OperatingSystem –ComputerName computername
    • Get-WMIObject Win32_ComputerSystem –ComputerName computername
    • WMIC /NODE:computername /OUTPUT:filename.txt OS
  • PowerShell ComputerName switch
    • Get-Process –ComputerName computername
    • Get-Service –ComputerName computername
    • Get-HotFix –ComputerName computername
  • PowerShell v2 Remoting
    • Enter-PSSession –ComputerName computername
    • Invoke-Command –ScriptBlock {foo} –ComputerName computername

In this post I'm going to focus on the first two items listed above.  For a thorough understanding of PowerShell v2 Remoting methods check out my previous article that explores all of the capabilities and prerequisites.

Command line automation wrapped in PowerShell

As much as we like to use PowerShell cmdlets and our own functions, PowerShell does a great job wrapping your old favorite command line utilities.  I used to be a big fan of batch functionality like FOR, reading a list of machines from a text file and then passing them into a command line utility.  Today we can do similar functionality, but we don't have to remember all of those switches for FOR.  Instead now we use a ForEach loop in PowerShell.

Before I forget to mention it you'll want to download the handy Windows Command Reference.  Wow! This CHM (compressed HTML) help file contains syntax and examples of nearly every Windows command line utility, including Active Directory commands. Every administrator needs a copy of this. Note: After downloading the file be sure to unblock it (Right click the file, Properties, click the Unblock button, OK). Then you will be able to view the contents.

While command line utilities like PING have been replaced by the cmdlet Test-Connection, we can still leverage great utilities like SYSTEMINFO.  The thing that makes SYSTEMINFO attractive in PowerShell is that it has a switch for CSV output and a switch for a remote system name.  Here is a sample of SYSTEMINFO output:

systeminfo /s workstation1 /fo csv | ConvertFrom-CSV

Host Name                 : WORKSTATION1
OS Name                   : Microsoft Windows 7 Enterprise
OS Version                : 6.1.7601 Service Pack 1 Build 7601
OS Manufacturer           : Microsoft Corporation
OS Configuration          : Member Workstation
OS Build Type             : Multiprocessor Free
Registered Owner          : frandall
Registered Organization   : Contoso
Product ID                : 00392-918-5012102-85560
Original Install Date     : 5/5/2010, 12:11:54 PM
System Boot Time          : 2/5/2012, 9:53:31 AM
System Manufacturer       : LENOVO
System Model              : 4022HG7
System Type               : x64-based PC
Processor(s)              : 1 Processor(s) Installed.,[01]: Intel64 Family 6 Model 23 Stepping 10 G
                            enuineIntel ~1573 Mhz
BIOS Version              : LENOVO 6FET79WW (3.09 ), 10/2/2009
Windows Directory         : C:\Windows
System Directory          : C:\Windows\system32
Boot Device               : \Device\HarddiskVolume1
System Locale             : en-us;English (United States)
Input Locale              : en-us;English (United States)
Time Zone                 : (UTC-05:00) Eastern Time (US & Canada)
Total Physical Memory     : 8,122 MB
Available Physical Memory : 5,502 MB
Virtual Memory: Max Size  : 16,242 MB
Virtual Memory: Available : 13,022 MB
Virtual Memory: In Use    : 3,220 MB
Page File Location(s)     : C:\pagefile.sys
Domain                    : na.contoso.com
Logon Server              : \\DC1
Hotfix(s)                 : 99 Hotfix(s) Installed.,[01]: 982861,[02]: KB958830,[03]: KB971033,[04]
                            : KB958559,[05]: KB2305420,[06]: KB2393802,[07]: KB2425227,[08]: KB2446
                            710,[09]: KB2475792,[10]: …(output truncated)
Network Card(s)           : 3 NIC(s) Installed.,[01]: Intel(R) 82567LM Gigabit Network Connection,
                                 Connection Name: Local Area Connection,      Status:          Medi
                            a disconnected,[02]: Intel(R) WiFi Link 5300 AGN,      Connection Name:
                             Wireless Network Connection,      Status:          Media disconnected,
                            [03]: Microsoft Virtual WiFi Miniport Adapter,      Connection Name: Wi
                            reless Network Connection 2,      Status:          Media disconnected

You'll notice that this output is almost perfect except for the mashed up Hotfixes and NICs.  The reason we use objects in PowerShell is so that we don't have to parse text.  That's what makes the CSV output option so cool here.  In this case the hotfixes and NICs can be parsed using the string Split method.  Notice that because these property names contain spaces and parentheses we have to put the property name in quotes.  That's something you don't see every day.

$si = systeminfo /s localhost /fo csv | ConvertFrom-CSV            
$si."Network Card(s)".Split(",")            
$si."Hotfix(s)".Split(",")

4 NIC(s) Installed.
[01]: Intel(R) 82567LM Gigabit Network Connection
      Connection Name: Local Area Connection
      Status:          Media disconnected
[02]: Intel(R) WiFi Link 5300 AGN
      Connection Name: Wireless Network Connection
      Status:          Media disconnected
[03]: Microsoft Virtual WiFi Miniport Adapter
      Connection Name: Wireless Network Connection 2
      Status:          Media disconnected
[04]: Bluetooth Device (Personal Area Network)
      Connection Name: Bluetooth Network Connection 2
      Status:          Media disconnected

99 Hotfix(s) Installed.
[01]: 982861
[02]: KB958830
[03]: KB971033
[04]: KB958559
[05]: KB2305420
etc…

This makes the output a little more useful for analysis, but it still isn't quite clean.  Ignoring that factor let's take this to the next level by passing multiple computer names into a loop:

$PCs = "workstation1","workstation2","workstation3"            
$OutputArray = @()
ForEach ($PC in $PCs) {
    $OutputArray += systeminfo /s $PC /fo csv | ConvertFrom-CSV
}
$OutputArray | Export-CSV SYSTEMINFO.csv -NoTypeInformation
Start-Process SYSTEMINFO.csv

Now that is some schnazzy PowerShell.  You've got an instant system inventory spreadsheet with tons of vital data.  You may be thinking that a FOR batch command could do the same thing, and you're almost correct.  The big difference is that now we have a PowerShell object returned for each remote machine.  We don't have to parse all of the command line output, and that is worth hours of code time on its own.

I've highlighted a couple examples of the PowerShell special sauce:

  • @() : creates an empty array.
  • += : appends each new output instance as an object in the array
  • | ConvertFrom-CSV : converts the CSV string blob into a rich PowerShell CSV object

With a minor tweak we can take this code sample, drop it into a script file, and then take a list of PCs as a parameter from the PowerShell console using the pirate variable $Args.  Or we could turn it into an advanced function that would take pipeline computer name input from anywhere we can query a list of names.  In the interest of time I'll let you develop those pieces, as those are pretty well documented.

In this particular example we did all of the heavy lifting with the SYSTEMINFO command line and its handy CSV output switch.  Imagine what you could do with any of your other favorite command line utilities in a simple PowerShell loop.

WMI – Unleash the fancy tentacles

The problem with command line utilities is that they are limited.  You cannot always get the specific data or make the specific changes you need with them.  The other downside with command line utilities is that they don't always give output in CSV or XML for PowerShell to easily consume.  That's where WMI comes to the rescue.  WMI has been baked into every major Windows OS since 2000.  With each OS release and product update there has been a growing database of objects just waiting for you to tap them.  This is where the data for SYSTEMINFO comes from.  I bet you can think of a few things you wish SYSTEMINFO would have included.  Here's your chance to get the rest of the data.

Using the same concept we had for SYSTEMINFO let's replace the body of the loop with a series of Get-WMIObject calls that will retrieve the specific data we want (not what is baked into a compiled EXE):

$PCs = "workstation1","workstation2","workstation3"            
# Or get the PCs from a text file:            
# $PCs = Get-Content PCs.txt            
$OutputArray = @()            
ForEach ($PC in $PCs) {            
    $bios = gwmi Win32_BIOS            -ComputerName $PC -Property Manufacturer, SerialNumber            
    $os   = gwmi Win32_OperatingSystem -ComputerName $PC -Property Caption, OSArchitecture            
    $cs   = gwmi Win32_ComputerSystem  -ComputerName $PC -Property DNSHostName
    $PCData = New-Object PSObject -Property @{            
         Manufacturer=$bios.Manufacturer;            
         SerialNumber=$bios.SerialNumber;            
         OSVersion=$os.Caption;            
         Arch=$os.OSArchitecture;            
         HostName=$cs.DNSHostName;            
        }            
    $OutputArray += $PCData            
}            
$OutputArray | Format-Table -AutoSize            

The output would look something like this:

SerialNumber Manufacturer OSVersion                       HostName     Arch 
------------ ------------ ---------                       --------     ---- 
ABCD123      LENOVO       Microsoft Windows 7 Enterprise  WORKSTATION1 64-bit

1234ABC      TOSHIBA      Microsoft Windows 7 Enterprise  WORKSTATION2 64-bit

A1B2C3D      HP           Microsoft Windows 7 Enterprise  WORKSTATION3 32-bit

The first thing you'll notice is that there is a little more code involved, but the rewards are immediately apparent.  Using this template you can leverage any of 1,000s of WMI classes to get exactly the data you need.

Release the Kraken!

What would happen if we combined SYSTEMINFO and WMI into a single, massive data collection script?  Let's find out.  I've wanted to write this for a long time, so here it is.  The script attached to this post collects the following client health data:

  • Ping test
  • Local time on remote PC (in case clock is out of sync)
  • C$ share access test (in case you need UNC access to the hard drive)
  • Free disk space on C:
  • Free memory
  • CPU utilization
  • Last 10 errors or warnings in the System event log
  • Last 10 errors or warnings in the Application event log
  • SYSTEMINFO report

To use the script follow these instructions:

  • Download the attachment at the bottom of the post.
  • Rename the file extension to PS1.
  • Right-click the file and choose Unblock.
  • Open up the PowerShell console and navigate to the folder where you put the script.
  • Type this (use a real computer name instead of workstation1):
    • .\Get-ComputerHealth.ps1 workstation1 > health.txt
    • .\health.txt
  • Study the output file for issues.

This script is just a sample for you to tweak and expand.  Insert your own WMI calls, check for custom configuration, etc.  The sky is the limit.  You can even write a different version for each specific in-house app at your company.  You could check for file versions, registry keys, etc. where you commonly find root causes for helpdesk calls.  Imagine how fast you could close tickets if you had a PowerShell script for each of the top ten most common issues.  You could even add logic that automatically repairs certain issues based on the data the script finds.

Fancy Helpdesk

I hope this post has inspired you to learn PowerShell and leverage it on the helpdesk.  Becoming the go-to-scripter will be a huge boost for your career, and you will be able to close tickets even faster.  Using PowerShell like this makes me excited about all of the possibilities, but I'm never going to eat a can of Fancy Octopus.