Measure-Object.ps1
function Measure-Object { [CmdletBinding()] Param( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [array] $InputObject , [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Property ) Begin { $Data = @() } Process { $InputObject | ForEach-Object { if (-Not ($_.PSObject.Properties | Where-Object Name -ieq $Property)) { throw ('Input object does not contain a property called <{0}>.' -f $Property) } $Data += $_ } } End { #region Percentiles require sorted data $Data = $Data | Sort-Object -Property $Property #endregion #region Grab basic measurements from upstream Measure-Object $Stats = $Data | Microsoft.PowerShell.Utility\Measure-Object -Property $Property -Minimum -Maximum -Sum -Average #endregion #region Calculate median Write-Debug ('[{0}] Number of data items is <{1}>' -f $MyInvocation.MyCommand.Name, $Data.Length) if ($Data.Length % 2 -eq 0) { Write-Debug ('[{0}] Even number of data items' -f $MyInvocation.MyCommand.Name) $MedianIndex = ($Data.Length / 2) - 1 Write-Debug ('[{0}] Index of Median is <{1}>' -f $MyInvocation.MyCommand.Name, $MedianIndex) $LowerMedian = $Data[$MedianIndex] | Select-Object -ExpandProperty $Property $UpperMedian = $Data[$MedianIndex + 1] | Select-Object -ExpandProperty $Property Write-Debug ('[{0}] Lower Median is <{1}> and upper Median is <{2}>' -f $MyInvocation.MyCommand.Name, $LowerMedian, $UpperMedian) $Median = ($LowerMedian + $UpperMedian) / 2 Write-Debug ('[{0}] Average of lower and upper Median is <{1}>' -f $MyInvocation.MyCommand.Name, $Median) } else { Write-Debug ('[{0}] Odd number of data items' -f $MyInvocation.MyCommand.Name) $MedianIndex = [math]::Ceiling(($Data.Length - 1) / 2) Write-Debug ('[{0}] Index of Median is <{1}>' -f $MyInvocation.MyCommand.Name, $MedianIndex) $Median = $Data[$MedianIndex] | Select-Object -ExpandProperty $Property Write-Debug ('[{0}] Median is <{1}>' -f $MyInvocation.MyCommand.Name, $Median) } Add-Member -InputObject $Stats -MemberType NoteProperty -Name 'Median' -Value $Median #endregion #region Calculate variance $Variance = 0 $Data | ForEach-Object { $Variance += [math]::Pow($_.$Property - $Stats.Average, 2) / $Stats.Count } $Variance /= $Stats.Count Add-Member -InputObject $Stats -MemberType NoteProperty -Name 'Variance' -Value $Variance #endregion #region Calculate Tukey's range for outliers $TukeysOutlier = 1.5 $TukeysRange = $TukeysOutlier * ($Stats.Percentile75 - $Stats.Percentile25) Add-Member -InputObject $Stats -MemberType NoteProperty -Name TukeysRange -Value $TukeysRange #endregion #region Calculate standard deviation $StandardDeviation = [math]::Sqrt($Stats.Variance) Add-Member -InputObject $Stats -MemberType NoteProperty -Name 'StandardDeviation' -Value $StandardDeviation #endregion #region Calculate percentiles $Percentile10Index = [math]::Ceiling(10 / 100 * $Data.Length) $Percentile25Index = [math]::Ceiling(25 / 100 * $Data.Length) $Percentile75Index = [math]::Ceiling(75 / 100 * $Data.Length) $Percentile90Index = [math]::Ceiling(90 / 100 * $Data.Length) Add-Member -InputObject $Stats -MemberType NoteProperty -Name 'Percentile10' -Value $Data[$Percentile10Index].$Property Add-Member -InputObject $Stats -MemberType NoteProperty -Name 'Percentile25' -Value $Data[$Percentile25Index].$Property Add-Member -InputObject $Stats -MemberType NoteProperty -Name 'Percentile75' -Value $Data[$Percentile75Index].$Property Add-Member -InputObject $Stats -MemberType NoteProperty -Name 'Percentile90' -Value $Data[$Percentile90Index].$Property #endregion #region Calculate confidence intervals $z = @{ '90' = 1.645 '95' = 1.96 '98' = 2.326 '99' = 2.576 } $Confidence95 = 1.96 * $Stats.StandardDeviation / [math]::Sqrt($Stats.Count) Add-Member -InputObject $Stats -MemberType NoteProperty -Name 'Confidence95' -Value $Confidence95 #endregion #region Return measurements $Stats #endregion } } New-Alias -Name mo -Value Measure-Object -Force |