Public/Get-Subtotal.ps1
function Get-Subtotal { <# .SYNOPSIS Adds subtotals to data .DESCRIPTION Combines Measure-Object and Group object - groups on one or more properties, and calculates different measures across properties in the group It does NOT support custom properties (so use Select-object and pipe into Get-Subtotal), and you if you ask for min, max and average, and Width and Height you will get six items, and if you want width_min, width_max, height_average, again Select-Object is there for the task. .EXAMPLE Get-ChildItem | Get-Subtotal Length -Allstats -Group Extension | ft Produces a table of count, average, sum, min, max and standard deviation for file length grouped by file extension. .EXAMPLE Get-ChildItem | Select-object *, @{n='Month';e={$_.LastWriteTime.toString("yyyy_MM")}} | Get-Subtotal -GroupByName Extension, month Adds a "Month" column in the form "2022-02" to files, groups by month and file extension and returns a count for each group .EXAMPLE Get-ChildItem | Get-Subtotal -GroupByName Extension -ValueName LastWriteTime, CreationTime -Maximum -Minimum | ft Produces a table by file extension of min and max dates for file creation and last write #> [cmdletbinding(DefaultParameterSetName='Default')] param ( #The property name(s) to group on. [Parameter(Position=0)] $GroupByName, #The property name(s) to aggregrate, using the switches used in Measure-Object [Parameter(Position=1,ParameterSetName='Default')] [Parameter(Position=1,ParameterSetName='Stats',Mandatory=$true)] [Parameter(Position=1,ParameterSetName='Chars',Mandatory=$true)] $ValueName, #The data to subtotal [Parameter(ValueFromPipeline=$true)] $InputObject, [Parameter(ParameterSetName='Default')] [Parameter(ParameterSetName='Stats')] [switch]$Count, [Parameter(ParameterSetName='Stats')] [switch]$AllStats, [Parameter(ParameterSetName='Stats')] [switch]$Average, [Parameter(ParameterSetName='Stats')] [switch]$Maximum, [Parameter(ParameterSetName='Stats')] [switch]$Minimum, [Parameter(ParameterSetName='Stats')] [switch]$StandardDeviation, [Parameter(ParameterSetName='Stats')] [switch]$Sum, [Parameter(ParameterSetName='Chars')] [switch]$Character, [Parameter(ParameterSetName='Chars')] [switch]$IgnoreWhiteSpace, [Parameter(ParameterSetName='Chars')] [switch]$Line, [Parameter(ParameterSetName='Chars')] [switch]$Word ) begin { $data = @() } process { $data += $inputObject } end { #Will use most of the parameters with Measure-Object $params = @{} + $PSBoundParameters [void]$params.Remove('InputObject') [void]$params.Remove('GroupByName') [void]$params.Remove('ValueName') [void]$params.Remove('Count') if ($IgnoreWhiteSpace -and -not ($Line -or $Character -or $Word)) {$params['Character']=$true} $data | Group-Object -Property $GroupByName | ForEach-Object { $newobj = [Ordered]@{} #For whatever we grouped on, get that value/those values from the first row of each group. Then total the properties we're interested in foreach ($g in $GroupByName) {$newobj[$g] = $_.Group[0].$g } if (-not $ValueName) { $newobj['Count'] = $_.Group.Count } foreach ($v in $ValueName) { #Some objects may not have all the properties e.g. Dir | measure length $totals = $_.Group | Measure-object -Property $v @params -ErrorAction SilentlyContinue if ($PSCmdlet.ParameterSetName -eq 'Chars') {#paramter is word/line/character, but property is words/lines/characters foreach ($agFn in $params.Keys.where({$_ -ne 'IgnoreWhiteSpace'})) { $newObj[($v + "_" + $agfn + "s")] = $totals."$agFn`s" } } else { if (-not $AllStats) { if ($count) {$newObj[($v + "_Count" )] = $totals.count} $agFunctions = $params.Keys } else { $agFunctions = "Count","Average","Sum","Maximum","Minimum","StandardDeviation"} foreach ($agFn in $agFunctions) { $newObj[($v + "_" + $agfn )] = $totals.$agFn } } } [pscustomobject]$newobj } } } |