1
+ Function Add-Subtotals {
2
+ param (
3
+ [Parameter (Mandatory = $true , Position = 0 )]
4
+ $ChangeColumnName , # = "Location"
5
+
6
+ [Parameter (Mandatory = $true , Position = 1 )]
7
+ [hashtable ]$AggregateColumn , # = @{"Sales" = "SUM" }
8
+
9
+ [Parameter (Position = 2 )]
10
+ $ExcelPath = ([System.IO.Path ]::GetTempFileName() -replace " \.tmp" , " .xlsx" ) ,
11
+
12
+ [Parameter (Position = 3 )]
13
+ $WorksheetName = " Sheet1" ,
14
+
15
+ [Parameter (ValueFromPipeline = $true )]
16
+ $InputObject , # $DataToPivot | Sort location, product
17
+
18
+ [switch ]$HideSingleRows ,
19
+ [switch ]$NoSort ,
20
+ [switch ]$NoOutLine ,
21
+ [switch ]$Show
22
+
23
+ )
24
+ begin {
25
+ if (-not $PSBoundParameters.ContainsKey (' ExcelPath' )) {$Show = $true }
26
+ $data = @ ()
27
+ $aggFunctions = [ordered ]@ {
28
+ " AVERAGE" = 1 ; " COUNT" = 2 ; " COUNTA" = 3 # (non empty cells) f
29
+ " MAX" = 4 ; " MIN" = 5 ; " PRODUCT" = 6 ; " STDEV" = 7 # (sample)
30
+ " STDEVP" = 8 # (whole population);
31
+ " SUM" = 9 ; " VAR" = 10 # (Variance sample)
32
+ " VARP" = 11 # (whole population) #add 100 to ignore hidden cells
33
+ }
34
+ }
35
+ process {
36
+ $data += $InputObject
37
+ }
38
+ end {
39
+ if (-not $NoSort ) {$data = $data | Sort-Object $changeColumnName }
40
+ $Header = $data [0 ].PSObject.Properties.Name
41
+ # region turn each entry in $AggregateColumn "=SUBTOTAL(a,x{0}}:x{1})" where a is the aggregate function number and x is the column letter
42
+ $aggFormulas = @ {}
43
+ foreach ($k in $AggregateColumn.Keys ) {
44
+ $columnNo = 0 ;
45
+ while ($columnNo -lt $header.count -and $header [$columnNo ] -ne $k ) {$columnNo ++ }
46
+ if ($columnNo -eq $header.count ) {
47
+ throw " '$k ' isn't a property of the first row of data." ; return
48
+ }
49
+ if ($AggregateColumn [$k ] -is [string ]) {
50
+ $aggfn = $aggFunctions [$AggregateColumn [$k ]]
51
+ if (-not $aggfn ) {
52
+ throw " $ ( $AggregateColumn [$k ]) is not a valid aggregation function - these are $ ( $aggFunctions.keys -join ' , ' ) " ; return
53
+ }
54
+ }
55
+ else {$aggfn = $AggregateColumn [$k ]}
56
+ $aggFormulas [$k ] = " =SUBTOTAL({0},{1}{{0}}:{1}{{1}})" -f $aggfn , (Get-ExcelColumnName ($columnNo + 1 ) ).ColumnName
57
+ }
58
+ if ($aggformulas.count -lt 1 ) {throw " We didn't get any aggregation formulas" }
59
+ $aggFormulas | out-string - Stream | Write-Verbose - Verbose
60
+ # endregion
61
+ $insertedRows = @ ()
62
+ $singleRows = @ ()
63
+ $previousValue = $data [0 ].$changeColumnName
64
+ $currentRow = $lastChangeRow = 2
65
+ # region insert subtotals and send to excel:
66
+ # each time there is a change in the column we're intetersted in.
67
+ # either Add a row with the value and subtotal(s) function(s) if there is more than one row to total
68
+ # or note the row if there was only one row with that value (we may hide it later.)
69
+ $excel = $data |
70
+ ForEach-Object - process {
71
+ if ($_ .$changeColumnName -ne $previousValue ) {
72
+ if ($lastChangeRow -lt ($currentrow - 1 )) {
73
+ $NewObj = @ {$changeColumnName = $previousValue }
74
+ foreach ($k in $aggFormulas.Keys ) {
75
+ $newobj [$k ] = $aggformulas [$k ] -f $lastChangeRow , ($currentRow - 1 )
76
+ }
77
+ $insertedRows += $currentRow
78
+ [pscustomobject ]$newobj
79
+ $currentRow += 1
80
+ }
81
+ else {$singleRows += $currentRow }
82
+ $lastChangeRow = $currentRow
83
+ $previousValue = $_ .$changeColumnName
84
+ }
85
+ $_
86
+ $currentRow += 1
87
+ } - end { # the process block won't output the last row
88
+ if ($lastChangeRow -lt ($currentrow - 1 )) {
89
+ $NewObj = @ {$changeColumnName = $previousValue }
90
+ foreach ($k in $aggFormulas.Keys ) {
91
+ $newobj [$k ] = $aggformulas [$k ] -f $lastChangeRow , ($currentRow - 1 )
92
+ }
93
+ $insertedRows += $currentRow
94
+ [pscustomobject ]$newobj
95
+ }
96
+ else {$singleRows += $currentRow }
97
+ } | Export-Excel - Path $ExcelPath - PassThru - AutoSize - AutoFilter - AutoNameRange - BoldTopRow - WorksheetName $WorksheetName - Activate - ClearSheet # -MaxAutoSizeRows 10000
98
+ # endregion
99
+ # Put the subtotal rows in bold optionally hide rows where only one has the value of interest.
100
+ $ws = $excel .$WorksheetName
101
+ # We kept lists of the total rows Since 1 rows won't get expand/collapse we can hide them.
102
+ foreach ($r in $insertedrows ) {$ws.Row ($r ).style.font.bold = $true }
103
+ if ($HideSingleRows ) {
104
+ foreach ($r in $hideRows ) { $ws.Row ($r ).hidden = $true }
105
+ }
106
+ $range = $ws.Dimension.Address
107
+ $ExcelPath = $excel.File.FullName
108
+ $SheetIndex = $ws.index
109
+ if ($NoOutline ) {
110
+ Close-ExcelPackage $excel - show:$Show
111
+ return
112
+ }
113
+ else {
114
+ Close-ExcelPackage $excel
115
+
116
+ try { $excelApp = New-Object - ComObject " Excel.Application" }
117
+ catch { Write-Warning " Could not start Excel application - which usually means it is not installed." ; return }
118
+
119
+ try { $excelWorkBook = $excelApp.Workbooks.Open ($ExcelPath ) }
120
+ catch { Write-Warning - Message " Could not Open $ExcelPath ." ; return }
121
+ $ws = $excelWorkBook.Worksheets.item ($SheetIndex )
122
+ $null = $ws.Range ($range ).Select()
123
+ $null = $excelapp.ActiveCell.AutoOutline ()
124
+ $null = $ws.Outline.ShowLevels (1 , $null )
125
+ $excelWorkBook.Save ()
126
+ if ($show ) {$excelApp.Visible = $true }
127
+ else {
128
+ [void ]$excelWorkBook.close ()
129
+ $excelapp.Quit ()
130
+ }
131
+ }
132
+ }
133
+ }
0 commit comments