My Solution to Iron Scripter 2019 Preparatory Challenge 1
Anyone who has competed in the scripting games before knows that I'm always looking for a challenge when it comes to writing PowerShell code. While the scripting games haven't been held in the last several years, they've somewhat been replaced by the Iron Scripter competition at the PowerShell + DevOps Global Summit and 2019 is shaping up to be no different. Think you've got skills? Bring them on! and Get-Involved.
Whether you've previously competed or not, you should definitely head over to IronScripter.us to see what it's all about. I'm not sure if there will be more than one practice scenario or not since I'm in no way officially affiliated with the competition other than being a participant. This particular scenario deals with the Get-Counter cmdlet which I used in my chapter on Finding Performance Bottlenecks with PowerShell in The PowerShell Conference Book (you can read my chapter in its entirety by downloading the free sample).
The challenge is to create a PowerShell function that Get-Counter
can be piped to which produces
friendlier output and returns specific properties. I used a regular expression in my chapter from
the previously referenced book. I simply reused it for this portion of the challenge.
1function Format-MrCounter {
2
3 [CmdletBinding()]
4 param (
5 [Parameter(Mandatory,
6 ValueFromPipeline)]
7 [Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSampleSet]$CounterSampleSet
8 )
9
10 foreach ($Counter in $CounterSampleSet.CounterSamples){
11
12 $ComputerName = $Counter.Path -replace '^\\\\|\\.*$'
13
14 [pscustomobject]@{
15 DateTime = $Counter.Timestamp
16 ComputerName = $ComputerName
17 CounterSet = $Counter.Path -replace "^\\\\$ComputerName\\|\\.*$"
18 Counter = $Counter.Path -replace '^.*\\'
19 Value = $Counter.CookedValue
20 }
21
22 }
23
24}
I try to avoid the ComputerName
parameter of commands when targeting remote systems because
they're usually implemented via older DCOM protocols which are typically blocked by firewalls on
modern systems. I simply use
Invoke-Command
to run commands locally on remote systems via PowerShell remoting since WinRM is much more firewall
friendly. Because of this design philosophy, I never tested my regular expressions when targeting
remote systems with Get-Counter
's ComputerName
parameter. Well, they don't work because of an
extra slash that exists after the computer name in the results when targeting a remote system.
1(Get-Counter).CounterSamples.Path
2(Get-Counter -ComputerName DC01).CounterSamples.Path
While I could rework the regex to take that into account, I've been there and done that so I thought I would take a different approach and eliminate the regular expressions altogether. While I'm at it, I'll also tackle the advanced portion of the challenge which wants a custom type and view defined to return four specific properties by default in a table.
I'm writing my code in a way that requires PowerShell version 3.0 or higher and since Get-Counter
only exists in Windows PowerShell, I'll define both of those requirements in a
requires statement.
I'm using
an approved verb
along with a singular noun and the command name is in Pascal case. This is followed by comment based
help. CmdletBinding
is declared which makes it an advanced function, the output type is declared,
and then the single InputObject
parameter which only accepts the type of object that Get-Counter
produces. The parameter is mandatory and accepts input via the pipeline by value (what I call by
type).
1#Requires -Version 3.0 -PSEdition Desktop
2function Format-MrCounter {
3
4<#
5.SYNOPSIS
6 Formats the output of the Get-Counter cmdlet into a friendlier format.
7
8.DESCRIPTION
9 The Format-MrCounter function accepts input from the Get-Counter function
10 and formats it into a much more useable and more object oriented format.
11
12.PARAMETER InputObject
13 Accepts the output of the results from the Get-Counter cmdlet. It expects a
14 Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSampleSet object type.
15
16.EXAMPLE
17 Get-Counter | Format-MrCounter
18
19.EXAMPLE
20 Get-Counter -ComputerName Server01, Server02 | Format-MrCounter
21
22.INPUTS
23 Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSampleSet
24
25.OUTPUTS
26 Mr.CounterInfo
27
28.NOTES
29 Author: Mike F Robbins
30 Website: http://mikefrobbins.com
31 Twitter: @mikefrobbins
32#>
33
34 [CmdletBinding()]
35 [OutputType('Mr.CounterInfo')]
36 param (
37 [Parameter(Mandatory,
38 ValueFromPipeline)]
39 [Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSampleSet[]]$InputObject
40 )
41
42 PROCESS {
43
44 foreach ($Counter in $InputObject.CounterSamples){
45
46 $CounterInfo = $Counter.Path.Split('\\', [System.StringSplitOptions]::RemoveEmptyEntries)
47
48 for ($i = 0; $i -lt $CounterInfo.Count; $i += 3){
49 [pscustomobject]@{
50 DateTime = $Counter.Timestamp
51 ComputerName = $CounterInfo[$i]
52 CounterSet = $CounterInfo[$i+1]
53 Counter = $CounterInfo[$i+2]
54 Value = $Counter.CookedValue
55 PSTypeName = 'Mr.CounterInfo'
56 } |
57 Add-Member -MemberType MemberSet -Name PSStandardMembers -Value (
58 [System.Management.Automation.PSMemberInfo[]]@(
59 New-Object -TypeName System.Management.Automation.PSPropertySet(
60 'DefaultDisplayPropertySet',[string[]]@(
61 'ComputerName', 'CounterSet', 'Counter', 'Value'
62 )
63 )
64 )
65 ) -PassThru
66 }
67
68 }
69
70 }
71
72}
I'm splitting the Path
property on backslashes, but since there are some double backslashes, I'm
using the Split
method instead of the Split
operator so I can take advantage of the .NET option
to remove empty entries. A
For loop
is used to iterate through the entries. PSCustomObject
is used to create a custom object and a
type name is specified within the PSCustomObject
to define a custom object type. Finally, since
only four properties are required in the default output and four or fewer properties are displayed
in a table by default, a Default Display Property Set is defined instead of going through the
trouble of creating a module, manifest, and custom format file in XML.
By default, the output is the four properties required by the advanced challenge specifications. All
of the properties can always be viewed in either a list (or table) when piping to either
Format-List,
Format-Table,
or
Select-Object
and specifying the asterisk wildcard (*) as the value for the Property
parameter.
1Get-Counter -ComputerName DC01 | Format-MrCounter
2Get-Counter -ComputerName DC01 | Format-MrCounter | Format-List -Property *
The Format-MrCounter
function shown in this blog article can be downloaded from my IronScripter
repository on GitHub.
If you enjoyed this blog article and the techniques I used in my solution, then I hope to see you in my Finding Performance Bottlenecks with PowerShell session at the PowerShell + DevOps Global Summit 2019.
µ