From bd40cfe829d6128ac39296805ed5c41ff424bc0f Mon Sep 17 00:00:00 2001 From: jhoneill Date: Sat, 21 Oct 2017 18:14:02 +0100 Subject: [PATCH 1/3] Append, and formatting Changed Export-Excel.ps1 #1 @ Line 197 Made new parameter sets . Default, and table already existed and use path. Added DefaultPackage and TablePackage A New parameter "Package" allows an ExcelPackage object returned by -passThru to be passed in ~Line 400 code to use package or path depending on path passed. (also added Open-ExcelPackage to get the object without exporting and Close-ExcelPackage to close it nicely - these are in their own file) #2. @ Line 256 added new parameter excludeProperty to remove unwanted properties without needing to go through select-object ~Line 459 added logic to exclude the properties specified in the new parameter #3 . @ Line 262 Added new parameter Append ~Line 420 code to read the existing headers and move the insertion point below the current data (normal behaviour is to check if headers exist when adding data in the process block, which makes this change wonderfully easy) ~Line 510 changed basis for identifying named ranges and changed scope for rangeName so it can be used on other sheets #4. ~Line 550. Remove any existing Pivot table before trying to [re]create it. Added formatting.ps1 which applies conditional and normal formats - requires an ExcelPackage to be open. Added Open-ExcelPackage.ps1 (which contains a close function as well to get the the object and save it ) open allows the sheet to be loaded into a package object without needing to export . Updated .psm1 to add the formating and open/close ps1 files. --- Export-Excel.ps1 | 77 +++++++++++----- ImportExcel.psm1 | 31 ++++--- Open-ExcelPackage.ps1 | 50 +++++++++++ formatting.ps1 | 205 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 328 insertions(+), 35 deletions(-) create mode 100644 Open-ExcelPackage.ps1 create mode 100644 formatting.ps1 diff --git a/Export-Excel.ps1 b/Export-Excel.ps1 index 28bfeaa..59532b0 100644 --- a/Export-Excel.ps1 +++ b/Export-Excel.ps1 @@ -1,4 +1,4 @@ -Function Export-Excel { +Function Export-Excel { <# .SYNOPSIS Export data to an Excel worksheet. @@ -196,8 +196,13 @@ Function Export-Excel { [CmdletBinding(DefaultParameterSetName = 'Default')] Param( - $Path, - [Parameter(ValueFromPipeline = $true)] + [Parameter(Mandatory=$true,ParameterSetName="Default",Position=0)] + [Parameter(Mandatory=$true,ParameterSetName="Table" ,Position=0)] + [String]$Path, + [Parameter(Mandatory=$true,ParameterSetName="PackageDefault")] + [Parameter(Mandatory=$true,ParameterSetName="PackageTable")] + [OfficeOpenXml.ExcelPackage]$ExcelPackage, + [Parameter(ValueFromPipeline=$true)] $TargetData, [String]$WorkSheetname = 'Sheet1', [String]$Title, @@ -225,6 +230,7 @@ Function Export-Excel { [Switch]$FreezeTopRowFirstColumn, [Int[]]$FreezePane, [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'PackageDefault')] [Switch]$AutoFilter, [Switch]$BoldTopRow, [Switch]$NoHeader, @@ -244,8 +250,10 @@ Function Export-Excel { } })] [Parameter(ParameterSetName = 'Table')] + [Parameter(ParameterSetName = 'PackageTable')] [String]$TableName, [Parameter(ParameterSetName = 'Table')] + [Parameter(ParameterSetName = 'PackageTable')] [OfficeOpenXml.Table.TableStyles]$TableStyle = 'Medium6', [Object[]]$ExcelChartDefinition, [String[]]$HideSheet, @@ -255,11 +263,13 @@ Function Export-Excel { [Int]$StartColumn = 1, [Switch]$PassThru, [String]$Numberformat = 'General', + [string[]]$ExcludeProperty, [String[]]$NoNumberConversion, [Object[]]$ConditionalFormat, [Object[]]$ConditionalText, [ScriptBlock]$CellStyleSB, - [Switch]$Now + [Switch]$Now, + [switch]$Append ) Begin { @@ -354,7 +364,7 @@ Function Export-Excel { if ($TitleBackgroundColor -AND ($TitleFillPattern -ne 'None')) { $ws.Cells[$Row, $StartColumn].Style.Fill.BackgroundColor.SetColor($TitleBackgroundColor) } - else { + elseif ($TitleBackgroundColor) { Write-Warning "Title Background Color ignored. You must set the TitleFillPattern parameter to a value other than 'None'. Try 'Solid'." } } @@ -403,35 +413,52 @@ Function Export-Excel { } } - $Path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) + if ($ExcelPackage) { + $pkg = $ExcelPackage + $Path = $pkg.File + } + Else { + $Path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) - if (Test-Path $Path) { - Write-Debug "Path '$Path' already exists" - } - - $pkg = New-Object OfficeOpenXml.ExcelPackage $Path - $ws = $pkg | Add-WorkSheet -WorkSheetname $WorkSheetname -NoClobber:$NoClobber + if (Test-Path $Path) { + Write-Debug "Path '$Path' already exists" + } + $pkg = New-Object OfficeOpenXml.ExcelPackage $Path + } + + [OfficeOpenXml.ExcelWorksheet]$ws = $pkg | Add-WorkSheet -WorkSheetname $WorkSheetname -NoClobber:$NoClobber + foreach ($format in $ConditionalFormat ) { $target = "Add$($format.Formatter)" $rule = ($ws.ConditionalFormatting).PSObject.Methods[$target].Invoke($format.Range, $format.IconType) $rule.Reverse = $format.Reverse } - $Row = $StartRow - - if ($Title) { - Add-Title - - $Row += 1 + if ($append) { + $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 + $row = $ws.Dimension.Rows + Write-Debug -Message ("Appending: headers are " + ($script:Header -join ", ") + "Start row $row") } - + elseif($Title) { #Can only add a title if not appending + $Row = $StartRow + Add-Title + $Row ++ + } + else { + $Row = $StartRow + + } + $ColumnIndex = $StartColumn $firstTimeThru = $true $isDataTypeValueType = $false $pattern = 'string|bool|byte|char|decimal|double|float|int|long|sbyte|short|uint|ulong|ushort' } Catch { - if ($AlreadyExists) { + if ($AlreadyExists) { #Is this set anywhere ? throw "Failed exporting worksheet '$WorkSheetname' to '$Path': The worksheet '$WorkSheetname' already exists." } else { @@ -460,7 +487,7 @@ Function Export-Excel { #region Add headers if (-not $script:Header) { $ColumnIndex = $StartColumn - $script:Header = $TargetData.PSObject.Properties.Name + $script:Header = $TargetData.PSObject.Properties.Name | Where-Object {$_ -notin $ExcludeProperty} if ($NoHeader) { # Don't push the headers to the spread sheet @@ -536,7 +563,9 @@ Function Export-Excel { $cer = $ws.Dimension.End.Row $cec = $script:Header.Count - $targetRange = $ws.Cells[$csr, $csc, $cer, $cec] + $targetRange = $ws.Cells[$csr, $csc, $cer, $cec] + #if we're appending data the table may already exist: but excel doesn't like the result if I put + # if ($ws.Tables[$TableName]) {$ws.Tables.Delete($TableName) } $tbl = $ws.Tables.Add($targetRange, $TableName) $tbl.TableStyle = $TableStyle } @@ -545,6 +574,8 @@ Function Export-Excel { foreach ($item in $PivotTableDefinition.GetEnumerator()) { $targetName = $item.Key $pivotTableName = $targetName #+ 'PivotTable' + #Make sure the Pivot table sheet doesn't already exist + try { $pkg.Workbook.Worksheets.Delete( $pivotTableName) } catch {} $wsPivot = $pkg | Add-WorkSheet -WorkSheetname $pivotTableName -NoClobber:$NoClobber $pivotTableDataName = $targetName + 'PivotTableData' @@ -611,6 +642,8 @@ Function Export-Excel { if ($IncludePivotTable) { $pivotTableName = $WorkSheetname + 'PivotTable' + #Make sure the Pivot table sheet doesn't already exist + try { $pkg.Workbook.Worksheets.Delete( $pivotTableName) } catch {} $wsPivot = $pkg | Add-WorkSheet -WorkSheetname $pivotTableName -NoClobber:$NoClobber $wsPivot.View.TabSelected = $true diff --git a/ImportExcel.psm1 b/ImportExcel.psm1 index 67552ed..14e47e7 100644 --- a/ImportExcel.psm1 +++ b/ImportExcel.psm1 @@ -1,4 +1,4 @@ -Add-Type -Path "$($PSScriptRoot)\EPPlus.dll" +Add-Type -Path "$($PSScriptRoot)\EPPlus.dll" . $PSScriptRoot\Charting.ps1 . $PSScriptRoot\ConvertFromExcelData.ps1 @@ -7,6 +7,7 @@ Add-Type -Path "$($PSScriptRoot)\EPPlus.dll" . $PSScriptRoot\Copy-ExcelWorkSheet.ps1 . $PSScriptRoot\Export-Excel.ps1 . $PSScriptRoot\Export-ExcelSheet.ps1 +. $PSScriptRoot\Formatting.ps1 . $PSScriptRoot\Get-ExcelColumnName.ps1 . $PSScriptRoot\Get-ExcelSheetInfo.ps1 . $PSScriptRoot\Get-ExcelWorkbookInfo.ps1 @@ -20,16 +21,20 @@ Add-Type -Path "$($PSScriptRoot)\EPPlus.dll" . $PSScriptRoot\New-ConditionalText.ps1 . $PSScriptRoot\New-ExcelChart.ps1 . $PSScriptRoot\New-PSItem.ps1 +. $PSScriptRoot\Open-ExcelPackage.ps1 . $PSScriptRoot\Pivot.ps1 . $PSScriptRoot\Set-CellStyle.ps1 . $PSScriptRoot\TrackingUtils.ps1 . $PSScriptRoot\Update-FirstObjectProperties.ps1 + if ($PSVersionTable.PSVersion.Major -ge 5) { . $PSScriptRoot\Plot.ps1 Function New-Plot { + [OutputType([PSPlot])] + Param() [PSPlot]::new() } @@ -46,11 +51,11 @@ Function Import-Excel { Create custom objects from the rows in an Excel worksheet. .DESCRIPTION - The Import-Excel cmdlet creates custom objects from the rows in an Excel worksheet. Each row represents one object. All of this is possible without installing Microsoft Excel and by using the .NET library ‘EPPLus.dll’. + The Import-Excel cmdlet creates custom objects from the rows in an Excel worksheet. Each row represents one object. All of this is possible without installing Microsoft Excel and by using the .NET library ‘EPPLus.dll’. By default, the property names of the objects are retrieved from the column headers. Because an object cannot have a blanc property name, only columns with column headers will be imported. - If the default behavior is not desired and you want to import the complete worksheet ‘as is’, the parameter ‘-NoHeader’ can be used. In case you want to provide your own property names, you can use the parameter ‘-HeaderName’. + If the default behavior is not desired and you want to import the complete worksheet ‘as is’, the parameter ‘-NoHeader’ can be used. In case you want to provide your own property names, you can use the parameter ‘-HeaderName’. .PARAMETER Path Specifies the path to the Excel file. @@ -71,18 +76,18 @@ Function Import-Excel { .PARAMETER NoHeader Automatically generate property names (P1, P2, P3, ..) instead of the ones defined in the column headers of the TopRow. - This switch is best used when you want to import the complete worksheet ‘as is’ and are not concerned with the property names. + This switch is best used when you want to import the complete worksheet ‘as is’ and are not concerned with the property names. .PARAMETER StartRow The row from where we start to import data, all rows above the StartRow are disregarded. By default this is the first row. - When the parameters ‘-NoHeader’ and ‘-HeaderName’ are not provided, this row will contain the column headers that will be used as property names. When one of both parameters are provided, the property names are automatically created and this row will be treated as a regular row containing data. + When the parameters ‘-NoHeader’ and ‘-HeaderName’ are not provided, this row will contain the column headers that will be used as property names. When one of both parameters are provided, the property names are automatically created and this row will be treated as a regular row containing data. .PARAMETER Password Accepts a string that will be used to open a password protected Excel file. .EXAMPLE - Import data from an Excel worksheet. One object is created for each row. The property names of the objects consist of the column names defined in the first row. In case a column doesn’t have a column header (usually in row 1 when ‘-StartRow’ is not used), then the unnamed columns will be skipped and the data in those columns will not be imported. + Import data from an Excel worksheet. One object is created for each row. The property names of the objects consist of the column names defined in the first row. In case a column doesn’t have a column header (usually in row 1 when ‘-StartRow’ is not used), then the unnamed columns will be skipped and the data in those columns will not be imported. ---------------------------------------------- | File: Movies.xlsx - Sheet: Actors | @@ -104,7 +109,7 @@ Function Import-Excel { Notice that column 'B' is not imported because there's no value in cell 'B1' that can be used as property name for the objects. .EXAMPLE - Import the complete Excel worksheet ‘as is’ by using the ‘-NoHeader’ switch. One object is created for each row. The property names of the objects will be automatically generated (P1, P2, P3, ..). + Import the complete Excel worksheet ‘as is’ by using the ‘-NoHeader’ switch. One object is created for each row. The property names of the objects will be automatically generated (P1, P2, P3, ..). ---------------------------------------------- | File: Movies.xlsx - Sheet: Actors | @@ -132,7 +137,7 @@ Function Import-Excel { Notice that the column header (row 1) is imported as an object too. .EXAMPLE - Import data from an Excel worksheet. One object is created for each row. The property names of the objects consist of the names defined in the parameter ‘-HeaderName’. The properties are named starting from the most left column (A) to the right. In case no value is present in one of the columns, that property will have an empty value. + Import data from an Excel worksheet. One object is created for each row. The property names of the objects consist of the names defined in the parameter ‘-HeaderName’. The properties are named starting from the most left column (A) to the right. In case no value is present in one of the columns, that property will have an empty value. ---------------------------------------------------------- | File: Movies.xlsx - Sheet: Movies | @@ -169,7 +174,7 @@ Function Import-Excel { Notice that empty rows are imported and that data for the property 'Genre' is not present in the worksheet. As such, the 'Genre' property will be blanc for all objects. .EXAMPLE - Import data from an Excel worksheet. One object is created for each row. The property names of the objects are automatically generated by using the switch ‘-NoHeader’ (P1, P@, P#, ..). The switch ‘-DataOnly’ will speed up the import because empty rows and empty columns are not imported. + Import data from an Excel worksheet. One object is created for each row. The property names of the objects are automatically generated by using the switch ‘-NoHeader’ (P1, P@, P#, ..). The switch ‘-DataOnly’ will speed up the import because empty rows and empty columns are not imported. ---------------------------------------------------------- | File: Movies.xlsx - Sheet: Movies | @@ -181,7 +186,7 @@ Function Import-Excel { |4 Skyfall 2012 9 | ---------------------------------------------------------- - PS C:\> Import-Excel -Path 'C:\Movies.xlsx' -WorkSheetname Movies –NoHeader -DataOnly + PS C:\> Import-Excel -Path 'C:\Movies.xlsx' -WorkSheetname Movies –NoHeader -DataOnly P1: The Bodyguard P2: 1992 @@ -198,7 +203,7 @@ Function Import-Excel { Notice that empty rows and empty columns are not imported. .EXAMPLE - Import data from an Excel worksheet. One object is created for each row. The property names are provided with the ‘-HeaderName’ parameter. The import will start from row 2 and empty columns and rows are not imported. + Import data from an Excel worksheet. One object is created for each row. The property names are provided with the ‘-HeaderName’ parameter. The import will start from row 2 and empty columns and rows are not imported. ---------------------------------------------------------- | File: Movies.xlsx - Sheet: Actors | @@ -209,13 +214,13 @@ Function Import-Excel { |3 Jean-Claude Vandamme Brussels | ---------------------------------------------------------- - PS C:\> Import-Excel -Path 'C:\Movies.xlsx' -WorkSheetname Actors -DataOnly -HeaderName 'FirstName', 'SecondName', 'City' –StartRow 2 + PS C:\> Import-Excel -Path 'C:\Movies.xlsx' -WorkSheetname Actors -DataOnly -HeaderName 'FirstName', 'SecondName', 'City' –StartRow 2 FirstName : Jean-Claude SecondName: Vandamme City : Brussels - Notice that only 1 object is imported with only 3 properties. Column B and row 2 are empty and have been disregarded by using the switch '-DataOnly'. The property names have been named with the values provided with the parameter '-HeaderName'. Row number 1 with ‘Chuck Norris’ has not been imported, because we started the import from row 2 with the parameter ‘-StartRow 2’. + Notice that only 1 object is imported with only 3 properties. Column B and row 2 are empty and have been disregarded by using the switch '-DataOnly'. The property names have been named with the values provided with the parameter '-HeaderName'. Row number 1 with ‘Chuck Norris’ has not been imported, because we started the import from row 2 with the parameter ‘-StartRow 2’. .LINK https://github.com/dfinke/ImportExcel diff --git a/Open-ExcelPackage.ps1 b/Open-ExcelPackage.ps1 new file mode 100644 index 0000000..a60a6f2 --- /dev/null +++ b/Open-ExcelPackage.ps1 @@ -0,0 +1,50 @@ +Function Open-ExcelPackage { +<# +.Synopsis + Returns an Excel Package Object with for the specified XLSX ile +.Example + $excel = Open-ExcelPackage -path $xlPath + $sheet1 = $excel.Workbook.Worksheets["sheet1"] + set-Format -Address $sheet1.Cells["E1:S1048576"], $sheet1.Cells["V1:V1048576"] -NFormat ([cultureinfo]::CurrentCulture.DateTimeFormat.ShortDatePattern) + close-ExcelPackage $excel -Show + + This will open the file at $xlPath, select sheet1 apply formatting to two blocks of the sheet and close the package +#> + [OutputType([OfficeOpenXml.ExcelPackage])] + Param ([Parameter(Mandatory=$true)]$path, + [switch]$KillExcel) + + if($KillExcel) { + Get-Process -Name "excel" -ErrorAction Ignore | Stop-Process + while (Get-Process -Name "excel" -ErrorAction Ignore) {} + } + + $Path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) + if (Test-Path $path) {New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList $Path } + Else {Write-Warning "Could not find $path" } + } + +Function Close-ExcelPackage { +<# +.Synopsis + Closes an Excel Package, saving, saving under a new name or abandoning changes and opening the file as required +#> + Param ( + #File to close + [parameter(Mandatory=$true, ValueFromPipeline=$true)] + [OfficeOpenXml.ExcelPackage]$ExcelPackage, + #Open the file + [switch]$Show + #Abandon the file without saving + [Switch]$NoSave, + #Save file with a new name (ignored if -NoSaveSpecified) + $SaveAs, + ) + if ( $NoSave) {$ExcelPackage.Dispose()} + else { + if ($SaveAs) {$ExcelPackage.SaveAs( $SaveAs ) } + Else {$ExcelPackage.Save(); $SaveAs = $ExcelPackage.File.FullName } + $ExcelPackage.Dispose() + if ($show) {Start-Process -FilePath $SaveAs } + } +} \ No newline at end of file diff --git a/formatting.ps1 b/formatting.ps1 new file mode 100644 index 0000000..ed3bde7 --- /dev/null +++ b/formatting.ps1 @@ -0,0 +1,205 @@ +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 + +Add-ConditionalFormatting -WorkSheet $excel.Workbook.Worksheets[1] -Address "b":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 + Fixed formats are then applied to dates in columns D..G and the top row is formatted + Finally the workbook is saved and the Excel closed. + +#> + Param ( + [OfficeOpenXml.ExcelWorksheet]$WorkSheet , + [OfficeOpenXml.ExcelAddress]$Range , + [Parameter(Mandatory=$true,ParameterSetName="NamedRule",Position=3)] + [OfficeOpenXml.ConditionalFormatting.eExcelConditionalFormattingRuleType]$RuleType , + [Alias("ForeGroundColour")] + [System.Drawing.Color]$ForeGroundColor, + [Parameter(Mandatory=$true,ParameterSetName="DataBar")] + [Alias("DataBarColour")] + [System.Drawing.Color]$DataBarColor, + [Parameter(Mandatory=$true,ParameterSetName="ThreeIconSet")] + [OfficeOpenXml.ConditionalFormatting.eExcelconditionalFormatting3IconsSetType]$ThreeIconsSet, + [Parameter(Mandatory=$true,ParameterSetName="FourIconSet")] + [OfficeOpenXml.ConditionalFormatting.eExcelconditionalFormatting4IconsSetType]$FourIconsSet, + [Parameter(Mandatory=$true,ParameterSetName="FiveIconSet")] + [OfficeOpenXml.ConditionalFormatting.eExcelconditionalFormatting5IconsSetType]$FiveIconsSet, + $ConditionValue, + $ConditionValue2, + [System.Drawing.Color]$BackgroundColor, + [OfficeOpenXml.Style.ExcelFillStyle]$BackgroundPattern, + [System.Drawing.Color]$PatternColor, + $NumberFormat, + [switch]$Bold, + [switch]$Italic, + [switch]$Underline, + [switch]$StrikeThru + ) + + If ($ThreeIconsSet) {$rule = $WorkSheet.ConditionalFormatting.AddThreeIconSet($Range , $ThreeIconsSet)} + elseif ($FourIconsSet) {$rule = $WorkSheet.ConditionalFormatting.AddFourIconSet( $Range , $FourIconsSet) } + elseif ($FiveIconsSet) {$rule = $WorkSheet.ConditionalFormatting.AddFiveIconSet( $Range , $IconType) } + elseif ($DataBarColor) {$rule = $WorkSheet.ConditionalFormatting.AddDatabar( $Range , $DataBarColor) } + else { $rule = ($WorkSheet.ConditionalFormatting)."Add$RuleType"($Range)} + + if ($ConditionValue -and $RuleType -match "Top|Botom") {$rule.Rank = $ConditionValue } + if ($ConditionValue -and $RuleType -match "StdDev") {$rule.StdDev = $ConditionValue } + if ($ConditionValue -and $RuleType -match "Than|Equal|Expression") {$rule.Formula = $ConditionValue } + if ($ConditionValue -and $RuleType -match "Text|With") {$rule.Text = $ConditionValue } + if ($ConditionValue -and + $ConditionValue2 -and $RuleType -match "Between") {$rule.Formula = $ConditionValue + $rule.Formula2 = $ConditionValue2} + $rule.Style.Font.Bold = $Bold + $rule.Style.Font.Italic = $Italic + $rule.Style.Font.Underline = $Underline + $rule.Style.Font.Strike = $StrikeThru + + if ($ForeGroundColor) {$rule.Style.Font.Color = $ForeGroundColor} + if ($BackgroundColor) {$rule.Style.Fill.BackgroundColor = $BackgroundColor } + if ($BackgroundPattern) {$rule.Style.Fill.PatternType = $BackgroundPattern } + if ($PatternColor) {$rule.Style.Fill.PatternColor = $PatternColor} + if ($NumberFormat) {$rule.Style.NumberFormat.Format = $NumberFormat} +} + +Function Set-Format { + Param ( + #A row, Column or block of cells to format + [Parameter(ValueFromPipeline=$true)] + $Address , + #Number format to apply to cells e.g. "dd/MM/yyyy HH:mm", "£#,##0.00;[Red]-£#,##0.00", "0.00%" , "##/##" , "0.0E+0" etc + [Alias("NFormat")] + $NumberFormat, + #Style of border to draw around the range + [OfficeOpenXml.Style.ExcelBorderStyle]$BorderAround, + #Colour for the text - if none specified it will be left as it it is + [System.Drawing.Color]$FontColor, + #Clear Bold, Italic, StrikeThrough and Underline and set colour to black + [switch]$ResetFont, + #Make text bold + [switch]$Bold, + #Make text italic + [switch]$Italic, + #Underline the text using the underline style in -underline type + [switch]$Underline, + #Should Underline use single or double, normal or accounting mode : default is single normal + [OfficeOpenXml.Style.ExcelUnderLineType]$UnderLineType = [OfficeOpenXml.Style.ExcelUnderLineType]::Single, + #StrikeThrough text + [switch]$StrikeThru, + #Subscript or superscript + [OfficeOpenXml.Style.ExcelVerticalAlignmentFont]$FontShift, + #Font to use - Excel defaults to Calibri + [String]$FontName, + #Point size for the text + [float]$FontSize, + #Change background colour + [System.Drawing.Color]$BackgroundColor, + #Background pattern - solid by default + [OfficeOpenXml.Style.ExcelFillStyle]$BackgroundPattern =[OfficeOpenXml.Style.ExcelFillStyle]::Solid , + #Secondary colour for background pattern + [Alias("PatternColour")] + [System.Drawing.Color]$PatternColor, + #Turn on text wrapping + [switch]$WrapText, + #Position cell contents to left, right or centre ... + [OfficeOpenXml.Style.ExcelHorizontalAlignment]$HorizontalAlignment, + #Position cell contents to top bottom or centre + [OfficeOpenXml.Style.ExcelVerticalAlignment]$VerticalAlignment, + #Degrees to rotate text. Up to +90 for anti-clockwise ("upwards"), or to -90 for clockwise. + [ValidateRange(-90,90)] + [int]$TextRotation , + #Autofit cells to width (columns or ranges only) + [switch]$AutoFit, + #Set cells to a fixed width (columns or ranges only), ignored if Autofit is specified + [float]$Width, + #Set cells to a fixed hieght (rows or ranges only) + [float]$Height, + #Hide a row or column (not a range) + [switch]$Hidden + ) + process { + Foreach ($range in $Address) { + if ($ResetFont) {$Range.Style.Font.Color.SetColor("Black") + $Range.Style.Font.Bold = $false + $Range.Style.Font.Italic = $false + $Range.Style.Font.UnderLine = $false + $Range.Style.Font.Strike = $falsee + } + if ($Underline) {$Range.Style.Font.UnderLine = $true + $Range.Style.Font.UnderLineType =$UnderLineType + } + if ($Bold) {$Range.Style.Font.Bold = $true } + if ($Italic) {$Range.Style.Font.Italic = $true } + if ($StrikeThru) {$Range.Style.Font.Strike = $true } + if ($FontShift) {$Range.Style.Font.VerticalAlign = $FontShift } + if ($FontColor) {$Range.Style.Font.Color.SetColor( $FontColor ) } + if ($BorderRound) {$Range.Style.Border.BorderAround( $BorderAround ) } + if ($NumberFormat) {$Range.Style.Numberformat.Format= $NumberFormat } + if ($TextRotation) {$Range.Style.TextRotation = $TextRotation } + if ($WrapText) {$Range.Style.WrapText = $true } + if ($HorizontalAlignment) {$Range.Style.HorizontalAlignment= $HorizontalAlignment } + if ($VerticalAlignment) {$Range.Style.VerticalAlignment = $VerticalAlignment } + + if ($BackgroundColor) { + $Range.Style.Fill.PatternType = $BackgroundPattern + $Range.Style.Fill.BackgroundColor.SetColor($BackgroundColor) + if ($PatternColor) { + $range.Style.Fill.PatternColor.SetColor( $PatternColor) + } + } + + if ($Height) { + if ($Range -is [OfficeOpenXml.ExcelRow] ) {$Range.Height = $Height } + elseif ($Range -is [OfficeOpenXml.ExcelRange] ) { + ($range.Start.Row)..($range.Start.Row + $range.Rows) | + ForEach-Object {$ws.Row($_).Height = $Height } + } + else {Write-Warning -Message ("Can set the height of a row or a range but not a {0} object" -f ($Range.GetType().name)) } + } + if ($AutoFit) { + if ($Range -is [OfficeOpenXml.ExcelColumn]) {$Range.AutoFit() } + elseif ($Range -is [OfficeOpenXml.ExcelRange] ) {$Range.AutoFitColumns() } + else {Write-Warning -Message ("Can autofit a column or a range but not a {0} object" -f ($Range.GetType().name)) } + + } + elseif ($Width) { + if ($Range -is [OfficeOpenXml.ExcelColumn]) {$Range.Width = $Width} + elseif ($Range -is [OfficeOpenXml.ExcelRange] ) { + ($range.Start.Column)..($range.Start.Column+ $range.Columns) | + ForEach-Object {$ws.Column($_).Width = $Width} + } + else {Write-Warning -Message ("Can set the width of a column or a range but not a {0} object" -f ($Range.GetType().name)) } + } + if ($Hidden) { + if ($Range -is [OfficeOpenXml.ExcelRow] -or + $Range -is [OfficeOpenXml.ExcelColumn] ) {$Range.Hidden = $True} + else {Write-Warning -Message ("Can hide a row or a column but not a {0} object" -f ($Range.GetType().name)) } + } + } + } +} + +#Argument completer for colours. If we have PS 5 or Tab expansion++ then we'll register it. Otherwise it does nothing. +Function ColorCompletion{ + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) + [System.Drawing.KnownColor].GetFields() | Where-Object {$_.IsStatic -and $_.name -like "$wordToComplete*" } | + Sort-Object name | ForEach-Object {New-CompletionResult $_.name $_.name + } +} + +if (Get-Command -Name register-argumentCompleter -ErrorAction SilentlyContinue) { + Register-ArgumentCompleter -CommandName Export-Excel -ParameterName TitleBackgroundColor -ScriptBlock $Function:ColorCompletion + Register-ArgumentCompleter -CommandName Add-ConditionalFormatting -ParameterName ForeGroundColor -ScriptBlock $Function:ColorCompletion + Register-ArgumentCompleter -CommandName Add-ConditionalFormatting -ParameterName DataBarColor -ScriptBlock $Function:ColorCompletion + Register-ArgumentCompleter -CommandName Add-ConditionalFormatting -ParameterName BackgroundColor -ScriptBlock $Function:ColorCompletion + Register-ArgumentCompleter -CommandName Set-Format -ParameterName FontColor -ScriptBlock $Function:ColorCompletion + Register-ArgumentCompleter -CommandName Set-Format -ParameterName BackgroundColor -ScriptBlock $Function:ColorCompletion + Register-ArgumentCompleter -CommandName Set-Format -ParameterName PatternColor -ScriptBlock $Function:ColorCompletion +} \ No newline at end of file From 890906ff102be5f7a757dc00d97cd76a595481e8 Mon Sep 17 00:00:00 2001 From: jhoneill Date: Tue, 24 Oct 2017 09:02:14 +0100 Subject: [PATCH 2/3] Fixes to formatting , new Export charts tool (requires Excel.exe) help updates and Export excel support for "No data" use Export-Excel.ps1 1. No code seems to act on the "NoClobber" parameter, I had previously added a "ClearSheet" parameter which removes the worksheet and any past data (before if there were fewer rows or columns in the new data, the old would not be over-written) 2. I have made the whole of the process{} block in export-Excel subject to IF ($target data) .... this way things which are processed in the end{} block can be done in a seperate call to Export-Excel without the need to load data. Because of this there should be no need for the Open and Close -ExcelPackage functions going forward, but I've left them in place for now 3. Fixed a bug when setting the target range it was previously setting the end of the range to be the number of columns which failed if the data was inserted at a column other than one. 4. I've moved the top left corner of a pivot chart so it sits closer to the data. 5. I've change the check for inserting a pivot table so if -InsertPivotChart is specified it implies -insertPivotTable. Formatting. 6. The previous check in was not up-to date and contained some issues with conditional formatting which I had fixed in the past. 7. Fixed a peformance bug in set-format where a foreach intended to allow the same format to be applied to multiple ranges would format a large blocks of cells one-by-one if it was the only range passed. . 8. Improved online help for the formatting commands. Export charts 9. My current project is importing a lot of data into Excel and needs to take Excel charts out as JPG files. I've thrown in the Export-Charts script as "something for the pot", even though the connection with the rest of work is loose, --- Export-Excel.ps1 | 165 ++++++++++++++++++++++++++++++++++++++++------ Export-charts.ps1 | 51 ++++++++++++++ ImportExcel.psm1 | 3 +- formatting.ps1 | 106 ++++++++++++++++++----------- 4 files changed, 267 insertions(+), 58 deletions(-) create mode 100644 Export-charts.ps1 diff --git a/Export-Excel.ps1 b/Export-Excel.ps1 index 59532b0..e131309 100644 --- a/Export-Excel.ps1 +++ b/Export-Excel.ps1 @@ -5,13 +5,83 @@ .DESCRIPTION Export data to an Excel file and where possible try to convert numbers so Excel recognizes them as numbers instead of text. After all. Excel is a spreadsheet program used for number manipulation and calculations. In case the number conversion is not desired, use the parameter '-NoNumberConversion *'. - + .PARAMETER Path + Path to a new or existing .XLSX file + .PARAMETER ExcelPackage + An object representing an Excel Package - usually this is returned by specifying -Passthru alllowing multiple commands to work on the same Workbook without saving and reloading each time. + .PARAMETER WorkSheetName + The name of a sheet within the workbook - "Sheet1" by default + .PARAMETER ClearSheet + If specified Export-Excel will remove any existing worksheet with the selected name. The Default behaviour is to overwrite cells in this sheet as needed (but leaving non-overwritten ones in place) + .PARAMETER Append + If specified data will be added to the end of an existing sheet, using the same column headings. + .PARAMETER TargetData + Data to insert onto the worksheet - this is often provided from the pipeline. + .PARAMETER ExcludeProperty + Speficies properties which may exist in the target data but should not be placed on the worksheet + .PARAMETER Title + Text of a title to be placed in Cell A1 + .PARAMETER TitleBold + Sets the title in boldface type + .PARAMETER TitleSize + Sets the point size for the title + .PARAMETER TitleBackgroundColor + Sets the cell background to solid and the chose colour for the title cell + .PARAMETER Password + Sets password protection on the workbook + .PARAMETER IncludePivotTable + Adds a Pivot table using the data in the worksheet + .PARAMETER PivotRows + Name(s) columns from the spreadhseet which will prvoide the row name(s) in the pivot table + .PARAMETER PivotColumns + Name(s) columns from the spreadhseet which will prvoide the Column name(s) in the pivot table + .PARAMETER PivotData + Hash table in the form ColumnName = Average|Count|CountNums|Max|Min|Product|None|StdDev|StdDevP|Sum|Var|VarP to provide the data in the Pivot table + .PARAMETER PivotTableDefinition, + HashTable(s) with Sheet PivotTows, PivotColumns, PivotData, IncludePivotChart and ChartType values to make it easier to specify a definition or multiple Pivots. + .PARAMETER IncludePivotChart, + Include a chart with the Pivot table - implies Include Pivot Table. + .PARAMETER NoLegend + Exclude the legend from the pivot chart + .PARAMETER ShowCategory + Add category labels to the pivot chart + .PARAMETER ShowPercent + Add Percentage labels to the pivot chart .PARAMETER ConditionalText Applies a 'Conditional formatting rule' in Excel on all the cells. When specific conditions are met a rule is triggered. - .PARAMETER NoNumberConversion By default we convert all values to numbers if possible, but this isn't always desirable. NoNumberConversion allows you to add exceptions for the conversion. Wildcards (like '*') are allowed. - + .PARAMETER BoldTopRow + Makes the top Row boldface. + .PARAMETER NoHeader + Does not put field names at the top of columns + .PARAMETER RangeName + Makes the data in the worksheet a named range + .PARAMETER TableName + Makes the data in the worksheet a table with a name applies a style to it. Name must not contain spaces + .PARAMETER TableStyle + Selects the style for the named table - defaults to 'Medium6' + .PARAMETER ExcelChartDefinition + A hash table containing ChartType, Title, NoLegend, ShowCategory, ShowPecent, Yrange, Xrange and SeriesHeader for one or more [non-pivot] charts + .PARAMETER HideSheet + Name(s) of Sheet(s) to hide in the workbook + .PARAMETER KillExcel + Closes Excel - prevents errors writing to the file because Excel has it open + .PARAMETER AutoNameRange + Makes each column a named range + .PARAMETER StartRow + Row to start adding data. 1 by default. Row 1 will contain the title if any. Then headers will appear (Unless -No header is specified) then the data appears + .PARAMETER StartColumn + Column to start adding data - 1 by default + + .PARAMETER FreezeTopRow + Freezes headers etc. in the top row + .PARAMETER FreezeFirstColumn + Freezes titles etc. in the left column + .PARAMETER FreezeTopRowFirstColumn + Freezes top row and left column (equivalent to Freeze pane 2,2 ) + .PARAMETER FreezePane + Freezes panes at specified coordinates (in the form RowNumber , ColumnNumber) .PARAMETER AutoFilter Enables the 'Filter' in Excel on the complete header row. So users can easily sort, filter and/or search the data in the select column from within Excel. @@ -51,7 +121,9 @@ .PARAMETER Show Opens the Excel file immediately after creation. Convenient for viewing the results instantly without having to search for the file first. - + .PARAMETER PassThru + If specified, Export-Excel returns an object representing the Excel package without saving the package first. To save it you need to call the save or Saveas method or send it back to Export-Excel + .EXAMPLE Get-Process | Export-Excel .\Test.xlsx -show Export all the processes to the Excel file 'Test.xlsx' and open the file immediately. @@ -189,7 +261,60 @@ .EXAMPLE Get-Service | Export-Excel 'c:\temp\test.xlsx' -Show -IncludePivotTable -PivotRows status -PivotData @{status='count'} + + .EXAMPLE + $pt = [ordered]@{} + $pt.pt1=@{ SourceWorkSheet = 'Sheet1'; + PivotRows = 'Status' + PivotData = @{'Status'='count'} + IncludePivotChart = $true + ChartType = 'BarClustered3D' + } + $pt.pt2=@{ SourceWorkSheet = 'Sheet2'; + PivotRows = 'Company' + PivotData = @{'Company'='count'} + IncludePivotChart = $true + ChartType = 'PieExploded3D' + } + Remove-Item -Path .\test.xlsx + 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' + Export-Excel -Path .\test.xlsx -PivotTableDefinition $pt -Show + This example defines two pivot tables. Then it puts Service data on Sheet1 with one call to Export-Excel and Process Data on sheet2 with a second call to Export-Excel + The thrid and final call adds the two pivot tables and opens the spreadsheet in Excel + + + .EXAMPLE + Remove-Item -Path .\test.xlsx + $excel = Get-Service | Select-Object -Property Status,Name,DisplayName,StartType | Export-Excel -Path .\test.xlsx -PassThru + $excel.Workbook.Worksheets["Sheet1"].Row(1).style.font.bold = $true + $excel.Workbook.Worksheets["Sheet1"].Column(3 ).width = 29 + $excel.Workbook.Worksheets["Sheet1"].Column(3 ).Style.wraptext = $true + $excel.Save() + $excel.Dispose() + Start-Process .\test.xlsx + + This example uses -passthrough - put service information into sheet1 of the work book and saves the excelPackageObject in $Excel + It then uses the package object to apply formatting, and then saves the workbook and disposes of the object before loading the document in Excel. + + .EXAMPLE + $excel = Get-Process | Select-Object -Property Name,Company,Handles,CPU,PM,NPM,WS | Export-Excel -Path .\test.xlsx -ClearSheet -WorkSheetname "Processes" -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 } + Export-Excel -ExcelPackage $excel -WorkSheetname "Processes" -IncludePivotChart -ChartType ColumnClustered -NoLegend -PivotRows company -PivotData @{'Name'='Count'} -Show + + This a more sophisticated version of the previous example showing different ways of using Set-Format, and also adding conditional formatting. + In the final command a Pivot chart is added and the workbook is opened in Excel. + .LINK https://github.com/dfinke/ImportExcel #> @@ -204,21 +329,23 @@ [OfficeOpenXml.ExcelPackage]$ExcelPackage, [Parameter(ValueFromPipeline=$true)] $TargetData, + [String]$Password, [String]$WorkSheetname = 'Sheet1', + [switch]$ClearSheet, + [switch]$Append, [String]$Title, [OfficeOpenXml.Style.ExcelFillStyle]$TitleFillPattern = 'None', [Switch]$TitleBold, [Int]$TitleSize = 22, [System.Drawing.Color]$TitleBackgroundColor, + [Switch]$IncludePivotTable, [String[]]$PivotRows, [String[]]$PivotColumns, $PivotData, - [Switch]$PivotDataToColumn, - [String]$Password, - [OfficeOpenXml.Drawing.Chart.eChartType]$ChartType = 'Pie', + [Switch]$PivotDataToColumn, [Hashtable]$PivotTableDefinition, - [Switch]$IncludePivotTable, [Switch]$IncludePivotChart, + [OfficeOpenXml.Drawing.Chart.eChartType]$ChartType = 'Pie', [Switch]$NoLegend, [Switch]$ShowCategory, [Switch]$ShowPercent, @@ -249,8 +376,8 @@ $true } })] - [Parameter(ParameterSetName = 'Table')] - [Parameter(ParameterSetName = 'PackageTable')] + [Parameter(ParameterSetName = 'Table' ,Mandatory = $true)] + [Parameter(ParameterSetName = 'PackageTable' ,Mandatory = $true)] [String]$TableName, [Parameter(ParameterSetName = 'Table')] [Parameter(ParameterSetName = 'PackageTable')] @@ -268,8 +395,7 @@ [Object[]]$ConditionalFormat, [Object[]]$ConditionalText, [ScriptBlock]$CellStyleSB, - [Switch]$Now, - [switch]$Append + [Switch]$Now ) Begin { @@ -427,8 +553,7 @@ $pkg = New-Object OfficeOpenXml.ExcelPackage $Path } - [OfficeOpenXml.ExcelWorksheet]$ws = $pkg | Add-WorkSheet -WorkSheetname $WorkSheetname -NoClobber:$NoClobber - + [OfficeOpenXml.ExcelWorksheet]$ws = $pkg | Add-WorkSheet -WorkSheetname $WorkSheetname -NoClobber:$NoClobber -ClearSheet:$ClearSheet #Add worksheet doesn't take any action for -noClobber foreach ($format in $ConditionalFormat ) { $target = "Add$($format.Formatter)" $rule = ($ws.ConditionalFormatting).PSObject.Methods[$target].Invoke($format.Range, $format.IconType) @@ -468,6 +593,7 @@ } Process { + if ($TargetData) { Try { if ($firstTimeThru) { $firstTimeThru = $false @@ -487,7 +613,7 @@ #region Add headers if (-not $script:Header) { $ColumnIndex = $StartColumn - $script:Header = $TargetData.PSObject.Properties.Name | Where-Object {$_ -notin $ExcludeProperty} + $script:Header = $TargetData.PSObject.Properties.Name | Where-Object {$_ -notin $ExcludeProperty} if ($NoHeader) { # Don't push the headers to the spread sheet @@ -518,6 +644,7 @@ Catch { throw "Failed exporting worksheet '$WorkSheetname' to '$Path': $_" } + } } End { @@ -561,7 +688,7 @@ } $csc = $StartColumn $cer = $ws.Dimension.End.Row - $cec = $script:Header.Count + $cec = $ws.Dimension.End.Column # was $script:Header.Count $targetRange = $ws.Cells[$csr, $csc, $cer, $cec] #if we're appending data the table may already exist: but excel doesn't like the result if I put @@ -633,14 +760,14 @@ } $chart = $wsPivot.Drawings.AddChart('PivotChart', $ChartType, $pivotTable) - $chart.SetPosition(1, 0, 3, 0) + $chart.SetPosition(0, 0, 2, 0) #Changed position to top row, next to a chart which doesn't pivot on columns $chart.SetSize(600, 400) } } } } - if ($IncludePivotTable) { + if ($IncludePivotTable -or $IncludePivotChart) { #changed so -includePivotChart Implies -includePivotTable. $pivotTableName = $WorkSheetname + 'PivotTable' #Make sure the Pivot table sheet doesn't already exist try { $pkg.Workbook.Worksheets.Delete( $pivotTableName) } catch {} @@ -686,7 +813,7 @@ $chart = $wsPivot.Drawings.AddChart('PivotChart', $ChartType, $pivotTable) $chart.DataLabel.ShowCategory = $ShowCategory $chart.DataLabel.ShowPercent = $ShowPercent - + $chart.SetPosition(0,0,2,0) # 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() } diff --git a/Export-charts.ps1 b/Export-charts.ps1 new file mode 100644 index 0000000..196d8e5 --- /dev/null +++ b/Export-charts.ps1 @@ -0,0 +1,51 @@ +<# + .Synopsis + Exports the charts in an Excel spreadSheet + .Example + Export-Charts .\test,xlsx + Exports the charts in test.xlsx to JPEG files in the current directory. + + .Example + Export-Charts -path .\test,xlsx -destination [System.Environment+SpecialFolder]::MyDocuments -outputType PNG -passthrough + Exports the charts to PNG files in MyDocuments , and returns file objects representing the newly created files + +#> +Param ( + #Path to the Excel file whose chars we will export. + $Path = "C:\Users\public\Documents\stats.xlsx", + #If specified, output file objects representing the image files. + [switch]$passthru, + #Format to write - JPG by default + [ValidateSet("JPG","PNG","GIF")] + $OutputType = "JPG", + #Folder to write image files to (defaults to same one as the Excel file is in) + $Destination +) + +#if no output folder was specified, set destination to the folder where the Excel file came from +if (-not $Destination) {$Destination = Split-Path -Path $path -Parent } + +#Call up Excel and tell it to open the file. +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($path) } +catch { Write-Warning "Could not start Excel application - which usually means it is not installed." ; return } + + +#For each worksheet, for each chart, jump to the chart, create a filename of "WorksheetName_ChartTitle.jpg", and export the file. +foreach ($excelWorkSheet in $excelWorkBook.Worksheets) { + #note somewhat unusual way of telling excel we want all the charts. + foreach ($excelchart in $excelWorkSheet.ChartObjects([System.Type]::Missing)) { + #if you don't go to the chart the image will be zero size ! + $excelApp.Goto($excelchart.TopLeftCell,$true) + $imagePath = Join-Path -Path $Destination -ChildPath ($excelWorkSheet.Name + "_" + ($excelchart.Chart.ChartTitle.Text -split "\s\d\d:\d\d,")[0] + ".$OutputType") + if ( $excelchart.Chart.Export($imagePath, $OutputType, $false) ) { # Export returs true/false for success/failure + if ($passThru) {Get-Item -Path $imagePath } # when succesful return a file object (-passthru) or print a verbose message, write warning for any failures + else {Write-Verbose -Message "Exported $imagePath"} + } + else {Write-Warning -Message "Failure exporting $imagePath" } + } +} + +$excelApp.Quit() \ No newline at end of file diff --git a/ImportExcel.psm1 b/ImportExcel.psm1 index 14e47e7..8ab5ed2 100644 --- a/ImportExcel.psm1 +++ b/ImportExcel.psm1 @@ -437,11 +437,12 @@ function Add-WorkSheet { [OfficeOpenXml.ExcelPackage] $ExcelPackage, [Parameter(Mandatory=$true)] [string] $WorkSheetname, + [switch] $ClearSheet, [Switch] $NoClobber ) $ws = $ExcelPackage.Workbook.Worksheets[$WorkSheetname] - + if($ClearSheet -and $ws) {$ExcelPackage.Workbook.Worksheets.Delete($WorkSheetname) ; $ws = $null } if(!$ws) { Write-Verbose "Add worksheet '$WorkSheetname'" $ws=$ExcelPackage.Workbook.Worksheets.Add($WorkSheetname) diff --git a/formatting.ps1 b/formatting.ps1 index ed3bde7..2ec8b58 100644 --- a/formatting.ps1 +++ b/formatting.ps1 @@ -1,79 +1,109 @@ Function Add-ConditionalFormatting { <# -.Synopsis Adds contitional formatting to worksheet +.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 + $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 "b":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 "b":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 - Fixed formats are then applied to dates in columns D..G and the top row is formatted - Finally the workbook is saved and the Excel 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 + Fixed formats are then applied to dates in columns D..G and the top row is formatted + Finally the workbook is saved and the Excel closed. #> Param ( + #The worksheet where the format is to be applied [OfficeOpenXml.ExcelWorksheet]$WorkSheet , + #The area of the worksheet where the format is to be applied [OfficeOpenXml.ExcelAddress]$Range , + #One of the standard named rules - Top / Bottom / Less than / Greater than / Contains etc [Parameter(Mandatory=$true,ParameterSetName="NamedRule",Position=3)] [OfficeOpenXml.ConditionalFormatting.eExcelConditionalFormattingRuleType]$RuleType , + #Text colour for matching objects [Alias("ForeGroundColour")] [System.Drawing.Color]$ForeGroundColor, + #colour for databar type charts [Parameter(Mandatory=$true,ParameterSetName="DataBar")] [Alias("DataBarColour")] [System.Drawing.Color]$DataBarColor, + #One of the three-icon set types (e.g. Traffic Lights) [Parameter(Mandatory=$true,ParameterSetName="ThreeIconSet")] [OfficeOpenXml.ConditionalFormatting.eExcelconditionalFormatting3IconsSetType]$ThreeIconsSet, + #A four-icon set name [Parameter(Mandatory=$true,ParameterSetName="FourIconSet")] [OfficeOpenXml.ConditionalFormatting.eExcelconditionalFormatting4IconsSetType]$FourIconsSet, + #A five-icon set name [Parameter(Mandatory=$true,ParameterSetName="FiveIconSet")] [OfficeOpenXml.ConditionalFormatting.eExcelconditionalFormatting5IconsSetType]$FiveIconsSet, - $ConditionValue, - $ConditionValue2, + #A value for the condition (e.g. "2000" if the test is 'lessthan 2000') + [string]$ConditionValue, + #A second value for the conditions like between x and Y + [string]$ConditionValue2, + #Background colour for matching items [System.Drawing.Color]$BackgroundColor, - [OfficeOpenXml.Style.ExcelFillStyle]$BackgroundPattern, + #Background pattern for matching items + [OfficeOpenXml.Style.ExcelFillStyle]$BackgroundPattern = [OfficeOpenXml.Style.ExcelFillStyle]::Solid, + #Secondary colour when a background pattern requires it [System.Drawing.Color]$PatternColor, + #Sets the numeric format for matching items $NumberFormat, + #Put matching items in bold face [switch]$Bold, + #Put matching items in italic [switch]$Italic, + #Underline matching items [switch]$Underline, + #Strikethrough text of matching items [switch]$StrikeThru ) - If ($ThreeIconsSet) {$rule = $WorkSheet.ConditionalFormatting.AddThreeIconSet($Range , $ThreeIconsSet)} - elseif ($FourIconsSet) {$rule = $WorkSheet.ConditionalFormatting.AddFourIconSet( $Range , $FourIconsSet) } - elseif ($FiveIconsSet) {$rule = $WorkSheet.ConditionalFormatting.AddFiveIconSet( $Range , $IconType) } - elseif ($DataBarColor) {$rule = $WorkSheet.ConditionalFormatting.AddDatabar( $Range , $DataBarColor) } - else { $rule = ($WorkSheet.ConditionalFormatting)."Add$RuleType"($Range)} + If ($ThreeIconsSet) {$rule = $WorkSheet.ConditionalFormatting.AddThreeIconSet($Range , $ThreeIconsSet)} + elseif ($FourIconsSet) {$rule = $WorkSheet.ConditionalFormatting.AddFourIconSet( $Range , $FourIconsSet) } + elseif ($FiveIconsSet) {$rule = $WorkSheet.ConditionalFormatting.AddFiveIconSet( $Range , $IconType) } + elseif ($DataBarColor) {$rule = $WorkSheet.ConditionalFormatting.AddDatabar( $Range , $DataBarColor) } + else { $rule = ($WorkSheet.ConditionalFormatting)."Add$RuleType"($Range)} - if ($ConditionValue -and $RuleType -match "Top|Botom") {$rule.Rank = $ConditionValue } - if ($ConditionValue -and $RuleType -match "StdDev") {$rule.StdDev = $ConditionValue } - if ($ConditionValue -and $RuleType -match "Than|Equal|Expression") {$rule.Formula = $ConditionValue } - if ($ConditionValue -and $RuleType -match "Text|With") {$rule.Text = $ConditionValue } - if ($ConditionValue -and - $ConditionValue2 -and $RuleType -match "Between") {$rule.Formula = $ConditionValue - $rule.Formula2 = $ConditionValue2} - $rule.Style.Font.Bold = $Bold - $rule.Style.Font.Italic = $Italic - $rule.Style.Font.Underline = $Underline - $rule.Style.Font.Strike = $StrikeThru - - if ($ForeGroundColor) {$rule.Style.Font.Color = $ForeGroundColor} - if ($BackgroundColor) {$rule.Style.Fill.BackgroundColor = $BackgroundColor } - if ($BackgroundPattern) {$rule.Style.Fill.PatternType = $BackgroundPattern } - if ($PatternColor) {$rule.Style.Fill.PatternColor = $PatternColor} - if ($NumberFormat) {$rule.Style.NumberFormat.Format = $NumberFormat} + if ($ConditionValue -and $RuleType -match "Top|Botom") {$rule.Rank = $ConditionValue } + if ($ConditionValue -and $RuleType -match "StdDev") {$rule.StdDev = $ConditionValue } + if ($ConditionValue -and $RuleType -match "Than|Equal|Expression") {$rule.Formula = $ConditionValue } + if ($ConditionValue -and $RuleType -match "Text|With") {$rule.Text = $ConditionValue } + if ($ConditionValue -and + $ConditionValue2 -and $RuleType -match "Between") {$rule.Formula = $ConditionValue + $rule.Formula2 = $ConditionValue2} + + if ($NumberFormat) {$rule.Style.NumberFormat.Format = $NumberFormat } + if ($Underline) {$rule.Style.Font.Underline = [OfficeOpenXml.Style.ExcelUnderLineType]::Single } + if ($Bold) {$rule.Style.Font.Bold = $true} + if ($Italic) {$rule.Style.Font.Italic = $true} + if ($StrikeThru) {$rule.Style.Font.Strike = $true} + if ($ForeGroundColor) {$rule.Style.Font.Color.color = $ForeGroundColor } + if ($BackgroundColor) {$rule.Style.Fill.BackgroundColor.color = $BackgroundColor } + if ($BackgroundPattern) {$rule.Style.Fill.PatternType = $BackgroundPattern } + if ($PatternColor) {$rule.Style.Fill.PatternColor.color = $PatternColor } } Function Set-Format { +<# +.SYNOPSIS + Applies Number, font, alignment and colour formatting to a range of Excel Cells +.EXAMPLE + $sheet.Column(3) | Set-Format -HorizontalAlignment Right -NumberFormat "#,###" + Selects column 3 from a sheet object (within a workbook object, which is a child of the ExcelPackage object) and passes it to Set-Format which formats as an integer with comma seperated groups +.EXAMPLE + Set-Format -Address $sheet.Cells["E1:H1048576"] -HorizontalAlignment Right -NumberFormat "#,###" + Instead of piping the address in this version specifies a block of cells and applies similar formatting + +#> Param ( - #A row, Column or block of cells to format + #One or more row(s), Column(s) and/or block(s) of cells to format [Parameter(ValueFromPipeline=$true)] - $Address , + [object[]]$Address , #Number format to apply to cells e.g. "dd/MM/yyyy HH:mm", "£#,##0.00;[Red]-£#,##0.00", "0.00%" , "##/##" , "0.0E+0" etc [Alias("NFormat")] $NumberFormat, From efadf83b6baad652b93c2360e61756d768a1fce3 Mon Sep 17 00:00:00 2001 From: jhoneill Date: Tue, 24 Oct 2017 09:17:32 +0100 Subject: [PATCH 3/3] Fixed a misplaced commas Param block in close was re-ordered leaving a parameter in the middle missing a comma and the parameter at the end had a comma it should not have done. --- Open-ExcelPackage.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Open-ExcelPackage.ps1 b/Open-ExcelPackage.ps1 index a60a6f2..ccb4b3a 100644 --- a/Open-ExcelPackage.ps1 +++ b/Open-ExcelPackage.ps1 @@ -34,11 +34,11 @@ Function Close-ExcelPackage { [parameter(Mandatory=$true, ValueFromPipeline=$true)] [OfficeOpenXml.ExcelPackage]$ExcelPackage, #Open the file - [switch]$Show + [switch]$Show, #Abandon the file without saving [Switch]$NoSave, #Save file with a new name (ignored if -NoSaveSpecified) - $SaveAs, + $SaveAs ) if ( $NoSave) {$ExcelPackage.Dispose()} else {