Prof. Powershell

The PowerShell Blacksmith Part 5: On the Fly

Part 5 will continue to focus on fine-tuning your custom PowerShell tool.

Once your tool is built, it might need a little breaking-in period. Hopefully you've worked out any bugs and at this point are merely trying it out, cutting down orcs or rescuing ogre princesses. While trying out the most current version of the Measure-Data tool we built over the last several articles, I realized I was missing something. Let me demonstrate.

My original thought was to get the average age of files, which led to thinking about the median and range. Yes, there's a lot going on inside my head, but sometimes it leaks out. Let's start with this.

PS C:\> $Path= "c:\scripts\*.ps1"
PS C:\> $data = dir $path -File | Select Name,Length,LastWriteTime,@{Name="Age";Expression={((Get-Date)-$_.LastWritetime).TotalDays}}
PS C:\> $data[0]
Name                    Length LastWriteTime                           Age
----                    ------ -------------                           ---
13ScriptBlocks-v2.ps1     3795 7/31/2013 4:47:28 PM       41.7881607840856

Because I'm running PowerShell 3.0 I can run an expression like this to get just the Age values for all my script files.

PS C:\> $data.age | measure-data

Median                                       Range
------                                       -----
861.073937254431                            2777.99298015725

And yes, my oldest last modified PowerShell script dates back to the beginning of 2006.

But now that I've done this I realize I need more. Measure-Object has values I'd also like. What I need to do is build a tool "on the fly."

PS C:\> $data.age | measure-data | Select *,@{Name="Average";Expression={ ($data | measure-object -property Age -average).Average}}

Median                         Range                      Average
------                         -----                      -------
861.073937254431              2777.99298015725             989.049924734305

With this one-line command I get my original result and I include a custom property that re-pipes the data to Measure-Object and calculates the Average. Better. But I'd still like to see minimum and maximum values. I don't want to do this:

$data.age | measure-data |
Select *,
@{Name="Average";Expression={ ($data | measure-object -property Age -average).Average}},
@{Name="Minimum";Expression={ ($data | measure-object -property Age -minimum).Minimum}},
@{Name="Maximum";Expression={ ($data | measure-object -property Age -maximum).Maximum}}

This will work, but I have to run Measure-Object 3 times which isn't very efficient. I try to only run commands once. Interactively, this seems to do the trick.

PS C:\> $Property= "age"
PS C:\> $stats = $data | measure-object -property $Property -sum -minimum -maximum -average
PS C:\> $mystats = $data.age | measure-data
PS C:\> $hash=[ordered]@{
Sum=$stats.sum
Average = $stats.average
Median = $mystats.median
Range = $mystats.range
Minimum = $stats.minimum
Maximum = $stats.maximum
Property = $Property
}

Because I'm running PowerShell 3.0 I'm going to create an ordered hash table so that the properties will always be in this order. The last step is to turn the hash table into a custom object.

PS C:\> [pscustomobject]$hash

Sum      : 1446980.03988629
Average  : 989.049924734305
Median   : 861.073937254431
Range    : 2777.99298015725
Minimum  : 0.0324432438657407
Maximum  : 2778.02542340111
Property : age

In my age example the Sum is irrelevant, but I'm thinking head. Perhaps that might be something else I want to measure where the sum is relevant. What I've really come up with is a replacement for Measure-Object, at least in terms of measuring numeric values. So why not modify my tool? I already have code that works.

I can insert the code towards the end of the End script block:

...
Write-Verbose "Median = $median"
Write-Verbose "Range = $range"

    #get statistics using Measure-Object
Write-Verbose "Running data through Measure-Object"
$stats = $data | Measure-Object -Sum -Maximum -Minimum -Average

#define a hash table for the custom object
$hash=[ordered]@{
Sum=$stats.sum
Average = $stats.average
Median = $median
Range = $range
Minimum = $stats.minimum
Maximum = $stats.maximum
}

    #write result object to pipeline
Write-Verbose "Writing result to the pipeline"
New-Object -TypeName PSobject -Property $hash

    #endregion
} #close end

You can see the results in Figure 1.

[Click on image for larger view.] Figure 1.

I decided to skip adding the property, although you certainly can. I'll let you figure out how. We'll continue learning more about PowerShell toolmaking. Remember, the journey is its own reward. You can download the latest version of the function, including revised help examples here.

About the Author

Jeffery Hicks is an IT veteran with over 25 years of experience, much of it spent as an IT infrastructure consultant specializing in Microsoft server technologies with an emphasis in automation and efficiency. He is a multi-year recipient of the Microsoft MVP Award in Windows PowerShell. He works today as an independent author, trainer and consultant. Jeff has written for numerous online sites and print publications, is a contributing editor at Petri.com, and a frequent speaker at technology conferences and user groups.

comments powered by Disqus
Most   Popular