As part of the new reality of working for Microsoft, I’m learning all the Microsoft ways of doing things. I’m in an Operations group doing very large operational things. The intersection of these two lines of thought are PowerShell and Perf Counters.
I know, I know… late to the party. To all the folks who have been doing PowerShell since 2006, I say “thank you” for leaving a large pile of information on teh internets. If you are like me, er, Mostly Elsewhere (doing things with the Penguin That Shall Not Be Named) then you are also in luck, since you too can skip the bad old days of early PowerShell, and jump into the newer, more better PS of today. Complete with better “IDEs” and everything. If you’re perhaps very very old, and only know MSDOS/Batch .bat file syntax, then you’ll be totally lost or pleasnatly surprised with PowerShell. If you’re used to Php, Bash, or other nice but sufficiently dangerous scripting languages you’ll be just fine. If you believe that PowerShell is like AppleScript, you’d be both right, and wrong, since AppleScript is/was a pretty awesome object oriented scripting language that Apple somehow managed to forget about. Mostly. But we digress!
Some Prep Work
You’ll need to install PowerShell on older versions of Windows, Windows 7 and Server 2008 should have it already. You’ll want to add your user to the Performance Monitor Users group. Once that’s done, you’ll be able to read and modify the values of existing performance counters, including ones you create. If you’re going to create or delete performance counters, you’ll need to do so from a PowerShell console (or editor) running as Administrator.
You Had Me At Hello (World)
I’m not going to bore you with a Hello World PowerShell script, or some simple perf counter stuff. Here’s a working script:
# ----------------------------- # Create a peformace counter group, with one counter, on the local machine # ----------------------------- # Parms: # string: your category name # string: your counter name # your counter type - from [System.Diagnostics.PerformanceCounterType]::xx but you need this to # be evaluated so either use a var or put in () # bool # # Whatever the "function" outputs is part of the "return" value, so no writing debug messages, # and any expressions that leave a value out there need to be > $null or [void] # Yuck. # function getUserCounter($categoryName, $counterName, $counterType, $deleteFirst=$false) { $exists = [System.Diagnostics.PerformanceCounterCategory]::Exists($categoryName) if ($exists) { # if it exists, do nothing (or delete/create if $true is passed in last) if ($deleteFirst) { # its here, delete it, then remake it [System.Diagnostics.PerformanceCounterCategory]::Delete($categoryName) | Out-Null # remake it $newc = New-Object System.Diagnostics.CounterCreationData # array of counters $counters = @() $newc.CounterName = $counterName $newc.CounterType = $counterType $counters += $newc $counterCollection = new-object System.Diagnostics.CounterCreationDataCollection $counters # yes, you reall do use ` to break long lines. # was this parser designed in 1975 or something? [System.Diagnostics.PerformanceCounterCategory]::Create($categoryName,"Some help text", ` [System.Diagnostics.PerformanceCounterCategoryType]::SingleInstance, $counterCollection) | Out-Null # that was one long line. Wow. } } # fetch and return a read/write copy $retperf = New-Object System.Diagnostics.PerformanceCounter($categoryName, $counterName, $false) return $retperf } # test # this gets one, doesn't nuke it first, but does create it if it doesn't exist $perf1 = getUserCounter "mytestgroup" "mytestcounter" ([System.Diagnostics.PerformanceCounterType]::NumberOfItems32) # if you see an error, then you're not running as Admin. # Apparently, ONLY admin can create/delete a counter # if you run this script multiple times the counter should go up by three every run. # if you append $true to the call above, then the counter will be recreated each run, so it will always # print 1/2/3 # test by pumping up the counter three times $i=0 while ($i -lt 3) { $perf1.rawvalue += 1 write $perf1.rawvalue $i++ } # "free" the object - closes the counter - no idea if you really need to do this or not. $perf1 = $null
I know what you’re thinking – It’s messy. The syntax is something only Bill Gates could love. What the heck do the brackets mean? Why oh Why are there parenthesis around the reference to the static enumeration value on line 47? Where is line 47?
The Walk-through
Let me take you on a magical trip, before I forget what all this does. First up, the magic of functions with optional parameters.
function getUserCounter($categoryName, $counterName, $counterType, $deleteFirst=$false) { #blah blah blah return $retperf }
You declare functions a number of ways – I prefer to pretend that PowerShell is a real scripting language (which is dangerous, as we will see in a moment) so I write in the style of Nearly Every Language That Is C-like. See this section of the docs for all your other options. The only tricky bit for us is the last parm, which we have helpfully provided with a default value of $false. Which means it’s optional. Which is handy dandy.
Skipping over the blah blah blah part…
We get to the bit at the end, where we pretend we return a value. Pretend you say? But that’s impossible! Well, yeah. Not really. You see, EVERYTHING you “write out” or “output” or “print” (all of which mean something which might be the same thing, possibly) inside a function is “returned” by the function. So if you Write-Output “all is well, because this is a debug message” and then later you return $mySpiffyReturnVar, you’ll actually get an array of values back from the function. Wait, What?
Ok, new rule. No crossing the streams. In a function, you only ever want to Write-Output one thing – the value you want to return. If you want to return an array of stuff, you can Write-Output many times. So what’s the return $whatever thing mean? Well, PowerShell knows you really want to believe it’s a programming language, so it will helpfully accept that, and internally behind your back when you’re not looking, it will Write-Output $whatever and then return (which essentially means exit this function now.)
Phew. I feel better now. So, what’s the blah blah blah?
function getUserCounter($categoryName, $counterName, $counterType, $deleteFirst=$false) { $exists = [System.Diagnostics.PerformanceCounterCategory]::Exists($categoryName) if ($exists) { # if it exists, do nothing (or delete/create if $true is passed in last) if ($deleteFirst) { # its here, delete it, then remake it [System.Diagnostics.PerformanceCounterCategory]::Delete($categoryName) | Out-Null # remake it $newc = New-Object System.Diagnostics.CounterCreationData # array of counters $counters = @() $newc.CounterName = $counterName $newc.CounterType = $counterType $counters += $newc $counterCollection = new-object System.Diagnostics.CounterCreationDataCollection $counters # yes, you reall do use ` to break long lines. # was this parser designed in 1975 or something? [System.Diagnostics.PerformanceCounterCategory]::Create($categoryName,"Some help text", ` [System.Diagnostics.PerformanceCounterCategoryType]::SingleInstance, $counterCollection) | Out-Null # that was one long line. Wow. } } # fetch and return a read/write copy $retperf = New-Object System.Diagnostics.PerformanceCounter($categoryName, $counterName, $false) return $retperf }
Here we get to the real power of PowerShell – pretty much anything you can do in .Net, which is pretty much anything you can do in Windows, you can do in PowerShell, You just have to want it bad enough. But not so bad that you’re willing to pound out some C# or VB. For me, I needed to find the MSDN class docs for Performance Counters. And then I had to find the real way to make them, which meant going up a level to here, and then a bunch of googleing… er Binging later, TADA! The above function will create or open a custom perf counter in a custom perf counter category.
You’ll notice one more horror of PowerShell. Since the parser apparently was written by a middle school intern, you have to warn the parser if, heaven forbid, you intend to break a line up with a scary scary return, so you can, you know, read it better. You warn the parser by using the helpful back-tick, shown here: ` No doubt you too will enjoy the nostalgia of using line continuation characters. Like in the Before Times.
Wrapping it all up, we actually call the function like so:
# this gets one, doesn't nuke it first, but does create it if it doesn't exist $perf1 = getUserCounter "mytestgroup" "mytestcounter" ([System.Diagnostics.PerformanceCounterType]::NumberOfItems32)
Wait a second… what is up with the ()s? you ask. We’re accessing a class enum value by giving the name of the class, in []s, which is the PowerShell way. We use ::thing, which is pretty much looking C++ish, or Javaish, or whatever… anyway, it looks plausible. However, PowerShell is not a programming language, or a scripting language, it’s… well it’s just odd. If you don’t wrap that whole thing in ()s it’s not really evaluated at all. It’s instead sorta passed as a reference to the thing you might have wanted. I guess. Anyway, wrapping it in parens makes it actually evaluate the expression, which is good.
More Later, the White Rabbit Calls!