With PowerShell, Sometimes Old is New Again

Not too long ago on Twitter someone lamented the fact that he came across an older PowerShell script and was amazed at much his skills had improved. The script, while perhaps an embarrassing example of PowerShell, demonstrated how much he had learned.

Back to the Future: PowerShell v1 and Monad

I have experienced the exact same set of emotions. I’ve been writing PowerShell scripts since the beta days of Monad. When I look at some of those older scripts, I cringe. But instead of deleting the script so that it never again sees the light of day, perhaps we should embrace these old friends. Yes, they are probably poor examples of PowerShell, but that is what makes them valuable. I’m sure many of the mistakes I made in my early scripts are the same types of mistakes PowerShell beginners are still making today.
I went through my Scripts folder and found one of the oldest PowerShell scripts I still have. Created February 2, 2006, this was even before PowerShell v1 was released. The script is called Showprocesses.ps1. This is the code as it exists in the file.

$strComputer = "."
$colItems = get-wmiobject -class "Win32_Process" -namespace "root\CIMV2" `
-computername $strComputer
foreach ($objItem in $colItems) {
if ($objItem.WorkingSetSize -gt 3000000) {
write-host $objItem.Name, $objItem.WorkingSetSize -foregroundcolor "magenta" }
else {write-host $objItem.Name, $objItem.WorkingSetSize}
}

You have to realize that 2006 I was still very heavily into VBScript and was just starting to get my head around this new-fangled PowerShell-thing. Probably like some of you today. The script’s purpose is to use WMI and display a list of processes from a computer. If the WorkingsetSize is greater than 300000 bytes (basically 3MB), then I wanted to display the result in Magenta.
The script works but it is hardly what I would consider “good” PoweShell by today’s standards. If you have worked in VBScript, you’ll probably recognize a VBScript-style to this script. I get a collection of items and then enumerate them. Even my variable naming is archaic and VBScript like. This script uses Hungarian style notation like strComputer and colItems. Although I am using full cmdlet and parameter names. But, I am not really taking advantage of the pipeline. Even more importantly, this script does not write any objects to the pipeline.
I run this script and try to do anything with the output like sort or send to a file, it will fail. Write-Host writes to the hosting application, such as the console not the pipeline. In this case, I was probably intrigued with the color options of Write-Host and didn’t think about how else I would use this script.
This is the original output.
100914 2005 WhatsOldisN2
Here’s a revised version of this script.

#requires -version 3.0
[cmdletbinding()]
Param(
[Parameter(Position=0)]
[ValidateNotNullorEmpty()]
[string]$Computername = $env:COMPUTERNAME,
[int]$WorkingLimit = 3MB
)
Try {
    $paramHash = @{
     Class = "Win32_process"
     ComputerName = $Computername
     filter = "WorkingsetSize >=$workingLimit"
     ErrorAction = "Stop"
    }
    Write-Verbose "Retrieving processes with a working set greater or equal to $workinglimit from $computername."
    Get-WmiObject @paramHash | Select-Object -Property Name,WorkingSetSize,PSComputername
} #Try
Catch {
    Write-Warning "Failed to retrieve process information from $computername. $($_.Exception.Message)"
} #catch
#end of script

The first big difference is that I now use parameters. In the old script if I wanted to query a different computer, I would need to edit the script. We don’t really want to do that. Anything that might be changed should be parameterized. Feel free to use default values as I’ve done here. You’ll notice I didn’t use “.” or “localhost” for the computername default. While some cmdlets will happily work with localhost, that might also show in the output. If I save that output and look at it later I may not know what computer was queried. By using the %COMPUTERNAME% environmental variable, I will always have a real computername.

My new version also uses error handling in the form of Try/Catch. In the v1 days, all we had were Trap statements and in February, 2006 I doubt I had gotten that far. You’ll also notice the use a hash table for parameter values to splat to the Get-WMIObject cmdlet. You’ll notice in my original script that I used the backtick (`) to break a long PowerShell command. This makes scripts harder to read I think. Using a hash table and splatting makes the script easier to read.
This version centers around a simple PowerShell expression to get processes over a certain threshold and then write selected object properties to the pipeline. In the original script I was still hung up on displaying text. That is a problem many people still face today.
Here’s the revised output.
100914 2005 WhatsOldisN3
Much neater and now that I’m writing objects I can pipe the script to other commands.
100914 2005 WhatsOldisN4
This is far from a production-worthy script but I hope you’ll pay attention to the concepts. But, you know I liked having the colorized output. So here’s a version that colorizes the display but still writes objects to the pipeline.

#requires -version 3.0
[cmdletbinding()]
Param(
[Parameter(Position=0)]
[ValidateNotNullorEmpty()]
[string]$Computername = $env:COMPUTERNAME,
[int]$WorkingLimit = 3MB
)
#do not run in the PowerShell ISE
if ($host.Name -ne 'ConsoleHost') {
  Write-Warning "This script will only work in the PowerShell console host."
  #bail out of the script
  Return
}
#save current foreground color
$savedfg = $host.ui.RawUI.ForegroundColor
Try {
    #define a hashtable of parameters for Get-WMIObject
    $paramHash = @{
     Class = "Win32_process"
     ComputerName = $Computername
     ErrorAction = "Stop"
    }
    Write-Verbose "Retrieving processes from $computername."
    #splat the hashtable
    Get-WmiObject @paramHash | foreach  {
     #look at the workset size and dynamically change the foreground color
     if ($_.workingSetSize -ge $WorkingLimit) {
        #use a hightlight color
        $host.ui.RawUI.ForegroundColor = 'Magenta'
     }
     else {
        #use the original color
        $host.ui.RawUI.ForegroundColor = $savedfg
     }
     #write each object to the pipeline
     $_ | Select-Object -Property Name,WorkingSetSize,PSComputername
    } #foreach
} #Try
Catch {
    Write-Warning "Failed to retrieve process information from $computername. $($_.Exception.Message)"
} #catch
#return to original foreground color
$host.ui.RawUI.ForegroundColor = $savedfg
#end of script


You’ll notice I have enough internal comments that even if you are new to PowerShell I think you can pick up on what the script is doing. This version gives me the flexibility I want in a PowerShell tool yet is not complicated to use.
100914 2005 WhatsOldisN5
I hope that if you have some old PowerShell scripts, that you’ll be brave enough to share them and talk about what you’ve learned and how you would solve the same problem today. If you blog about your own experiences, I hope you’ll leave a link in the comments here.