diff --git a/AddConditionalFormatting.ps1 b/AddConditionalFormatting.ps1 index 2b54282..6d35dbd 100644 --- a/AddConditionalFormatting.ps1 +++ b/AddConditionalFormatting.ps1 @@ -1,22 +1,22 @@ Function Add-ConditionalFormatting { -<# - .Synopsis - Adds contitional formatting to worksheet. - .Example - $excel = $avdata | Export-Excel -Path (Join-path $FilePath "\Machines.XLSX" ) -WorksheetName "Server Anti-Virus" -AutoSize -FreezeTopRow -AutoFilter -PassThru + <# + .Synopsis + Adds contitional formatting to worksheet. + .Example + $excel = $avdata | Export-Excel -Path (Join-path $FilePath "\Machines.XLSX" ) -WorksheetName "Server Anti-Virus" -AutoSize -FreezeTopRow -AutoFilter -PassThru - Add-ConditionalFormatting -WorkSheet $excel.Workbook.Worksheets[1] -Address "b2:b1048576" -ForeGroundColor "RED" -RuleType ContainsText -ConditionValue "2003" - Add-ConditionalFormatting -WorkSheet $excel.Workbook.Worksheets[1] -Address "i2:i1048576" -ForeGroundColor "RED" -RuleType ContainsText -ConditionValue "Disabled" - $excel.Workbook.Worksheets[1].Cells["D1:G1048576"].Style.Numberformat.Format = [cultureinfo]::CurrentCulture.DateTimeFormat.ShortDatePattern - $excel.Workbook.Worksheets[1].Row(1).style.font.bold = $true - $excel.Save() ; $excel.Dispose() + Add-ConditionalFormatting -WorkSheet $excel.Workbook.Worksheets[1] -Address "b2:b1048576" -ForeGroundColor "RED" -RuleType ContainsText -ConditionValue "2003" + Add-ConditionalFormatting -WorkSheet $excel.Workbook.Worksheets[1] -Address "i2:i1048576" -ForeGroundColor "RED" -RuleType ContainsText -ConditionValue "Disabled" + $excel.Workbook.Worksheets[1].Cells["D1:G1048576"].Style.Numberformat.Format = [cultureinfo]::CurrentCulture.DateTimeFormat.ShortDatePattern + $excel.Workbook.Worksheets[1].Row(1).style.font.bold = $true + $excel.Save() ; $excel.Dispose() - Here Export-Excel is called with the -passThru parameter so the Excel Package object is stored in $Excel - The desired worksheet is selected and the then columns B and i are conditially formatted (excluding the top row) to show red text if - the columns contain "2003" or "Disabled respectively. A fixed date formats are then applied to columns D..G, and the top row is formatted. - Finally the workbook is saved and the Excel object closed. + Here Export-Excel is called with the -passThru parameter so the Excel Package object is stored in $Excel + The desired worksheet is selected and the then columns B and i are conditially formatted (excluding the top row) to show red text if + the columns contain "2003" or "Disabled respectively. A fixed date formats are then applied to columns D..G, and the top row is formatted. + Finally the workbook is saved and the Excel object closed. -#> + #> Param ( #The worksheet where the format is to be applied [Parameter(Mandatory = $true, ParameterSetName = "NamedRule")] diff --git a/Export-Excel.Tests.ps1 b/Export-Excel.Tests.ps1 index c59ae7f..c5a77db 100644 --- a/Export-Excel.Tests.ps1 +++ b/Export-Excel.Tests.ps1 @@ -1,94 +1,620 @@ -#Requires -Modules Pester -#Requires -Modules Assert +#Requires -Modules Pester $here = Split-Path -Parent $MyInvocation.MyCommand.Path -$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.' + +Import-Module $here -Force -Import-Module $here -Force +if (Get-process -Name Excel,xlim -ErrorAction SilentlyContinue) { Write-Warning -Message "You need to close Excel before running the tests." ; return} +Describe ExportExcel { + + Context "#Example 1 # Creates and opens a file with the right number of rows and columns" { + $path = "$env:TEMP\Test.xlsx" + Remove-item -Path $path -ErrorAction SilentlyContinue + $processes = Get-Process + $propertyNames = $Processes[0].psobject.properties.name + $rowcount = $Processes.Count + $Processes | Export-Excel $path -show + + it "Created a new file " { + Test-Path -Path $path -ErrorAction SilentlyContinue | should be $true + } -$WarningPreference = 'SilentlyContinue' -$ProgressPreference = 'SilentlyContinue' + it "Started Excel to display the file " { + Get-process -Name Excel,xlim -ErrorAction SilentlyContinue | should not benullorempty + } + + Start-Sleep -Seconds 5 ; -Function Test-isNumeric { - Param ( - [Parameter(ValueFromPipeline)]$x - ) + #Open-ExcelPackage with -Create is tested in Export-Excel + #This is a test of using it with -KillExcel + #TODO Need to test opening pre-existing file with no -create switch (and graceful failure when file does not exist) somewhere else + $Excel = Open-ExcelPackage -Path $path -KillExcel + it "Killed Excel when Open-Excelpackage was told to " { + Get-process -Name Excel,xlim -ErrorAction SilentlyContinue | should benullorempty + } - Return $x -is [byte] -or $x -is [int16] -or $x -is [int32] -or $x -is [int64] ` - -or $x -is [sbyte] -or $x -is [uint16] -or $x -is [uint32] -or $x -is [uint64] ` - -or $x -is [float] -or $x -is [double] -or $x -is [decimal] -} + it "Created 1 worksheet " { + $Excel.Workbook.Worksheets.count | should be 1 + } -$fakeData = [PSCustOmobject]@{ - Property_1_Date = (Get-Date).ToString('d') # US '10/16/2017' BE '16/10/2107' - Property_2_Formula = '=SUM(G2:H2)' - Property_3_String = 'My String' - Property_4_String = 'a' - Property_5_IPAddress = '10.10.25.5' - Property_6_Number = '0' - Property_7_Number = '5' - Property_8_Number = '007' - Property_9_Number = (33).ToString('F2') # US '33.00' BE '33,00' - Property_10_Number = (5/3).ToString('F2') # US '1.67' BE '1,67' - Property_11_Number = (15999998/3).ToString('N2') # US '5,333,332.67' BE '5.333.332,67' - Property_12_Number = '1.555,83' - Property_13_PhoneNr = '+32 44' - Property_14_PhoneNr = '+32 4 4444 444' - Property_15_PhoneNr = '+3244444444' -} - -$Path = 'Test.xlsx' - -Describe 'Export-Excel' { - in $TestDrive { - Describe 'Number conversion' { - Context 'numerical values expected' { - #region Create test file - $fakeData | Export-Excel -Path $Path - - $Path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) - $Excel = New-Object OfficeOpenXml.ExcelPackage $Path - $Worksheet = $Excel.Workbook.WorkSheets[1] - #endregion - - it 'zero' { - $fakeData.Property_6_Number | Should -BeExactly '0' - $Worksheet.Cells[2, 6].Text | Should -BeExactly $fakeData.Property_6_Number - $Worksheet.Cells[2, 6].Value | Test-isNumeric | Should -Be $true - } - - It 'regular number' { - $fakeData.Property_7_Number | Should -BeExactly '5' - $Worksheet.Cells[2, 7].Text | Should -BeExactly $fakeData.Property_7_Number - $Worksheet.Cells[2, 7].Value | Test-isNumeric | Should -Be $true - } - - It 'number starting with zero' { - $fakeData.Property_8_Number | Should -BeExactly '007' - $Worksheet.Cells[2, 8].Text | Should -BeExactly '7' - $Worksheet.Cells[2, 8].Value | Test-isNumeric | Should -Be $true - } - - It 'decimal number' { - # US '33.00' BE '33,00' - $fakeData.Property_9_Number | Should -BeExactly (33).ToString('F2') - $Worksheet.Cells[2, 9].Text | Should -BeExactly '33' - $Worksheet.Cells[2, 9].Value | Test-isNumeric | Should -Be $true - - # US '1.67' BE '1,67' - $fakeData.Property_10_Number | Should -BeExactly (5/3).ToString('F2') - $Worksheet.Cells[2, 10].Text | Should -BeExactly $fakeData.Property_10_Number - $Worksheet.Cells[2, 10].Value | Test-isNumeric | Should -Be $true - } - - It 'thousand seperator and decimal number' { - # US '5,333,332.67' BE '5.333.332,67' - # Excel BE '5333332,67' - $fakeData.Property_11_Number | Should -BeExactly (15999998/3).ToString('N2') - $Worksheet.Cells[2, 11].Text | Should -BeExactly $fakeData.Property_11_Number - $Worksheet.Cells[2, 11].Value | Test-isNumeric | Should -Be $true - } + $ws = $Excel.Workbook.Worksheets[1] + it "Created the worksheet with the expected name, number of rows and number of columns " { + $ws.Name | should be "sheet1" + $ws.Dimension.Columns | should be $propertyNames.Count + $ws.Dimension.Rows | should be ($rowcount + 1) + } + + $headingNames = $ws.cells["1:1"].Value + it "Created the worksheet with the correct header names " { + foreach ($p in $propertyNames) { + $headingnames -contains $p | should be $true } } + + it "Formatted the process StartTime field as 'local short date' " { + $STHeader = $ws.cells["1:1"].where({$_.Value -eq "StartTime"})[0] + $STCell = $STHeader.Address -replace '1$','2' + $ws.cells[$stcell].Style.Numberformat.NumFmtID | should be 22 + } + + it "Formatted the process ID field as 'General' " { + $IDHeader = $ws.cells["1:1"].where({$_.Value -eq "ID"})[0] + $IDCell = $IDHeader.Address -replace '1$','2' + $ws.cells[$IDcell].Style.Numberformat.NumFmtID | should be 0 + } } -} \ No newline at end of file + + Context " # NoAliasOrScriptPropeties -ExcludeProperty and -DisplayPropertySet work" { + $path = "$env:TEMP\Test.xlsx" + Remove-item -Path $path -ErrorAction SilentlyContinue + $processes = Get-Process + $propertyNames = $Processes[0].psobject.properties.where( {$_.MemberType -eq 'Property'}).name + $rowcount = $Processes.Count + #TestCreating a range with a name which needs illegal chars removing + $warnVar = $null + $Processes | Export-Excel $path -NoAliasOrScriptPropeties -RangeName "No Spaces" -WarningVariable warnvar -WarningAction SilentlyContinue + + $Excel = Open-ExcelPackage -Path $path + $ws = $Excel.Workbook.Worksheets[1] + it "Created a new file with alias & Script Properties removed. " { + $ws.Name | should be "sheet1" + $ws.Dimension.Columns | should be $propertyNames.Count + $ws.Dimension.Rows | should be ($rowcount + 1 ) # +1 for the header. + } + it "Created a Range - even though the name given was invalid. " { + $ws.Names["No_spaces"] | should not beNullOrEmpty + $ws.Names["No_spaces"].End.Column | should be $propertyNames.Count + $ws.names["No_spaces"].End.Row | should be ($rowcount + 1 ) # +1 for the header. + $warnVar.Count | should be 1 + } + #This time use clearsheet instead of deleting the file + $Processes | Export-Excel $path -NoAliasOrScriptPropeties -ExcludeProperty SafeHandle, modules, MainModule, StartTime, Threads -ClearSheet + + $Excel = Open-ExcelPackage -Path $path + $ws = $Excel.Workbook.Worksheets[1] + it "Created a new file with a further 5 properties excluded and cleared the old sheet " { + $ws.Name | should be "sheet1" + $ws.Dimension.Columns | should be ($propertyNames.Count - 5) + $ws.Dimension.Rows | should be ($rowcount + 1) # +1 for the header + } + + $propertyNames = $Processes[0].psStandardmembers.DefaultDisplayPropertySet.ReferencedPropertyNames + Remove-item -Path $path -ErrorAction SilentlyContinue + $Processes | Export-Excel $path -DisplayPropertySet + + $Excel = Open-ExcelPackage -Path $path + $ws = $Excel.Workbook.Worksheets[1] + it "Created a new file with just the members of the Display Property Set " { + $ws.Name | should be "sheet1" + $ws.Dimension.Columns | should be $propertyNames.Count + $ws.Dimension.Rows | should be ($rowcount + 1) + } + } + + Context "#Example 2 # Exports a list of numbers and applies number format " { + + $path = "$env:TEMP\Test.xlsx" + Remove-item -Path $path -ErrorAction SilentlyContinue + #testing -ReturnRange switch + $returnedRange = Write-Output -1 668 34 777 860 -0.5 119 -0.1 234 788 | Export-Excel -NumberFormat '[Blue]$#,##0.00;[Red]-$#,##0.00' -Path $path -ReturnRange + it "Created a new file and returned the expected range " { + Test-Path -Path $path -ErrorAction SilentlyContinue | should be $true + $returnedRange | should be "A1:A10" + } + + $Excel = Open-ExcelPackage -Path $path + it "Created 1 worksheet " { + $Excel.Workbook.Worksheets.count | should be 1 + } + + $ws = $Excel.Workbook.Worksheets[1] + it "Created the worksheet with the expected name, number of rows and number of columns " { + $ws.Name | should be "sheet1" + $ws.Dimension.Columns | should be 1 + $ws.Dimension.Rows | should be 10 + } + + it "Set the default style for the sheet as expected " { + $ws.cells.Style.Numberformat.Format | should be '[Blue]$#,##0.00;[Red]-$#,##0.00' + } + + it "Set the default style and value for Cell A1 as expected " { + $ws.cells[1,1].Style.Numberformat.Format | should be '[Blue]$#,##0.00;[Red]-$#,##0.00' + $ws.cells[1,1].Value | should be -1 + } + } + + Context "#Examples 3 & 4 # Setting cells for different data types Also added test for URI type" { + + $path = "$env:TEMP\Test.xlsx" + Remove-item -Path $path -ErrorAction SilentlyContinue + [PSCustOmobject][Ordered]@{ + Date = Get-Date + Formula1 = '=SUM(F2:G2)' + String1 = 'My String' + String2 = 'a' + IPAddress = '10.10.25.5' + Number1 = '07670' + Number2 = '0,26' + Number3 = '1.555,83' + Number4 = '1.2' + Number5 = '-31' + PhoneNr1 = '+32 44' + PhoneNr2 = '+32 4 4444 444' + PhoneNr3 = '+3244444444' + Link = [uri]"https://github.com/dfinke/ImportExcel" + } | Export-Excel -NoNumberConversion IPAddress, Number1 -Path $path + it "Created a new file " { + Test-Path -Path $path -ErrorAction SilentlyContinue | should be $true + } + + $Excel = Open-ExcelPackage -Path $path + it "Created 1 worksheet " { + $Excel.Workbook.Worksheets.count | should be 1 + } + + $ws = $Excel.Workbook.Worksheets[1] + it "Created the worksheet with the expected name, number of rows and number of columns " { + $ws.Name | should be "sheet1" + $ws.Dimension.Columns | should be 14 + $ws.Dimension.Rows | should be 2 + } + + it "Set a date in Cell A2 " { + $ws.Cells[2,1].Value.Gettype().name | should be 'DateTime' + } + + it "Set a formula in Cell B2 " { + $ws.Cells[2,2].Formula | should be '=SUM(F2:G2)' + } + + it "Set strings in Cells E2 and F2 " { + $ws.Cells[2,5].Value.GetType().name | should be 'String' + $ws.Cells[2,6].Value.GetType().name | should be 'String' + } + + it "Set a number in Cell I2 " { + ($ws.Cells[2,9].Value -is [valuetype] ) | should be $true + } + + it "Set a hyperlink in Cell N2 " { + $ws.Cells[2,14].Hyperlink | should be "https://github.com/dfinke/ImportExcel" + } + } + + + Context "# # Setting cells for different data types with -noHeader" { + + $path = "$env:TEMP\Test.xlsx" + Remove-item -Path $path -ErrorAction SilentlyContinue + [PSCustOmobject][Ordered]@{ + Date = Get-Date + Formula1 = '=SUM(F1:G1)' + String1 = 'My String' + String2 = 'a' + IPAddress = '10.10.25.5' + Number1 = '07670' + Number2 = '0,26' + Number3 = '1.555,83' + Number4 = '1.2' + Number5 = '-31' + PhoneNr1 = '+32 44' + PhoneNr2 = '+32 4 4444 444' + PhoneNr3 = '+3244444444' + Link = [uri]"https://github.com/dfinke/ImportExcel" + } | Export-Excel -NoNumberConversion IPAddress, Number1 -Path $path -NoHeader + it "Created a new file " { + Test-Path -Path $path -ErrorAction SilentlyContinue | should be $true + } + + $Excel = Open-ExcelPackage -Path $path + it "Created 1 worksheet " { + $Excel.Workbook.Worksheets.count | should be 1 + } + + $ws = $Excel.Workbook.Worksheets[1] + it "Created the worksheet with the expected name, number of rows and number of columns " { + $ws.Name | should be "sheet1" + $ws.Dimension.Columns | should be 14 + $ws.Dimension.Rows | should be 1 + } + + it "Set a date in Cell A1 " { + $ws.Cells[1,1].Value.Gettype().name | should be 'DateTime' + } + + it "Set a formula in Cell B1 " { + $ws.Cells[1,2].Formula | should be '=SUM(F1:G1)' + } + + it "Set strings in Cells E1 and F1 " { + $ws.Cells[1,5].Value.GetType().name | should be 'String' + $ws.Cells[1,6].Value.GetType().name | should be 'String' + } + + it "Set a number in Cell I1 " { + ($ws.Cells[1,9].Value -is [valuetype] ) | should be $true + } + + it "Set a hyperlink in Cell N1 " { + $ws.Cells[1,14].Hyperlink | should be "https://github.com/dfinke/ImportExcel" + } + } + + Context "#Example 5 # Adding a single conditional format " { + ### TODO New-ConditionalText doesn't a lot of options in Add-ConditionalFormat. + # It would be good to pull the logic out of Export-Excel and have EE call Add-ConditionalFormat. + $ct = New-ConditionalText -ConditionalType GreaterThan 525 -ConditionalTextColor DarkRed -BackgroundColor LightPink + it "Created a Conditional format description " { + $ct.BackgroundColor -is [System.Drawing.Color] | should be $true + $ct.ConditionalTextColor -is [System.Drawing.Color] | should be $true + $ct.ConditionalType -in [enum]::GetNames( [OfficeOpenXml.ConditionalFormatting.eExcelConditionalFormattingRuleType] ) | + should be $true + } + + $path = "$env:TEMP\Test.xlsx" + Remove-item -Path $path -ErrorAction SilentlyContinue + Write-Output 489 668 299 777 860 151 119 497 234 788 | Export-Excel -Path $path -ConditionalText $ct + + it "Created a new file " { + Test-Path -Path $path -ErrorAction SilentlyContinue | should be $true + } + + #ToDo need to test applying conitional formatting to a pre-existing worksheet + $Excel = Open-ExcelPackage -Path $path + $ws = $Excel.Workbook.Worksheets[1] + + it "Added one block of conditional formating for the data range " { + $ws.ConditionalFormatting.Count | should be 1 + $ws.ConditionalFormatting[0].Address | should be ($ws.Dimension.Address) + } + + $cf = $ws.ConditionalFormatting[0] + it "Set the conditional formatting properties correctly " { + $cf.Formula | should be $ct.Text + $cf.Type.ToString() | should be $ct.ConditionalType + #$cf.Style.Fill.BackgroundColor | should be $ct.BackgroundColor + # $cf.Style.Font.Color | should be $ct.ConditionalTextColor - have to compare r.g.b + + + } + } + + Context "#Example 6 # Adding multiple conditional formats using short form syntax. " { + #this is a test of adding more than one conditional block and using the minimal syntax for new-ConditionalText = + $path = "$env:TEMP\Test.xlsx" + Remove-item -Path $path -ErrorAction SilentlyContinue + + #Testing -Passthrough + $Excel = Get-Service | Select-Object Name, Status, DisplayName, ServiceName | + Export-Excel $path -PassThru -ConditionalText $( + New-ConditionalText Stop DarkRed LightPink + New-ConditionalText Running Blue Cyan + ) + $ws = $Excel.Workbook.Worksheets[1] + it "Added two blocks of conditional formating for the data range " { + $ws.ConditionalFormatting.Count | should be 2 + $ws.ConditionalFormatting[0].Address | should be ($ws.Dimension.Address) + $ws.ConditionalFormatting[1].Address | should be ($ws.Dimension.Address) + } + it "Set the conditional formatting properties correctly " { + $ws.ConditionalFormatting[0].Text | should be "Stop" + $ws.ConditionalFormatting[1].Text | should be "Running" + $ws.ConditionalFormatting[0].Type | should be "ContainsText" + $ws.ConditionalFormatting[1].Type | should be "ContainsText" + #Add RGB Comparison + } + Close-ExcelPackage -ExcelPackage $Excel + } + + context "#Example 7 # Update-FirstObjectProperties works "{ + $Array = @() + + $Obj1 = [PSCustomObject]@{ + Member1 = 'First' + Member2 = 'Second' + } + + $Obj2 = [PSCustomObject]@{ + Member1 = 'First' + Member2 = 'Second' + Member3 = 'Third' + } + + $Obj3 = [PSCustomObject]@{ + Member1 = 'First' + Member2 = 'Second' + Member3 = 'Third' + Member4 = 'Fourth' + } + + $Array = $Obj1, $Obj2, $Obj3 + $newarray = $Array | Update-FirstObjectProperties + it "Outputs as many objects as it input " { + $newarray.Count | should be $Array.Count + } + it "Added properties to item 0 " { + $newarray[0].psobject.Properties.name.Count | should be 4 + $newarray[0].Member1 | should be 'First' + $newarray[0].Member2 | should be 'Second' + $newarray[0].Member3 | should beNullOrEmpty + $newarray[0].Member4 | should beNullOrEmpty + } + } + + Context "#Examples 8 & 9 # Adding Pivot tables and charts from parameters" { + $path = "$env:TEMP\Test.xlsx" + #This time we are not deleting the XLSX file so this should create a new, named, sheet. + $Excel = Get-Process | Select-Object -first 50 -Property Name,cpu,pm,handles,company | Export-Excel $path -WorkSheetname Processes -PassThru + #Testing -passthru and adding the Pivot as a second step. Want to save and re-open it ... + Export-Excel -ExcelPackage $Excel -WorkSheetname Processes -IncludePivotTable -PivotRows Company -PivotData PM + + $Excel = Open-ExcelPackage $path + $PTws = $Excel.Workbook.Worksheets["ProcessesPivotTable"] + $wCount = $Excel.Workbook.Worksheets.Count + it "Added the named sheet and pivot table to the workbook " { + $PTws | should not beNullOrEmpty + $PTws.PivotTables.Count | should be 1 + $Excel.Workbook.Worksheets["Processes"] | should not beNullOrEmpty + $Excel.Workbook.Worksheets.Count | should beGreaterThan 2 + $excel.Workbook.Worksheets["Processes"].Dimension.rows | should be 51 #50 data + 1 header + } + $pt = $PTws.PivotTables[0] + it "Built the expected Pivot table " { + $pt.RowFields.Count | should be 1 + $pt.RowFields[0].Name | should be "Company" + $pt.DataFields.Count | should be 1 + $pt.DataFields[0].Function | should be "Count" + $pt.DataFields[0].Field.Name | should be "PM" + $PTws.Drawings.Count | should be 0 + } + #using the already open sheet add the pivot chart + $warnvar = $null + Export-Excel -ExcelPackage $Excel -WorkSheetname Processes -IncludePivotTable -PivotRows Company -PivotData PM -IncludePivotChart -ChartType PieExploded3D -WarningAction SilentlyContinue -WarningVariable warnvar + $Excel = Open-ExcelPackage $path + it "Added a chart to the pivot table without rebuilding " { + $ws = $Excel.Workbook.Worksheets["ProcessesPivotTable"] + $Excel.Workbook.Worksheets.Count | should be $wCount + $ws.Drawings.count | should be 1 + $ws.Drawings[0].ChartType.ToString() | should be "PieExploded3D" + } + it "Generated a message on re-processing the Pivot table " { + $warnVar | Should not beNullOrEmpty + } + $warnVar = $null + Get-Process | Select-Object -Last 50 -Property Name,cpu,pm,handles,company | Export-Excel $path -WorkSheetname Processes -Append -IncludePivotTable -PivotRows Company -PivotData PM -IncludePivotChart -ChartType PieExploded3D -WarningAction SilentlyContinue -WarningVariable warnvar + $Excel = Open-ExcelPackage $path + $pt = $Excel.Workbook.Worksheets["ProcessesPivotTable"].PivotTables[0] + it "Appended to the Worksheet and Extended the Pivot table " { + $Excel.Workbook.Worksheets.Count | should be $wCount + $excel.Workbook.Worksheets["Processes"].Dimension.rows | should be 101 #appended 50 rows to the previous total + $pt.CacheDefinition.CacheDefinitionXml.pivotCacheDefinition.cacheSource.worksheetSource.ref | + should be "A1:E101" + } + it "Generated a message on extending the Pivot table " { + $warnVar | Should not beNullOrEmpty + } + } + + Context " # Add-Worksheet inserted sheets, moved them correctly, and copied a sheet" { + $path = "$env:TEMP\Test.xlsx" + + $Excel = Open-ExcelPackage $path + #At this point Sheets should be in the order Sheet1, Processes, ProcessesPivotTable + $null = Add-WorkSheet -ExcelPackage $Excel -WorkSheetname "Processes" -MoveToEnd # order now Sheet1, ProcessesPivotTable, Processes + $null = Add-WorkSheet -ExcelPackage $Excel -WorkSheetname "NewSheet" -MoveAfter "*" -CopySource ($excel.Workbook.Worksheets["Sheet1"]) # Now its NewSheet, Sheet1, ProcessesPivotTable, Processes + $null = Add-WorkSheet -ExcelPackage $Excel -WorkSheetname "Sheet1" -MoveAfter "*" # Now its NewSheet, ProcessesPivotTable, Processes, Sheet1 + $null = Add-WorkSheet -ExcelPackage $Excel -WorkSheetname "Another" -MoveToStart # Now its Another, NewSheet, ProcessesPivotTable, Processes, Sheet1 + $null = Add-WorkSheet -ExcelPackage $Excel -WorkSheetname "OneLast" -MoveBefore "ProcessesPivotTable" # Now its Another, NewSheet, Onelast, ProcessesPivotTable, Processes, Sheet1 + Close-ExcelPackage $Excel + + $Excel = Open-ExcelPackage $path + + it "Got the Sheets in the right order " { + $excel.Workbook.Worksheets[1].Name | should be "Another" + $excel.Workbook.Worksheets[2].Name | should be "NewSheet" + $excel.Workbook.Worksheets[3].Name | should be "Onelast" + $excel.Workbook.Worksheets[4].Name | should be "ProcessesPivotTable" + $excel.Workbook.Worksheets[5].Name | should be "Processes" + $excel.Workbook.Worksheets[6].Name | should be "Sheet1" + } + + it "Cloned 'Sheet1' to 'NewSheet' "{ + $newWs = $excel.Workbook.Worksheets["NewSheet"] + $newWs.Dimension.Address | should be ($excel.Workbook.Worksheets["Sheet1"].Dimension.Address) + $newWs.ConditionalFormatting.Count | should be ($excel.Workbook.Worksheets["Sheet1"].ConditionalFormatting.Count) + $newWs.ConditionalFormatting[0].Address.Address | should be ($excel.Workbook.Worksheets["Sheet1"].ConditionalFormatting[0].Address.Address) + $newWs.ConditionalFormatting[0].Formula | should be ($excel.Workbook.Worksheets["Sheet1"].ConditionalFormatting[0].Formula) + } + + } + + Context " # Create and append with Start row and Start Column, inc ranges and Pivot table" { + $path = "$env:TEMP\Test.xlsx" + #Catch warning + $warnVar = $null + #Test Append with no existing sheet. + get-process | Select-Object -first 10 -Property Name,cpu,pm,handles,company | export-excel -StartRow 3 -StartColumn 3 -AutoFilter -AutoNameRange -BoldTopRow -IncludePivotTable -PivotRows Company -PivotData PM -Path $path -WorkSheetname withOffset -append + get-process | Select-Object -last 10 -Property Name,cpu,pm,handles,company | export-excel -StartRow 3 -StartColumn 3 -AutoFilter -AutoNameRange -BoldTopRow -IncludePivotTable -PivotRows Company -PivotData PM -Path $path -WorkSheetname withOffset -append -WarningAction SilentlyContinue -WarningVariable warnvar + $Excel = Open-ExcelPackage $path + $dataWs = $Excel.Workbook.Worksheets["withOffset"] + $pt = $Excel.Workbook.Worksheets["withOffsetPivotTable"].PivotTables[0] + it "Created and appended to a sheet offset from the top left corner " { + $dataWs.Cells[1,1].Value | Should beNullOrEmpty + $dataWs.Cells[2,2].Value | Should beNullOrEmpty + $dataWs.Cells[3,3].Value | Should not beNullOrEmpty + $dataWs.Cells[3,3].Style.Font.Bold | Should be $true + $dataWs.Dimension.End.Row | Should be 23 + $dataWs.names[0].end.row | Should be 23 + $dataWs.names[0].name | Should be 'Name' + $dataWs.names.Count | Should be 6 + $dataWs.cells[$dataws.Dimension].AutoFilter | Should be true + $pt.CacheDefinition.CacheDefinitionXml.pivotCacheDefinition.cacheSource.worksheetSource.ref | + Should be "C3:G23" + } + it "Generated a message on extending the Pivot table " { + $warnVar | Should not beNullOrEmpty + } + } + + Context "#Example 11 # Create and append with title, inc ranges and Pivot table" { + $path = "$env:TEMP\Test.xlsx" + #Catch warning + $ptDef = [ordered]@{} + $ptDef += New-PivotTableDefinition -PivotTableName "PT1" -SourceWorkSheet 'Sheet1' -PivotRows "Status" -PivotData @{'Status' = 'Count'} -PivotFilter "StartType" -IncludePivotChart -ChartType BarClustered3D -ChartTitle "Services by status" -ChartHeight 512 -ChartWidth 768 -ChartRow 10 -ChartColumn 0 -NoLegend + $ptDef += New-PivotTableDefinition -PivotTableName "PT2" -SourceWorkSheet 'Sheet2' -PivotRows "Company" -PivotData @{'Company' = 'Count'} -IncludePivotChart -ChartType PieExploded3D -ShowPercent -WarningAction SilentlyContinue + + it "Built a pivot definition using New-PivotTableDefinition " { + $ptDef.PT1.SourceWorkSheet | Should be 'Sheet1' + $ptDef.PT1.PivotRows | Should be 'Status' + $ptDef.PT1.PivotData.Status | Should be 'Count' + $ptDef.PT1.PivotFilter | Should be 'StartType' + $ptDef.PT1.IncludePivotChart | Should be $true + $ptDef.PT1.ChartType.tostring() | Should be 'BarClustered3D' + } + Remove-Item -Path $path + $warnvar = $null + Get-Service | Select-Object -Property Status, Name, DisplayName, StartType | Export-Excel -Path $path -AutoSize -TableName "All Services" -TableStyle Medium1 -WarningAction SilentlyContinue -WarningVariable warnvar + Get-Process | Select-Object -Property Name, Company, Handles, CPU, VM | Export-Excel -Path $path -AutoSize -WorkSheetname 'sheet2' -TableName "Processes" -TableStyle Light1 -Title "Processes" -TitleFillPattern Solid -TitleBackgroundColor AliceBlue -TitleBold -TitleSize 22 -PivotTableDefinition $ptDef + $Excel = Open-ExcelPackage $path + $ws1 = $Excel.Workbook.Worksheets["Sheet1"] + $ws2 = $Excel.Workbook.Worksheets["Sheet2"] + + + it "Set Column widths (with autosize) " { + $ws1.Column(2).Width | Should not be $ws1.DefaultColWidth + $ws2.Column(1).width | Should not be $ws2.DefaultColWidth + } + + it "Added tables to both sheets (handling illegal chars) and a title in sheet 2 " { + $warnvar.count | Should be 1 + $ws1.tables.Count | Should be 1 + $ws2.tables.Count | Should be 1 + $ws1.Tables[0].Address.Start.Row | Should be 1 + $ws2.Tables[0].Address.Start.Row | Should be 2 #Title in row 1 + $ws1.Tables[0].Address.End.Address | Should be $ws1.Dimension.End.Address + $ws2.Tables[0].Address.End.Address | Should be $ws2.Dimension.End.Address + $ws2.Tables[0].Name | Should be "Processes" + $ws2.Tables[0].StyleName | Should be "TableStyleLight1" + $ws2.Cells["A1"].Value | Should be "Processes" + $ws2.Cells["A1"].Style.Font.Bold | Should be $true + $ws2.Cells["A1"].Style.Font.Size | Should be 22 + $ws2.Cells["A1"].Style.Fill.PatternType.tostring() | Should be "solid" + $ws2.Cells["A1"].Style.Fill.BackgroundColor.Rgb | Should be "fff0f8ff" + } + + $ptsheet1 = $Excel.Workbook.Worksheets["Pt1"] + $ptsheet2 = $Excel.Workbook.Worksheets["Pt2"] + $PT1 = $ptsheet1.PivotTables[0] + $PT2 = $ptsheet2.PivotTables[0] + $PC1 = $ptsheet1.Drawings[0] + $PC2 = $ptsheet2.Drawings[0] + it "Created the correct pivot tables and charts from the definitions. " { + + $PT1.CacheDefinition.CacheDefinitionXml.pivotCacheDefinition.cacheSource.worksheetSource.ref | + Should be ("A1:" + $ws1.Dimension.End.Address) + $PT2.CacheDefinition.CacheDefinitionXml.pivotCacheDefinition.cacheSource.worksheetSource.ref | + Should be ("A2:" + $ws2.Dimension.End.Address) #Title in row 1 + + $pt1.PageFields[0].Name | Should be 'StartType' + $pt1.RowFields[0].Name | Should be 'Status' + $pt1.DataFields[0].Field.name | Should be 'Status' + $pt1.DataFields[0].Function | Should be 'Count' + $pc1.ChartType | Should be 'BarClustered3D' + $pc1.From.Column | Should be 0 #chart 1 at 0,10 chart 2 at 4,0 (default) + $pc2.From.Column | Should be 4 + $pc1.From.Row | Should be 10 + $pc2.From.Row | Should be 0 + $pc1.Legend.Font | should beNullOrEmpty #Best check for legend removed. + $pc2.Legend.Font | should not beNullOrEmpty + $pc1.Title.Text | Should be 'Services by status' + $pc2.DataLabel.ShowPercent | should be $true + } + } + + Context "#Example 13 # Formatting and another way to do a pivot. " { + $path = "$env:TEMP\Test.xlsx" + Remove-Item $path + $excel = Get-Process | Select-Object -Property Name,Company,Handles,CPU,PM,NPM,WS | Export-Excel -Path $path -ClearSheet -WorkSheetname "Processes" -FreezeTopRowFirstColumn -PassThru + $sheet = $excel.Workbook.Worksheets["Processes"] + $sheet.Column(1) | Set-Format -Bold -AutoFit + $sheet.Column(2) | Set-Format -Width 29 -WrapText + $sheet.Column(3) | Set-Format -HorizontalAlignment Right -NFormat "#,###" + Set-Format -Address $sheet.Cells["E1:H1048576"] -HorizontalAlignment Right -NFormat "#,###" + Set-Format -Address $sheet.Column(4) -HorizontalAlignment Right -NFormat "#,##0.0" -Bold + Set-Format -Address $sheet.Row(1) -Bold -HorizontalAlignment Center + Add-ConditionalFormatting -WorkSheet $sheet -Range "D2:D1048576" -DataBarColor Red + Add-ConditionalFormatting -WorkSheet $sheet -Range "G2:G1048576" -RuleType GreaterThan -ConditionValue "104857600" -ForeGroundColor Red + foreach ($c in 5..9) {Set-Format $sheet.Column($c) -AutoFit } + Add-PivotTable -PivotTableName "PT_Procs" -ExcelPackage $excel -SourceWorkSheet "Processes" -PivotRows Company -PivotData @{'Name'='Count'} -IncludePivotChart -ChartType ColumnClustered -NoLegend + Close-ExcelPackage $excel + + $excel = Open-ExcelPackage $path + $sheet = $excel.Workbook.Worksheets["Processes"] + + it "Applied the formating" { + $sheet | should not beNullOrEmpty + $sheet.Column(1).wdith | should not be $sheet.DefaultColWidth + $sheet.Column(7).wdith | should not be $sheet.DefaultColWidth + $sheet.Column(1).style.font.bold | should be $true + $sheet.Column(2).style.wraptext | should be $true + $sheet.Column(2).width | should be 29 + $sheet.Column(3).style.horizontalalignment | should be 'right' + $sheet.Column(4).style.horizontalalignment | should be 'right' + $sheet.Cells["A1"].Style.HorizontalAlignment | should be 'Center' + $sheet.Cells['E2'].Style.HorizontalAlignment | should be 'right' + $sheet.Cells['A1'].Style.Font.Bold | should be $true + $sheet.Cells['D2'].Style.Font.Bold | should be $true + $sheet.Cells['E2'].style.numberformat.format | should be '#,###' + $sheet.Column(3).style.numberformat.format | should be '#,###' + $sheet.Column(4).style.numberformat.format | should be '#,##0.0' + $sheet.ConditionalFormatting.Count | should be 2 + $sheet.ConditionalFormatting[0].type | should be 'Databar' + $sheet.ConditionalFormatting[0].Color.name | should be 'ffff0000' + $sheet.ConditionalFormatting[0].Address.Address | should be 'D2:D1048576' + $sheet.ConditionalFormatting[1].type | should be 'GreaterThan' + $sheet.ConditionalFormatting[1].Formula | should be '104857600' + $sheet.ConditionalFormatting[1].Style.Font.Color.Color.Name | should be 'ffff0000' + } + it "Froze the panes" { + $sheet.view.Panes.Count | should be 3 + } + $ptsheet1 = $Excel.Workbook.Worksheets["Pt_procs"] + + it "Created the pivot table" { + $ptsheet1 | should not beNullOrEmpty + $ptsheet1.PivotTables[0].DataFields[0].Field.Name | should be "Name" + $ptsheet1.PivotTables[0].DataFields[0].Function | should be "Count" + $ptsheet1.PivotTables[0].RowFields[0].Name | should be "Company" + $ptsheet1.PivotTables[0].CacheDefinition.CacheDefinitionXml.pivotCacheDefinition.cacheSource.worksheetSource.ref | + Should be $sheet.Dimension.address + } + } + + ## To do + ## More pivot options & other FreezePanes settings ? + ## Charts + ## Style script block + ## Rezip ? + +} diff --git a/Export-Excel.ps1 b/Export-Excel.ps1 index d50204a..c6dddfe 100644 --- a/Export-Excel.ps1 +++ b/Export-Excel.ps1 @@ -18,6 +18,10 @@ Data to insert onto the worksheet - this is often provided from the pipeline. .PARAMETER ExcludeProperty Specifies properties which may exist in the target data but should not be placed on the worksheet. + .PARAMETER NoAliasOrScriptPropeties + Some objects duplicate properties with aliases, or have Script properties which take a long time to return a value and slow the export down, if specified this removes these properties + .PARAMETER DisplayPropertySet, + Many (but not all) objects have a hidden property named psStandardmembers with a child property DefaultDisplayPropertySet ; this parameter reduces the properties exported to those in this set. .PARAMETER Title Text of a title to be placed in Cell A1. .PARAMETER TitleBold @@ -329,6 +333,7 @@ #> [CmdletBinding(DefaultParameterSetName = 'Default')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "")] Param( [Parameter(ParameterSetName = "Default", Position = 0)] [Parameter(ParameterSetName = "Table" , Position = 0)] @@ -371,20 +376,16 @@ [Switch]$AutoFilter, [Switch]$BoldTopRow, [Switch]$NoHeader, + [ValidateScript( { + if (-not $_) { throw 'RangeName is null or empty.' } + elseif ($_[0] -notmatch '[a-z]') { throw 'RangeName starts with an invalid character.' } + else { $true } + })] [String]$RangeName, [ValidateScript( { - if ($_.Contains(' ')) { - throw 'Tablename has spaces.' - } - elseif (-not $_) { - throw 'Tablename is null or empty.' - } - elseif ($_[0] -notmatch '[a-z]') { - throw 'Tablename starts with an invalid character.' - } - else { - $true - } + if (-not $_) { throw 'Tablename is null or empty.' } + elseif ($_[0] -notmatch '[a-z]') { throw 'Tablename starts with an invalid character.' } + else { $true } })] [Parameter(ParameterSetName = 'Table' , Mandatory = $true)] [Parameter(ParameterSetName = 'PackageTable' , Mandatory = $true)] @@ -405,12 +406,13 @@ [Switch]$PassThru, [String]$Numberformat = 'General', [string[]]$ExcludeProperty, + [Switch]$NoAliasOrScriptPropeties, + [Switch]$DisplayPropertySet, [String[]]$NoNumberConversion, [Object[]]$ConditionalFormat, [Object[]]$ConditionalText, [ScriptBlock]$CellStyleSB, [Parameter(ParameterSetName = 'Now')] - # [Parameter(ParameterSetName = 'TableNow')] [Switch]$Now, [Switch]$ReturnRange, [Switch]$NoTotalsInPivot, @@ -436,58 +438,61 @@ [Object]$TargetCell, [Object]$CellValue ) - + #The write-verbose commands have been commented out below - even if verbose is silenced they cause a significiant performance impact and if it's on they will cause a flood of messages. Switch ($CellValue) { - {($_ -is [String]) -and ($_.StartsWith('='))} { - #region Save an Excel formula - $TargetCell.Formula = $_ - Write-Verbose "Cell '$Row`:$ColumnIndex' header '$Name' add value '$_' as formula" - break - #endregion - } - { $_ -is [URI] } { - #region Save a hyperlink - $TargetCell.Value = $_.AbsoluteUri - $TargetCell.HyperLink = $_ - $TargetCell.Style.Font.Color.SetColor([System.Drawing.Color]::Blue) - $TargetCell.Style.Font.UnderLine = $true - Write-Verbose "Cell '$Row`:$ColumnIndex' header '$Name' add value '$($_.AbsoluteUri)' as Hyperlink" - break - #endregion - } { $_ -is [DateTime]} { - #region Save a date with an international valid format + # Save a date with an international valid format $TargetCell.Value = $_ $TargetCell.Style.Numberformat.Format = 'm/d/yy h:mm' # This is not a custom format, but a preset recognized as date and localized. - Write-Verbose "Cell '$Row`:$ColumnIndex' header '$Name' add value '$_' as date" + #Write-Verbose "Cell '$Row`:$ColumnIndex' header '$Name' add value '$_' as date" + break + + } + { $_ -is [System.ValueType]} { + # Save numerics, setting format if need be. + $TargetCell.Value = $_ + if ($setNumformat) {$targetCell.Style.Numberformat.Format = $Numberformat } + #Write-Verbose "Cell '$Row`:$ColumnIndex' header '$Name' add value '$_' as value" break - #endregion } {(($NoNumberConversion) -and ($NoNumberConversion -contains $Name)) -or ($NoNumberConversion -eq '*')} { - #region Save a value without converting to number + #Save text without it to converting to number $TargetCell.Value = $_ - Write-Verbose "Cell '$Row`:$ColumnIndex' header '$Name' add value '$($TargetCell.Value)' unconverted" + #Write-Verbose "Cell '$Row`:$ColumnIndex' header '$Name' add value '$($TargetCell.Value)' unconverted" break - #endregion } - + {($_ -is [String]) -and ($_[0] -eq '=')} { + #region Save an Excel formula + $TargetCell.Formula = $_ + #Write-Verbose "Cell '$Row`:$ColumnIndex' header '$Name' add value '$_' as formula" + break + } + { $_ -is [Uri] } { + # Save a hyperlink + $TargetCell.Value = $_.AbsoluteUri + $TargetCell.HyperLink = $_ + $TargetCell.Style.Font.Color.SetColor([System.Drawing.Color]::Blue) + $TargetCell.Style.Font.UnderLine = $true + #Write-Verbose "Cell '$Row`:$ColumnIndex' header '$Name' add value '$($_.AbsoluteUri)' as Hyperlink" + break + } + Default { - #region Save a value as a number if possible + #Save a value as a number if possible $number = $null - if ([Double]::TryParse([String]$_, [System.Globalization.NumberStyles]::Any, - [System.Globalization.NumberFormatInfo]::CurrentInfo, [Ref]$number)) { + if ([Double]::TryParse( $_ , [ref]$number)) { + #was [Double]::TryParse([String]$_, [System.Globalization.NumberStyles]::Any,[System.Globalization.NumberFormatInfo]::CurrentInfo, [Ref]$number)) { $TargetCell.Value = $number - $targetCell.Style.Numberformat.Format = $Numberformat - Write-Verbose "Cell '$Row`:$ColumnIndex' header '$Name' add value '$($TargetCell.Value)' as number converted from '$_' with format '$Numberformat'" + if ($setNumformat) {$targetCell.Style.Numberformat.Format = $Numberformat } + #Write-Verbose "Cell '$Row`:$ColumnIndex' header '$Name' add value '$($TargetCell.Value)' as number converted from '$_' with format '$Numberformat'" } else { $TargetCell.Value = $_ - Write-Verbose "Cell '$Row`:$ColumnIndex' header '$Name' add value '$($TargetCell.Value)' as string" + #Write-Verbose "Cell '$Row`:$ColumnIndex' header '$Name' add value '$($TargetCell.Value)' as string" } break - #endregion } } } @@ -506,17 +511,16 @@ } if ($ExcelPackage) { - $pkg = $ExcelPackage - $Path = $pkg.File + $pkg = $ExcelPackage + $Path = $pkg.File } - Else { $pkg = Open-ExcelPackage -Path $Path -Create -KillExcel:$KillExcel} + Else { $pkg = Open-ExcelPackage -Path $Path -Create -KillExcel:$KillExcel} - $params = @{} + $params = @{} if ($NoClobber) {Write-Warning -Message "-NoClobber parameter is no longer used" } - foreach ($p in @("WorkSheetname","ClearSheet","MoveToStart","MoveToEnd","MoveBefore","MoveAfter")) {if ($PSBoundParameters[$p]) {$params[$p] = $PSBoundParameters[$p]}} + foreach ($p in @("WorkSheetname", "ClearSheet", "MoveToStart", "MoveToEnd", "MoveBefore", "MoveAfter")) {if ($PSBoundParameters[$p]) {$params[$p] = $PSBoundParameters[$p]}} $ws = $pkg | Add-WorkSheet @params - $ws.View.TabSelected = $true foreach ($format in $ConditionalFormat ) { $target = "Add$($format.Formatter)" $rule = ($ws.ConditionalFormatting).PSObject.Methods[$target].Invoke($format.Range, $format.IconType) @@ -524,14 +528,12 @@ } if ($append -and $ws.Dimension) { - $headerRange = $ws.Dimension.Address -replace "\d+$", "1" - #if there is a title or anything else above the header row, specifying StartRow will skip it. - if ($StartRow -ne 1) {$headerRange = $headerRange -replace "1", "$StartRow"} - #$script:Header = $ws.Cells[$headerrange].Value + #if there is a title or anything else above the header row, append needs to be combined wih a suitable startrow parameter + $headerRange = $ws.Dimension.Address -replace "\d+$", $StartRow #using a slightly odd syntax otherwise header ends up as a 2D array $ws.Cells[$headerRange].Value | ForEach-Object -Begin {$Script:header = @()} -Process {$Script:header += $_ } - $row = $ws.Dimension.Rows - Write-Debug -Message ("Appending: headers are " + ($script:Header -join ", ") + "Start row $row") + $row = $ws.Dimension.End.Row + Write-Debug -Message ("Appending: headers are " + ($script:Header -join ", ") + " Start row is $row") } elseif ($Title) { #Can only add a title if not appending! @@ -545,7 +547,7 @@ $ws.Cells[$Row, $StartColumn].Style.Font.Bold = $True } #Can only set TitleBackgroundColor if TitleFillPattern is something other than None. - if ($TitleBackgroundColor -and ($TitleFillPattern -eq 'None')) { + if ($TitleBackgroundColor -and ($TitleFillPattern -ne 'None')) { $TitleFillPattern = [OfficeOpenXml.Style.ExcelFillStyle]::Solid } $ws.Cells[$Row, $StartColumn].Style.Fill.PatternType = $TitleFillPattern @@ -554,9 +556,11 @@ $ws.Cells[$Row, $StartColumn].Style.Fill.BackgroundColor.SetColor($TitleBackgroundColor) } $Row ++ ; $startRow ++ - } + } else { $Row = $StartRow } $ColumnIndex = $StartColumn + $setNumformat = ($numberformat -ne $ws.Cells.Style.Numberformat.Format) + $firstTimeThru = $true $isDataTypeValueType = $false } @@ -566,7 +570,7 @@ throw "Failed exporting worksheet '$WorkSheetname' to '$Path': The worksheet '$WorkSheetname' already exists." } else { - throw "Failed exporting worksheet '$WorkSheetname' to '$Path': $_" + throw "Failed preparing to export to worksheet '$WorkSheetname' to '$Path': $_" } } } @@ -591,8 +595,13 @@ #region Add headers if (-not $script:Header) { $ColumnIndex = $StartColumn - $script:Header = $TargetData.PSObject.Properties.Name | Where-Object {$_ -notin $ExcludeProperty} - + if ($DisplayPropertySet -and $TargetData.psStandardmembers.DefaultDisplayPropertySet.ReferencedPropertyNames) { + $script:Header = $TargetData.psStandardmembers.DefaultDisplayPropertySet.ReferencedPropertyNames.Where( {$_ -notin $ExcludeProperty}) + } + else { + if ($NoAliasOrScriptPropeties) {$propType = "Property"} else {$propType = "*"} + $script:Header = $TargetData.PSObject.Properties.where( {$_.MemberType -like $propType -and $_.Name -notin $ExcludeProperty}).Name + } if ($NoHeader) { # Don't push the headers to the spreadsheet $Row -= 1 @@ -620,36 +629,46 @@ } } Catch { - throw "Failed exporting worksheet '$WorkSheetname' to '$Path': $_" + throw "Failed exporting data to worksheet '$WorkSheetname' to '$Path': $_" } } } End { - Try { - if ($AutoNameRange) { + if ($AutoNameRange) { + Try { if (-not $script:header) { - $headerRange = $ws.Dimension.Address -replace "\d+$", "1" - #if there is a title or anything else above the header row, specifying StartRow will skip it. - if ($StartRow -ne 1) {$headerRange = $headerRange -replace "1", "$StartRow"} + # if there aren't any headers, use the the first row of data to name the ranges: this is the last point that headers will be used. + $headerRange = $ws.Dimension.Address -replace "\d+$", $StartRow #using a slightly odd syntax otherwise header ends up as a 2D array $ws.Cells[$headerRange].Value | ForEach-Object -Begin {$Script:header = @()} -Process {$Script:header += $_ } + #if there is no header start the range at $startRow + $targetRow = $StartRow } - $totalRows = $ws.Dimension.End.Row - $totalColumns = $ws.Dimension.Columns - foreach ($c in 0..($totalColumns - 1)) { - $targetRangeName = "$($script:Header[$c])" + else { + #if there is a header, start the range and the next row down. + $targetRow = $StartRow + 1 + } + + #Dimension.start.row always seems to be one so we work out the target row + #, but start.column is the first populated one and .Columns is the count of populated ones. + # if we have 5 columns from 3 to 8, headers are numbered 0..4, so that is in the for loop and used for getting the name... + # but we have to add the start column on when referencing positions + foreach ($c in 0..($ws.Dimension.Columns - 1)) { + $targetRangeName = $script:Header[$c] -replace '\W' , '_' $targetColumn = $c + $StartColumn - $theCell = $ws.Cells[($startrow + 1), $targetColumn, $totalRows , $targetColumn ] - if ($ws.names[$targetRangeName]) { $ws.names[$targetRangeName].Address = $theCell.FullAddressAbsolute } - else {$ws.Names.Add($targetRangeName, $theCell) | Out-Null } + $theRange = $ws.Cells[$targetRow, $targetColumn, $ws.Dimension.End.Row , $targetColumn ] + if ($ws.names[$targetRangeName]) { $ws.names[$targetRangeName].Address = $theRange.FullAddressAbsolute } + else {$ws.Names.Add($targetRangeName, $theRange) | Out-Null } if ([OfficeOpenXml.FormulaParsing.ExcelUtilities.ExcelAddressUtil]::IsValidAddress($targetRangeName)) { Write-Warning "AutoNameRange: Property name '$targetRangeName' is also a valid Excel address and may cause issues. Consider renaming the property name." } } } - + Catch {Write-Warning -Message "Failed adding named ranges to worksheet '$WorkSheetname': $_" } + } + try { if ($Title) { $startAddress = $ws.Dimension.Start.address -replace "$($ws.Dimension.Start.row)`$", "$($ws.Dimension.Start.row + 1)" } @@ -662,19 +681,29 @@ Write-Debug "Data Range '$dataRange'" if (-not [String]::IsNullOrEmpty($RangeName)) { + if ($RangeName -match "\W") { + Write-Warning -Message "At least one character in $RangeName is illegal in a range name and will be replaced with '_' . " + $RangeName = $RangeName -replace '\W', '_' + } + #If named range exists, update it, else create it if ($ws.Names[$RangeName]) { $ws.Names[$rangename].Address = $ws.Cells[$dataRange].FullAddressAbsolute } else {$ws.Names.Add($RangeName, $ws.Cells[$dataRange]) | Out-Null } } - - if (-not [String]::IsNullOrEmpty($TableName)) { + } + Catch { Write-Warning -Message "Failed adding range '$RangeName' to worksheet '$WorkSheetname': $_" } + if (-not [String]::IsNullOrEmpty($TableName)) { + try { $csr = $StartRow $csc = $StartColumn $cer = $ws.Dimension.End.Row $cec = $ws.Dimension.End.Column # was $script:Header.Count - + if ($TableName -match "\W") { + Write-Warning -Message "At least one character in $TableName is illegal in a table name and will be replaced with '_' . " + $TableName = $TableName -replace '\W', '_' + } $targetRange = $ws.Cells[$csr, $csc, $cer, $cec] - #if we're appending data the table may already exist. + #if the table exists, update it. if ($ws.Tables[$TableName]) { $ws.Tables[$TableName].TableXml.table.ref = $targetRange.Address $ws.Tables[$TableName].TableStyle = $TableStyle @@ -683,187 +712,65 @@ $tbl = $ws.Tables.Add($targetRange, $TableName) $tbl.TableStyle = $TableStyle } + Write-Verbose -Message "Defined table '$TableName' at $($targetRange.Address)" } - - if ($PivotTableDefinition) { - foreach ($item in $PivotTableDefinition.GetEnumerator()) { - $pivotTableName = $item.Key - $pivotTableDataName = $item.Key + 'PivotTableData' - if ($item.Value.PivotFilter) {$PivotTableStartCell = "A3"} else { $PivotTableStartCell = "A1"} - - #Make sure the Pivot table sheet doesn't already exist. - #try { $pkg.Workbook.Worksheets.Delete( $pivotTableName) } catch {} - [OfficeOpenXml.ExcelWorksheet]$wsPivot = $pkg | Add-WorkSheet -WorkSheetname $pivotTableName -NoClobber:$NoClobber - - #If it is a pivot for the default sheet and it doesn't exist - create it - if (-not $item.Value.SourceWorkSheet -and -not $wsPivot.PivotTables[$pivotTableDataName] ) { - $pivotTable = $wsPivot.PivotTables.Add($wsPivot.Cells[$PivotTableStartCell], $ws.Cells[$dataRange], $pivotTableDataName) - } - #If it is a pivot for the default sheet and it exists - update the range. - elseif (-not $item.Value.SourceWorkSheet -and $wsPivot.PivotTables[$pivotTableDataName] ) { - $wsPivot.PivotTables[$pivotTableDataName].CacheDefinition.CacheDefinitionXml.pivotCacheDefinition.cacheSource.worksheetSource.ref = $WS.Cells[$dataRange].Address - } - #if it is a pivot for a named sheet and it doesn't exist, create it. - elseif ($item.Value.SourceWorkSheet -and -not $wsPivot.PivotTables[$pivotTableDataName] ) { - #find the worksheet - $workSheet = $pkg.Workbook.Worksheets.where( {$_.name -match $item.Value.SourceWorkSheet})[0] - if (-not $workSheet) {Write-Warning -Message "Could not find Worksheet '$($item.Value.SourceWorkSheet)' specified in pivot-table definition $($item.key)." } - else { - if ($item.Value.SourceRange) { $targetdataRange = $item.Value.SourceRange } - else { $targetDataRange = $workSheet.Dimension.Address} - $pivotTable = $wsPivot.PivotTables.Add($wsPivot.Cells[$PivotTableStartCell], $workSheet.Cells[$targetDataRange], $pivotTableDataName) - } - } - - #if we created the pivot table, set up the rows, columns and data if we didn't, put out a message 'existed' or 'error' . - if ($pivotTable) { - foreach ($Row in $item.Value.PivotRows) { - try {$null = $pivotTable.RowFields.Add($pivotTable.Fields[$Row]) } - catch {Write-Warning -message "Could not add '$row' to Rows in PivotTable $pivotTableName." } - } - foreach ($Column in $item.Value.PivotColumns) { - try {$null = $pivotTable.ColumnFields.Add($pivotTable.Fields[$Column])} - catch {Write-Warning -message "Could not add '$Column' to Columns in PivotTable $pivotTableName." } - } - if ($item.Value.PivotData -is [HashTable] -or $item.Value.PivotData -is [System.Collections.Specialized.OrderedDictionary]) { - $item.Value.PivotData.Keys | ForEach-Object { - try { - $df = $pivotTable.DataFields.Add($pivotTable.Fields[$_]) - $df.Function = $item.Value.PivotData.$_ - } - catch {Write-Warning -message "Problem adding data fields to PivotTable $pivotTableName." } - } - } - else { - foreach ($field in $item.Value.PivotData) { - try { - $df = $pivotTable.DataFields.Add($pivotTable.Fields[$field]) - $df.Function = 'Count' - } - catch {Write-Warning -message "Problem adding data field '$field' to PivotTable $pivotTableName." } - } - } - foreach ( $pFilter in $item.Value.PivotFilter) { - try { $null = $pivotTable.PageFields.Add($pivotTable.Fields[$pFilter])} - catch {Write-Warning -message "Could not add '$pFilter' to Filter/Page fields in PivotTable $pivotTableName." } - } - if ($item.Value.NoTotalsInPivot -or $NoTotalsInPivot) { $pivotTable.RowGrandTotals = $false } - if ($item.Value.PivotDataToColumn -or $PivotDataToColumn) { $pivotTable.DataOnRows = $false } - } - elseif ($wsPivot.PivotTables[$pivotTableDataName]) { - Write-Warning -Message "Pivot table defined in $($item.key) already exists." - } - else { Write-Warning -Message "Could not create the pivot table defined in $($item.key)."} - - #Create the chart if it doesn't exist, leave alone if it does. - if ($item.Value.IncludePivotChart -and -not $wsPivot.Drawings['PivotChart'] ) { - if ($item.Value.ChartType) { $ChartType = $item.Value.ChartType} # $ChartType may be passed as a parameter, has default of "Pie", over-ride that if it is in the pivot definition - [OfficeOpenXml.Drawing.Chart.ExcelChart] $chart = $wsPivot.Drawings.AddChart('PivotChart', $ChartType, $pivotTable) - if (-not $item.Value.ChartHeight) {$item.Value.ChartHeight = 400 } - if (-not $item.Value.ChartWidth) {$item.Value.ChartWidth = 600 } - if (-not $item.Value.ChartRow) {$item.Value.ChartRow = 0 } - if (-not $item.Value.ChartColumn) {$item.Value.ChartColumn = 4 } - if (-not $item.Value.ChartRowOffSetPixels) {$item.Value.ChartRowOffSetPixels = 0 } - if (-not $item.Value.ChartColumnOffSetPixels) {$item.Value.ChartColumnOffSetPixels = 0 } - $chart.SetPosition($item.Value.ChartRow , $item.Value.ChartRowOffSetPixels , $item.Value.ChartColumn, $item.Value.ChartColumnOffSetPixels) - $chart.SetSize( $item.Value.ChartWidth, $item.Value.ChartHeight) - if ($chart.DataLabel) { - $chart.DataLabel.ShowCategory = [boolean]$item.Value.ShowCategory - $chart.DataLabel.ShowPercent = [boolean]$item.Value.ShowPercent - } - if ([boolean]$item.Value.NoLegend -or $NoLegend) {$chart.Legend.Remove()} - if ( $item.Value.ChartTitle) {$chart.Title.Text = $item.Value.chartTitle} - } - } + catch {Write-Warning -Message "Failed adding table '$TableName' to worksheet '$WorkSheetname': $_"} + } + if ($PivotTableDefinition) { + foreach ($item in $PivotTableDefinition.GetEnumerator()) { + $params = $item.value + if ($params.keys -notcontains "SourceRange" -and + ($params.Keys -notcontains "SourceWorkSheet" -or $params.SourceWorkSheet -eq $WorkSheetname)) {$params.SourceRange = $dataRange} + if ($params.Keys -notcontains "SourceWorkSheet") {$params.SourceWorkSheet = $ws } + if ($params.Keys -notcontains "NoTotalsInPivot" -and $NoTotalsInPivot ) {$params.NoTotalsInPivot = $true} + if ($params.Keys -notcontains "PivotDataToColumn" -and $PivotDataToColumn) {$params.PivotDataToColumn = $true} + + Add-PivotTable -ExcelPackage $pkg -PivotTableName $item.key @Params } - - if ($IncludePivotTable -or $IncludePivotChart) { - if ($PivotFilter) {$PivotTableStartCell = "A3"} else {$PivotTableStartCell = "A1"} - - $pivotTableName = $WorkSheetname + 'PivotTable' - $wsPivot = $pkg | Add-WorkSheet -WorkSheetname $pivotTableName -NoClobber:$NoClobber - - $wsPivot.View.TabSelected = $true - - $pivotTableDataName = $WorkSheetname + 'PivotTableData' - if ($wsPivot.PivotTables[$pivotTableDataName] ) { - $pivotTable = $wsPivot.PivotTables[$pivotTableDataName] - $pivotTable.CacheDefinition.CacheDefinitionXml.pivotCacheDefinition.cacheSource.worksheetSource.ref = $WS.Cells[$dataRange].Address - Write-Warning -Message "Pivot table for $worksheetName already exists; updating the data range, but other properties will not be changed" - } - else { - $pivotTable = $wsPivot.PivotTables.Add($wsPivot.Cells[$PivotTableStartCell], $ws.Cells[$dataRange], $pivotTableDataName) - - foreach ($Row in $PivotRows) { - try {$null = $pivotTable.RowFields.Add($pivotTable.Fields[$Row]) } - catch {Write-Warning -message "Could not add '$row' to PivotTable Rows." } - } - - foreach ($Column in $PivotColumns) { - try {$null = $pivotTable.ColumnFields.Add($pivotTable.Fields[$Column])} - catch {Write-Warning -message "Could not add '$Column' to PivotTable Columns." } - } - - if ($PivotData -is [HashTable] -or $PivotData -is [System.Collections.Specialized.OrderedDictionary]) { - $PivotData.Keys | ForEach-Object { - try { - $df = $pivotTable.DataFields.Add($pivotTable.Fields[$_]) - $df.Function = $PivotData.$_ - } - catch {Write-Warning "Problem adding to Pivot table data fields." } - } - } - else { - foreach ($Item in $PivotData) { - try { - $df = $pivotTable.DataFields.Add($pivotTable.Fields[$Item]) - $df.Function = 'Count' - } - catch {Write-Warning "Problem adding '$item' to Pivot table data fields." } - } - } - - if ($PivotDataToColumn) { $pivotTable.DataOnRows = $false } - - foreach ($pFilter in $PivotFilter) { - try {$null = $pivotTable.PageFields.Add($pivotTable.Fields[$pFilter]) } - catch {Write-Warning "Problem adding 'pFilter' to Pivot table page/filter fields." } - } - - if ($NoTotalsInPivot) { $pivotTable.RowGrandTotals = $false } - } - - if ($IncludePivotChart) { - if (-not $wsPivot.Drawings['PivotChart']) { - $chart = $wsPivot.Drawings.AddChart('PivotChart', $ChartType, $pivotTable) - if ($chart.DataLabel) { - $chart.DataLabel.ShowCategory = $ShowCategory - $chart.DataLabel.ShowPercent = $ShowPercent - } - $chart.SetPosition(0, 26, 2, 26) # if Pivot table is rows+data only it will be 2 columns wide if has pivot columns we don't know how wide it will be - if ($NoLegend) { $chart.Legend.Remove() } - } - } + } + if ($IncludePivotTable -or $IncludePivotChart) { + $params = @{ + "PivotTableName" = ($WorkSheetname + 'PivotTable') ; + "SourceRange" = $dataRange } - - if ($Password) { - $ws.Protection.SetPassword($Password) + if ($PivotFilter) {$params.PivotFilter = $PivotFilter} + if ($PivotRows) {$params.PivotRows = $PivotRows} + if ($PivotColumns) {$Params.PivotColumns = $PivotColumns} + if ($PivotData) {$Params.PivotData = $PivotData} + if ($NoTotalsInPivot) {$params.NoTotalsInPivot = $true} + if ($PivotDataToColumn) {$params.PivotDataToColumn = $true} + if ($IncludePivotChart) { + $params.IncludePivotChart = $true + $Params.ChartType = $ChartType + if ($ShowCategory) {$params.ShowCategory = $true} + if ($ShowPercent) {$params.ShowPercent = $true} + if ($NoLegend) {$params.NoLegend = $true} } + Add-PivotTable -ExcelPackage $pkg -SourceWorkSheet $ws @params + } - if ($AutoFilter) { - $ws.Cells[$dataRange].AutoFilter = $true + if ($AutoFilter) { + try { + $ws.Cells[$dataRange].AutoFilter = $true + Write-Verbose -Message "Enabeld autofilter. " } + catch {Write-Warning -Message "Failed adding autofilter to worksheet '$WorkSheetname': $_"} + } + try { if ($FreezeTopRow) { - $ws.View.FreezePanes(2, 1) + $ws.View.FreezePanes(2, 1) + Write-Verbose -Message "Froze top row" } if ($FreezeTopRowFirstColumn) { - $ws.View.FreezePanes(2, 2) + $ws.View.FreezePanes(2, 2) + Write-Verbose -Message "Froze top row and first column" } if ($FreezeFirstColumn) { - $ws.View.FreezePanes(1, 2) + $ws.View.FreezePanes(1, 2) + Write-Verbose -Message "Froze first column" } if ($FreezePane) { @@ -874,164 +781,128 @@ if ($freezeRow -gt 1) { $ws.View.FreezePanes($freezeRow, $freezeColumn) + Write-Verbose -Message "Froze pandes at row $freezeRow and column $FreezeColumn" } } - - if ($BoldTopRow) { + } + catch {Write-Warning -Message "Failed adding Freezing the panes in worksheet '$WorkSheetname': $_"} + + if ($BoldTopRow) { + try { if ($Title) { - $range = $ws.Dimension.Address -replace '\d+', '2' + $range = $ws.Dimension.Address -replace '\d+', ($StartRow + 1) } else { - $range = $ws.Dimension.Address -replace '\d+', '1' + $range = $ws.Dimension.Address -replace '\d+', $StartRow } - $ws.Cells[$range].Style.Font.Bold = $true + Write-Verbose -Message "Set $range font style to bold." } - - if ($AutoSize) { - $ws.Cells.AutoFitColumns() + catch {Write-Warning -Message "Failed setting the top row to bold in worksheet '$WorkSheetname': $_"} + } + if ($AutoSize) { + try { + $ws.Cells.AutoFitColumns() + Write-Verbose -Message "Auto-sized columns" } + catch { Write-Warning -Message "Failed autosizing columns of worksheet '$WorkSheetname': $_"} + } - foreach ($Sheet in $HideSheet) { + foreach ($Sheet in $HideSheet) { + try { $pkg.Workbook.WorkSheets[$Sheet].Hidden = 'Hidden' + Write-verbose -Message "Sheet '$sheet' Hidden." } + catch {Write-Warning -Message "Failed hiding worksheet '$sheet': $_"} + } - foreach ($chartDef in $ExcelChartDefinition) { - $ChartName = 'Chart' + (Split-Path -Leaf ([System.IO.path]::GetTempFileName())) -replace 'tmp|\.', '' - $chart = $ws.Drawings.AddChart($ChartName, $chartDef.ChartType) - $chart.Title.Text = $chartDef.Title - - if ($chartDef.NoLegend) { - $chart.Legend.Remove() - } - - if ($chart.Datalabel -ne $null) { - $chart.Datalabel.ShowCategory = $chartDef.ShowCategory - $chart.Datalabel.ShowPercent = $chartDef.ShowPercent - } - - $chart.SetPosition($chartDef.Row, $chartDef.RowOffsetPixels, $chartDef.Column, $chartDef.ColumnOffsetPixels) - $chart.SetSize($chartDef.Width, $chartDef.Height) - - $chartDefCount = @($chartDef.YRange).Count - if ($chartDefCount -eq 1) { - $Series = $chart.Series.Add($chartDef.YRange, $chartDef.XRange) - - $SeriesHeader = $chartDef.SeriesHeader - if (-not $SeriesHeader) { - $SeriesHeader = 'Series 1' - } - - $Series.Header = $SeriesHeader - } - else { - for ($idx = 0; $idx -lt $chartDefCount; $idx += 1) { - $Series = $chart.Series.Add($chartDef.YRange[$idx], $chartDef.XRange) - - if ($chartDef.SeriesHeader.Count -gt 0) { - $SeriesHeader = $chartDef.SeriesHeader[$idx] - } - - if (-not $SeriesHeader) { - $SeriesHeader = "Series $($idx)" - } - - $Series.Header = $SeriesHeader - $SeriesHeader = $null - } + foreach ($chartDef in $ExcelChartDefinition) { + $params = @{} + $chardef.PSObject.Properties | ForEach-Object {if ($_.value -ne $null) {$params[$_.name] = $_.value}} + Add-ExcelChart $params + } + + foreach ($ct in $ConditionalText) { + try { + $cfParams = @{RuleType = $ct.ConditionalType; ConditionValue = $ct.text ; + BackgroundColor = $ct.BackgroundColor; BackgroundPattern = $ct.PatternType ; + ForeGroundColor = $ct.ConditionalTextColor } + if ($ct.Range) {$cfParams.range = $ct.range} else { $cfParams.Range = $ws.Dimension.Address } + Add-ConditionalFormatting -WorkSheet $ws @cfParams + Write-Verbose -Message "Added conditional formatting to range $($ct.range)" } + catch {Write-Warning -Message "Failed adding conditional formatting to worksheet '$WorkSheetname': $_"} + } - if ($ConditionalText) { - foreach ($targetConditionalText in $ConditionalText) { - $target = "Add$($targetConditionalText.ConditionalType)" - - $Range = $targetConditionalText.Range - if (-not $Range) { - $Range = $ws.Dimension.Address - } - - $rule = ($ws.Cells[$Range].ConditionalFormatting).PSObject.Methods[$target].Invoke() - - if ($targetConditionalText.Text) { - if ($targetConditionalText.ConditionalType -match 'equal|notequal|lessthan|lessthanorequal|greaterthan|greaterthanorequal') { - $rule.Formula = $targetConditionalText.Text - } - else { - $rule.Text = $targetConditionalText.Text - } - } - - $rule.Style.Font.Color.Color = $targetConditionalText.ConditionalTextColor - $rule.Style.Fill.PatternType = $targetConditionalText.PatternType - $rule.Style.Fill.BackgroundColor.Color = $targetConditionalText.BackgroundColor - } - } - - if ($CellStyleSB) { + if ($CellStyleSB) { + try { $TotalRows = $ws.Dimension.Rows $LastColumn = (Get-ExcelColumnName $ws.Dimension.Columns).ColumnName & $CellStyleSB $ws $TotalRows $LastColumn } + catch {Write-Warning -Message "Failed processing CellStyleSB in worksheet '$WorkSheetname': $_"} + } - if ($PassThru) { - $pkg + if ($Password) { + try { + $ws.Protection.SetPassword($Password) + Write-Verbose -Message "Set password on workbook" } - else { - if ($ReturnRange) { - $ws.Dimension.Address + + catch {throw "Failed setting password for worksheet '$WorkSheetname': $_"} + } + + if ($PassThru) { $pkg } + else { + if ($ReturnRange) {$ws.Dimension.Address } + + $pkg.Save() + Write-Verbose -Message "Saved workbook $($pkg.File)" + if ($ReZip) { + Write-Verbose -Message "Re-Zipping $($pkg.file) using .NET ZIP library" + try { + Add-Type -AssemblyName "System.IO.Compression.Filesystem" -ErrorAction stop + } + catch { + Write-Error "The -ReZip parameter requires .NET Framework 4.5 or later to be installed. Recommend to install Powershell v4+" + continue } - - $pkg.Save() - - if ($ReZip) { - write-verbose "Re-Zipping $($pkg.file) using .NET ZIP library" - $zipAssembly = "System.IO.Compression.Filesystem" - try { - Add-Type -assembly $zipAssembly -ErrorAction stop - } - catch { - write-error "The -ReZip parameter requires .NET Framework 4.5 or later to be installed. Recommend to install Powershell v4+" - continue - } - - $TempZipPath = Join-Path -path ([System.IO.Path]::GetTempPath()) -ChildPath ([System.IO.Path]::GetRandomFileName()) - [io.compression.zipfile]::ExtractToDirectory($pkg.File, $TempZipPath) | Out-Null + try { + $TempZipPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([System.IO.Path]::GetRandomFileName()) + [io.compression.zipfile]::ExtractToDirectory($pkg.File, $TempZipPath) | Out-Null Remove-Item $pkg.File -Force [io.compression.zipfile]::CreateFromDirectory($TempZipPath, $pkg.File) | Out-Null } - - $pkg.Dispose() - - if ($Show) { - Invoke-Item $Path - } + catch {throw "Error resizipping $path : $_"} } + + $pkg.Dispose() + + if ($Show) { Invoke-Item $Path } } - Catch { - throw "Failed exporting worksheet '$WorkSheetname' to '$Path': $_" - } + } } function New-PivotTableDefinition { -<# - .Synopsis - Creates Pivot table definitons for export excel - .Description - Export-Excel allows a single Pivot table to be defined using the parameters -IncludePivotTable, -PivotColumns -PivotRows, - =PivotData, -PivotFilter, -NoTotalsInPivot, -PivotDataToColumn, -IncludePivotChart and -ChartType. - Its -PivotTableDefintion paramater allows multiple pivot tables to be defined, with additional parameters. - New-PivotTableDefinition is a convenient way to build these definitions. - .Example - $pt = New-PivotTableDefinition -PivotTableName "PT1" -SourceWorkSheet "Sheet1" -PivotRows "Status" -PivotData @{Status='Count' } -PivotFilter 'StartType' -IncludePivotChart -ChartType BarClustered3D - $Pt += New-PivotTableDefinition -PivotTableName "PT2" -SourceWorkSheet "Sheet2" -PivotRows "Company" -PivotData @{Company='Count'} -IncludePivotChart -ChartType PieExploded3D -ShowPercent -ChartTitle "Breakdown of processes by company" - Get-Service | Select-Object -Property Status,Name,DisplayName,StartType | Export-Excel -Path .\test.xlsx -AutoSize - Get-Process | Select-Object -Property Name,Company,Handles,CPU,VM | Export-Excel -Path .\test.xlsx -AutoSize -WorkSheetname 'sheet2' - $excel = Export-Excel -Path .\test.xlsx -PivotTableDefinition $pt -Show + <# + .Synopsis + Creates Pivot table definitons for export excel + .Description + Export-Excel allows a single Pivot table to be defined using the parameters -IncludePivotTable, -PivotColumns -PivotRows, + =PivotData, -PivotFilter, -NoTotalsInPivot, -PivotDataToColumn, -IncludePivotChart and -ChartType. + Its -PivotTableDefintion paramater allows multiple pivot tables to be defined, with additional parameters. + New-PivotTableDefinition is a convenient way to build these definitions. + .Example + $pt = New-PivotTableDefinition -PivotTableName "PT1" -SourceWorkSheet "Sheet1" -PivotRows "Status" -PivotData @{Status='Count' } -PivotFilter 'StartType' -IncludePivotChart -ChartType BarClustered3D + $Pt += New-PivotTableDefinition -PivotTableName "PT2" -SourceWorkSheet "Sheet2" -PivotRows "Company" -PivotData @{Company='Count'} -IncludePivotChart -ChartType PieExploded3D -ShowPercent -ChartTitle "Breakdown of processes by company" + Get-Service | Select-Object -Property Status,Name,DisplayName,StartType | Export-Excel -Path .\test.xlsx -AutoSize + Get-Process | Select-Object -Property Name,Company,Handles,CPU,VM | Export-Excel -Path .\test.xlsx -AutoSize -WorkSheetname 'sheet2' + $excel = Export-Excel -Path .\test.xlsx -PivotTableDefinition $pt -Show - This is a re-work of one of the examples in Export-Excel - instead of writing out the pivot definition hash table it is built by calling New-PivotTableDefinition. -#> + This is a re-work of one of the examples in Export-Excel - instead of writing out the pivot definition hash table it is built by calling New-PivotTableDefinition. + #> param( [Parameter(Mandatory)] [Alias("PivtoTableName")]#Previous typo - use alias to avoid breaking scripts @@ -1087,3 +958,254 @@ function New-PivotTableDefinition { @{$PivotTableName = $parameters} } +function Add-WorkSheet { + [cmdletBinding()] + [OutputType([OfficeOpenXml.ExcelWorksheet])] + param( + #An object representing an Excel Package. + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = "Package", Position = 0)] + [OfficeOpenXml.ExcelPackage]$ExcelPackage, + #An Excel workbook to which the Worksheet will be added - a package contains one workbook so you can use whichever fits at the time. + [Parameter(Mandatory = $true, ParameterSetName = "WorkBook")] + [OfficeOpenXml.ExcelWorkbook]$ExcelWorkbook, + #The name of the worksheet 'Sheet1' by default. + [string]$WorkSheetname = 'Sheet1', + #If the worksheet already exists, by default it will returned, unless -ClearSheet is specified in which case it will be deleted and re-created. + [switch]$ClearSheet, + #If specified, the worksheet will be moved to the start of the workbook. + #MoveToStart takes precedence over MoveToEnd, Movebefore and MoveAfter if more than one is specified. + [Switch]$MoveToStart, + #If specified, the worksheet will be moved to the end of the workbook. + #(This is the default position for newly created sheets, but this can be used to move existing sheets.) + [Switch]$MoveToEnd, + #If specified, the worksheet will be moved before the nominated one (which can be a postion starting from 1, or a name). + #MoveBefore takes precedence over MoveAfter if both are specified. + $MoveBefore , + # If specified, the worksheet will be moved after the nominated one (which can be a postion starting from 1, or a name or *). + # If * is used, the worksheet names will be examined starting with the first one, and the sheet placed after the last sheet which comes before it alphabetically. + $MoveAfter , + #If worksheet is provided as a copy source the new worksheet will be a copy of it. The source can be in the same workbook, or in a different file. + [OfficeOpenXml.ExcelWorksheet]$CopySource, + #Ignored but retained for backwards compatibility. + [Switch] $NoClobber + ) + + if ($ExcelPackage -and -not $ExcelWorkbook) {$ExcelWorkbook = $ExcelPackage.Workbook} + + $ws = $ExcelWorkbook.Worksheets[$WorkSheetname] + if ( $ws -and $ClearSheet) { $ExcelWorkbook.Worksheets.Delete($WorkSheetname) ; $ws = $null } + if (!$ws -and $CopySource) { + Write-Verbose -Message "Copying into worksheet '$WorkSheetname'." + $ws = $ExcelWorkbook.Worksheets.Add($WorkSheetname, $CopySource) + } + elseif (!$ws) { + Write-Verbose -Message "Adding worksheet '$WorkSheetname'." + $ws = $ExcelWorkbook.Worksheets.Add($WorkSheetname) + } + else {Write-Verbose -Message "Worksheet '$WorkSheetname' already existed."} + if ($MoveToStart) {$ExcelWorkbook.Worksheets.MoveToStart($worksheetName) } + elseif ($MoveToEnd ) {$ExcelWorkbook.Worksheets.MoveToEnd($worksheetName) } + elseif ($MoveBefore ) { + if ($ExcelWorkbook.Worksheets[$MoveBefore]) { + if ($MoveBefore -is [int]) { + $ExcelWorkbook.Worksheets.MoveBefore($ws.Index, $MoveBefore) + } + else {$ExcelWorkbook.Worksheets.MoveBefore($worksheetname, $MoveBefore)} + } + else {Write-Warning "Can't find worksheet '$MoveBefore'; worsheet '$WorkSheetname' will not be moved."} + } + elseif ($MoveAfter ) { + if ($MoveAfter = "*") { + if ($WorkSheetname -lt $ExcelWorkbook.Worksheets[1].Name) {$ExcelWorkbook.Worksheets.MoveToStart($worksheetName)} + else { + $i = 1 + While ($i -lt $ExcelWorkbook.Worksheets.Count -and ($ExcelWorkbook.Worksheets[$i + 1].Name -le $worksheetname) ) { $i++} + $ExcelWorkbook.Worksheets.MoveAfter($ws.Index, $i) + } + } + elseif ($ExcelWorkbook.Worksheets[$MoveAfter]) { + if ($MoveAfter -is [int]) { + $ExcelWorkbook.Worksheets.MoveAfter($ws.Index, $MoveAfter) + } + else { + $ExcelWorkbook.Worksheets.MoveAfter($worksheetname, $MoveAfter) + } + } + else {Write-Warning "Can't find worksheet '$MoveAfter'; worsheet '$WorkSheetname' will not be moved."} + } + return $ws +} +function Add-PivotTable { + + param ( + # Parameter help description + [Parameter(Mandatory = $true)] + $PivotTableName, + $ExcelPackage, + #Worksheet where the data is found + $SourceWorkSheet, + #Address range in the worksheet e.g "A10:F20" - the first row must be column names: if not specified the whole sheet will be used/ + $SourceRange, + #Fields to set as rows in the Pivot table + $PivotRows, + #A hash table in form "FieldName"="Function", where function is one of + #Average, Count, CountNums, Max, Min, Product, None, StdDev, StdDevP, Sum, Var, VarP + $PivotData, + #Fields to set as columns in the Pivot table + $PivotColumns, + #Fields to use to filter in the Pivot table + $PivotFilter, + [Switch]$PivotDataToColumn, + [Switch]$NoTotalsInPivot, + #If specified a chart Will be included. + [Switch]$IncludePivotChart, + #Optional title for the pivot chart, by default the title omitted. + [String]$ChartTitle, + #Height of the chart in Pixels (400 by default) + [int]$ChartHeight = 400 , + #Width of the chart in Pixels (600 by default) + [int]$ChartWidth = 600, + #Cell position of the top left corner of the chart, there will be this number of rows above the top edge of the chart (default is 0, chart starts at top edge of row 1). + [Int]$ChartRow = 0 , + #Cell position of the top left corner of the chart, there will be this number of cells to the left of the chart (default is 4, chart starts at left edge of column E) + [Int]$ChartColumn = 4, + #Vertical offset of the chart from the cell corner. + [Int]$ChartRowOffSetPixels = 0 , + #Horizontal offset of the chart from the cell corner. + [Int]$ChartColumnOffSetPixels = 0, + #Type of chart + [OfficeOpenXml.Drawing.Chart.eChartType]$ChartType = 'Pie', + #If specified hides the chart legend + [Switch]$NoLegend, + #if specified attaches the category to slices in a pie chart : not supported on all chart types, this may give errors if applied to an unsupported type. + [Switch]$ShowCategory, + #If specified attaches percentages to slices in a pie chart. + [Switch]$ShowPercent + ) + + $pivotTableDataName = $pivotTableName + 'PivotTableData' + [OfficeOpenXml.ExcelWorksheet]$wsPivot = Add-WorkSheet -ExcelPackage $ExcelPackage -WorkSheetname $pivotTableName + # $wsPivot.View.TabSelected = $true + + #if the pivot doesn't exist, create it. + if (-not $wsPivot.PivotTables[$pivotTableDataName] ) { + try { + #Accept a string or a worksheet object as $Source Worksheet. + if ($SourceWorkSheet -is [string]) { + $SourceWorkSheet = $ExcelPackage.Workbook.Worksheets.where( {$_.name -match $SourceWorkSheet})[0] + } + if (-not ($SourceWorkSheet -is [OfficeOpenXml.ExcelWorksheet])) {Write-Warning -Message "Could not find source Worksheet for pivot-table '$pivotTableName'." } + else { + if ($PivotFilter) {$PivotTableStartCell = "A3"} else { $PivotTableStartCell = "A1"} + if (-not $SourceRange) { $SourceRange = $SourceWorkSheet.Dimension.Address} + $pivotTable = $wsPivot.PivotTables.Add($wsPivot.Cells[$PivotTableStartCell], $SourceWorkSheet.Cells[ $SourceRange], $pivotTableDataName) + } + foreach ($Row in $PivotRows) { + try {$null = $pivotTable.RowFields.Add($pivotTable.Fields[$Row]) } + catch {Write-Warning -message "Could not add '$row' to Rows in PivotTable $pivotTableName." } + } + foreach ($Column in $PivotColumns) { + try {$null = $pivotTable.ColumnFields.Add($pivotTable.Fields[$Column])} + catch {Write-Warning -message "Could not add '$Column' to Columns in PivotTable $pivotTableName." } + } + if ($PivotData -is [HashTable] -or $PivotData -is [System.Collections.Specialized.OrderedDictionary]) { + $PivotData.Keys | ForEach-Object { + try { + $df = $pivotTable.DataFields.Add($pivotTable.Fields[$_]) + $df.Function = $PivotData.$_ + } + catch {Write-Warning -message "Problem adding data fields to PivotTable $pivotTableName." } + } + } + else { + foreach ($field in $PivotData) { + try { + $df = $pivotTable.DataFields.Add($pivotTable.Fields[$field]) + $df.Function = 'Count' + } + catch {Write-Warning -message "Problem adding data field '$field' to PivotTable $pivotTableName." } + } + } + foreach ( $pFilter in $PivotFilter) { + try { $null = $pivotTable.PageFields.Add($pivotTable.Fields[$pFilter])} + catch {Write-Warning -message "Could not add '$pFilter' to Filter/Page fields in PivotTable $pivotTableName." } + } + if ($NoTotalsInPivot) { $pivotTable.RowGrandTotals = $false } + if ($PivotDataToColumn ) { $pivotTable.DataOnRows = $false } + } + catch {Write-Warning -Message "Failed adding PivotTable '$pivotTableName': $_"} + } + else { + Write-Warning -Message "Pivot table defined in $($pivotTableName) already exists, only the data range will be changed." + $pivotTable = $wsPivot.PivotTables[$pivotTableDataName] + $pivotTable.CacheDefinition.CacheDefinitionXml.pivotCacheDefinition.cacheSource.worksheetSource.ref = $SourceRange + } + + #Create the chart if it doesn't exist, leave alone if it does. + if ($IncludePivotChart -and -not $wsPivot.Drawings['PivotChart'] ) { + try { + [OfficeOpenXml.Drawing.Chart.ExcelChart] $chart = $wsPivot.Drawings.AddChart('PivotChart', $ChartType, $pivotTable) + $chart.SetPosition($ChartRow , $ChartRowOffSetPixels , $ChartColumn, $ChartColumnOffSetPixels) + $chart.SetSize( $ChartWidth, $ChartHeight) + if ($chart.DataLabel) { + $chart.DataLabel.ShowCategory = [boolean]$ShowCategory + $chart.DataLabel.ShowPercent = [boolean]$ShowPercent + } + if ($NoLegend) { $chart.Legend.Remove()} + if ($ChartTitle) {$chart.Title.Text = $ChartTitle} + } + catch { catch {Write-Warning -Message "Failed adding chart for pivotable '$pivotTableName': $_"} + } + + } +} +function Add-ExcelChart { + param( + $Worksheet, + $Title = "Chart Title", + $Header, + [OfficeOpenXml.Drawing.Chart.eChartType]$ChartType = "ColumnStacked", + $XRange, + $YRange, + $Width = 500, + $Height = 350, + $Row = 0, + $RowOffSetPixels = 10, + $Column = 6, + $ColumnOffSetPixels = 5, + [Switch]$NoLegend, + [Switch]$ShowCategory, + [Switch]$ShowPercent, + $SeriesHeader + ) + try { + $ChartName = 'Chart' + (Split-Path -Leaf ([System.IO.path]::GetTempFileName())) -replace 'tmp|\.', '' + $chart = $Worksheet.Drawings.AddChart($ChartName, $ChartType) + $chart.Title.Text = $Title + + if ($NoLegend) { $chart.Legend.Remove() } + + if ($chart.Datalabel -ne $null) { + $chart.Datalabel.ShowCategory = [boolean]$ShowCategory + $chart.Datalabel.ShowPercent = [boolean]$ShowPercent + } + + $chart.SetPosition($Row, $RowOffsetPixels, $Column, $ColumnOffsetPixels) + $chart.SetSize($Width, $Height) + + $chartDefCount = @($YRange).Count + if ($chartDefCount -eq 1) { + $Series = $chart.Series.Add($YRange, $XRange) + if ($SeriesHeader) { $Series.Header = $SeriesHeader} + else { $Series.Header = 'Series 1'} + } + else { + for ($idx = 0; $idx -lt $chartDefCount; $idx += 1) { + $Series = $chart.Series.Add($YRange[$idx], $XRange) + if ($SeriesHeader.Count -gt 0) { $Series.Header = $SeriesHeader[$idx] } + else { $Series.Header = "Series $($idx)"} + } + } + } + catch {Write-Warning -Message "Failed adding Chart to worksheet '$($WorkSheet).name': $_"} +} \ No newline at end of file diff --git a/ImportExcel.psm1 b/ImportExcel.psm1 index 9789da6..8db1908 100644 --- a/ImportExcel.psm1 +++ b/ImportExcel.psm1 @@ -422,84 +422,6 @@ function Import-Excel { } } -function Add-WorkSheet { - [cmdletBinding()] - [OutputType([OfficeOpenXml.ExcelWorksheet])] - param( - #An object representing an Excel Package. - [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName = "Package", Position=0)] - [OfficeOpenXml.ExcelPackage]$ExcelPackage, - #An Excel workbook to which the Worksheet will be added - a package contains one workbook so you can use whichever fits at the time. - [Parameter(Mandatory=$true, ParameterSetName = "WorkBook")] - [OfficeOpenXml.ExcelWorkbook]$ExcelWorkbook, - #The name of the worksheet 'Sheet1' by default. - [string]$WorkSheetname = 'Sheet1', - #If the worksheet already exists, by default it will returned, unless -ClearSheet is specified in which case it will be deleted and re-created. - [switch]$ClearSheet, - #If specified, the worksheet will be moved to the start of the workbook. - #MoveToStart takes precedence over MoveToEnd, Movebefore and MoveAfter if more than one is specified. - [Switch]$MoveToStart, - #If specified, the worksheet will be moved to the end of the workbook. - #(This is the default position for newly created sheets, but this can be used to move existing sheets.) - [Switch]$MoveToEnd, - #If specified, the worksheet will be moved before the nominated one (which can be a postion starting from 1, or a name). - #MoveBefore takes precedence over MoveAfter if both are specified. - $MoveBefore , - # If specified, the worksheet will be moved after the nominated one (which can be a postion starting from 1, or a name or *). - # If * is used, the worksheet names will be examined starting with the first one, and the sheet placed after the last sheet which comes before it alphabetically. - $MoveAfter , - #If worksheet is provided as a copy source the new worksheet will be a copy of it. The source can be in the same workbook, or in a different file. - [OfficeOpenXml.ExcelWorksheet]$CopySource, - #Ignored but retained for backwards compatibility. - [Switch] $NoClobber - ) - - if ($ExcelPackage -and -not $ExcelWorkbook) {$ExcelWorkbook = $ExcelPackage.Workbook} - - $ws = $ExcelWorkbook.Worksheets[$WorkSheetname] - if( $ws -and $ClearSheet) { $ExcelWorkbook.Worksheets.Delete($WorkSheetname) ; $ws = $null } - if(!$ws -and $CopySource) { - Write-Verbose -Message "Copying into worksheet '$WorkSheetname'." - $ws=$ExcelWorkbook.Worksheets.Add($WorkSheetname, $CopySource) - } - elseif(!$ws) { - Write-Verbose -Message "Adding worksheet '$WorkSheetname'." - $ws=$ExcelWorkbook.Worksheets.Add($WorkSheetname) - } - else {Write-Verbose -Message "Worksheet '$WorkSheetname' already existed."} - if ($MoveToStart) {$ExcelWorkbook.Worksheets.MoveToStart($worksheetName) } - elseif ($MoveToEnd ) {$ExcelWorkbook.Worksheets.MoveToEnd($worksheetName) } - elseif ($MoveBefore ) { - if ($ExcelWorkbook.Worksheets[$MoveBefore]) { - if ($MoveBefore -is [int]) { - $ExcelWorkbook.Worksheets.MoveBefore($ws.Index, $MoveBefore) - } - else {$ExcelWorkbook.Worksheets.MoveBefore($worksheetname, $MoveBefore)} - } - else {Write-Warning "Can't find worksheet '$MoveBefore'; worsheet '$WorkSheetname' will not be moved."} - } - elseif ($MoveAfter ) { - if ($MoveAfter = "*") { - if ($WorkSheetname -lt $ExcelWorkbook.Worksheets[1].Name) {$ExcelWorkbook.Worksheets.MoveToStart($worksheetName)} - else { - $i = 1 - While ($i -lt $ExcelWorkbook.Worksheets.Count -and $ExcelWorkbook.Worksheets[$i + 1].Name -lt $worksheetname ) { $i++} - $ExcelWorkbook.Worksheets.MoveAfter($ws.Index, $i) - } - } - elseif ($ExcelWorkbook.Worksheets[$MoveAfter]) { - if ($MoveAfter -is [int]) { - $ExcelWorkbook.Worksheets.MoveAfter($ws.Index, $MoveAfter) - } - else { - $ExcelWorkbook.Worksheets.MoveAfter($worksheetname, $MoveAfter) - } - } - else {Write-Warning "Can't find worksheet '$MoveAfter'; worsheet '$WorkSheetname' will not be moved."} - } - return $ws -} - function ConvertFrom-ExcelSheet { <# .Synopsis diff --git a/Old_Export-Excel.Tests.ps1 b/Old_Export-Excel.Tests.ps1 new file mode 100644 index 0000000..62db210 --- /dev/null +++ b/Old_Export-Excel.Tests.ps1 @@ -0,0 +1,94 @@ +#Requires -Modules Pester + + +$here = Split-Path -Parent $MyInvocation.MyCommand.Path + + +Import-Module $here -Force + +$WarningPreference = 'SilentlyContinue' +$ProgressPreference = 'SilentlyContinue' + +Function Test-isNumeric { + Param ( + [Parameter(ValueFromPipeline)]$x + ) + + Return $x -is [byte] -or $x -is [int16] -or $x -is [int32] -or $x -is [int64] ` + -or $x -is [sbyte] -or $x -is [uint16] -or $x -is [uint32] -or $x -is [uint64] ` + -or $x -is [float] -or $x -is [double] -or $x -is [decimal] +} + +$fakeData = [PSCustOmobject]@{ + Property_1_Date = (Get-Date).ToString('d') # US '10/16/2017' BE '16/10/2107' + Property_2_Formula = '=SUM(G2:H2)' + Property_3_String = 'My String' + Property_4_String = 'a' + Property_5_IPAddress = '10.10.25.5' + Property_6_Number = '0' + Property_7_Number = '5' + Property_8_Number = '007' + Property_9_Number = (33).ToString('F2') # US '33.00' BE '33,00' + Property_10_Number = (5/3).ToString('F2') # US '1.67' BE '1,67' + Property_11_Number = (15999998/3).ToString('N2') # US '5,333,332.67' BE '5.333.332,67' + Property_12_Number = '1.555,83' + Property_13_PhoneNr = '+32 44' + Property_14_PhoneNr = '+32 4 4444 444' + Property_15_PhoneNr = '+3244444444' +} + +$Path = 'Test.xlsx' + +Describe 'Export-Excel' { + in $TestDrive { + Describe 'Number conversion' { + Context 'numerical values expected' { + #region Create test file + $fakeData | Export-Excel -Path $Path + + $Path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) + $Excel = New-Object OfficeOpenXml.ExcelPackage $Path + $Worksheet = $Excel.Workbook.WorkSheets[1] + #endregion + + it 'zero' { + $fakeData.Property_6_Number | Should -BeExactly '0' + $Worksheet.Cells[2, 6].Text | Should -BeExactly $fakeData.Property_6_Number + $Worksheet.Cells[2, 6].Value | Test-isNumeric | Should -Be $true + } + + It 'regular number' { + $fakeData.Property_7_Number | Should -BeExactly '5' + $Worksheet.Cells[2, 7].Text | Should -BeExactly $fakeData.Property_7_Number + $Worksheet.Cells[2, 7].Value | Test-isNumeric | Should -Be $true + } + + It 'number starting with zero' { + $fakeData.Property_8_Number | Should -BeExactly '007' + $Worksheet.Cells[2, 8].Text | Should -BeExactly '7' + $Worksheet.Cells[2, 8].Value | Test-isNumeric | Should -Be $true + } + + It 'decimal number' { + # US '33.00' BE '33,00' + $fakeData.Property_9_Number | Should -BeExactly (33).ToString('F2') + $Worksheet.Cells[2, 9].Text | Should -BeExactly '33' + $Worksheet.Cells[2, 9].Value | Test-isNumeric | Should -Be $true + + # US '1.67' BE '1,67' + $fakeData.Property_10_Number | Should -BeExactly (5/3).ToString('F2') + $Worksheet.Cells[2, 10].Text | Should -BeExactly $fakeData.Property_10_Number + $Worksheet.Cells[2, 10].Value | Test-isNumeric | Should -Be $true + } + + It 'thousand seperator and decimal number' { + # US '5,333,332.67' BE '5.333.332,67' + # Excel BE '5333332,67' + $fakeData.Property_11_Number | Should -BeExactly (15999998/3).ToString('N2') + $Worksheet.Cells[2, 11].Text | Should -BeExactly $fakeData.Property_11_Number + $Worksheet.Cells[2, 11].Value | Test-isNumeric | Should -Be $true + } + } + } + } +} \ No newline at end of file diff --git a/Open-ExcelPackage.ps1 b/Open-ExcelPackage.ps1 index 00dc7ba..49a77f2 100644 --- a/Open-ExcelPackage.ps1 +++ b/Open-ExcelPackage.ps1 @@ -24,7 +24,7 @@ Get-Process -Name "excel" -ErrorAction Ignore | Stop-Process while (Get-Process -Name "excel" -ErrorAction Ignore) {} } - + $Path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) #If -Create was not specified only open the file if it exists already (send a warning if it doesn't exist). if ($Create) { diff --git a/README.md b/README.md index aefb734..4eb9bc3 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,43 @@ iex (new-object System.Net.WebClient).DownloadString('https://raw.github.com/dfi # What's new +- New commands - Diff , Merge and Join + - `Compare-Worksheet` (introduced in 5.0) uses the built in `Compare-object` command, to output a command-line DIFF and/or colour the worksheet to show differences. For example, if my sheets are Windows services the *extra* rows or rows where the startup status has changed get highlighted + - `Merge-Worksheet` (also introduced in 5.0) joins two lumps, side by highlighting the differences. So now I can have server A's services and Server Bs Services on the same page. I figured out a way to do multiple sheets. So I can have Server A,B,C,D on one page :-) that is `Merge-MultpleSheets` + For this release I've fixed heaven only knows how many typos and proof reading errors in the help for these two, but the code is unchanged - although correcting the spelling of Merge-MultipleSheets is potentially a breaking change (and it is still plural!) + - `Join-Worksheet` is **new** for ths release. At it's simplest it copies all the data in Worksheet A to the end of Worksheet B +- Add-Worksheet + - I have moved this from ImportExcel.psm1 to ExportExcel.ps1 and it now can move a new worksheet to the right place, and can copy an existing worksheet (from the same or a different workbook) to a new one, and I set the Set return-type to aid intellisense +- New-PivotTableDefinition + - Now Supports `-PivotFilter` and `-PivotDataToColumn`, `-ChartHeight/width` `-ChartRow/Column`, `-ChartRow/ColumnPixelOffset` parameters +- Set-Format + - Fixed a bug where the `-address` parameter had to be named, although the examples in `export-excel` help showed it working by position (which works now. ) +- Export-Excel + - I've done some re-factoring + 1. I "flattened out" small "called-once" functions , add-title, convert-toNumber and Stop-ExcelProcess. + 2. It now uses Add-Worksheet, Open-ExcelPackage and Add-ConditionalFormat instead of duplicating their functionality. + 3. I've moved the PivotTable functionality (which was doubled up) out to a new function "Add-PivotTable" which supports some extra parameters PivotFilter and PivotDataToColumn, ChartHeight/width ChartRow/Column, ChartRow/ColumnPixelOffsets. + 4. I've made the try{} catch{} blocks cover smaller blocks of code to give a better idea where a failure happend, some of these now Warn instead of throwing - I'd rather save the data with warnings than throw it away because we can't add a chart. Along with this I've added some extra write-verbose messages + - Bad column-names specified for Pivots now generate warnings instead of throwing. + - Fixed issues when pivottables / charts already exist and an export tries to create them again. + - Fixed issue where AutoNamedRange, NamedRange, and TableName do not work when appending to a sheet which already contains the range(s) / table + - Fixed issue where AutoNamedRange may try to create ranges with an illegal name. + - Added check for illegal characters in RangeName or Table Name (replace them with "_"), changed tablename validation to allow spaces and applied same validation to RangeName + - Fixed a bug where BoldTopRow is always bolds row 1 even if the export is told to start at a lower row. + - Fixed a bug where titles throw pivot table creation out of alignment. + - Fixed a bug where Append can overwrite the last rows of data if the initial export had blank rows at the top of the sheet. + - Removed the need to specify a fill type when specifying a title background color + - Added MoveToStart, MoveToEnd, MoveBefore and MoveAfter Parameters - these go straight through to Add worksheet + - Added "NoScriptOrAliasProperties" "DisplayPropertySet" switches (names subject to change) - combined with ExcludeProperty these are a quick way to reduce the data exported (and speed things up) + - Add-CellValue now understands URI item properties. If a property is of type URI it is created as a hyperlink to speed up Add-CellValue + - Commented out the write verbose statements even if verbose is silenced they cause a significiant performance impact and if it's on they will cause a flood of messages. + - Re-ordered the choices in the switch and added an option to say "If it is numeric already post it as is" + - Added an option to only set the number format if doesn't match the default for the sheet. +-Export-Excel Pester Tests + - I have converted examples 1-9, 11 and 13 from Export-Excel help into tests and have added some additional tests, and extra parameters to the example command to ge better test coverage. The test so far has 184 "should" conditions grouped as 58 "IT" statements; but is still a work in progress. +--- + + - [James O'Neill](https://twitter.com/jamesoneill) added `Compare-Worksheet` - Compares two worksheets with the same name in different files. diff --git a/SetFormat.ps1 b/SetFormat.ps1 index 3c24aa4..d4a1928 100644 --- a/SetFormat.ps1 +++ b/SetFormat.ps1 @@ -12,7 +12,7 @@ #> Param ( #One or more row(s), Column(s) and/or block(s) of cells to format - [Parameter(ValueFromPipeline = $true,ParameterSetName="Address",Mandatory=$True)] + [Parameter(ValueFromPipeline = $true,ParameterSetName="Address",Mandatory=$True,Position=0)] $Address , #The worksheet where the format is to be applied [Parameter(ParameterSetName="SheetAndRange",Mandatory=$True)]