diff --git a/Add-Subtotals.ps1 b/Add-Subtotals.ps1 new file mode 100644 index 0000000..024f8fa --- /dev/null +++ b/Add-Subtotals.ps1 @@ -0,0 +1,133 @@ +Function Add-Subtotals { + param( + [Parameter(Mandatory=$true, Position=0)] + $ChangeColumnName , # = "Location" + + [Parameter(Mandatory=$true, Position=1)] + [hashtable]$AggregateColumn , #= @{"Sales" = "SUM" } + + [Parameter(Position=2)] + $ExcelPath = ([System.IO.Path]::GetTempFileName() -replace "\.tmp", ".xlsx") , + + [Parameter(Position=3)] + $WorksheetName = "Sheet1", + + [Parameter(ValueFromPipeline=$true)] + $InputObject, #$DataToPivot | Sort location, product + + [switch]$HideSingleRows, + [switch]$NoSort, + [switch]$NoOutLine, + [switch]$Show + + ) + begin { + if (-not $PSBoundParameters.ContainsKey('ExcelPath')) {$Show = $true} + $data = @() + $aggFunctions = [ordered]@{ + "AVERAGE" = 1; "COUNT" = 2; "COUNTA" = 3 #(non empty cells) f + "MAX" = 4; "MIN" = 5; "PRODUCT" = 6; "STDEV" = 7 # (sample) + "STDEVP" = 8 # (whole population); + "SUM" = 9; "VAR" = 10 # (Variance sample) + "VARP" = 11 # (whole population) #add 100 to ignore hidden cells + } + } + process { + $data += $InputObject + } + end { + if (-not $NoSort) {$data = $data | Sort-Object $changeColumnName} + $Header = $data[0].PSObject.Properties.Name + #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 + $aggFormulas = @{} + foreach ($k in $AggregateColumn.Keys) { + $columnNo = 0 ; + while ($columnNo -lt $header.count -and $header[$columnNo] -ne $k) {$columnNo ++} + if ($columnNo -eq $header.count) { + throw "'$k' isn't a property of the first row of data."; return + } + if ($AggregateColumn[$k] -is [string]) { + $aggfn = $aggFunctions[$AggregateColumn[$k]] + if (-not $aggfn) { + throw "$($AggregateColumn[$k]) is not a valid aggregation function - these are $($aggFunctions.keys -join ', ')" ; return + } + } + else {$aggfn = $AggregateColumn[$k]} + $aggFormulas[$k] = "=SUBTOTAL({0},{1}{{0}}:{1}{{1}})" -f $aggfn , (Get-ExcelColumnName ($columnNo+1) ).ColumnName + } + if ($aggformulas.count -lt 1) {throw "We didn't get any aggregation formulas"} + $aggFormulas | out-string -Stream | Write-Verbose -Verbose + #endregion + $insertedRows = @() + $singleRows = @() + $previousValue = $data[0].$changeColumnName + $currentRow = $lastChangeRow = 2 + #region insert subtotals and send to excel: + #each time there is a change in the column we're intetersted in. + #either Add a row with the value and subtotal(s) function(s) if there is more than one row to total + #or note the row if there was only one row with that value (we may hide it later.) + $excel = $data | + ForEach-Object -process { + if ($_.$changeColumnName -ne $previousValue) { + if ($lastChangeRow -lt ($currentrow - 1)) { + $NewObj = @{$changeColumnName = $previousValue} + foreach ($k in $aggFormulas.Keys) { + $newobj[$k] = $aggformulas[$k] -f $lastChangeRow, ($currentRow - 1) + } + $insertedRows += $currentRow + [pscustomobject]$newobj + $currentRow += 1 + } + else {$singleRows += $currentRow } + $lastChangeRow = $currentRow + $previousValue = $_.$changeColumnName + } + $_ + $currentRow += 1 + } -end { # the process block won't output the last row + if ($lastChangeRow -lt ($currentrow - 1)) { + $NewObj = @{$changeColumnName = $previousValue} + foreach ($k in $aggFormulas.Keys) { + $newobj[$k] = $aggformulas[$k] -f $lastChangeRow, ($currentRow - 1) + } + $insertedRows += $currentRow + [pscustomobject]$newobj + } + else {$singleRows += $currentRow } + } | Export-Excel -Path $ExcelPath -PassThru -AutoSize -AutoFilter -AutoNameRange -BoldTopRow -WorksheetName $WorksheetName -Activate -ClearSheet #-MaxAutoSizeRows 10000 + #endregion + #Put the subtotal rows in bold optionally hide rows where only one has the value of interest. + $ws = $excel.$WorksheetName + #We kept lists of the total rows Since 1 rows won't get expand/collapse we can hide them. + foreach ($r in $insertedrows) {$ws.Row($r).style.font.bold = $true } + if ($HideSingleRows) { + foreach ($r in $hideRows) { $ws.Row($r).hidden = $true} + } + $range = $ws.Dimension.Address + $ExcelPath = $excel.File.FullName + $SheetIndex = $ws.index + if ($NoOutline) { + Close-ExcelPackage $excel -show:$Show + return + } + else { + Close-ExcelPackage $excel + + try { $excelApp = New-Object -ComObject "Excel.Application" } + catch { Write-Warning "Could not start Excel application - which usually means it is not installed." ; return } + + try { $excelWorkBook = $excelApp.Workbooks.Open($ExcelPath) } + catch { Write-Warning -Message "Could not Open $ExcelPath." ; return } + $ws = $excelWorkBook.Worksheets.item($SheetIndex) + $null = $ws.Range($range).Select() + $null = $excelapp.ActiveCell.AutoOutline() + $null = $ws.Outline.ShowLevels(1,$null) + $excelWorkBook.Save() + if ($show) {$excelApp.Visible = $true} + else { + [void]$excelWorkBook.close() + $excelapp.Quit() + } + } + } +} \ No newline at end of file diff --git a/Examples/Extra/Get-ModuleStats.ps1 b/Examples/Extra/Get-ModuleStats.ps1 index 267ea50..2c8252b 100644 --- a/Examples/Extra/Get-ModuleStats.ps1 +++ b/Examples/Extra/Get-ModuleStats.ps1 @@ -6,15 +6,16 @@ #> param( - [Parameter(Mandatory=$true)] - $moduleName, + $moduleName = "ImportExcel", [ValidateSet('Column','Bar','Line','Pie')] $chartType="Line" ) -$galleryUrl = "https://www.powershellgallery.com/packages/$moduleName" -$nolegend = '-nolegend' -if($chartType -eq 'pie') {$nolegend = $null} -$code = "$($chartType)Chart (Get-HtmlTable $galleryUrl -FirstDataRow 1 | sort lastupdated -desc) -title 'Download stats for $moduleName' $nolegend" +$download = Get-HtmlTable "https://www.powershellgallery.com/packages/$moduleName" -FirstDataRow 1 | + Select-Object @{n="Version";e={$v = $Null ; if ($_.version -is [valuetype]) {[string][version]($_.version.tostring("0.0")) } + elseif ($_.version -is [string] -and [version]::TryParse($_.version.trim(),[ref]$v)) {$v} + else {$_.Version.trim() -replace "\s+"," " } }}, + Downloads, @{n="LastUpdated";e={[datetime]$_.last_updated}} | + Sort-Object lastupdated -Descending -$code | Invoke-Expression \ No newline at end of file +& "$($chartType)Chart" $download "Download stats for $moduleName" -nolegend:($chartype -ne 'pie') diff --git a/Examples/Grouping/GroupDateColumn.ps1 b/Examples/Grouping/GroupDateColumn.ps1 new file mode 100644 index 0000000..6ee05cf --- /dev/null +++ b/Examples/Grouping/GroupDateColumn.ps1 @@ -0,0 +1,13 @@ +try {Import-Module $PSScriptRoot\..\..\ImportExcel.psd1} catch {throw ; return} + +#Get rid of pre-exisiting sheet +$xlSourcefile = "$env:TEMP\ImportExcelExample.xlsx" +Write-Verbose -Verbose -Message "Save location: $xlSourcefile" +Remove-Item $xlSourcefile -ErrorAction Ignore + +$PivotTableDefinition = New-PivotTableDefinition -Activate -PivotTableName Points ` + -PivotRows Driver -PivotColumns Date -PivotData @{Points = "SUM"} -GroupDateColumn Date -GroupDatePart Years, Months + +Import-Csv "$PSScriptRoot\First10Races.csv" | + Select-Object Race, @{n = "Date"; e = {[datetime]::ParseExact($_.date, "dd/MM/yyyy", (Get-Culture))}}, FinishPosition, Driver, GridPosition, Team, Points | + Export-Excel $xlSourcefile -Show -AutoSize -PivotTableDefinition $PivotTableDefinition \ No newline at end of file diff --git a/Examples/Grouping/GroupDate.ps1 b/Examples/Grouping/GroupDateRow.ps1 similarity index 100% rename from Examples/Grouping/GroupDate.ps1 rename to Examples/Grouping/GroupDateRow.ps1 diff --git a/Examples/Grouping/GroupNumericColumn.ps1 b/Examples/Grouping/GroupNumericColumn.ps1 new file mode 100644 index 0000000..4b97391 --- /dev/null +++ b/Examples/Grouping/GroupNumericColumn.ps1 @@ -0,0 +1,13 @@ +try {Import-Module $PSScriptRoot\..\..\ImportExcel.psd1} catch {throw ; return} + +#Get rid of pre-exisiting sheet +$xlSourcefile = "$env:TEMP\ImportExcelExample.xlsx" +Write-Verbose -Verbose -Message "Save location: $xlSourcefile" +Remove-Item $xlSourcefile -ErrorAction Ignore + +$PivotTableDefinition = New-PivotTableDefinition -Activate -PivotTableName Places ` + -PivotRows Driver -PivotColumns FinishPosition -PivotData @{Date = "Count"} -GroupNumericColumn FinishPosition -GroupNumericMin 1 -GroupNumericMax 25 -GroupNumericInterval 3 + +Import-Csv "$PSScriptRoot\First10Races.csv" | + Select-Object Race, @{n = "Date"; e = {[datetime]::ParseExact($_.date, "dd/MM/yyyy", (Get-Culture))}}, FinishPosition, Driver, GridPosition, Team, Points | + Export-Excel $xlSourcefile -Show -AutoSize -PivotTableDefinition $PivotTableDefinition \ No newline at end of file diff --git a/Examples/Grouping/GroupNumeric.ps1 b/Examples/Grouping/GroupNumericRow.ps1 similarity index 100% rename from Examples/Grouping/GroupNumeric.ps1 rename to Examples/Grouping/GroupNumericRow.ps1 diff --git a/Public/Add-PivotTable.ps1 b/Public/Add-PivotTable.ps1 index 1b3f967..ebeea21 100644 --- a/Public/Add-PivotTable.ps1 +++ b/Public/Add-PivotTable.ps1 @@ -18,8 +18,10 @@ [String]$PivotTotals = "Both", [Switch]$NoTotalsInPivot, [String]$GroupDateRow, + [String]$GroupDateColumn, [OfficeOpenXml.Table.PivotTable.eDateGroupBy[]]$GroupDatePart, [String]$GroupNumericRow, + [String]$GroupNumericColumn, [double]$GroupNumericMin = 0 , [double]$GroupNumericMax = [Double]::MaxValue , [double]$GroupNumericInterval = 100 , @@ -139,11 +141,21 @@ if (-not $r ) {Write-Warning -Message "Could not find a Row field named '$GroupNumericRow'; no numeric grouping will be done."} else {$r.AddNumericGrouping($GroupNumericMin, $GroupNumericMax, $GroupNumericInterval)} } + elseif ($GroupNumericColumn) { + $c = $pivotTable.ColumnFields.Where( {$_.name -eq $GroupNumericColumn }) + if (-not $c ) {Write-Warning -Message "Could not find a Column field named '$GroupNumericColumn'; no numeric grouping will be done."} + else {$c.AddNumericGrouping($GroupNumericMin, $GroupNumericMax, $GroupNumericInterval)} + } if ($GroupDateRow -and $PSBoundParameters.ContainsKey("GroupDatePart")) { $r = $pivotTable.RowFields.Where( {$_.name -eq $GroupDateRow }) if (-not $r ) {Write-Warning -Message "Could not find a Row field named '$GroupDateRow'; no date grouping will be done."} else {$r.AddDateGrouping($GroupDatePart)} } + elseif ($GroupDateColumn -and $PSBoundParameters.ContainsKey("GroupDatePart")) { + $c = $pivotTable.ColumnFields.Where( {$_.name -eq $GroupDateColumn }) + if (-not $c ) {Write-Warning -Message "Could not find a Column field named '$GroupDateColumn'; no date grouping will be done."} + else {$c.AddDateGrouping($GroupDatePart)} + } } catch {Write-Warning -Message "Failed adding PivotTable '$pivotTableName': $_"} } diff --git a/Public/New-PivotTableDefinition.ps1 b/Public/New-PivotTableDefinition.ps1 index 884579e..0568cf0 100644 --- a/Public/New-PivotTableDefinition.ps1 +++ b/Public/New-PivotTableDefinition.ps1 @@ -16,8 +16,10 @@ function New-PivotTableDefinition { [String]$PivotTotals = "Both", [Switch]$NoTotalsInPivot, [String]$GroupDateRow, + [String]$GroupDateColumn, [OfficeOpenXml.Table.PivotTable.eDateGroupBy[]]$GroupDatePart, [String]$GroupNumericRow, + [String]$GroupNumericColumn, [double]$GroupNumericMin = 0 , [double]$GroupNumericMax = [Double]::MaxValue , [double]$GroupNumericInterval = 100 , diff --git a/README.original.md b/README.original.md index b614b3b..0c2648d 100644 --- a/README.original.md +++ b/README.original.md @@ -65,6 +65,11 @@ Plus, wiring the [PowerShell ScriptAnalyzer Excel report](https://github.com/dfi ![](.gitbook/assets/ScriptAnalyzerReport.png) + +## What's new for 7.1.4 + +- Added GroupNumericColumn and GroupDateColumn to New-PivotTableDefinition and Add-PivotTable. + ## What's new 7.1.3 - Changed to `ProviderPath`. Thanks [Trevor Walker](https://github.com/sporkabob) diff --git a/mdHelp/en/add-pivottable.md b/mdHelp/en/add-pivottable.md index f060d9a..f24352e 100644 --- a/mdHelp/en/add-pivottable.md +++ b/mdHelp/en/add-pivottable.md @@ -331,6 +331,22 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -GroupDateColumn + +The name of a Column field which should be grouped by parts of the date/time \(ignored if GroupDateRow is not specified\) + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -GroupDatePart The Part\(s\) of the date to use in the grouping \(ignored if GroupDateRow is not specified\) @@ -364,6 +380,22 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -GroupNumericColumn + +The name of a Column field which should be grouped by Number \(e.g. 0-99, 100-199, 200-299 \) + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -GroupNumericMin The starting point for grouping diff --git a/mdHelp/en/new-pivottabledefinition.md b/mdHelp/en/new-pivottabledefinition.md index cc96c6e..75b838b 100644 --- a/mdHelp/en/new-pivottabledefinition.md +++ b/mdHelp/en/new-pivottabledefinition.md @@ -225,6 +225,22 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -GroupDateColumn + +The name of a column field which should be grouped by parts of the date/time \(ignored if GroupDatePart is not specified\) + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -GroupDatePart The Part\(s\) of the date to use in the grouping \(ignored if GroupDateRow is not specified\) @@ -234,7 +250,6 @@ Type: eDateGroupBy[] Parameter Sets: (All) Aliases: Accepted values: Years, Quarters, Months, Days, Hours, Minutes, Seconds - Required: False Position: Named Default value: None @@ -250,7 +265,6 @@ The name of a row field which should be grouped by Number \(e.g 0-99, 100-199, 2 Type: String Parameter Sets: (All) Aliases: - Required: False Position: Named Default value: None @@ -258,6 +272,23 @@ Accept pipeline input: False Accept wildcard characters: False ``` + +### -GroupNumericColumn + +The name of a column field which should be grouped by Number \(e.g 0-99, 100-199, 200-299 \) + +```yaml +Type: String +Parameter Sets: (All) +Aliases: +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + + ### -GroupNumericMin The starting point for grouping