diff --git a/AddConditionalFormatting.ps1 b/AddConditionalFormatting.ps1 index 6d35dbd..b362dc1 100644 --- a/AddConditionalFormatting.ps1 +++ b/AddConditionalFormatting.ps1 @@ -12,7 +12,7 @@ $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 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. @@ -32,14 +32,14 @@ [Parameter(Mandatory = $true, ParameterSetName = "FourIconSet")] [Parameter(Mandatory = $true, ParameterSetName = "FiveIconSet")] [OfficeOpenXml.ExcelAddress]$Range , - #One or more row(s), Column(s) and/or block(s) of cells to format + #One or more row(s), column(s) and/or block(s) of cells to format [Parameter(Mandatory = $true, ParameterSetName = "NamedRuleAddress")] [Parameter(Mandatory = $true, ParameterSetName = "DataBarAddress")] [Parameter(Mandatory = $true, ParameterSetName = "ThreeIconSetAddress")] [Parameter(Mandatory = $true, ParameterSetName = "FourIconSetAddress")] [Parameter(Mandatory = $true, ParameterSetName = "FiveIconSetAddress")] $Address , - #One of the standard named rules - Top / Bottom / Less than / Greater than / Contains etc + #One of the standard named rules - Top / Bottom / Less than / Greater than / Contains etc. [Parameter(Mandatory = $true, ParameterSetName = "NamedRule", Position = 3)] [Parameter(Mandatory = $true, ParameterSetName = "NamedRuleAddress", Position = 3)] [OfficeOpenXml.ConditionalFormatting.eExcelConditionalFormattingRuleType]$RuleType , @@ -65,7 +65,7 @@ [OfficeOpenXml.ConditionalFormatting.eExcelconditionalFormatting5IconsSetType]$FiveIconsSet, #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 + #A second value for the conditions like "between x and Y" [string]$ConditionValue2, #Background colour for matching items [System.Drawing.Color]$BackgroundColor, @@ -84,11 +84,11 @@ #Strikethrough text of matching items [switch]$StrikeThru ) - #Allow conditional formatting to work like Set-Format (with single ADDRESS parameter), split it to get worksheet and range of cells. + #Allow conditional formatting to work like Set-Format (with single ADDRESS parameter), split it to get worksheet and range of cells. If ($Address -and -not $WorkSheet -and -not $Range) { $WorkSheet = $Address.Worksheet[0] - $Range = $Address.Address - } + $Range = $Address.Address + } If ($ThreeIconsSet) {$rule = $WorkSheet.ConditionalFormatting.AddThreeIconSet($Range , $ThreeIconsSet)} elseif ($FourIconsSet) {$rule = $WorkSheet.ConditionalFormatting.AddFourIconSet( $Range , $FourIconsSet) } elseif ($FiveIconsSet) {$rule = $WorkSheet.ConditionalFormatting.AddFiveIconSet( $Range , $IconType) } diff --git a/ConvertExcelToImageFile.ps1 b/ConvertExcelToImageFile.ps1 index 6381759..c89f069 100644 --- a/ConvertExcelToImageFile.ps1 +++ b/ConvertExcelToImageFile.ps1 @@ -1,53 +1,53 @@ Function Convert-XlRangeToImage { -<# - .Synopsis - Gets the specified part of an Excel file and exports it as an image - .Description - Excel allows charts to be exported directly to a file, but can't do this with the rest of a sheet. To work round this this function - * Opens a copy of Excel and loads a file - * Selects a worksheet and then a range of cells in that worksheet - * Copies the select to the clipboard - * Saves the clipboard contents as an image file (it will save as .JPG unless the file name ends .BMP or .PNG) - * Copies a single cell to the clipboard (to prevent the "you have put a lot in the clipboard" message appearing) - * Closes Excel -#> -Param ( - #Path to the Excel file - [parameter(Mandatory=$true)] - $Path, - #Worksheet name - if none is specified "Sheet1" will be assumed - $workSheetname = "Sheet1" , - #Range of cells within the sheet, e.g "A1:Z99" - [parameter(Mandatory=$true)] - $range, - #A bmp, png or jpg file where the result will be saved - $destination = "$pwd\temp.png", - #If specified opens the image in the default viewer. - [switch]$show -) - $extension = $destination -replace '^.*\.(\w+)$' ,'$1' - if ($extension -in @('JPEG','BMP','PNG')) { - $Format = [system.Drawing.Imaging.ImageFormat]$extension - } #if we don't recognise the extension OR if it is JPG with an E, use JPEG format - else { $Format = [system.Drawing.Imaging.ImageFormat]::Jpeg} - Write-Progress -Activity "Exporting $range of $workSheetname in $Path" -Status "Starting Excel" - $xlApp = New-Object -ComObject "Excel.Application" - Write-Progress -Activity "Exporting $range of $workSheetname in $Path" -Status "Opening Workbook and copying data" - $xlWbk = $xlApp.Workbooks.Open($Path) - $xlWbk.Worksheets($workSheetname).Select() - $xlWbk.ActiveSheet.Range($range).Select() | Out-Null - $xlApp.Selection.Copy() | Out-Null - Write-Progress -Activity "Exporting $range of $workSheetname in $Path" -Status "Saving copied data" - # Get-Clipboard came in with PS5. Older versions can use [System.Windows.Clipboard] but it is ugly. - $image = Get-Clipboard -Format Image - $image.Save($destination, $Format) - Write-Progress -Activity "Exporting $range of $workSheetname in $Path" -Status "Closing Excel" - $xlWbk.ActiveSheet.Range("a1").Select() | Out-Null - $xlApp.Selection.Copy() | Out-Null - $xlApp.Quit() - Write-Progress -Activity "Exporting $range of $workSheetname in $Path" -Completed - if ($show) {Start-Process -FilePath $destination} - else {Get-Item -Path $destination} + <# + .Synopsis + Gets the specified part of an Excel file and exports it as an image + .Description + Excel allows charts to be exported directly to a file, but can't do this with the rest of a sheet. To work round this this function + * Opens a copy of Excel and loads a file + * Selects a worksheet and then a range of cells in that worksheet + * Copies the select to the clipboard + * Saves the clipboard contents as an image file (it will save as .JPG unless the file name ends .BMP or .PNG) + * Copies a single cell to the clipboard (to prevent the "you have put a lot in the clipboard" message appearing) + * Closes Excel + #> + Param ( + #Path to the Excel file + [parameter(Mandatory=$true)] + $Path, + #Worksheet name - if none is specified "Sheet1" will be assumed + $workSheetname = "Sheet1" , + #Range of cells within the sheet, e.g "A1:Z99" + [parameter(Mandatory=$true)] + $range, + #A bmp, png or jpg file where the result will be saved + $destination = "$pwd\temp.png", + #If specified opens the image in the default viewer. + [switch]$show + ) + $extension = $destination -replace '^.*\.(\w+)$' ,'$1' + if ($extension -in @('JPEG','BMP','PNG')) { + $Format = [system.Drawing.Imaging.ImageFormat]$extension + } #if we don't recognise the extension OR if it is JPG with an E, use JPEG format + else { $Format = [system.Drawing.Imaging.ImageFormat]::Jpeg} + Write-Progress -Activity "Exporting $range of $workSheetname in $Path" -Status "Starting Excel" + $xlApp = New-Object -ComObject "Excel.Application" + Write-Progress -Activity "Exporting $range of $workSheetname in $Path" -Status "Opening Workbook and copying data" + $xlWbk = $xlApp.Workbooks.Open($Path) + $xlWbk.Worksheets($workSheetname).Select() + $xlWbk.ActiveSheet.Range($range).Select() | Out-Null + $xlApp.Selection.Copy() | Out-Null + Write-Progress -Activity "Exporting $range of $workSheetname in $Path" -Status "Saving copied data" + # Get-Clipboard came in with PS5. Older versions can use [System.Windows.Clipboard] but it is ugly. + $image = Get-Clipboard -Format Image + $image.Save($destination, $Format) + Write-Progress -Activity "Exporting $range of $workSheetname in $Path" -Status "Closing Excel" + $xlWbk.ActiveSheet.Range("a1").Select() | Out-Null + $xlApp.Selection.Copy() | Out-Null + $xlApp.Quit() + Write-Progress -Activity "Exporting $range of $workSheetname in $Path" -Completed + if ($show) {Start-Process -FilePath $destination} + else {Get-Item -Path $destination} } <# del demo*.xlsx diff --git a/ConvertFromExcelData.ps1 b/ConvertFromExcelData.ps1 index bd75895..3421bc1 100644 --- a/ConvertFromExcelData.ps1 +++ b/ConvertFromExcelData.ps1 @@ -1,25 +1,25 @@ function ConvertFrom-ExcelData { <# - .SYNOPSIS - Reads data from a sheet, and for each row, calls a custom scriptblock with a list of property names and the row of data. + .SYNOPSIS + Reads data from a sheet, and for each row, calls a custom scriptblock with a list of property names and the row of data. - - .EXAMPLE - ConvertFrom-ExcelData .\testSQLGen.xlsx { - param($propertyNames, $record) + + .EXAMPLE + ConvertFrom-ExcelData .\testSQLGen.xlsx { + param($propertyNames, $record) - $reportRecord = @() - foreach ($pn in $propertyNames) { - $reportRecord += "{0}: {1}" -f $pn, $record.$pn + $reportRecord = @() + foreach ($pn in $propertyNames) { + $reportRecord += "{0}: {1}" -f $pn, $record.$pn + } + $reportRecord +="" + $reportRecord -join "`r`n" } - $reportRecord +="" - $reportRecord -join "`r`n" -} -First: John -Last: Doe -The Zip: 12345 -.... + First: John + Last: Doe + The Zip: 12345 + .... #> param( [Alias("FullName")] diff --git a/ConvertFromExcelToSQLInsert.ps1 b/ConvertFromExcelToSQLInsert.ps1 index 0f4708a..7a3f00c 100644 --- a/ConvertFromExcelToSQLInsert.ps1 +++ b/ConvertFromExcelToSQLInsert.ps1 @@ -1,51 +1,34 @@ function ConvertFrom-ExcelToSQLInsert { <# - .SYNOPSIS + .SYNOPSIS Generate SQL insert statements from Excel spreadsheet. - - .DESCRIPTION + .DESCRIPTION Generate SQL insert statements from Excel spreadsheet. - - .PARAMETER TableName + .PARAMETER TableName Name of the target database table. - - .PARAMETER Path + .PARAMETER Path Path to an existing .XLSX file - This parameter is passed to Import-Excel as is. - - .PARAMETER WorkSheetname + .PARAMETER WorkSheetname Specifies the name of the worksheet in the Excel workbook to import. By default, if no name is provided, the first worksheet will be imported. - This parameter is passed to Import-Excel as is. - - .PARAMETER StartRow + .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. - - .PARAMETER Header + .PARAMETER Header Specifies custom property names to use, instead of the values defined in the column headers of the TopRow. - - In case you provide less header names than there is data in the worksheet, then only the data with a corresponding header name will be imported and the data without header name will be disregarded. - - In case you provide more header names than there is data in the worksheet, then all data will be imported and all objects will have all the property names you defined in the header names. As such, the last properties will be blanc as there is no data for them. - - .PARAMETER NoHeader + If you provide fewr header names than there is data in the worksheet, then only the data with a corresponding header name will be imported and the data without header name will be disregarded. + If you provide more header names than there is data in the worksheet, then all data will be imported and all objects will have all the property names you defined in the header names. As such, the last properties will be blank as there is no data for them. + .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. - - .PARAMETER DataOnly + .PARAMETER DataOnly Import only rows and columns that contain data, empty rows and empty columns are not imported. - - - .PARAMETER ConvertEmptyStringsToNull + .PARAMETER ConvertEmptyStringsToNull If specified, cells without any data are replaced with NULL, instead of an empty string. - This is to address behviors in certain DBMS where an empty string is insert as 0 for INT column, instead of a NULL value. - .EXAMPLE + .EXAMPLE Generate SQL insert statements from Movies.xlsx file, leaving blank cells as empty strings: ---------------------------------------------------------- @@ -65,7 +48,7 @@ function ConvertFrom-ExcelToSQLInsert { INSERT INTO Movies ('Movie Name', 'Year', 'Rating') Values('Skyfall', '2012', '9'); INSERT INTO Movies ('Movie Name', 'Year', 'Rating') Values('The Avengers', '2012', ''); - .EXAMPLE + .EXAMPLE Generate SQL insert statements from Movies.xlsx file, specify NULL instead of an empty string. ---------------------------------------------------------- @@ -85,7 +68,6 @@ function ConvertFrom-ExcelToSQLInsert { INSERT INTO Movies ('Movie Name', 'Year', 'Rating') Values('Skyfall', '2012', '9'); INSERT INTO Movies ('Movie Name', 'Year', 'Rating') Values('The Avengers', '2012', NULL); - .NOTES #> [CmdletBinding()] param( diff --git a/Examples/JoinWorksheet/Join-Worksheet.sample.ps1 b/Examples/JoinWorksheet/Join-Worksheet.sample.ps1 new file mode 100644 index 0000000..94ba68f --- /dev/null +++ b/Examples/JoinWorksheet/Join-Worksheet.sample.ps1 @@ -0,0 +1,43 @@ +#Get rid of pre-exisiting sheet +$path = "$Env:TEMP\test.xlsx" +remove-item -Path $path -ErrorAction SilentlyContinue + +#Create simple pages for 3 stores with product ID, Product Name, quanity price and total + +@" +ID,Product,Quantity,Price,Total +12001,Nails,37,3.99,147.63 +12002,Hammer,5,12.10,60.5 +12003,Saw,12,15.37,184.44 +12010,Drill,20,8,160 +12011,Crowbar,7,23.48,164.36 +"@ | ConvertFrom-Csv| Export-Excel -Path $path -WorkSheetname Oxford + +@" +ID,Product,Quantity,Price,Total +12001,Nails,53,3.99,211.47 +12002,Hammer,6,12.10,72.60 +12003,Saw,10,15.37,153.70 +12010,Drill,10,8,80 +12012,Pliers,2,14.99,29.98 +"@ | ConvertFrom-Csv| Export-Excel -Path $path -WorkSheetname Abingdon + + +@" +ID,Product,Quantity,Price,Total +12001,Nails,20,3.99,79.80 +12002,Hammer,2,12.10,24.20 +12010,Drill,11,8,88 +12012,Pliers,3,14.99,44.97 +"@ | ConvertFrom-Csv| Export-Excel -Path $path -WorkSheetname Banbury + +#define a pivot table with a chart to show a sales by store, broken down by product +$ptdef = New-PivotTableDefinition -PivotTableName "Summary" -PivotRows "Store" -PivotColumns "Product" -PivotData @{"Total"="SUM"} -IncludePivotChart -ChartTitle "Sales Breakdown" -ChartType ColumnStacked -ChartColumn 10 + +#Join the 3 worksheets. +#Name the combined page "Total" and Name the column with the sheet names "store" (as the sheets 'Oxford','Abingdon' and 'Banbury' are the names of the stores +#Format the data as a table named "Summary", using the style "Light1", put the column headers in bold +#Put in a title and freeze to top of the sheet including title and colmun headings +#Add the Pivot table. +#Show the result +Join-Worksheet -Path $path -WorkSheetName "Total" -Clearsheet -FromLabel "Store" -TableName "Summary" -TableStyle Light1 -AutoSize -BoldTopRow -FreezePane 2,1 -Title "Store Sales Summary" -TitleBold -TitleSize 14 -PivotTableDefinition $ptdef -show diff --git a/Examples/JoinWorksheet/Join-worksheet-blocks.sample.ps1 b/Examples/JoinWorksheet/Join-worksheet-blocks.sample.ps1 new file mode 100644 index 0000000..5ee0506 --- /dev/null +++ b/Examples/JoinWorksheet/Join-worksheet-blocks.sample.ps1 @@ -0,0 +1,11 @@ + +$path = "$env:TEMP\Test.xlsx" +Remove-item -Path $path -ErrorAction SilentlyContinue + Get-WmiObject -Class win32_logicaldisk | + Select-Object -Property DeviceId,VolumeName, Size,Freespace | + Export-Excel -Path $path -WorkSheetname Volumes -NumberFormat "0,000" +Get-NetAdapter | + Select-Object -Property Name,InterfaceDescription,MacAddress,LinkSpeed | + Export-Excel -Path $path -WorkSheetname NetAdapters + +Join-Worksheet -Path $path -HideSource -WorkSheetName Summary -NoHeader -LabelBlocks -AutoSize -Title "Summary" -TitleBold -TitleSize 22 -show diff --git a/Export-Excel.Tests.ps1 b/Export-Excel.Tests.ps1 deleted file mode 100644 index 1d63afc..0000000 --- a/Export-Excel.Tests.ps1 +++ /dev/null @@ -1,620 +0,0 @@ -#Requires -Modules Pester - -# $here = Split-Path -Parent $MyInvocation.MyCommand.Path -# Import-Module $here -Force -Verbose -Import-Module $PSScriptRoot\..\ImportExcel.psd1 -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 - } - - it "Started Excel to display the file " { - Get-process -Name Excel,xlim -ErrorAction SilentlyContinue | should not benullorempty - } - - Start-Sleep -Seconds 5 ; - - #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 -Skip "Killed Excel when Open-Excelpackage was told to " { - Get-process -Name Excel,xlim -ErrorAction SilentlyContinue | should benullorempty - } - - 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 $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 - } - } - - 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. Test adding a named pivot table from a command line parameter - 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 -PivotTableName 'PTOffset' -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 -PivotTableName 'PTOffset' -Path $path -WorkSheetname withOffset -append -WarningAction SilentlyContinue -WarningVariable warnvar - $Excel = Open-ExcelPackage $path - $dataWs = $Excel.Workbook.Worksheets["withOffset"] - $pt = $Excel.Workbook.Worksheets["PTOffset"].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" - $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 - #Catch warning - $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 f608fb6..ded2d94 100644 --- a/Export-Excel.ps1 +++ b/Export-Excel.ps1 @@ -16,14 +16,14 @@ 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 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 NoAliasOrScriptPropeties + Some objects duplicate existing properties by adding 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 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. + Text of a title to be placed in the top left cell. .PARAMETER TitleBold Sets the title in boldface type. .PARAMETER TitleSize @@ -36,22 +36,33 @@ Sets password protection on the workbook. .PARAMETER IncludePivotTable Adds a Pivot table using the data in the worksheet. + .PARAMETER PivotTableName + If a Pivot table is created from command line parameters, specificies the name of the new sheet holding the pivot. If Omitted this will be "WorksheetName-PivotTable" .PARAMETER PivotRows - Name(s) columns from the spreadhseet which will provide the row name(s) in the pivot table. + Name(s) columns from the spreadhseet which will provide the Row name(s) in a pivot table created from command line parameters. .PARAMETER PivotColumns - Name(s) columns from the spreadhseet which will provide the Column name(s) in the pivot table. + Name(s) columns from the spreadhseet which will provide the Column name(s) in a pivot table created from command line parameters. + .PARAMETER PivotFilter + Name(s) columns from the spreadhseet which will provide the Filter name(s) in a pivot table created from command line parameters. .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. + In a pivot table created from command line parameters, the fields to use in the table body is given as a Hash table in the form ColumnName = Average|Count|CountNums|Max|Min|Product|None|StdDev|StdDevP|Sum|Var|VarP . + .PARAMETER NoTotalsInPivot + In a pivot table created from command line parameters, prevents the addition of totals to rows and columns. + .PARAMETER PivotTableDefinition + Instead of describing a single pivot table with mutliple commandline paramters; you can use a HashTable in the form PivotTableName = Definition; + Definition is itself a hashtable with Sheet PivotTows, PivotColumns, PivotData, IncludePivotChart and ChartType values. + .PARAMETER IncludePivotChart + Include a chart with the Pivot table - implies -IncludePivotTable. + .PARAMETER ChartType + The type for Pivot chart (one of Excel's defined chart types) .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 ConditionalFormat + One or more conditional formatting rules defined with New-ConditionalFormattingIconSet. .PARAMETER ConditionalText Applies a 'Conditional formatting rule' in Excel on all the cells. When specific conditions are met a rule is triggered. .PARAMETER NoNumberConversion @@ -66,6 +77,14 @@ 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 BarChart + Creates a "quick" bar chart using the first text column as labels and the first numeric column as values + .PARAMETER ColumnChart + Creates a "quick" column chart using the first text column as labels and the first numeric column as values + .PARAMETER LineChart + Creates a "quick" line chart using the first text column as labels and the first numeric column as values + .PARAMETER PieChart + Creates a "quick" pie chart using the first text column as labels and the first numeric column as values .PARAMETER ExcelChartDefinition A hash table containing ChartType, Title, NoLegend, ShowCategory, ShowPercent, Yrange, Xrange and SeriesHeader for one or more [non-pivot] charts. .PARAMETER HideSheet @@ -99,7 +118,7 @@ .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. + Enables the 'Filter' in Excel on the complete header row. So users can easily sort, filter and/or search the data in the selected column from within Excel. .PARAMETER AutoSize Sizes the width of the Excel column to the maximum width needed to display all the containing data in that cell. .PARAMETER Now @@ -132,8 +151,12 @@ # Blue color for positive numbers and a red color for negative numbers. All numbers will be proceeded by a dollar sign '$'. '[Blue]$#,##0.00;[Red]-$#,##0.00' + .PARAMETER ReZip + If specified, Export-Excel will expand the contents of the .XLSX file (which is multiple files in a zip archive) and rebuilt it. .PARAMETER Show Opens the Excel file immediately after creation. Convenient for viewing the results instantly without having to search for the file first. + .PARAMETER ReturnRange + If specified, Export-Excel returns the range of added cells in the format "A1:Z100" .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. @@ -390,12 +413,16 @@ elseif ($_[0] -notmatch '[a-z]') { throw 'Tablename starts with an invalid character.' } else { $true } })] - [Parameter(ParameterSetName = 'Table' , Mandatory = $true)] - [Parameter(ParameterSetName = 'PackageTable' , Mandatory = $true)] + [Parameter(ParameterSetName = 'Table' , Mandatory = $true, ValueFromPipelineByPropertyName)] + [Parameter(ParameterSetName = 'PackageTable' , Mandatory = $true, ValueFromPipelineByPropertyName)] [String]$TableName, [Parameter(ParameterSetName = 'Table')] [Parameter(ParameterSetName = 'PackageTable')] [OfficeOpenXml.Table.TableStyles]$TableStyle = 'Medium6', + [Switch]$Barchart, + [Switch]$PieChart, + [Switch]$LineChart , + [Switch]$ColumnChart , [Object[]]$ExcelChartDefinition, [String[]]$HideSheet, [Switch]$MoveToStart, @@ -438,8 +465,8 @@ #> Param ( - [Object]$TargetCell, - [Object]$CellValue + $TargetCell, + $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) { @@ -465,7 +492,7 @@ #Write-Verbose "Cell '$Row`:$ColumnIndex' header '$Name' add value '$_' as formula" break } - {[System.Uri]::IsWellFormedUriString($_, [System.UriKind]::Absolute)} { + { [System.Uri]::IsWellFormedUriString($_ , [System.UriKind]::Absolute) } { # Save a hyperlink $TargetCell.Value = $_.AbsoluteUri $TargetCell.HyperLink = $_ @@ -474,8 +501,8 @@ #Write-Verbose "Cell '$Row`:$ColumnIndex' header '$Name' add value '$($_.AbsoluteUri)' as Hyperlink" break } - {(($NoNumberConversion) -and ($NoNumberConversion -contains $Name)) -or - ($NoNumberConversion -eq '*')} { + {( $NoNumberConversion -and ( + ($NoNumberConversion -contains $Name) -or ($NoNumberConversion -eq '*'))) } { #Save text without it to converting to number $TargetCell.Value = $_ #Write-Verbose "Cell '$Row`:$ColumnIndex' header '$Name' add value '$($TargetCell.Value)' unconverted" @@ -484,7 +511,7 @@ Default { #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($_, [System.Globalization.NumberStyles]::Any, [System.Globalization.NumberFormatInfo]::CurrentInfo, [Ref]$number)) { # as simpler version using [Double]::TryParse( $_ , [ref]$number)) was found to cause problems reverted back to the longer version $TargetCell.Value = $number if ($setNumformat) {$targetCell.Style.Numberformat.Format = $Numberformat } @@ -583,15 +610,14 @@ if ($firstTimeThru) { $firstTimeThru = $false $isDataTypeValueType = $TargetData.GetType().name -match 'string|bool|byte|char|decimal|double|float|int|long|sbyte|short|uint|ulong|ushort' + if ($isDataTypeValueType) {$row -= 1} #row incremented before adding values, so it is set to the number of rows inserted at the end Write-Debug "DataTypeName is '$($TargetData.GetType().name)' isDataTypeValueType '$isDataTypeValueType'" } if ($isDataTypeValueType) { $ColumnIndex = $StartColumn - - Add-CellValue -TargetCell $ws.Cells[$Row, $ColumnIndex] -CellValue $TargetData - $Row += 1 + Add-CellValue -TargetCell $ws.Cells[$Row, $ColumnIndex] -CellValue $TargetData } else { #region Add headers @@ -708,7 +734,7 @@ #if the table exists, update it. if ($ws.Tables[$TableName]) { $ws.Tables[$TableName].TableXml.table.ref = $dataRange - $ws.Tables[$TableName].TableStyle = $TableStyle + $ws.Tables[$TableName].TableStyle = $TableStyle } else { $tbl = $ws.Tables.Add($ws.Cells[$dataRange], $TableName) @@ -743,36 +769,35 @@ $params = @{ "SourceRange" = $dataRange } - if ($PivotTableName) {$params.PivotTableName = $PivotTableName} - else {$params.PivotTableName = $WorkSheetname + 'PivotTable'} - 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 ($PivotTableName) {$params.PivotTableName = $PivotTableName} + else {$params.PivotTableName = $WorkSheetname + 'PivotTable'} + 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} + $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 } try { - if ($FreezeTopRow) { - $ws.View.FreezePanes(2, 1) - Write-Verbose -Message "Froze top row" - } - - if ($FreezeTopRowFirstColumn) { + #Allow single switch or two seperate ones. + if ($FreezeTopRowFirstColumn -or ($FreezeTopRow -and $FreezeFirstColumn)) { $ws.View.FreezePanes(2, 2) Write-Verbose -Message "Froze top row and first column" } - - if ($FreezeFirstColumn) { + elseif ($FreezeTopRow) { + $ws.View.FreezePanes(2, 1) + Write-Verbose -Message "Froze top row" + } + elseif ($FreezeFirstColumn) { $ws.View.FreezePanes(1, 2) Write-Verbose -Message "Froze first column" } @@ -791,7 +816,7 @@ } catch {Write-Warning -Message "Failed adding Freezing the panes in worksheet '$WorkSheetname': $_"} - if ($BoldTopRow) { + if ($BoldTopRow) { #it sets bold as far as there are populated cells: for whole row could do $ws.row($x).style.font.bold = $true try { if ($Title) { $range = $ws.Dimension.Address -replace '\d+', ($StartRow + 1) @@ -822,10 +847,34 @@ foreach ($chartDef in $ExcelChartDefinition) { $params = @{} - $chartDef.PSObject.Properties | ForEach-Object {if ($_.value -ne $null) {$params[$_.name] = $_.value}} + $chartDef.PSObject.Properties | ForEach-Object {if ( $null -ne $_.value) {$params[$_.name] = $_.value}} Add-ExcelChart -Worksheet $ws @params } + if ($Barchart -or $PieChart -or $LineChart -or $ColumnChart) { + if ($NoHeader) {$FirstDataRow = $startRow} + else {$FirstDataRow = $startRow + 1 } + $range = [OfficeOpenXml.ExcelAddress]::TranslateFromR1C1("R[$FirstDataRow]C[$startColumn]:R[$FirstDataRow]C[$lastCol]",0,0) + $xCol = $ws.cells[$range] | Where-Object {$_.value -is [string] } | ForEach-Object {$_.start.column} | Sort-Object | Select-Object -first 1 + $yCol = $ws.cells[$range] | Where-Object {$_.value -is [valueType] } | ForEach-Object {$_.start.column} | Sort-Object | Select-Object -first 1 + $params = @{ + xrange = [OfficeOpenXml.ExcelAddress]::TranslateFromR1C1("R[$FirstDataRow]C[$xcol]:R[$($lastrow)]C[$xcol]",0,0) ; + yrange = [OfficeOpenXml.ExcelAddress]::TranslateFromR1C1("R[$FirstDataRow]C[$ycol]:R[$($lastrow)]C[$ycol]",0,0) ; + title = ""; + Column = ($lastCol +1) ; + Width = 1200 + } + if ($NoHeader) {$params["NoHeader"] = $true} + else {$Params["SeriesHeader"] = $ws.Cells[$startRow, $YCol].Value} + if ($ColumnChart) {$Params["chartType"] = "ColumnStacked" } + elseif ($Barchart) {$Params["chartType"] = "BarStacked" } + elseif ($PieChart) {$Params["chartType"] = "PieExploded3D" } + elseif ($LineChart) {$Params["chartType"] = "Line" } + + Add-ExcelChart -Worksheet $ws @params + + } + foreach ($ct in $ConditionalText) { try { $cfParams = @{RuleType = $ct.ConditionalType; ConditionValue = $ct.text ; @@ -859,7 +908,7 @@ if ($PassThru) { $pkg } else { - if ($ReturnRange) {$ws.Dimension.Address } + if ($ReturnRange) {$dataRange } $pkg.Save() Write-Verbose -Message "Saved workbook $($pkg.File)" @@ -874,7 +923,7 @@ } try { $TempZipPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([System.IO.Path]::GetRandomFileName()) - [io.compression.zipfile]::ExtractToDirectory($pkg.File, $TempZipPath) | Out-Null + [io.compression.zipfile]::ExtractToDirectory($pkg.File, $TempZipPath) | Out-Null Remove-Item $pkg.File -Force [io.compression.zipfile]::CreateFromDirectory($TempZipPath, $pkg.File) | Out-Null } @@ -892,7 +941,7 @@ function New-PivotTableDefinition { <# .Synopsis - Creates Pivot table definitons for export excel + 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. @@ -963,6 +1012,13 @@ function New-PivotTableDefinition { @{$PivotTableName = $parameters} } function Add-WorkSheet { + <# + .Synopsis + Adds a workshet to an existing workbook. + .Description + If the named worksheet already exists, the -clearsheet parameter decides whether it should be deleted and a new one returned, + or if not specified the existing sheet will be returned. + #> [cmdletBinding()] [OutputType([OfficeOpenXml.ExcelWorksheet])] param( @@ -1040,15 +1096,21 @@ function Add-WorkSheet { return $ws } function Add-PivotTable { - +<# + .Synopsis + Adds a Pivot table (and optional pivot chart) to a workbook + .Description + If the pivot table already exists, the source data will be updated. +#> param ( - # Parameter help description + #Name for the new Pivot table - this will be the name of a sheet in the workbook [Parameter(Mandatory = $true)] $PivotTableName, + #An excel package object for the workbook. $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/ + #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, @@ -1164,11 +1226,17 @@ function Add-PivotTable { } } function Add-ExcelChart { + <# + .Synopsis + Creates a chart in an Existing excel worksheet + #> [cmdletbinding()] param( + #An object representing the worksheet where the chart should be added. [OfficeOpenXml.ExcelWorksheet]$Worksheet, [String]$Title = "Chart Title", #$Header, Not used but referenced previously + #The Type of chart (Area, Line, Pie etc) [OfficeOpenXml.Drawing.Chart.eChartType]$ChartType = "ColumnStacked", $XRange, $YRange, @@ -1208,12 +1276,13 @@ function Add-ExcelChart { try { $ChartName = 'Chart' + (Split-Path -Leaf ([System.IO.path]::GetTempFileName())) -replace 'tmp|\.', '' $chart = $Worksheet.Drawings.AddChart($ChartName, $ChartType) - $chart.Title.Text = $Title - if ($TitleBold) {$chart.Title.Font.Bold = $true} - if ($TitleSize) {$chart.Title.Font.Size = $TitleSize} - + if ($Title) { + $chart.Title.Text = $Title + if ($TitleBold) {$chart.Title.Font.Bold = $true} + if ($TitleSize) {$chart.Title.Font.Size = $TitleSize} + } if ($NoLegend) { $chart.Legend.Remove() } - else { + else { if ($LegendPostion) {$Chart.Legend.Position = $LegendPostion} if ($LegendSize) {$chart.Legend.Font.Size = $LegendSize} if ($legendBold) {$chart.Legend.Font.Bold = $true} @@ -1227,8 +1296,8 @@ function Add-ExcelChart { if ($XAxisPosition) {$chart.XAxis.AxisPosition = $XAxisPosition} if ($XMajorUnit) {$chart.XAxis.MajorUnit = $XMajorUnit} if ($XMinorUnit) {$chart.XAxis.MinorUnit = $XMinorUnit} - if ($XMinValue -ne $null) {$chart.XAxis.MinValue = $XMinValue} - if ($XMaxValue -ne $null) {$chart.XAxis.MaxValue = $XMaxValue} + if ($null -ne $XMinValue) {$chart.XAxis.MinValue = $XMinValue} + if ($null -ne $XMaxValue) {$chart.XAxis.MaxValue = $XMaxValue} if ($XAxisNumberformat) {$chart.XAxis.Format = $XAxisNumberformat} if ($YAxisTitleText) { @@ -1239,12 +1308,12 @@ function Add-ExcelChart { if ($YAxisPosition) {$chart.YAxis.AxisPosition = $YAxisPosition} if ($YMajorUnit) {$chart.YAxis.MajorUnit = $YMajorUnit} if ($YMinorUnit) {$chart.YAxis.MinorUnit = $YMinorUnit} - if ($YMinValue-ne $null) {$chart.YAxis.MinValue = $YMinValue} - if ($YMaxValue-ne $null) {$chart.YAxis.MaxValue = $YMaxValue} + if ($null -ne $YMinValue){$chart.YAxis.MinValue = $YMinValue} + if ($null -ne $YMaxValue){$chart.YAxis.MaxValue = $YMaxValue} if ($YAxisNumberformat) {$chart.YAxis.Format = $YAxisNumberformat} - if ($chart.Datalabel -ne $null) { - $chart.Datalabel.ShowCategory = [boolean]$ShowCategory - $chart.Datalabel.ShowPercent = [boolean]$ShowPercent + if ($null -ne $chart.Datalabel) { + $chart.Datalabel.ShowCategory = [boolean]$ShowCategory + $chart.Datalabel.ShowPercent = [boolean]$ShowPercent } $chart.SetPosition($Row, $RowOffsetPixels, $Column, $ColumnOffsetPixels) diff --git a/Export-charts.ps1 b/Export-charts.ps1 index 27ec7ab..e51d894 100644 --- a/Export-charts.ps1 +++ b/Export-charts.ps1 @@ -2,7 +2,7 @@ .Synopsis Exports the charts in an Excel spreadSheet .Example - Export-Charts .\test,xlsx + Export-Charts .\test.xlsx Exports the charts in test.xlsx to JPEG files in the current directory. .Example @@ -13,7 +13,7 @@ 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. + #If specified, output file objects representing the image files [switch]$Passthru, #Format to write - JPG by default [ValidateSet("JPG","PNG","GIF")] diff --git a/Get-ExcelSheetInfo.ps1 b/Get-ExcelSheetInfo.ps1 index 6ab93f5..bb00ee3 100644 --- a/Get-ExcelSheetInfo.ps1 +++ b/Get-ExcelSheetInfo.ps1 @@ -1,24 +1,20 @@ Function Get-ExcelSheetInfo { <# - .SYNOPSIS + .SYNOPSIS Get worksheet names and their indices of an Excel workbook. - - .DESCRIPTION + .DESCRIPTION The Get-ExcelSheetInfo cmdlet gets worksheet names and their indices of an Excel workbook. - - .PARAMETER Path + .PARAMETER Path Specifies the path to the Excel file. This parameter is required. - - .EXAMPLE + .EXAMPLE Get-ExcelSheetInfo .\Test.xlsx - .NOTES + .NOTES CHANGELOG 2016/01/07 Added Created by Johan Akerstrom (https://github.com/CosmosKey) - .LINK + .LINK https://github.com/dfinke/ImportExcel - #> [CmdletBinding()] diff --git a/Get-ExcelWorkbookInfo.ps1 b/Get-ExcelWorkbookInfo.ps1 index acd2e92..564e139 100644 --- a/Get-ExcelWorkbookInfo.ps1 +++ b/Get-ExcelWorkbookInfo.ps1 @@ -1,15 +1,12 @@ Function Get-ExcelWorkbookInfo { <# - .SYNOPSIS + .SYNOPSIS Retrieve information of an Excel workbook. - - .DESCRIPTION + .DESCRIPTION The Get-ExcelWorkbookInfo cmdlet retrieves information (LastModifiedBy, LastPrinted, Created, Modified, ...) fron an Excel workbook. These are the same details that are visible in Windows Explorer when right clicking the Excel file, selecting Properties and check the Details tabpage. - - .PARAMETER Path + .PARAMETER Path Specifies the path to the Excel file. This parameter is required. - - .EXAMPLE + .EXAMPLE Get-ExcelWorkbookInfo .\Test.xlsx CorePropertiesXml : #document @@ -32,11 +29,11 @@ Modified : 10/02/2017 12:45:37 CustomPropertiesXml : #document - .NOTES + .NOTES CHANGELOG 2016/01/07 Added Created by Johan Akerstrom (https://github.com/CosmosKey) - .LINK + .LINK https://github.com/dfinke/ImportExcel #> diff --git a/Get-XYRange.ps1 b/Get-XYRange.ps1 index 9aa0cff..179679d 100644 --- a/Get-XYRange.ps1 +++ b/Get-XYRange.ps1 @@ -1,7 +1,7 @@ function Get-XYRange { param($targetData) - $record = $targetData| Select-Object -First 1 + $record = $targetData| select -First 1 $p=$record.psobject.Properties.name $infer = for ($idx = 0; $idx -lt $p.Count; $idx++) { @@ -15,12 +15,12 @@ function Get-XYRange { Name = $name Value = $value DataType = $result.DataType - ExcelColumn = [OfficeOpenXml.ExcelAddress]::TranslateFromR1C1("R[1]C[$($idx+1)]", 0 , 0) -replace "\d+", "" #(Get-ExcelColumnName ($idx + 1)).ColumnName + ExcelColumn = (Get-ExcelColumnName ($idx+1)).ColumnName } } [PSCustomObject]@{ - XRange = $infer | ? {$_.datatype -match 'string'} | Select-Object -First 1 excelcolumn, name - YRange = $infer | ? {$_.datatype -match 'int|double'} |Select-Object -First 1 excelcolumn, name + XRange = $infer | ? {$_.datatype -match 'string'} | select -First 1 excelcolumn, name + YRange = $infer | ? {$_.datatype -match 'int|double'} |select -First 1 excelcolumn, name } } \ No newline at end of file diff --git a/ImportExcel.psm1 b/ImportExcel.psm1 index 352056c..c214007 100644 --- a/ImportExcel.psm1 +++ b/ImportExcel.psm1 @@ -370,7 +370,7 @@ function Import-Excel { $colHash = @{} $rowHash = @{} foreach ($cell in $Worksheet.Cells[$range]) { - if ($cell.Value -ne $null) {$colHash[$cell.Start.Column]=1; $rowHash[$cell.Start.row]=1 } + if ($null -ne $cell.Value ) {$colHash[$cell.Start.Column]=1; $rowHash[$cell.Start.row]=1 } } $rows = ( $StartRow..$EndRow ).Where({$rowHash[$_]}) $columns = ($StartColumn..$EndColumn).Where({$colHash[$_]}) diff --git a/Join-Worksheet.ps1 b/Join-Worksheet.ps1 index e986d91..8786d1e 100644 --- a/Join-Worksheet.ps1 +++ b/Join-Worksheet.ps1 @@ -1,4 +1,5 @@ function Join-Worksheet { + [CmdletBinding(DefaultParameterSetName = 'Default')] <# .SYNOPSIS Combines data on all the sheets in an Excel worksheet onto a single sheet. @@ -23,24 +24,24 @@ .EXAMPLE Get-WmiObject -Class win32_logicaldisk | select -Property DeviceId,VolumeName, Size,Freespace | - Export-Excel -Path "$env:computerName.xlsx" -WorkSheetname Volumes + Export-Excel -Path "$env:computerName.xlsx" -WorkSheetname Volumes -NumberFormat "0,000" Get-NetAdapter | Select-Object Name,InterfaceDescription,MacAddress,LinkSpeed | Export-Excel -Path "$env:COMPUTERNAME.xlsx" -WorkSheetname NetAdapter - Join-Worksheet -Path "$env:COMPUTERNAME.xlsx" -WorkSheetName Summay -Title "Summary" -TitleBold -TitleSize 22 -NoHeader -LabelBlocks -AutoSize -HideSource + Join-Worksheet -Path "$env:COMPUTERNAME.xlsx" -WorkSheetName Summary -Title "Summary" -TitleBold -TitleSize 22 -NoHeader -LabelBlocks -AutoSize -HideSource -show The first two command get logical disk and network card information; each type is exported to its own sheet in a workbook. The Join-worksheet command copies both onto a page named "Summary". Because the data is disimilar -NoHeader is specified, ensuring the whole of each page is copied. Specifying -LabelBlocks causes each sheet's name to become a title on the summary page above the copied data. The source data is hidden, a title is addded in 22 point boldface and the columns are sized to fit the data. #> - - [CmdletBinding(DefaultParameterSetName = 'Default')] param ( # Path to a new or existing .XLSX file. [Parameter(ParameterSetName = "Default", Position = 0)] + [Parameter(ParameterSetName = "Table" , Position = 0)] [String]$Path , # An object representing an Excel Package - usually this is returned by specifying -Passthru allowing multiple commands to work on the same Workbook without saving and reloading each time. - [Parameter(Mandatory = $true, ParameterSetName = "Package")] + [Parameter(Mandatory = $true, ParameterSetName = "PackageDefault")] + [Parameter(Mandatory = $true, ParameterSetName = "PackageTable")] [OfficeOpenXml.ExcelPackage]$ExcelPackage, # The name of a sheet within the workbook where the other sheets will be joined together - "Combined" by default. $WorkSheetName = 'Combined', @@ -63,6 +64,8 @@ # Freezes panes at specified coordinates (in the form RowNumber , ColumnNumber). [Int[]]$FreezePane, #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. + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'PackageDefault')] [Switch]$AutoFilter, #Makes the top Row boldface. [Switch]$BoldTopRow, @@ -78,10 +81,36 @@ [Switch]$TitleBold, #Sets the point size for the title. [Int]$TitleSize = 22, - # Hashtable(s) with Sheet PivotRows, PivotColumns, PivotData, IncludePivotChart and ChartType values to specify a definition for one or more pivot table(s). + #Hashtable(s) with Sheet PivotRows, PivotColumns, PivotData, IncludePivotChart and ChartType values to specify a definition for one or more pivot table(s). [Hashtable]$PivotTableDefinition, - # A hashtable containing ChartType, Title, NoLegend, ShowCategory, ShowPercent, Yrange, Xrange and SeriesHeader for one or more [non-pivot] charts. + #A hashtable containing ChartType, Title, NoLegend, ShowCategory, ShowPercent, Yrange, Xrange and SeriesHeader for one or more [non-pivot] charts. [Object[]]$ExcelChartDefinition, + [Object[]]$ConditionalFormat, + #Applies a 'Conditional formatting rule' in Excel on all the cells. When specific conditions are met a rule is triggered. + [Object[]]$ConditionalText, + #Makes each column a named range. + [switch]$AutoNameRange, + #Makes the data in the worksheet a named range. + [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 (-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)] + # Makes the data in the worksheet a table with a name applies a style to it. Name must not contain spaces. + [String]$TableName, + [Parameter(ParameterSetName = 'Table')] + [Parameter(ParameterSetName = 'PackageTable')] + [OfficeOpenXml.Table.TableStyles]$TableStyle = 'Medium6', + #Selects the style for the named table - defaults to 'Medium6'. + [switch]$ReturnRange, #Opens the Excel file immediately after creation. Convenient for viewing the results instantly without having to search for the file first. [switch]$Show, #If specified, an object representing the unsaved Excel package will be returned, it then needs to be saved. @@ -90,6 +119,7 @@ #region get target worksheet, select it and move it to the end. if ($Path -and -not $ExcelPackage) {$ExcelPackage = Open-ExcelPackage -path $Path } $destinationSheet = Add-WorkSheet -ExcelPackage $ExcelPackage -WorkSheetname $WorkSheetName -ClearSheet:$Clearsheet + foreach ($w in $ExcelPackage.Workbook.Worksheets) {$w.view.TabSelected = $false} $destinationSheet.View.TabSelected = $true $ExcelPackage.Workbook.Worksheets.MoveToEnd($WorkSheetName) #row to insert at will be 1 on a blank sheet and lastrow + 1 on populated one @@ -153,6 +183,7 @@ 'Path', 'Clearsheet', 'NoHeader', 'FromLabel', 'LabelBlocks', 'HideSource', 'Title', 'TitleFillPattern', 'TitleBackgroundColor', 'TitleBold', 'TitleSize' | ForEach-Object {[void]$params.Remove($_)} if ($params.Keys.Count) { + if ($Title) { $params.StartRow = 2} $params.WorkSheetName = $WorkSheetName $params.ExcelPackage = $ExcelPackage Export-Excel @Params diff --git a/Merge-worksheet.ps1 b/Merge-worksheet.ps1 index b052731..3046bfb 100644 --- a/Merge-worksheet.ps1 +++ b/Merge-worksheet.ps1 @@ -5,68 +5,68 @@ .Description The Compare-Worksheet command takes two worksheets and marks differences in the source document, and optionally outputs a grid showing the changes. By contrast the Merge-Worksheet command takes the worksheets and combines them into a single sheet showing the old and new data side by side . - Although it is designed to work with Excel data it can work with arrays of any kind of object; so it can be a merge *of* worksheets, or a merge *to* worksheet. - .Example + Although it is designed to work with Excel data it can work with arrays of any kind of object; so it can be a merge *of* worksheets, or a merge *to* worksheet. + .Example merge-worksheet "Server54.xlsx" "Server55.xlsx" -WorkSheetName services -OutputFile Services.xlsx -OutputSheetName 54-55 -show - The workbooks contain audit information for two servers, one page contains a list of services. This command creates a worksheet named 54-55 - in a workbook named services which shows all the services and their differences, and opens it in Excel. - .Example + The workbooks contain audit information for two servers, one page contains a list of services. This command creates a worksheet named 54-55 + in a workbook named services which shows all the services and their differences, and opens it in Excel. + .Example merge-worksheet "Server54.xlsx" "Server55.xlsx" -WorkSheetName services -OutputFile Services.xlsx -OutputSheetName 54-55 -HideEqual -AddBackgroundColor LightBlue -show - This modifies the previous command to hide the equal rows in the output sheet and changes the color used to mark rows added to the second file. + This modifies the previous command to hide the equal rows in the output sheet and changes the color used to mark rows added to the second file. .Example merge-worksheet -OutputFile .\j1.xlsx -OutputSheetName test11 -ReferenceObject (dir .\ImportExcel\4.0.7) -DifferenceObject (dir .\ImportExcel\4.0.8) -Property Length -Show - This version compares two directories, and marks what has changed. - Because no "Key" property is given, "Name" is assumed to be the key and the only other property examined is length. + This version compares two directories, and marks what has changed. + Because no "Key" property is given, "Name" is assumed to be the key and the only other property examined is length. Files which are added or deleted or have changed size will be highlighed in the output sheet. Changes to dates or other attributes will be ignored. .Example - merge-worksheet -RefO (dir .\ImportExcel\4.0.7) -DiffO (dir .\ImportExcel\4.0.8) -Pr Length | Out-GridView - This time no file is written and the results -which include all properties, not just length, are output and sent to Out-Gridview. + merge-worksheet -RefO (dir .\ImportExcel\4.0.7) -DiffO (dir .\ImportExcel\4.0.8) -Pr Length | Out-GridView + This time no file is written and the results -which include all properties, not just length, are output and sent to Out-Gridview. This version uses aliases to shorten the parameters, - (OutputFileName can be "outFile" and the sheet "OutSheet" : DifferenceObject & ReferenceObject can be DiffObject & RefObject). + (OutputFileName can be "outFile" and the sheet "OutSheet" : DifferenceObject & ReferenceObject can be DiffObject & RefObject). #> - [cmdletbinding(SupportsShouldProcess=$true)] + [cmdletbinding(SupportsShouldProcess=$true)] Param( #First Excel file to compare. You can compare two Excel files or two other objects but not one of each. [parameter(ParameterSetName='A',Mandatory=$true,Position=0)] [parameter(ParameterSetName='B',Mandatory=$true,Position=0)] [parameter(ParameterSetName='C',Mandatory=$true,Position=0)] $Referencefile , - + #Second Excel file to compare. [parameter(ParameterSetName='A',Mandatory=$true,Position=1)] [parameter(ParameterSetName='B',Mandatory=$true,Position=1)] [parameter(ParameterSetName='C',Mandatory=$true,Position=1)] [parameter(ParameterSetName='E',Mandatory=$true,Position=1)] - $Differencefile , - + $Differencefile , + #Name(s) of worksheets to compare, [parameter(ParameterSetName='A',Position=2)] [parameter(ParameterSetName='B',Position=2)] [parameter(ParameterSetName='C',Position=2)] [parameter(ParameterSetName='E',Position=2)] $WorkSheetName = "Sheet1", - + #The row from where we start to import data, all rows above the StartRow are disregarded. By default this is the first row. [parameter(ParameterSetName='A')] [parameter(ParameterSetName='B')] [parameter(ParameterSetName='C')] [parameter(ParameterSetName='E')] - [int]$Startrow = 1, - + [int]$Startrow = 1, + #Specifies custom property names to use, instead of the values defined in the column headers of the TopRow. [Parameter(ParameterSetName='B',Mandatory=$true)] - [String[]]$Headername, - + [String[]]$Headername, + #Automatically generate property names (P1, P2, P3, ..) instead of the using the values the top row of the sheet. [Parameter(ParameterSetName='C',Mandatory=$true)] - [switch]$NoHeader, - - #Object to compare if a worksheet is NOT being used. + [switch]$NoHeader, + + #Object to compare if a worksheet is NOT being used. [parameter(ParameterSetName='D',Mandatory=$true)] [parameter(ParameterSetName='E',Mandatory=$true)] [Alias('RefObject')] $ReferenceObject , - #Object to compare if a worksheet is NOT being used. + #Object to compare if a worksheet is NOT being used. [parameter(ParameterSetName='D',Mandatory=$true,Position=1)] [Alias('DiffObject')] $DifferenceObject , @@ -76,185 +76,183 @@ #File to hold merged data. [parameter(Position=3)] [Alias('OutFile')] - $OutputFile , - #Name of worksheet to output - if none specified will use the reference worksheet name. - [parameter(Position=4)] + $OutputFile , + #Name of worksheet to output - if none specified will use the reference worksheet name. + [parameter(Position=4)] [Alias('OutSheet')] $OutputSheetName = "Sheet1", #Properties to include in the DIFF - supports wildcards, default is "*". $Property = "*" , - #Properties to exclude from the the search - supports wildcards. + #Properties to exclude from the the search - supports wildcards. $ExcludeProperty , #Name of a column which is unique used to pair up rows from the refence and difference side, default is "Name". $Key = "Name" , - #Sets the font color for the "key" field; this means you can filter by color to get only changed rows. - [System.Drawing.Color]$KeyFontColor = "DarkRed", - #Sets the background color for changed rows. + #Sets the font color for the "key" field; this means you can filter by color to get only changed rows. + [System.Drawing.Color]$KeyFontColor = "DarkRed", + #Sets the background color for changed rows. [System.Drawing.Color]$ChangeBackgroundColor = "Orange", - #Sets the background color for rows in the reference but deleted from the difference sheet. - [System.Drawing.Color]$DeleteBackgroundColor = "LightPink", - #Sets the background color for rows not in the reference but added to the difference sheet. - [System.Drawing.Color]$AddBackgroundColor = "PaleGreen", - #if Specified hides the rows in the spreadsheet that are equal and only shows changes, added or deleted rows. + #Sets the background color for rows in the reference but deleted from the difference sheet. + [System.Drawing.Color]$DeleteBackgroundColor = "LightPink", + #Sets the background color for rows not in the reference but added to the difference sheet. + [System.Drawing.Color]$AddBackgroundColor = "PaleGreen", + #if Specified hides the rows in the spreadsheet that are equal and only shows changes, added or deleted rows. [switch]$HideEqual , #If specified outputs the data to the pipeline (you can add -whatif so it the command only outputs to the pipeline). [switch]$Passthru , - #If specified, opens the output workbook. + #If specified, opens the output workbook. [Switch]$Show ) - + #region Read Excel data if ($Referencefile -and $Differencefile) { - #if the filenames don't resolve, give up now. + #if the filenames don't resolve, give up now. try { $oneFile = ((Resolve-Path -Path $Referencefile -ErrorAction Stop).path -eq (Resolve-Path -Path $Differencefile -ErrorAction Stop).path)} - Catch { Write-Warning -Message "Could not Resolve the filenames." ; return } - - #If we have one file , we must have two different worksheet names. If we have two files $worksheetName can be a single string or two strings. + Catch { Write-Warning -Message "Could not Resolve the filenames." ; return } + + #If we have one file , we must have two different worksheet names. If we have two files $worksheetName can be a single string or two strings. if ($onefile -and ( ($WorkSheetName.count -ne 2) -or $WorkSheetName[0] -eq $WorkSheetName[1] ) ) { - Write-Warning -Message "If both the Reference and difference file are the same then worksheet name must provide 2 different names" + Write-Warning -Message "If both the Reference and difference file are the same then worksheet name must provide 2 different names" return } - if ($WorkSheetName.count -eq 2) {$workSheet2 = $DiffPrefix = $WorkSheetName[1] ; $worksheet1 = $WorkSheetName[0] ; } - elseif ($WorkSheetName -is [string]) {$worksheet2 = $workSheet1 = $WorkSheetName ; + if ($WorkSheetName.count -eq 2) {$workSheet2 = $DiffPrefix = $WorkSheetName[1] ; $worksheet1 = $WorkSheetName[0] ; } + elseif ($WorkSheetName -is [string]) {$worksheet2 = $workSheet1 = $WorkSheetName ; $DiffPrefix = (Split-Path -Path $Differencefile -Leaf) -replace "\.xlsx$","" } - else {Write-Warning -Message "You must provide either a single worksheet name or two names." ; return } - - $params= @{ ErrorAction = [System.Management.Automation.ActionPreference]::Stop } + else {Write-Warning -Message "You must provide either a single worksheet name or two names." ; return } + + $params= @{ ErrorAction = [System.Management.Automation.ActionPreference]::Stop } foreach ($p in @("HeaderName","NoHeader","StartRow")) {if ($PSBoundParameters[$p]) {$params[$p] = $PSBoundParameters[$p]}} try { - $ReferenceObject = Import-Excel -Path $Referencefile -WorksheetName $WorkSheet1 @params - $DifferenceObject = Import-Excel -Path $Differencefile -WorksheetName $WorkSheet2 @Params + $ReferenceObject = Import-Excel -Path $Referencefile -WorksheetName $WorkSheet1 @params + $DifferenceObject = Import-Excel -Path $Differencefile -WorksheetName $WorkSheet2 @Params } - Catch {Write-Warning -Message "Could not read the worksheet from $Referencefile::$worksheet1 and/or $Differencefile::$worksheet2." ; return } - if ($NoHeader) {$firstDataRow = $Startrow } else {$firstDataRow = $Startrow + 1} + Catch {Write-Warning -Message "Could not read the worksheet from $Referencefile::$worksheet1 and/or $Differencefile::$worksheet2." ; return } + if ($NoHeader) {$firstDataRow = $Startrow } else {$firstDataRow = $Startrow + 1} } - elseif ( $Differencefile) { - if ($WorkSheetName -isnot [string]) {Write-Warning -Message "You must provide a single worksheet name." ; return } - $params = @{WorkSheetName=$WorkSheetName; Path=$Differencefile; ErrorAction = [System.Management.Automation.ActionPreference]::Stop ;} + elseif ( $Differencefile) { + if ($WorkSheetName -isnot [string]) {Write-Warning -Message "You must provide a single worksheet name." ; return } + $params = @{WorkSheetName=$WorkSheetName; Path=$Differencefile; ErrorAction = [System.Management.Automation.ActionPreference]::Stop ;} try {$DifferenceObject = Import-Excel @Params } - Catch {Write-Warning -Message "Could not read the worksheet '$WorkSheetName' from $Differencefile::$WorkSheetName." ; return } - if ($DiffPrefix -eq "=>" ) { - $DiffPrefix = (Split-Path -Path $Differencefile -Leaf) -replace "\.xlsx$","" + Catch {Write-Warning -Message "Could not read the worksheet '$WorkSheetName' from $Differencefile::$WorkSheetName." ; return } + if ($DiffPrefix -eq "=>" ) { + $DiffPrefix = (Split-Path -Path $Differencefile -Leaf) -replace "\.xlsx$","" } - if ($NoHeader) {$firstDataRow = $Startrow } else {$firstDataRow = $Startrow + 1} + if ($NoHeader) {$firstDataRow = $Startrow } else {$firstDataRow = $Startrow + 1} } - else { $firstDataRow = 1 } - #endregion + else { $firstDataRow = 1 } + #endregion #region Set lists of properties and row numbers - #Make a list of properties/headings using the Property (default "*") and ExcludeProperty parameters - $propList = @() + #Make a list of properties/headings using the Property (default "*") and ExcludeProperty parameters + $propList = @() $DifferenceObject = $DifferenceObject | Update-FirstObjectProperties - $headings = $DifferenceObject[0].psobject.Properties.Name # This preserves the sequence - using get-member would sort them alphabetically! There may be extra properties in - if ($NoHeader -and "Name" -eq $Key) {$Key = "p1"} + $headings = $DifferenceObject[0].psobject.Properties.Name # This preserves the sequence - using get-member would sort them alphabetically! There may be extra properties in + if ($NoHeader -and "Name" -eq $Key) {$Key = "p1"} if ($headings -notcontains $Key -and ('*' -ne $Key)) {Write-Warning -Message "You need to specify one of the headings in the sheet '$worksheet1' as a key." ; return } - foreach ($p in $Property) { $propList += ($headings.where({$_ -like $p}) )} - foreach ($p in $ExcludeProperty) { $propList = $propList.where({$_ -notlike $p}) } + foreach ($p in $Property) { $propList += ($headings.where({$_ -like $p}) )} + foreach ($p in $ExcludeProperty) { $propList = $propList.where({$_ -notlike $p}) } if (($propList -notcontains $Key) -and - ('*' -ne $Key)) { $propList += $Key} #If $key isn't one of the headings we will have bailed by now - $propList = $propList | Select-Object -Unique #so, prolist must contain at least $key if nothing else - - #If key is "*" we treat it differently , and we will create a script property which concatenates all the Properties in $Proplist - $ConCatblock = [scriptblock]::Create( ($proplist | ForEach-Object {'$this."' + $_ + '"'}) -join " + ") + ('*' -ne $Key)) { $propList += $Key} #If $key isn't one of the headings we will have bailed by now + $propList = $propList | Select-Object -Unique #so, prolist must contain at least $key if nothing else - #Build the list of the properties to output, in order. - $diffpart = @() + #If key is "*" we treat it differently , and we will create a script property which concatenates all the Properties in $Proplist + $ConCatblock = [scriptblock]::Create( ($proplist | ForEach-Object {'$this."' + $_ + '"'}) -join " + ") + + #Build the list of the properties to output, in order. + $diffpart = @() $refpart = @() - foreach ($p in $proplist.Where({$key -ne $_}) ) {$refPart += $p ; $diffPart += "$DiffPrefix $p" } - #Last reference column will be A if there the only one property (which might be the key), B if there are two properties, C if there are 3 etc - $lastRefCol = [char](64 + $propList.count) - #First difference column will be the next one (we'll trap the case of only having the key later) - $FirstDiffCol = [char](65 + $propList.count) - - if ($key -ne '*') { - $outputProps = @($key) + $refpart + $diffpart - #If we are using a single column as the key, don't duplicate it, so the last difference column will be A if there is one property, C if there are two, E if there are 3 - $lastDiffCol = [char](63 + 2 * $propList.count) + foreach ($p in $proplist.Where({$key -ne $_}) ) {$refPart += $p ; $diffPart += "$DiffPrefix $p" } + $lastRefColNo = $proplist.count + $FirstDiffColNo = $lastRefColNo + 1 + + if ($key -ne '*') { + $outputProps = @($key) + $refpart + $diffpart + #If we are using a single column as the key, don't duplicate it, so the last difference column will be A if there is one property, C if there are two, E if there are 3 + $lastDiffColNo = (2 * $proplist.count) - 1 } else { - $outputProps = @( ) + $refpart + $diffpart - #If we not using a single column as a key all columns are duplicated so, the Last difference column will be B if there is one property, D if there are two, F if there are 3 - $lastDiffCol = [char](64 + 2 * $propList.count) - } - + $outputProps = @( ) + $refpart + $diffpart + #If we not using a single column as a key all columns are duplicated so, the Last difference column will be B if there is one property, D if there are two, F if there are 3 + $lastDiffColNo = (2 * $proplist.count ) + } + #Add RowNumber to every row - #If one sheet has extra rows we can get a single "==" result from compare, with the row from the reference sheet, but + #If one sheet has extra rows we can get a single "==" result from compare, with the row from the reference sheet, but #the row in the other sheet might be different so we will look up the row number from the key field - build a hash table for that here - #If we have "*" as the key ad the script property to concatenate the [selected] properties. - - $Rowhash = @{} + #If we have "*" as the key ad the script property to concatenate the [selected] properties. + + $Rowhash = @{} $rowNo = $firstDataRow foreach ($row in $ReferenceObject) { - if ($row._row -eq $null) {Add-Member -InputObject $row -MemberType NoteProperty -Value ($rowNo ++) -Name "_Row" } - else {$rowNo++ } + if ($null -eq $row._row) {Add-Member -InputObject $row -MemberType NoteProperty -Value ($rowNo ++) -Name "_Row" } + else {$rowNo++ } if ($Key -eq '*' ) {Add-Member -InputObject $row -MemberType ScriptProperty -Value $ConCatblock -Name "_All" } } - $rowNo = $firstDataRow + $rowNo = $firstDataRow foreach ($row in $DifferenceObject) { - Add-Member -InputObject $row -MemberType NoteProperty -Value $rowNo -Name "$DiffPrefix Row" -Force + Add-Member -InputObject $row -MemberType NoteProperty -Value $rowNo -Name "$DiffPrefix Row" -Force if ($Key -eq '*' ) { - Add-Member -InputObject $row -MemberType ScriptProperty -Value $ConCatblock -Name "_All" - $Rowhash[$row._All] = $rowNo - } + Add-Member -InputObject $row -MemberType ScriptProperty -Value $ConCatblock -Name "_All" + $Rowhash[$row._All] = $rowNo + } else {$Rowhash[$row.$key] = $rowNo } $rowNo ++ } - if ($Key -eq '*') {$key = "_ALL"} - #endregion + if ($Key -eq '*') {$key = "_ALL"} + #endregion $expandedDiff = Compare-Object -ReferenceObject $ReferenceObject -DifferenceObject $DifferenceObject -Property $propList -PassThru -IncludeEqual | - Group-Object -Property $key | ForEach-Object { - #The value of the key column is the name of the group. + Group-Object -Property $key | ForEach-Object { + #The value of the key column is the name of the group. $keyval = $_.name - #we're going to create a custom object from a hash table. ??Might no longer need to preserve the field order - $hash = [ordered]@{} + #we're going to create a custom object from a hash table. ??Might no longer need to preserve the field order + $hash = [ordered]@{} foreach ($result in $_.Group) { - if ($result.SideIndicator -ne "=>") {$hash["_Row"] = $result._Row } + if ($result.SideIndicator -ne "=>") {$hash["_Row"] = $result._Row } elseif (-not $hash["$DiffPrefix Row"]) {$hash["_Row"] = "" } - #if we have already set the side, be this must the second record, so set side to indicate "changed" + #if we have already set the side, be this must the second record, so set side to indicate "changed" if ($hash.Side) {$hash.Side = "<>"} else {$hash["Side"] = $result.SideIndicator} switch ($hash.side) { '==' { $hash["$DiffPrefix is"] = 'Same' } '=>' { $hash["$DiffPrefix is"] = 'Added' } '<>' { if (-not $hash["_Row"]) { - $hash["$DiffPrefix is"] = 'Added' + $hash["$DiffPrefix is"] = 'Added' } else { $hash["$DiffPrefix is"] = 'Changed' - } - } - '<=' { $hash["$DiffPrefix is"] = 'Removed'} + } + } + '<=' { $hash["$DiffPrefix is"] = 'Removed'} } - #find the number of the row in the the "difference" object which has this key. If it is the object is only the reference this will be blank. - $hash["$DiffPrefix Row"] = $Rowhash[$keyval] - $hash[$key] = $keyval - #Create FieldName and/or =>FieldName columns + #find the number of the row in the the "difference" object which has this key. If it is the object is only the reference this will be blank. + $hash["$DiffPrefix Row"] = $Rowhash[$keyval] + $hash[$key] = $keyval + #Create FieldName and/or =>FieldName columns foreach ($p in $result.psobject.Properties.name.where({$_ -ne $key -and $_ -ne "SideIndicator" -and $_ -ne "$DiffPrefix Row" })) { - if ($result.SideIndicator -eq "==" -and $p -in $propList) + if ($result.SideIndicator -eq "==" -and $p -in $propList) {$hash[("$p")] = $hash[("$DiffPrefix $p")] = $result.$P} elseif ($result.SideIndicator -eq "==" -or $result.SideIndicator -eq "<=") {$hash[("$p")] = $result.$P} elseif ($result.SideIndicator -eq "=>") { $hash[("$DiffPrefix $p")] = $result.$P} } - } + } [Pscustomobject]$hash - } | Sort-Object -Property "_row" - - #Already sorted by reference row number, fill in any blanks in the difference-row column. - for ($i = 1; $i -lt $expandedDiff.Count; $i++) {if (-not $expandedDiff[$i]."$DiffPrefix Row") {$expandedDiff[$i]."$DiffPrefix Row" = $expandedDiff[$i-1]."$DiffPrefix Row" } } - - #Now re-Sort by difference row number, and fill in any blanks in the reference-row column. - $expandedDiff = $expandedDiff | Sort-Object -Property "$DiffPrefix Row" - for ($i = 1; $i -lt $expandedDiff.Count; $i++) {if (-not $expandedDiff[$i]."_Row") {$expandedDiff[$i]."_Row" = $expandedDiff[$i-1]."_Row" } } - - $AllProps = @("_Row") + $OutputProps + $expandedDiff[0].psobject.properties.name.where({$_ -notin ($outputProps + @("_row","side","SideIndicator","_ALL" ))}) + } | Sort-Object -Property "_row" - if ($PassThru -or -not $OutputFile) {return ($expandedDiff | Select-Object -Property $allprops | Sort-Object -Property "_row", "$DiffPrefix Row" | Update-FirstObjectProperties ) } - elseif ($PSCmdlet.ShouldProcess($OutputFile,"Write Output to Excel file")) { - $expandedDiff = $expandedDiff | Sort-Object -Property "_row", "$DiffPrefix Row" - $xl = $expandedDiff | Select-Object -Property $OutputProps | Update-FirstObjectProperties | - Export-Excel -Path $OutputFile -WorkSheetname $OutputSheetName -FreezeTopRow -BoldTopRow -AutoSize -AutoFilter -PassThru - $ws = $xl.Workbook.Worksheets[$OutputSheetName] + #Already sorted by reference row number, fill in any blanks in the difference-row column. + for ($i = 1; $i -lt $expandedDiff.Count; $i++) {if (-not $expandedDiff[$i]."$DiffPrefix Row") {$expandedDiff[$i]."$DiffPrefix Row" = $expandedDiff[$i-1]."$DiffPrefix Row" } } + + #Now re-Sort by difference row number, and fill in any blanks in the reference-row column. + $expandedDiff = $expandedDiff | Sort-Object -Property "$DiffPrefix Row" + for ($i = 1; $i -lt $expandedDiff.Count; $i++) {if (-not $expandedDiff[$i]."_Row") {$expandedDiff[$i]."_Row" = $expandedDiff[$i-1]."_Row" } } + + $AllProps = @("_Row") + $OutputProps + $expandedDiff[0].psobject.properties.name.where({$_ -notin ($outputProps + @("_row","side","SideIndicator","_ALL" ))}) + + if ($PassThru -or -not $OutputFile) {return ($expandedDiff | Select-Object -Property $allprops | Sort-Object -Property "_row", "$DiffPrefix Row" | Update-FirstObjectProperties ) } + elseif ($PSCmdlet.ShouldProcess($OutputFile,"Write Output to Excel file")) { + $expandedDiff = $expandedDiff | Sort-Object -Property "_row", "$DiffPrefix Row" + $xl = $expandedDiff | Select-Object -Property $OutputProps | Update-FirstObjectProperties | + Export-Excel -Path $OutputFile -WorkSheetname $OutputSheetName -FreezeTopRow -BoldTopRow -AutoSize -AutoFilter -PassThru + $ws = $xl.Workbook.Worksheets[$OutputSheetName] for ($i = 0; $i -lt $expandedDiff.Count; $i++ ) { if ( $expandedDiff[$i].side -ne "==" ) { Set-Format -WorkSheet $ws -Range ("A" + ($i + 2 )) -FontColor $KeyFontColor @@ -265,212 +263,216 @@ Set-Format -WorkSheet $ws -Range $range -BackgroundColor $ChangeBackgroundColor } elseif ( $expandedDiff[$i].side -eq "<=" ) { - $range = "A" + ($i + 2 ) + ":" + $lastRefCol + ($i + 2 ) - Set-Format -WorkSheet $ws -Range $range -BackgroundColor $DeleteBackgroundColor + $rangeR1C1 = "R[{0}]C[1]:R[{0}]C[{1}]" -f ($i + 2 ) , $lastRefColNo + $range = [OfficeOpenXml.ExcelAddress]::TranslateFromR1C1($rangeR1C1,0,0) + Set-Format -WorkSheet $ws -Range $range -BackgroundColor $DeleteBackgroundColor } elseif ( $expandedDiff[$i].side -eq "=>" ) { if ($propList.count -gt 1) { - $range = $FirstDiffCol + ($i + 2 ) + ":" + $lastDiffCol + ($i + 2 ) + $rangeR1C1 = "R[{0}]C[{1}]:R[{0}]C[{2}]" -f ($i + 2 ) , $FirstDiffColNo , $lastDiffColNo + $range = [OfficeOpenXml.ExcelAddress]::TranslateFromR1C1($rangeR1C1,0,0) Set-Format -WorkSheet $ws -Range $range -BackgroundColor $AddBackgroundColor } - Set-Format -WorkSheet $ws -Range ("A" + ($i + 2 )) -BackgroundColor $AddBackgroundColor - } - } - Close-ExcelPackage -ExcelPackage $xl -Show:$Show - } + Set-Format -WorkSheet $ws -Range ("A" + ($i + 2 )) -BackgroundColor $AddBackgroundColor + } + } + Close-ExcelPackage -ExcelPackage $xl -Show:$Show + } } Function Merge-MultipleSheets { +[cmdletbinding()] +[Alias("Merge-MulipleSheets")] <# .Synopsis - Merges worksheets into a single worksheet with differences marked up. + Merges worksheets into a single worksheet with differences marked up. .Description - The Merge worksheet command combines 2 sheets. Merge-MultipleSheets is designed to merge more than 2. - So if asked to merge sheets A,B,C which contain Services, with a Name, Displayname and Start mode, where "name" is treated as the key - Merge-MultipleSheets calls Merge-Worksheet to merge Name, Displayname and Start mode, from sheets A and C + The Merge worksheet command combines 2 sheets. Merge-MultipleSheets is designed to merge more than 2. + So if asked to merge sheets A,B,C which contain Services, with a Name, Displayname and Start mode, where "name" is treated as the key + Merge-MultipleSheets calls Merge-Worksheet to merge Name, Displayname and Start mode, from sheets A and C the result has column headings -Row, Name, DisplayName, Startmode, C-DisplayName, C-StartMode C-Is, C-Row Merge-MultipleSheets then calls Merge-Worsheet with this result and sheet B, comparing 'Name', 'Displayname' and 'Start mode' columns on each side - which outputs _Row, Name, DisplayName, Startmode, B-DisplayName, B-StartMode B-Is, B-Row, C-DisplayName, C-StartMode C-Is, C-Row - Any columns in the "reference" side which are not used in the comparison are appended on the right, which is we compare the sheets in reverse order. - - The "Is" column holds "Same", "Added", "Removed" or "Changed" and is used for conditional formatting in the output sheet (this is hidden by default), - and when the data is written to Excel the "reference" columns, in this case "DisplayName" and "Start" are renamed to reflect their source, + which outputs _Row, Name, DisplayName, Startmode, B-DisplayName, B-StartMode B-Is, B-Row, C-DisplayName, C-StartMode C-Is, C-Row + Any columns in the "reference" side which are not used in the comparison are appended on the right, which is we compare the sheets in reverse order. + + The "Is" column holds "Same", "Added", "Removed" or "Changed" and is used for conditional formatting in the output sheet (this is hidden by default), + and when the data is written to Excel the "reference" columns, in this case "DisplayName" and "Start" are renamed to reflect their source, so become "A-DisplayName" and "A-Start". - - Conditional formatting is also applied to the "key" column (name in this case) so the view can be filtered to rows with changes by filtering this column on color. - - Note: the processing order can affect what is seen as a change. For example if there is an extra item in sheet B in the example above, + + Conditional formatting is also applied to the "key" column (name in this case) so the view can be filtered to rows with changes by filtering this column on color. + + Note: the processing order can affect what is seen as a change. For example if there is an extra item in sheet B in the example above, Sheet C will be processed and that row and will not be seen to be missing. When sheet B is processed it is marked as an addition, and the conditional formatting marks - the entries from sheet A to show that a values were added in at least one sheet. - However if Sheet B is the reference sheet, A and C will be seen to have an item removed; - and if B is processed before C, the extra item is known when C is processed and so C is considered to be missing that item. + the entries from sheet A to show that a values were added in at least one sheet. + However if Sheet B is the reference sheet, A and C will be seen to have an item removed; + and if B is processed before C, the extra item is known when C is processed and so C is considered to be missing that item. .Example dir Server*.xlsx | Merge-MulipleSheets -WorkSheetName Services -OutputFile Test2.xlsx -OutputSheetName Services -Show - We are auditing servers and each one has a workbook in the current directory which contains a "Services" worksheet (the result of + We are auditing servers and each one has a workbook in the current directory which contains a "Services" worksheet (the result of Get-WmiObject -Class win32_service | Select-Object -Property Name, Displayname, Startmode - No key is specified so the key is assumed to be the "Name" column. The files are merged and the result is opened on completion. - .Example - dir Serv*.xlsx | Merge-MulipleSheets -WorkSheetName Software -Key "*" -ExcludeProperty Install* -OutputFile Test2.xlsx -OutputSheetName Software -Show - The server audit files in the previous example also have "Software" worksheet, but no single field on that sheet works as a key. - Specifying "*" for the key produces a compound key using all non-excluded fields (and the installation date and file location are excluded). - .Example + No key is specified so the key is assumed to be the "Name" column. The files are merged and the result is opened on completion. + .Example + dir Serv*.xlsx | Merge-MulipleSheets -WorkSheetName Software -Key "*" -ExcludeProperty Install* -OutputFile Test2.xlsx -OutputSheetName Software -Show + The server audit files in the previous example also have "Software" worksheet, but no single field on that sheet works as a key. + Specifying "*" for the key produces a compound key using all non-excluded fields (and the installation date and file location are excluded). + .Example Merge-MulipleSheets -Path hotfixes.xlsx -WorkSheetName Serv* -Key hotfixid -OutputFile test2.xlsx -OutputSheetName hotfixes -HideRowNumbers -Show This time all the servers have written their hofix information to their own worksheets in a shared Excel workbook named "Hotfixes" (the information was obtained by running Get-Hotfix | Sort-Object -Property description,hotfixid | Select-Object -Property Description,HotfixID) - This ignores any sheets which are not named "Serv*", and uses the HotfixID as the key ; in this version the row numbers are hidden. + This ignores any sheets which are not named "Serv*", and uses the HotfixID as the key ; in this version the row numbers are hidden. #> param ( - [Parameter(Mandatory=$true,ValueFromPipeline=$true)] + [Parameter(Mandatory=$true,ValueFromPipeline=$true)] [string[]]$Path , #The row from where we start to import data, all rows above the StartRow are disregarded. By default this is the first row. - [int]$Startrow = 1, - + [int]$Startrow = 1, + #Specifies custom property names to use, instead of the values defined in the column headers of the TopRow. - [String[]]$Headername, + [String[]]$Headername, #Automatically generate property names (P1, P2, P3, ..) instead of the using the values the top row of the sheet. - [switch]$NoHeader, - + [switch]$NoHeader, + #Name(s) of worksheets to compare, $WorkSheetName = "Sheet1", - #File to write output to + #File to write output to [Alias('OutFile')] - $OutputFile = ".\temp.xlsx", - #Name of worksheet to output - if none specified will use the reference worksheet name. + $OutputFile = ".\temp.xlsx", + #Name of worksheet to output - if none specified will use the reference worksheet name. [Alias('OutSheet')] $OutputSheetName = "Sheet1", #Properties to include in the DIFF - supports wildcards, default is "*". $Property = "*" , - #Properties to exclude from the the search - supports wildcards. + #Properties to exclude from the the search - supports wildcards. $ExcludeProperty , #Name of a column which is unique used to pair up rows from the refence and difference side, default is "Name". $Key = "Name" , - #Sets the font color for the "key" field; this means you can filter by color to get only changed rows. - [System.Drawing.Color]$KeyFontColor = "Red", - #Sets the background color for changed rows. + #Sets the font color for the "key" field; this means you can filter by color to get only changed rows. + [System.Drawing.Color]$KeyFontColor = "Red", + #Sets the background color for changed rows. [System.Drawing.Color]$ChangeBackgroundColor = "Orange", - #Sets the background color for rows in the reference but deleted from the difference sheet. - [System.Drawing.Color]$DeleteBackgroundColor = "LightPink", - #Sets the background color for rows not in the reference but added to the difference sheet. - [System.Drawing.Color]$AddBackgroundColor = "Orange", - #if Specified hides the columns in the spreadsheet that contain the row numbers + #Sets the background color for rows in the reference but deleted from the difference sheet. + [System.Drawing.Color]$DeleteBackgroundColor = "LightPink", + #Sets the background color for rows not in the reference but added to the difference sheet. + [System.Drawing.Color]$AddBackgroundColor = "Orange", + #if Specified hides the columns in the spreadsheet that contain the row numbers [switch]$HideRowNumbers , #If specified outputs the data to the pipeline (you can add -whatif so it the command only outputs to the command) [switch]$Passthru , - #If specified, opens the output workbook. + #If specified, opens the output workbook. [Switch]$Show ) begin { $filestoProcess = @() } - process { $filestoProcess += $Path} + process { $filestoProcess += $Path} end { if ($filestoProcess.Count -eq 1 -and $WorkSheetName -match '\*') { Write-Progress -Activity "Merging sheets" -CurrentOperation "Expanding * to names of sheets in $($filestoProcess[0]). " - $excel = Open-ExcelPackage -Path $filestoProcess + $excel = Open-ExcelPackage -Path $filestoProcess $WorksheetName = $excel.Workbook.Worksheets.Name.where({$_ -like $WorkSheetName}) Close-ExcelPackage -NoSave -ExcelPackage $excel } - #Merge indentically named sheets in different work books; + #Merge indentically named sheets in different work books; if ($filestoProcess.Count -ge 2 -and $WorkSheetName -is "string" ) { Get-Variable -Name 'HeaderName','NoHeader','StartRow','Key','Property','ExcludeProperty','WorkSheetName' -ErrorAction SilentlyContinue | - Where-Object {$_.Value} | ForEach-Object -Begin {$params= @{} } -Process {$params[$_.Name] = $_.Value} - + Where-Object {$_.Value} | ForEach-Object -Begin {$params= @{} } -Process {$params[$_.Name] = $_.Value} + Write-Progress -Activity "Merging sheets" -CurrentOperation "Comparing $($filestoProcess[-1]) against $($filestoProcess[0]). " $merged = Merge-Worksheet @params -Referencefile $filestoProcess[0] -Differencefile $filestoProcess[-1] $nextFileNo = 2 - while ($nextFileNo -lt $filestoProcess.count -and $merged) { + while ($nextFileNo -lt $filestoProcess.count -and $merged) { Write-Progress -Activity "Merging sheets" -CurrentOperation "Comparing $($filestoProcess[-$nextFileNo]) against $($filestoProcess[0]). " - $merged = Merge-Worksheet @params -ReferenceObject $merged -Differencefile $filestoProcess[-$nextFileNo] + $merged = Merge-Worksheet @params -ReferenceObject $merged -Differencefile $filestoProcess[-$nextFileNo] $nextFileNo ++ } } - #Merge different sheets from one workbook + #Merge different sheets from one workbook elseif ($filestoProcess.Count -eq 1 -and $WorkSheetName.Count -ge 2 ) { Get-Variable -Name 'HeaderName','NoHeader','StartRow','Key','Property','ExcludeProperty' -ErrorAction SilentlyContinue | - Where-Object {$_.Value} | ForEach-Object -Begin {$params= @{} } -Process {$params[$_.Name] = $_.Value} - + Where-Object {$_.Value} | ForEach-Object -Begin {$params= @{} } -Process {$params[$_.Name] = $_.Value} + Write-Progress -Activity "Merging sheets" -CurrentOperation "Comparing $($WorkSheetName[-1]) against $($WorkSheetName[0]). " $merged = Merge-Worksheet @params -Referencefile $filestoProcess[0] -Differencefile $filestoProcess[0] -WorkSheetName $WorkSheetName[0,-1] $nextSheetNo = 2 - while ($nextSheetNo -lt $WorkSheetName.count -and $merged) { + while ($nextSheetNo -lt $WorkSheetName.count -and $merged) { Write-Progress -Activity "Merging sheets" -CurrentOperation "Comparing $($WorkSheetName[-$nextSheetNo]) against $($WorkSheetName[0]). " $merged = Merge-Worksheet @params -ReferenceObject $merged -Differencefile $filestoProcess[0] -WorkSheetName $WorkSheetName[-$nextSheetNo] -DiffPrefix $WorkSheetName[-$nextSheetNo] $nextSheetNo ++ } } - #We either need one worksheet name and many files or one file and many sheets. + #We either need one worksheet name and many files or one file and many sheets. else { Write-Warning -Message "Need at least two files to process" ; return } - #if the process didn't return data then abandon now. + #if the process didn't return data then abandon now. if (-not $merged) {Write-Warning -Message "The merge operation did not return any data."; return } Write-Progress -Activity "Merging sheets" -CurrentOperation "Creating output sheet '$OutputSheetName' in $OutputFile" $excel = $merged | Sort-Object "_row" | Update-FirstObjectProperties | - Export-Excel -Path $OutputFile -WorkSheetname $OutputSheetName -ClearSheet -BoldTopRow -AutoFilter -PassThru - $sheet = $excel.Workbook.Worksheets[$OutputSheetName] - + Export-Excel -Path $OutputFile -WorkSheetname $OutputSheetName -ClearSheet -BoldTopRow -AutoFilter -PassThru + $sheet = $excel.Workbook.Worksheets[$OutputSheetName] + #We will put in a conditional format for "if all the others are not flagged as 'same'" to mark rows where something is added, removed or changed $sameChecks = @() - - #All the 'difference' columns in the sheet are labeled with the file they came from, 'reference' columns need their - #headers prefixed with the ref file name, $colnames is the basis of a regular expression to identify what should have $refPrefix appended - $colNames = @("_Row") - if ($key -ne "*") - {$colnames += $Key} + + #All the 'difference' columns in the sheet are labeled with the file they came from, 'reference' columns need their + #headers prefixed with the ref file name, $colnames is the basis of a regular expression to identify what should have $refPrefix appended + $colNames = @("^_Row$") + if ($key -ne "*") + {$colnames += "^$Key$"} if ($filesToProcess.Count -ge 2) { - $refPrefix = (Split-Path -Path $filestoProcess[0] -Leaf) -replace "\.xlsx$"," " + $refPrefix = (Split-Path -Path $filestoProcess[0] -Leaf) -replace "\.xlsx$"," " } else {$refPrefix = $WorkSheetName[0] } Write-Progress -Activity "Merging sheets" -CurrentOperation "Applying formatting to sheet '$OutputSheetName' in $OutputFile" - #Find the column headings which are in the form "diffFile is"; which will hold 'Same', 'Added' or 'Changed' + #Find the column headings which are in the form "diffFile is"; which will hold 'Same', 'Added' or 'Changed' foreach ($cell in $sheet.Cells[($sheet.Dimension.Address -replace "\d+$","1")].Where({$_.value -match "\sIS$"}) ) { - #Work leftwards across the headings applying conditional formatting which says - # 'Format this cell if the "IS" column has a value of ...' until you find a heading which doesn't have the prefix. - $prefix = $cell.value -replace "\sIS$","" - $columnNo = $cell.start.Column -1 - $cellAddr = [OfficeOpenXml.ExcelAddress]::TranslateFromR1C1("R1C$columnNo",1,$columnNo) + #Work leftwards across the headings applying conditional formatting which says + # 'Format this cell if the "IS" column has a value of ...' until you find a heading which doesn't have the prefix. + $prefix = $cell.value -replace "\sIS$","" + $columnNo = $cell.start.Column -1 + $cellAddr = [OfficeOpenXml.ExcelAddress]::TranslateFromR1C1("R1C$columnNo",1,$columnNo) while ($sheet.cells[$cellAddr].value -match $prefix) { - $condFormattingParams = @{RuleType='Expression'; BackgroundPattern='None'; WorkSheet=$sheet; Range=$([OfficeOpenXml.ExcelAddress]::TranslateFromR1C1("R[1]C[$columnNo]:R[1048576]C[$columnNo]",0,0)) } - Add-ConditionalFormatting @condFormattingParams -ConditionValue ($cell.Address + '="Added"' ) -BackgroundColor $AddBackgroundColor - Add-ConditionalFormatting @condFormattingParams -ConditionValue ($cell.Address + '="Changed"') -BackgroundColor $ChangeBackgroundColor - Add-ConditionalFormatting @condFormattingParams -ConditionValue ($cell.Address + '="Removed"') -BackgroundColor $DeleteBackgroundColor - $columnNo -- - $cellAddr = [OfficeOpenXml.ExcelAddress]::TranslateFromR1C1("R1C$columnNo",1,$columnNo) + $condFormattingParams = @{RuleType='Expression'; BackgroundPattern='None'; WorkSheet=$sheet; Range=$([OfficeOpenXml.ExcelAddress]::TranslateFromR1C1("R[1]C[$columnNo]:R[1048576]C[$columnNo]",0,0)) } + Add-ConditionalFormatting @condFormattingParams -ConditionValue ($cell.Address + '="Added"' ) -BackgroundColor $AddBackgroundColor + Add-ConditionalFormatting @condFormattingParams -ConditionValue ($cell.Address + '="Changed"') -BackgroundColor $ChangeBackgroundColor + Add-ConditionalFormatting @condFormattingParams -ConditionValue ($cell.Address + '="Removed"') -BackgroundColor $DeleteBackgroundColor + $columnNo -- + $cellAddr = [OfficeOpenXml.ExcelAddress]::TranslateFromR1C1("R1C$columnNo",1,$columnNo) } #build up a list of prefixes in $colnames - we'll use that to set headers on rows from the reference file; and build up the "if the 'is' cell isn't same" list $colNames += $prefix $sameChecks += (($cell.Address -replace "1","2") +'<>"Same"') } - - #For all the columns which don't match one of the Diff-file prefixes or "_Row" or the 'Key' columnn name; add the reference file prefix to their header. - $nameRegex = $colNames -Join "|" + + #For all the columns which don't match one of the Diff-file prefixes or "_Row" or the 'Key' columnn name; add the reference file prefix to their header. + $nameRegex = $colNames -Join '|' foreach ($cell in $sheet.Cells[($sheet.Dimension.Address -replace "\d+$","1")].Where({$_.value -Notmatch $nameRegex}) ) { $cell.Value = $refPrefix + $cell.Value $condFormattingParams = @{RuleType='Expression'; BackgroundPattern='None'; WorkSheet=$sheet; Range=[OfficeOpenXml.ExcelAddress]::TranslateFromR1C1("R[2]C[$($cell.start.column)]:R[1048576]C[$($cell.start.column)]",0,0)} - Add-ConditionalFormatting @condFormattingParams -ConditionValue ("OR(" +(($sameChecks -join ",") -replace '<>"Same"','="Added"') +")" ) -BackgroundColor $DeleteBackgroundColor + Add-ConditionalFormatting @condFormattingParams -ConditionValue ("OR(" +(($sameChecks -join ",") -replace '<>"Same"','="Added"') +")" ) -BackgroundColor $DeleteBackgroundColor } - #We've made a bunch of things wider so now is the time to autofit columns. Any hiding has to come AFTER this, because it unhides things - $sheet.Cells.AutoFitColumns() - - #if we have a key field (we didn't concatenate all fields) use what we built up in $sameChecks to apply conditional formatting to it (Row no will be in column A, Key in Column B) + #We've made a bunch of things wider so now is the time to autofit columns. Any hiding has to come AFTER this, because it unhides things + $sheet.Cells.AutoFitColumns() + + #if we have a key field (we didn't concatenate all fields) use what we built up in $sameChecks to apply conditional formatting to it (Row no will be in column A, Key in Column B) if ($Key -ne '*') { - Add-ConditionalFormatting -WorkSheet $sheet -Range "B2:B1048576" -ForeGroundColor $KeyFontColor -BackgroundPattern 'None' -RuleType Expression -ConditionValue ("OR(" +($sameChecks -join ",") +")" ) + Add-ConditionalFormatting -WorkSheet $sheet -Range "B2:B1048576" -ForeGroundColor $KeyFontColor -BackgroundPattern 'None' -RuleType Expression -ConditionValue ("OR(" +($sameChecks -join ",") +")" ) $sheet.view.FreezePanes(2, 3) } else {$sheet.view.FreezePanes(2, 2) } - #Go back over the headings to find and hide the "is" columns; + #Go back over the headings to find and hide the "is" columns; foreach ($cell in $sheet.Cells[($sheet.Dimension.Address -replace "\d+$","1")].Where({$_.value -match "\sIS$"}) ) { $sheet.Column($cell.start.Column).HIDDEN = $true } - - #If specified, look over the headings for "row" and hide the columns which say "this was in row such-and-such" + + #If specified, look over the headings for "row" and hide the columns which say "this was in row such-and-such" if ($HideRowNumbers) { foreach ($cell in $sheet.Cells[($sheet.Dimension.Address -replace "\d+$","1")].Where({$_.value -match "Row$"}) ) { $sheet.Column($cell.start.Column).HIDDEN = $true } } - - Close-ExcelPackage -ExcelPackage $excel -Show:$Show + + Close-ExcelPackage -ExcelPackage $excel -Show:$Show Write-Progress -Activity "Merging sheets" -Completed } } diff --git a/New-ExcelChart.ps1 b/New-ExcelChart.ps1 index 67e91b7..841151c 100644 --- a/New-ExcelChart.ps1 +++ b/New-ExcelChart.ps1 @@ -1,6 +1,6 @@ function New-ExcelChartDefinition { [cmdletbinding()] - [Alias("New-ExcelChart")] #This was the former name. The new name reflects that we are defining a chart, not making one in the workbook. + [Alias("New-ExcelChart")] #This was the former name. The new name reflects that we are defining a chart, not making one in the workbook. param( $Title = "Chart Title", $Header, @@ -13,12 +13,35 @@ function New-ExcelChartDefinition { $RowOffSetPixels = 10, $Column = 6, $ColumnOffSetPixels = 5, + $LegendSize, + [Switch]$legendBold, [Switch]$NoLegend, [Switch]$ShowCategory, [Switch]$ShowPercent, - $SeriesHeader + $SeriesHeader, + [Switch]$TitleBold, + [Int]$TitleSize , + [String]$XAxisTitleText, + [Switch]$XAxisTitleBold, + $XAxisTitleSize , + [string]$XAxisNumberformat, + $XMajorUnit, + $XMinorUnit, + $XMaxValue, + $XMinValue, + [OfficeOpenXml.Drawing.Chart.eAxisPosition]$XAxisPosition , + [String]$YAxisTitleText, + [Switch]$YAxisTitleBold, + $YAxisTitleSize, + [string]$YAxisNumberformat, + $YMajorUnit, + $YMinorUnit, + $YMaxValue, + $YMinValue, + [OfficeOpenXml.Drawing.Chart.eAxisPosition]$YAxisPosition ) - if ( $Header ) {Write-Warning "The header parameter is ignored."} #Nothing was done with it when creating a chart. + if ( $Header ) {Write-Warning "The header parameter is ignored."} #Nothing was done with it when creating a chart. + #might be able to do [PSCustomObject]$PsboundParameters, the defaults here match those in Add-Excel Chart [PSCustomObject]@{ Title = $Title ChartType = $ChartType @@ -30,9 +53,29 @@ function New-ExcelChartDefinition { RowOffSetPixels = $RowOffSetPixels Column = $Column ColumnOffSetPixels = $ColumnOffSetPixels - NoLegend = $NoLegend -as [Boolean] - ShowCategory = $ShowCategory-as [Boolean] - ShowPercent = $ShowPercent -as [Boolean] + NoLegend = $NoLegend -as [Boolean] + ShowCategory = $ShowCategory -as [Boolean] + ShowPercent = $ShowPercent -as [Boolean] + TitleBold = $TitleBold -as [Boolean] + TitleSize = $TitleSize SeriesHeader = $SeriesHeader + XAxisTitleText = $XAxisTitleText + XAxisTitleBold = $XAxisTitleBold -as [Boolean] + XAxisTitleSize = $XAxisTitleSize + XAxisNumberformat = $XAxisNumberformat + XMajorUnit = $XMajorUnit + XMinorUnit = $XMinorUnit + XMaxValue = $XMaxValue + XMinValue = $XMinValue + XAxisPosition = $XAxisPosition + YAxisTitleText = $YAxisTitleText + YAxisTitleBold = $YAxisTitleBold -as [Boolean] + YAxisTitleSize = $YAxisTitleSize + YAxisNumberformat = $YAxisNumberformat + YMajorUnit = $YMajorUnit + YMinorUnit = $YMinorUnit + YMaxValue = $YMaxValue + YMinValue = $YMinValue + YAxisPosition = $YAxisPosition } } \ No newline at end of file diff --git a/Open-ExcelPackage.ps1 b/Open-ExcelPackage.ps1 index 49a77f2..da81907 100644 --- a/Open-ExcelPackage.ps1 +++ b/Open-ExcelPackage.ps1 @@ -1,22 +1,22 @@ Function Open-ExcelPackage { <# -.Synopsis - Returns an Excel Package Object with for the specified XLSX ile +.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) + $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 save the package, and launch it in Excel. + This will open the file at $xlPath, select sheet1 apply formatting to two blocks of the sheet and save the package, and launch it in Excel. #> [OutputType([OfficeOpenXml.ExcelPackage])] Param ( #The Path to the file to open [Parameter(Mandatory=$true)]$Path, - #If specified, any running instances of Excel will be terminated before opening the file. + #If specified, any running instances of Excel will be terminated before opening the file. [switch]$KillExcel, - #By default open only opens an existing file; -Create instructs it to create a new file if required. + #By default open only opens an existing file; -Create instructs it to create a new file if required. [switch]$Create ) @@ -24,33 +24,33 @@ 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 was not specified only open the file if it exists already (send a warning if it doesn't exist). if ($Create) { - #Create the directory if required. + #Create the directory if required. $targetPath = Split-Path -Parent -Path $Path if (!(Test-Path -Path $targetPath)) { Write-Debug "Base path $($targetPath) does not exist, creating" $null = New-item -ItemType Directory -Path $targetPath -ErrorAction Ignore } - New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList $Path + New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList $Path } elseif (Test-Path -Path $path) {New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList $Path } - else {Write-Warning "Could not find $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 in Excel as required. +.Synopsis + Closes an Excel Package, saving, saving under a new name or abandoning changes and opening the file in Excel as required. #> Param ( #File to close. [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 -NoSave Specified). @@ -58,10 +58,10 @@ Function Close-ExcelPackage { ) if ( $NoSave) {$ExcelPackage.Dispose()} else { - if ($SaveAs) {$ExcelPackage.SaveAs( $SaveAs ) } + if ($SaveAs) {$ExcelPackage.SaveAs( $SaveAs ) } Else {$ExcelPackage.Save(); $SaveAs = $ExcelPackage.File.FullName } - $ExcelPackage.Dispose() - if ($show) {Start-Process -FilePath $SaveAs } + $ExcelPackage.Dispose() + if ($show) {Start-Process -FilePath $SaveAs } } } diff --git a/README.md b/README.md index 4fcd85e..4bd0a88 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,13 @@ -

- - - - -

- PowerShell Import-Excel - +Install from the [PowerShell Gallery](https://www.powershellgallery.com/packages/ImportExcel/). + This PowerShell Module allows you to read and write Excel files without installing Microsoft Excel on your system. No need to bother with the cumbersome Excel COM-object. Creating Tables, Pivot Tables, Charts and much more has just become a lot easier. ![](https://raw.githubusercontent.com/dfinke/ImportExcel/master/images/testimonial.png) -# How to Videos +# How to Vidoes * [PowerShell Excel Module - ImportExcel](https://www.youtube.com/watch?v=U3Ne_yX4tYo&list=PL5uoqS92stXioZw-u-ze_NtvSo0k0K0kq) Installation @@ -39,24 +31,30 @@ To install to your personal modules folder (e.g. ~\Documents\WindowsPowerShell\M iex (new-object System.Net.WebClient).DownloadString('https://raw.github.com/dfinke/ImportExcel/master/Install.ps1') ``` -# What's new to 5th July 18 -- Moved chart creation into its own function (Add-Excel chart) within Export-Excel.ps1. Renamed New-ExcelChart to New-ExcelChartDefinition to make it clearer that it is not making anything in the workbook (but for compatibility put an alias of New-ExcelChart in so existing code does not break). Found -Header does nothing, so removed it. -- Added parameters for managing Axes and legend -- Fixed a bug introduced into Compare-Worksheet by the change described in the June changes below, this meant the font color was only being set in one sheet, when a row was changed. Also found that the PowerShell ISE and shell return Compare-Object results in different sequences which broke some tests. Applied a sort to ensure things are in a predictable order. (#375) -- Fixed some bad code which had been checked-in in-error and caused adding charts to break. (This was not seen outside GitHub #377) -- Added chart tests to Export-Excel.tests.ps1. -- Removed (2) calls to Get-ExcelColumnName +# What's new to 14th July 18 +- Moved chart creation into its own function (Add-Excel chart) within Export-Excel.ps1. Renamed New-Excelchart to New-ExcelChartDefinition to make it clearer that it is not making anything in the workbook (but for compatiblity put an alias of New-ExcelChart in so existing code does not break). Found that -Header does nothing, so it isn't Add-Excel chart and there is a message that does nothing in New-ExcelChartDefinition . +- Added -BarChart -ColumnChart -LineChart -PieChart parameters to Export-Excel for quick charts without giving a full chart definition. +- Added parameters for managing chart Axes and legend +- Added some chart tests to Export-Excel.tests.ps1. (but tests & examples for quick charts , axes or legends still on the to do list ) +- Fixed some bad code which had been checked-in in-error and caused adding charts to break. (This was not seen outside Github #377) +- Fixed a bug introduced into Compare-Worksheet by the change descibed in the June changes below, this meant the font color was only being set in one sheet, when a row was changed. Also found that the PowerShell ISE and shell return Compare-Object resuls in different sequences which broke some tests. Applied a sort to ensure things are in a predictable order. (#375) +- Removed (2) calls to Get-ExcelColumnName (Removed and then restored function itself) - Fixed an issue in Export-Excel where formulas were inserted as strings if "NoNumberConversion" is applied (#374), and made sure formatting is applied to formula cells -- Reverted the [double]::tryParse in export excel to the previous way, as the shorter way, although quicker was not behaving correctly with with the number formats in certain regions. (also #374) -- Changed Table, Range and AutoRangeNames to apply to whole data area if no data has been inserted OR to inserted data only if it has. (#376) +- Fixed an issue with parameter sets in Export-Excel not being determined correctly in some cases (I think this had been resolved before and might have regressed) +- Reverted the [double]::tryParse in export excel to the previous (longer) way, as the shorter way was not behaving correctly with with the number formats in certain regions. (also #374) +- Changed Table, Range and AutoRangeNames to apply to whole data area if no data has been inserted OR to inserted data only if it has.(#376) This means that if there are multiple inserts only inserted data is touched, rather than going as far down and/or right as the furthest used cell. Added a test for this. +- Added more of the Parameters from Export-Excel to Join-worksheet, join just calls export-excel with these parameters so there is no code behind them (#383) +- Added more of the Parameters from Export-Excel to Send-SQLDataToExcel, send just calls export-excel with these parameters... +- Added support for passing a System.Data.DataTable directly to Send-SQLDataToExcel +- Fixed a bug in Merge-MultipleSheets where if the key was "name", columns like "displayName" would not be processed correctly, nor would names like "something_ROW". Added tests for Compare, Merge and Join Worksheet # New in June 18 - 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 color 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 + - `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, the only code change is to fix a bug if two worksheets have different names, are in different files and the Comparison sends the delta in the second back before the one in first, then highlighting changed properties could throw an error. Correcting the spelling of Merge-MultipleSheets is potentially a breaking change (and it is still plural!) also fixed a bug in compare worksheet where color might not be applied correctly when the worksheets came from different files and had different name. - - `Join-Worksheet` is **new** for this release. At it's simplest it copies all the data in Worksheet A to the end of Worksheet B + - `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 @@ -68,9 +66,9 @@ iex (new-object System.Net.WebClient).DownloadString('https://raw.github.com/dfi 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 happened, 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 + 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 pivot tables / charts already exist and an export tries to create them again. + - 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 @@ -82,11 +80,11 @@ iex (new-object System.Net.WebClient).DownloadString('https://raw.github.com/dfi - 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) - Added PivotTableName String (in line with 5.0.1 release) - 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 significant performance impact and if it's on they will cause a flood of messages. + - 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 get better test coverage. The test so far has 184 "should" conditions grouped as 58 "IT" statements; but is still a work in progress. + - 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. - Compare-Worksheet pester tests --- @@ -100,7 +98,7 @@ Thanks to the community yet again - [ili101](https://github.com/ili101) for fixes and features - Removed `[PSPlot]` as OutputType. Fixes it throwing an error - [Nasir Zubair](https://github.com/nzubair) added `ConvertEmptyStringsToNull` to the function `ConvertFrom-ExcelToSQLInsert` - - If specified, cells without any data are replaced with NULL, instead of an empty string. This is to address behaviors in certain DBMS where an empty string is insert as 0 for INT column, instead of a NULL value. + - If specified, cells without any data are replaced with NULL, instead of an empty string. This is to address behviors in certain DBMS where an empty string is insert as 0 for INT column, instead of a NULL value. #### 4/10/2018 @@ -112,7 +110,7 @@ Super helpful! #### 3/31/2018 - Updated `Set-Format` - * Added parameters to set borders for cells, including top, bottom, left and right + * Added parameters to set borders for cells, including top, bottm, left and right * Added parameters to set `value` and `formula` ```powershell @@ -155,7 +153,7 @@ North,A1,Grape,140,2.5 * Add example to set the background color of a column * Supports excluding Row Grand Totals for PivotTables * Allow xlsm files to be read -* Fix `Set-Column.ps1`, `Set-Row.ps1`, `SetFormat.ps1`, `formatting.ps1` **$false** and **$BorderRound** +* Fix `Set-Column.ps1`, `Set-Row.ps1`, `SetFormat.ps1`, `formatting.ps1` **$falsee** and **$BorderRound** #### 1/1/2018 * Added switch `[Switch]$NoTotalsInPivot`. Allows hiding of the row totals in the pivot table. Thanks you to [jameseholt](https://github.com/jameseholt) for the request. @@ -181,7 +179,7 @@ More great additions and thanks to [James O'Neill](https://twitter.com/jamesonei * Corrected a typo PivotTableName was PivtoTableName in definition of New-PivotTableDefinition * Add-ConditionalFormat and Set-Format added to the parameters so each has the choice of working more like the other. * Added Set-Row and Set-Column - fill a formula down or across. -* Added Send-SQLDataToExcel. Insert a rowset and then call Export-Excel for ranges, charts, pivots etc. +* Added Send-SQLDataToExcel. Insert a rowset and then call Export-Excel for ranges, charts, pivots etc #### 10/30/2017 Huge thanks to [James O'Neill](https://twitter.com/jamesoneill). PowerShell aficionado. He always brings a flare when working with PowerShell. This is no exception. @@ -199,8 +197,8 @@ Huge thanks to [James O'Neill](https://twitter.com/jamesoneill). PowerShell afic (Check out the examples `help Export-Excel -Examples`) * New function `Export-Charts` (requires Excel to be installed) - Export Excel charts out as JPG files -* New function `Add-ConditionalFormatting` Adds conditional formatting to worksheet -* New function `Set-Format` Applies Number, font, alignment and color formatting to a range of Excel Cells +* New function `Add-ConditionalFormatting` Adds contitional formatting to worksheet +* New function `Set-Format` Applies Number, font, alignment and colour formatting to a range of Excel Cells * `ColorCompletion` an argument completer for `Colors` for params across functions I also worked out the parameters so you can do this, which is the same as passing `-Now`. It creates an Excel file name for you, does an auto fit and sets up filters. @@ -210,7 +208,7 @@ I also worked out the parameters so you can do this, which is the same as passin #### 10/13/2017 Added `New-PivotTableDefinition`. You can create and wire up a PivotTable to a WorkSheet. You can also create as many PivotTable Worksheets to point a one Worksheet. Or, you create many Worksheets and many corresponding PivotTable Worksheets. -Here you can create a WorkSheet with the data from `Get-Service`. Then create four PivotTables, pointing to the data each pivoting on a different dimension and showing a different chart +Here you can create a WorkSheet with the data from `Get-Service`. Then create four PivotTables, pointing to the data each pivoting on a differnt dimension and showing a differnet chart ```powershell $base = @{ @@ -392,7 +390,7 @@ Get-Process | ![](https://github.com/dfinke/ImportExcel/blob/master/images/CellFormatting.png?raw=true) #### 9/28/2016 -[Fixed](https://github.com/dfinke/ImportExcel/pull/126) PowerShell 3.0 compatibility. Thanks to [headsphere](https://github.com/headsphere). He used `$obj.PSObject.Methods[$target]` syntax to make it backward compatible. PS v4.0 and later allow `$obj.$target`. +[Fixed](https://github.com/dfinke/ImportExcel/pull/126) PowerShell 3.0 compatibility. Thanks to [headsphere](https://github.com/headsphere). He used `$obj.PSObject.Methods[$target]` snytax to make it backward compatible. PS v4.0 and later allow `$obj.$target`. Thank you to [xelsirko](https://github.com/xelsirko) for fixing - *Import-module importexcel gives version warning if started inside background job* @@ -428,7 +426,7 @@ Thanks Attila. #### 4/30/2016 Huge thank you to [Willie Möller](https://github.com/W1M0R) -* He added a version check so the PowerShell Classes don't cause issues for down-level version of PowerShell +* He added a version check so the PowerShell Classes don't cause issues for downlevel version of PowerShell * He also contributed the first Pester tests for the module. Super! Check them out, they'll be the way tests will be implemented going forward #### 4/18/2016 @@ -655,7 +653,7 @@ Or #### 9/25/2015 **Hide worksheets** -Got a great request from [forensicsguy20012004](https://github.com/forensicsguy20012004) to hide worksheets. You create a few pivotables, generate charts and then pivot table worksheets don't need to be visible. +Got a great request from [forensicsguy20012004](https://github.com/forensicsguy20012004) to hide worksheets. You create a few pivotables, generate charts and then pivotable worksheets don't need to be visible. `Export-Excel` now has a `-HideSheet` parameter that takes and array of worksheet names and hides them. @@ -847,4 +845,4 @@ You can also find EPPLus on [Nuget](https://www.nuget.org/packages/EPPlus/). * Using `-IncludePivotTable`, if that pivot table name exists, you'll get an error. * Investigating a solution - * *Workaround* delete the Excel file first, then do the export \ No newline at end of file + * *Workaround* delete the Excel file first, then do the export diff --git a/Send-SqlDataToExcel.ps1 b/Send-SqlDataToExcel.ps1 index 96d0db7..30eb821 100644 --- a/Send-SqlDataToExcel.ps1 +++ b/Send-SqlDataToExcel.ps1 @@ -1,55 +1,174 @@ Function Send-SQLDataToExcel { - [CmdLetBinding()] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword","")] - <# - .Synopsis - Runs a SQL query and inserts the results into an ExcelSheet, more efficiently than sending it via Export-Excel - .Description - This command takes either an object representing a session with a SQL server or ODBC database, or a connection String to make one. - It the runs a SQL command, and inserts the rows of data returned into a worksheet. - It takes most of the parameters of Export-Excel, but it is more efficient than getting dataRows and piping them into Export-Excel, - data-rows have additional properties which need to be stripped off. - .Example - C:\> Send-SQLDataToExcel -MsSQLserver -Connection localhost -SQL "select name,type,type_desc from [master].[sys].[all_objects]" -Path .\temp.xlsx -WorkSheetname master -AutoSize -FreezeTopRow -AutoFilter -BoldTopRow - Connects to the local SQL server and selects 3 columns from [Sys].[all_objects] and exports then to a sheet named master with some basic header manager - .Example - C:\> $SQL="SELECT top 25 DriverName, Count(RaceDate) as Races, Count(Win) as Wins, Count(Pole) as Poles, Count(FastestLap) as Fastlaps FROM Results GROUP BY DriverName ORDER BY (count(win)) DESC" - C:\> $Connection = 'Driver={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};DriverId=790;ReadOnly=0;Dbq=C:\users\James\Documents\f1Results.xlsx;' - C:\> Send-SQLDataToExcel -Connection $connection -SQL $sql -path .\demo4.xlsx -WorkSheetname "Winners" -AutoSize -AutoNameRange + <# + .SYNOPSIS + Inserts a DataTable - returned by SQL query into an ExcelSheet, more efficiently than sending it via Export-Excel + .DESCRIPTION + This command can accept a data table object or take a SQL command and run it against a database connection. + If running the SQL command, it accepts an object representing a session with a SQL server or ODBC database, or a connection String to make a session. + It the DataTable is inserted into the Excel sheet + It takes most of the parameters of Export-Excel, but it is more efficient than getting dataRows and piping them into Export-Excel, + data-rows have additional properties which need to be stripped off. + .PARAMETER DataTable + A System.Data.DataTable object containing the data to be inserted into the spreadsheet without running a query. + .PARAMETER Session + An active ODBC Connection or SQL connection object representing a session with a database which will be queried to get the data . + .PARAMETER Connection + Database connection string; either DSN=ODBC_Data_Source_Name, a full odbc or SQL Connection string, or the name of a SQL server. This is used to create a database session. + .PARAMETER MSSQLServer + Specifies the connection string is for SQL server, not ODBC . + .PARAMETER SQL + The SQL query to run against the session which was passed in -Session or set up from $Connection. + .PARAMETER Database + Switches to a specific database on a SQL server. + .PARAMETER QueryTimeout + Override the default query time of 30 seconds. + .PARAMETER Path + Path to a new or existing .XLSX file. + .PARAMETER WorkSheetName + The name of a sheet within the workbook - "Sheet1" by default . + .PARAMETER KillExcel + Closes Excel - prevents errors writing to the file because Excel has it open + .PARAMETER Title + Text of a title to be placed in the top left cell. + .PARAMETER TitleBold + Sets the title in boldface type. + .PARAMETER TitleSize + Sets the point size for the title. + .PARAMETER TitleBackgroundColor + Sets the cell background color for the title cell. + .PARAMETER TitleFillPattern + Sets the fill pattern 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 PivotTableName + If a Pivot table is created from command line parameters, specificies the name of the new sheet holding the pivot. If Omitted this will be "WorksheetName-PivotTable" + .PARAMETER PivotRows + Name(s) columns from the spreadhseet which will provide the Row name(s) in a pivot table created from command line parameters. + .PARAMETER PivotColumns + Name(s) columns from the spreadhseet which will provide the Column name(s) in a pivot table created from command line parameters. + .PARAMETER PivotFilter + Name(s) columns from the spreadhseet which will provide the Filter name(s) in a pivot table created from command line parameters. + .PARAMETER PivotData + In a pivot table created from command line parameters, the fields to use in the table body is given as a Hash table in the form ColumnName = Average|Count|CountNums|Max|Min|Product|None|StdDev|StdDevP|Sum|Var|VarP . + .PARAMETER NoTotalsInPivot + In a pivot table created from command line parameters, prevents the addition of totals to rows and columns. + .PARAMETER IncludePivotChart + Include a chart with the Pivot table - implies -IncludePivotTable. + .PARAMETER ChartType + The type for Pivot chart (one of Excel's defined chart types) + .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 PivotTableDefinition + Instead of describing a single pivot table with mutliple commandline paramters; you can use a HashTable in the form PivotTableName = Definition; + Definition is itself a hashtable with Sheet PivotTows, PivotColumns, PivotData, IncludePivotChart and ChartType values. + .PARAMETER ConditionalFormat + One or more conditional formatting rules defined with New-ConditionalFormattingIconSet. + .PARAMETER ConditionalText + Applies a 'Conditional formatting rule' in Excel on all the cells. When specific conditions are met a rule is triggered. + .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 AutoNameRange + Makes each column 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 BarChart + Creates a "quick" bar chart using the first text column as labels and the first numeric column as values + .PARAMETER ColumnChart + Creates a "quick" column chart using the first text column as labels and the first numeric column as values + .PARAMETER LineChart + Creates a "quick" line chart using the first text column as labels and the first numeric column as values + .PARAMETER PieChart + Creates a "quick" pie chart using the first text column as labels and the first numeric column as values + .PARAMETER ExcelChartDefinition + A hash table containing ChartType, Title, NoLegend, ShowCategory, ShowPercent, Yrange, Xrange and SeriesHeader for one or more [non-pivot] charts. + .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. + .PARAMETER AutoSize + Sizes the width of the Excel column to the maximum width needed to display all the containing data in that cell. + .PARAMETER Show + Opens the Excel file immediately after creation. Convenient for viewing the results instantly without having to search for the file first. + .PARAMETER ReturnRange + If specified, Export-Excel returns the range of added cells in the format "A1:Z100" + .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. - This declares a SQL statement and creates an ODBC connection string to read from an Excel file, it then runs the statement and outputs the resulting data to a new spreadsheet. - .Example - C:\> Send-SQLDataToExcel -path .\demo4.xlsx -WorkSheetname "LR" -Connection "DSN=LR" -sql "SELECT name AS CollectionName FROM AgLibraryCollection Collection ORDER BY CollectionName" + .EXAMPLE + C:\> Send-SQLDataToExcel -MsSQLserver -Connection localhost -SQL "select name,type,type_desc from [master].[sys].[all_objects]" -Path .\temp.xlsx -WorkSheetname master -AutoSize -FreezeTopRow -AutoFilter -BoldTopRow + Connects to the local SQL server and selects 3 columns from [Sys].[all_objects] and exports then to a sheet named master with some basic header manager + .EXAMPLE + C:\> $SQL="SELECT top 25 DriverName, Count(RaceDate) as Races, Count(Win) as Wins, Count(Pole) as Poles, Count(FastestLap) as Fastlaps FROM Results GROUP BY DriverName ORDER BY (count(win)) DESC" + C:\> $Connection = 'Driver={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};DriverId=790;ReadOnly=0;Dbq=C:\users\James\Documents\f1Results.xlsx;' + C:\> Send-SQLDataToExcel -Connection $connection -SQL $sql -path .\demo1.xlsx -WorkSheetname "Winners" -AutoSize -AutoNameRange - This example uses an Existing ODBC datasource name "LR" which maps to an adobe lightroom database and gets a list of collection names into a worksheet - + This declares a SQL statement and creates an ODBC connection string to read from an Excel file, it then runs the statement and outputs the resulting data to a new spreadsheet. + (the F1 results database is available from https://1drv.ms/x/s!AhfYu7-CJv4ehNdZWxJE9LMAX_N5sg ) + .EXAMPLE + C:\> $SQL = "SELECT top 25 DriverName, Count(RaceDate) as Races, Count(Win) as Wins, Count(Pole) as Poles, Count(FastestLap) as Fastlaps FROM Results GROUP BY DriverName ORDER BY (count(win)) DESC" + C:\> Get-SQL -Session F1 -excel -Connection "C:\Users\mcp\OneDrive\public\f1\f1Results.xlsx" -sql $sql -OutputVariable Table | out-null + C:\> Send-SQLDataToExcel -DataTable $Table -Path ".\demo3.xlsx" -WorkSheetname Gpwinners -autosize -TableName winners -TableStyle Light6 -show + This uses Get-SQL (at least V1.1 download from the gallery with Install-Module -Name GetSQL - note the function is get-SQL the module is GetSQL without the "-" ) + to simplify making database connections and building /submitting SQL statements. + Here it uses the same SQL statement as before; -OutputVariable leaves a System.Data.DataTable object in $table + and Send-SQLDataToExcel puts $table into the worksheet and sets it as an Excel table. + (the F1 results database is available from https://1drv.ms/x/s!AhfYu7-CJv4ehNdZWxJE9LMAX_N5sg ) + .EXAMPLE + C:\> $SQL = "SELECT top 25 DriverName, Count(Win) as Wins FROM Results GROUP BY DriverName ORDER BY (count(win)) DESC" + C:\> Send-SQLDataToExcel -Session $DbSessions["f1"] -SQL $sql -Path ".\demo3.xlsx" -WorkSheetname Gpwinners -autosize -ColumnChart - #> - param ( - #Database connection string; either DSN=ODBC_Data_Source_Name, a full odbc or SQL Connection string, or the name of a SQL server + Like the previous example, this uses Get-SQL (download from the gallery with Install-Module -Name GetSQL).It uses the connection which Get-SQL made rather than an ODFBC connection string + Here the data is presented as a quick chart. + .EXAMPLE + C:\> Send-SQLDataToExcel -path .\demo3.xlsx -WorkSheetname "LR" -Connection "DSN=LR" -sql "SELECT name AS CollectionName FROM AgLibraryCollection Collection ORDER BY CollectionName" + + This example uses an Existing ODBC datasource name "LR" which maps to an adobe lightroom database and gets a list of collection names into a worksheet + #> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword","")] + param ( [Parameter(ParameterSetName="SQLConnection", Mandatory=$true)] [Parameter(ParameterSetName="ODBCConnection",Mandatory=$true)] - $Connection, - #A pre-existing database session object - [Parameter(ParameterSetName="ExistingSession",Mandatory=$true)] + $Connection, + [Parameter(ParameterSetName="ExistingSession",Mandatory=$true)] [System.Data.Common.DbConnection]$Session, - #Specifies the connection string is for SQL server not ODBC [Parameter(ParameterSetName="SQLConnection",Mandatory=$true)] [switch]$MsSQLserver, - #Switches to a specific database on a SQL server [Parameter(ParameterSetName="SQLConnection")] [String]$DataBase, - #The SQL query to run - [Parameter(Mandatory=$true)] - [string]$SQL, - #Override the default query time of 30 seconds. - [int]$QueryTimeout, - #File name for the Excel File - $Path, - [String]$WorkSheetname = 'Sheet1', - [Switch]$KillExcel, - #If Specified, open the file created. + [Parameter(ParameterSetName="SQLConnection", Mandatory=$true)] + [Parameter(ParameterSetName="ODBCConnection",Mandatory=$true)] + [Parameter(ParameterSetName="ExistingSession",Mandatory=$true)] + [string]$SQL, + [int]$QueryTimeout, + [Parameter(ParameterSetName="Pre-FetchedData",Mandatory=$true)] + [System.Data.DataTable]$DataTable, + $Path, + [String]$WorkSheetname = 'Sheet1', + [Switch]$KillExcel, [Switch]$Show, [String]$Title, [OfficeOpenXml.Style.ExcelFillStyle]$TitleFillPattern = 'None', @@ -57,11 +176,14 @@ [Int]$TitleSize = 22, [System.Drawing.Color]$TitleBackgroundColor, [String]$Password, + [Hashtable]$PivotTableDefinition, + [Switch]$IncludePivotTable, [String[]]$PivotRows, [String[]]$PivotColumns, $PivotData, - [Switch]$PivotDataToColumn, - [Hashtable]$PivotTableDefinition, + [String[]]$PivotFilter, + [Switch]$PivotDataToColumn, + [Switch]$NoTotalsInPivot, [Switch]$IncludePivotChart, [OfficeOpenXml.Drawing.Chart.eChartType]$ChartType = 'Pie', [Switch]$NoLegend, @@ -78,6 +200,10 @@ [String]$RangeName, [String]$TableName, [OfficeOpenXml.Table.TableStyles]$TableStyle = 'Medium6', + [Switch]$Barchart, + [Switch]$PieChart, + [Switch]$LineChart , + [Switch]$ColumnChart , [Object[]]$ExcelChartDefinition, [Switch]$AutoNameRange, [Object[]]$ConditionalFormat, @@ -85,55 +211,58 @@ [ScriptBlock]$CellStyleSB, [Int]$StartRow = 1, [Int]$StartColumn = 1, - #If Specified, return an ExcelPackage object to allow further work to be done on the file. + [Switch]$ReturnRange, [Switch]$Passthru ) - + if ($KillExcel) { Get-Process excel -ErrorAction Ignore | Stop-Process - while (Get-Process excel -ErrorAction Ignore) {} + while (Get-Process excel -ErrorAction Ignore) {Start-Sleep -Milliseconds 250} } - + #We were either given a session object or a connection string (with, optionally a MSSQLServer parameter) # If we got -MSSQLServer, create a SQL connection, if we didn't but we got -Connection create an ODBC connection - if ($MsSQLserver) { - if ($Connection -notmatch "=") {$Connection = "server=$Connection;trusted_connection=true;timeout=60"} - $Session = New-Object -TypeName System.Data.SqlClient.SqlConnection -ArgumentList $Connection - if ($Session.State -ne 'Open') {$Session.Open()} + if ($MsSQLserver -and $Connection) { + if ($Connection -notmatch "=") {$Connection = "server=$Connection;trusted_connection=true;timeout=60"} + $Session = New-Object -TypeName System.Data.SqlClient.SqlConnection -ArgumentList $Connection + if ($Session.State -ne 'Open') {$Session.Open()} if ($DataBase) {$Session.ChangeDatabase($DataBase) } } elseif ($Connection) { - $Session = New-Object -TypeName System.Data.Odbc.OdbcConnection -ArgumentList $Connection ; $Session.ConnectionTimeout = 30 + $Session = New-Object -TypeName System.Data.Odbc.OdbcConnection -ArgumentList $Connection ; $Session.ConnectionTimeout = 30 } - #A session was either passed in or just created. If it's a SQL one make a SQL DataAdapter, otherwise make an ODBC one - if ($Session.GetType().name -match "SqlConnection") { - $dataAdapter = New-Object -TypeName System.Data.SqlClient.SqlDataAdapter -ArgumentList ( - New-Object -TypeName System.Data.SqlClient.SqlCommand -ArgumentList $SQL, $Session) + If ($session) { + #A session was either passed in or just created. If it's a SQL one make a SQL DataAdapter, otherwise make an ODBC one + if ($Session.GetType().name -match "SqlConnection") { + $dataAdapter = New-Object -TypeName System.Data.SqlClient.SqlDataAdapter -ArgumentList ( + New-Object -TypeName System.Data.SqlClient.SqlCommand -ArgumentList $SQL, $Session) + } + else { + $dataAdapter = New-Object -TypeName System.Data.Odbc.OdbcDataAdapter -ArgumentList ( + New-Object -TypeName System.Data.Odbc.OdbcCommand -ArgumentList $SQL, $Session ) + } + if ($QueryTimeout) {$dataAdapter.SelectCommand.CommandTimeout = $ServerTimeout} + + #Both adapter types output the same kind of table, create one and fill it from the adapter + $DataTable = New-Object -TypeName System.Data.DataTable + $rowCount = $dataAdapter.fill($dataTable) + Write-Verbose -Message "Query returned $rowCount row(s)" } - else { - $dataAdapter = New-Object -TypeName System.Data.Odbc.OdbcDataAdapter -ArgumentList ( - New-Object -TypeName System.Data.Odbc.OdbcCommand -ArgumentList $SQL, $Session ) + if ($DataTable.Rows) { + #ExportExcel user a -NoHeader parameter so that's what we use here, but needs to be the other way around. + $printHeaders = -not $NoHeader + if ($Title) {$r = $StartRow +1 } + else {$r = $StartRow} + #Get our Excel sheet and fill it with the data + $excelPackage = Export-Excel -Path $Path -WorkSheetname $WorkSheetname -PassThru + $excelPackage.Workbook.Worksheets[$WorkSheetname].Cells[$r,$StartColumn].LoadFromDataTable($dataTable, $printHeaders ) | Out-Null + + #Call export-excel with any parameters which don't relate to the SQL query + "Connection", "Database" , "Session", "MsSQLserver", "Destination" , "SQL" , "DataTable", "Path" | ForEach-Object {$null = $PSBoundParameters.Remove($_) } + Export-Excel -ExcelPackage $excelPackage @PSBoundParameters } - if ($QueryTimeout) {$dataAdapter.SelectCommand.CommandTimeout = $ServerTimeout} - - #Both adapter types output the same kind of table, create one and fill it from the adapter - $dataTable = New-Object -TypeName System.Data.DataTable - $rowCount = $dataAdapter.fill($dataTable) - Write-Verbose -Message "Query returned $rowCount row(s)" - - #ExportExcel user a -NoHeader parameter so that's what we use here, but needs to be the other way around. - $printHeaders = -not $NoHeader - if ($Title) {$r = $StartRow +1 } - else {$r = $StartRow} - #Get our Excel sheet and fill it with the data - $excelPackage = Export-Excel -Path $Path -WorkSheetname $WorkSheetname -PassThru - $excelPackage.Workbook.Worksheets[$WorkSheetname].Cells[$r,$StartColumn].LoadFromDataTable($dataTable, $printHeaders ) | Out-Null - - #Call export-excel with any parameters which don't relate to the SQL query - "Connection", "Database" , "Session", "MsSQLserver", "Destination" , "SQL" ,"Path" | ForEach-Object {$null = $PSBoundParameters.Remove($_) } - Export-Excel -ExcelPackage $excelPackage @PSBoundParameters - - #If we were not passed a session close the session we created. - if ($Connection) {$Session.close() } + else {Write-Warning -Message "No Data to insert."} + #If we were passed a connection and opened a session, close that session. + if ($Connection) {$Session.close() } } diff --git a/Set-Column.ps1 b/Set-Column.ps1 index 5246a6a..76e1d6c 100644 --- a/Set-Column.ps1 +++ b/Set-Column.ps1 @@ -1,31 +1,32 @@ Function Set-Column { -<# - .SYNOPSIS + <# + .SYNOPSIS Adds a column to the existing data area in an Excel sheet, fills values and sets formatting - .DESCRIPTION + .DESCRIPTION Set-Column takes a value which is either string containing a value or formula or a scriptblock which evaluates to a string, and optionally a column number and fills that value down the column. A column name can be specified and the new column can be made a named range. The column can be formatted. - .Example + .Example C:> Set-Column -Worksheet $ws -Heading "WinsToFastLaps" -Value {"=E$row/C$row"} -Column 7 -AutoSize -AutoNameRange Here $WS already contains a worksheet which contains counts of races won and fastest laps recorded by racing drivers (in columns C and E) Set-Column specifies that Column 7 should have a heading of "WinsToFastLaps" and the data cells should contain =E2/C2 , =E3/C3 - the data celss should become a named range, which will also be "WinsToFastLaps" the column width will be set automatically - -#> -[cmdletbinding()] + the data cells should become a named range, which will also be "WinsToFastLaps" the column width will be set automatically + #> + [cmdletbinding()] Param ( [Parameter(ParameterSetName="Package",Mandatory=$true)] [OfficeOpenXml.ExcelPackage]$ExcelPackage, - #Sheet to update + #The sheet to update can be a given as a name or an Excel Worksheet object - this sets it by name [Parameter(ParameterSetName="Package")] + #The sheet to update can be a given as a name or an Excel Worksheet object - $workSheet contains the object $Worksheetname = "Sheet1", [Parameter(ParameterSetName="sheet",Mandatory=$true)] [OfficeOpenXml.ExcelWorksheet] $Worksheet, #Column to fill down - first column is 1. 0 will be interpreted as first unused column $Column = 0 , + #First row to fill data in [Int]$StartRow , #value, formula or script block for to fill in. Script block can use $row, $column [number], $ColumnName [letter(s)], $startRow, $startColumn, $endRow, $endColumn [parameter(Mandatory=$true)] @@ -76,8 +77,9 @@ [Switch]$AutoSize, #Set cells to a fixed width, ignored if Autosize is specified [float]$Width, - #Set the inserted data to be a named range (ignored if header is not specified) d + #Set the inserted data to be a named range (ignored if header is not specified) [Switch]$AutoNameRange, + #If Specified, return an ExcelPackage object to allow further work to be done on the file. [switch]$PassThru ) #if we were passed a package object and a worksheet name , get the worksheet. @@ -125,7 +127,7 @@ if ($HorizontalAlignment) { $Worksheet.Column( $Column).Style.HorizontalAlignment = $HorizontalAlignment} if ($VerticalAlignment) { $Worksheet.Column( $Column).Style.VerticalAlignment = $VerticalAlignment } if ($FontColor) { $Worksheet.Column( $Column).Style.Font.Color.SetColor( $FontColor ) } - if ($BorderAround) { $Worksheet.Column( $Column).Style.Border.BorderAround( $BorderAround ) } + if ($BorderAround) { $Worksheet.Column( $Column).Style.Border.BorderAround( $BorderAround ) } if ($BackgroundColor) { $Worksheet.Column( $Column).Style.Fill.PatternType = $BackgroundPattern $Worksheet.Column( $Column).Style.Fill.BackgroundColor.SetColor($BackgroundColor ) diff --git a/Set-Row.ps1 b/Set-Row.ps1 index 9250c1a..a05cc26 100644 --- a/Set-Row.ps1 +++ b/Set-Row.ps1 @@ -1,24 +1,22 @@ Function Set-Row { -<# -.Synopsis - Fills values into a row in a Excel spreadsheet -.Description - Set-Row accepts either a Worksheet object or an Excel package object returned by Export-Excel and the name of a sheet, - and inserts the chosen contents into a row of the sheet. - The contents can be a constant "42" , a formula or a script block which is converted into a constant or formula. - The first cell of the row can optional be given a heading. -.Example - Set-row -Worksheet $ws -Heading Total -Value {"=sum($columnName`2:$columnName$endrow)" } + <# + .Synopsis + Fills values into a row in a Excel spreadsheet + .Description + Set-Row accepts either a Worksheet object or an Excel package object returned by Export-Excel and the name of a sheet, + and inserts the chosen contents into a row of the sheet. + The contents can be a constant "42" , a formula or a script block which is converted into a constant or formula. + The first cell of the row can optional be given a heading. + .Example + Set-row -Worksheet $ws -Heading Total -Value {"=sum($columnName`2:$columnName$endrow)" } - $Ws contains a worksheet object, and no Row number is specified so Set-Row will select the next row after the end of the data in the sheet - The first cell will contain "Total", and each other cell will contain - =Sum(xx2:xx99) - where xx is the column name, and 99 is the last row of data. - Note the use of `2 to Prevent 2 becoming part of the variable "ColumnName" - The script block can use $row, $column, $ColumnName, $startRow/Column $endRow/Column - - -#> -[cmdletbinding()] + $Ws contains a worksheet object, and no Row number is specified so Set-Row will select the next row after the end of the data in the sheet + The first cell will contain "Total", and each other cell will contain + =Sum(xx2:xx99) - where xx is the column name, and 99 is the last row of data. + Note the use of `2 to Prevent 2 becoming part of the variable "ColumnName" + The script block can use $row, $column, $ColumnName, $startRow/Column $endRow/Column + #> + [cmdletbinding()] Param ( #An Excel package object - e.g. from Export-Excel -passthru - requires a sheet name [Parameter(ParameterSetName="Package",Mandatory=$true)] @@ -75,11 +73,12 @@ [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. + #Degrees to rotate text. Up to +90 for anti-clockwise ("upwards"), or to -90 for clockwise [ValidateRange(-90, 90)] [int]$TextRotation , #Set cells to a fixed hieght [float]$Height, + #If Specified, return an ExcelPackage object to allow further work to be done on the file [switch]$PassThru ) @@ -130,7 +129,7 @@ if ($VerticalAlignment) { $worksheet.row( $Row ).Style.VerticalAlignment = $VerticalAlignment } if ($Height) { $worksheet.row( $Row ).Height = $Height } if ($FontColor) { $worksheet.row( $Row ).Style.Font.Color.SetColor( $FontColor ) } - if ($BorderAround) { $worksheet.row( $Row ).Style.Border.BorderAround( $BorderAround ) } + if ($BorderAround) { $worksheet.row( $Row ).Style.Border.BorderAround( $BorderAround ) } if ($BackgroundColor) { $worksheet.row( $Row ).Style.Fill.PatternType = $BackgroundPattern $worksheet.row( $Row ).Style.Fill.BackgroundColor.SetColor($BackgroundColor ) diff --git a/SetFormat.ps1 b/SetFormat.ps1 index d4a1928..7d83579 100644 --- a/SetFormat.ps1 +++ b/SetFormat.ps1 @@ -83,7 +83,7 @@ begin { #Allow Set-Format to take Worksheet and range parameters (like Add Contitional formatting) - convert them to an address if ($WorkSheet -and $Range) {$Address = $WorkSheet.Cells[$Range] } - } + } process { if ($Address -is [Array]) { @@ -92,51 +92,49 @@ } else { if ($ResetFont) { - $Address.Style.Font.Color.SetColor("Black") - $Address.Style.Font.Bold = $false - $Address.Style.Font.Italic = $false - $Address.Style.Font.UnderLine = $false - $Address.Style.Font.Strike = $false + $Address.Style.Font.Color.SetColor("Black") + $Address.Style.Font.Bold = $false + $Address.Style.Font.Italic = $false + $Address.Style.Font.UnderLine = $false + $Address.Style.Font.Strike = $false } - if ($Underline) { - $Address.Style.Font.UnderLine = $true - $Address.Style.Font.UnderLineType = $UnderLineType + if ($Underline) { + $Address.Style.Font.UnderLine = $true + $Address.Style.Font.UnderLineType = $UnderLineType } - if ($Bold) {$Address.Style.Font.Bold = $true } - if ($Italic) {$Address.Style.Font.Italic = $true } - if ($StrikeThru) {$Address.Style.Font.Strike = $true } - if ($FontShift) {$Address.Style.Font.VerticalAlign = $FontShift } - if ($FontColor) {$Address.Style.Font.Color.SetColor( $FontColor ) } - - if ($BorderAround) { - $Address.Style.Border.BorderAround($BorderAround, $BorderColor) - } + if ($Bold) {$Address.Style.Font.Bold = $true } + if ($Italic) {$Address.Style.Font.Italic = $true } + if ($StrikeThru) {$Address.Style.Font.Strike = $true } + if ($FontShift) {$Address.Style.Font.VerticalAlign = $FontShift } + if ($FontColor) {$Address.Style.Font.Color.SetColor( $FontColor ) } + if ($NumberFormat) {$Address.Style.Numberformat.Format = $NumberFormat } + if ($TextRotation) {$Address.Style.TextRotation = $TextRotation } + if ($WrapText) {$Address.Style.WrapText = $true } + if ($HorizontalAlignment) {$Address.Style.HorizontalAlignment = $HorizontalAlignment } + if ($VerticalAlignment) {$Address.Style.VerticalAlignment = $VerticalAlignment } + if ($Value) {$Address.Value = $Value } + if ($Formula) {$Address.Formula = $Formula } + if ($BorderAround) {$Address.Style.Border.BorderAround($BorderAround, $BorderColor)} - if ($BorderBottom) { + if ($BorderBottom) { $Address.Style.Border.Bottom.Style=$BorderBottom $Address.Style.Border.Bottom.Color.SetColor($BorderColor) } - if ($BorderTop) { + if ($BorderTop) { $Address.Style.Border.Top.Style=$BorderTop $Address.Style.Border.Top.Color.SetColor($BorderColor) } - if ($BorderLeft) { + if ($BorderLeft) { $Address.Style.Border.Left.Style=$BorderLeft $Address.Style.Border.Left.Color.SetColor($BorderColor) } - if ($BorderRight) { + if ($BorderRight) { $Address.Style.Border.Right.Style=$BorderRight $Address.Style.Border.Right.Color.SetColor($BorderColor) } - - if ($NumberFormat) {$Address.Style.Numberformat.Format = $NumberFormat } - if ($TextRotation) {$Address.Style.TextRotation = $TextRotation } - if ($WrapText) {$Address.Style.WrapText = $true } - if ($HorizontalAlignment) {$Address.Style.HorizontalAlignment = $HorizontalAlignment } - if ($VerticalAlignment) {$Address.Style.VerticalAlignment = $VerticalAlignment } if ($BackgroundColor) { $Address.Style.Fill.PatternType = $BackgroundPattern @@ -157,7 +155,7 @@ if ($Autosize) { if ($Address -is [OfficeOpenXml.ExcelColumn]) {$Address.AutoFit() } elseif ($Address -is [OfficeOpenXml.ExcelRange] ) { - $Address.AutoFitColumns() + $Address.AutoFitColumns() } else {Write-Warning -Message ("Can autofit a column or a range but not a {0} object" -f ($Address.GetType().name)) } @@ -179,13 +177,6 @@ else {Write-Warning -Message ("Can hide a row or a column but not a {0} object" -f ($Address.GetType().name)) } } - if ($Value) { - $Address.Value = $Value - } - - if ($Formula) { - $Address.Formula = $Formula - } } } } \ No newline at end of file diff --git a/ToDo.md b/ToDo.md index 82fad23..d3f8bff 100644 --- a/ToDo.md +++ b/ToDo.md @@ -1 +1,11 @@ - [ ] Create an autocomplete for WorkSheetName param on ImportExcel +- [ ] Add help text for parmaters which don't have it ( PivotDataToColumn , NoClobber and CellStyleSB ) in Export Excel, copy to Send-SQLDataToExcel +- [ ] Add checks for valid worksheet names (also check pivot names, range names and table names are valid) +- [ ] Investigate regional support for number conversion & possible date conversion +- [ ] Add help in ConvertToExcelXLSx.ps1, Copy-ExcelWorkSheet.ps1 (probably re-write copy) +- [ ] Add Help (continued) in Get-HTMLTable.ps1, GetRange.PS1, GetExcelTable.Ps1, Import-HTML.PS1, New-ConditionalFormattingIconSet.Ps1, NewConditionalText.PS1, New-Psitem.PS1, Remove-Worksheet.ps1 + [ ] Copy parameter help from function Add-ExcelChart into New-ExcelChart.ps1 +- [ ] Examples and tests for new "Quick charts" in Export Excel +- [ ] Charting.ps1,GetXYRange.ps1, InferData.PS1 move to deprecated. (replace examples) +- [ ] Refactor Set-Row and Set-Column to use set-format and add conditional format support. +- [ ] Examples and tests for set-Row and Set-column; review test coverage and examples for Set-Format adn Add-Conditional formatting \ No newline at end of file diff --git a/Update-FirstObjectProperties.ps1 b/Update-FirstObjectProperties.ps1 index 6716a59..f19935b 100644 --- a/Update-FirstObjectProperties.ps1 +++ b/Update-FirstObjectProperties.ps1 @@ -72,7 +72,8 @@ Function Update-FirstObjectProperties { .NOTES CHANGELOG - 2017/06/08 Function born #> + 2017/06/08 Function born + #> Try { $Union = @() diff --git a/__tests__/Export-Excel.Tests.ps1 b/__tests__/Export-Excel.Tests.ps1 index d415518..a53a6af 100644 --- a/__tests__/Export-Excel.Tests.ps1 +++ b/__tests__/Export-Excel.Tests.ps1 @@ -7,62 +7,62 @@ Import-Module $PSScriptRoot\..\ImportExcel.psd1 -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 + 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 - # } + it "Created a new file " { + Test-Path -Path $path -ErrorAction SilentlyContinue | Should be $true + } - # it "Started Excel to display the file " { - # Get-process -Name Excel, xlim -ErrorAction SilentlyContinue | Should not benullorempty - # } + # it "Started Excel to display the file " { + # Get-process -Name Excel, xlim -ErrorAction SilentlyContinue | Should not benullorempty + # } - # Start-Sleep -Seconds 5 ; + Start-Sleep -Seconds 5 ; - # #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 - # } + #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 + } - # it "Created 1 worksheet " { - # $Excel.Workbook.Worksheets.count | Should be 1 - # } + 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 $propertyNames.Count - # $ws.Dimension.Rows | Should be ($rowcount + 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 $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 - # } - # } + $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 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 - # } - # } + 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 + } + } Context " # NoAliasOrScriptPropeties -ExcludeProperty and -DisplayPropertySet work" { $path = "$env:TEMP\Test.xlsx" @@ -81,7 +81,7 @@ Describe ExportExcel { $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. " { + 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. @@ -146,59 +146,91 @@ Describe ExportExcel { Context "#Examples 3 & 4 # Setting cells for different data types Also added test for URI type" { + if ((Get-Culture).NumberFormat.CurrencySymbol -eq "£") {$OtherCurrencySymbol = "$"} + else {$OtherCurrencySymbol = "£"} $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 + Date = Get-Date + Formula1 = '=SUM(F2:G2)' + String1 = 'My String' + Float = [math]::pi + IPAddress = '10.10.25.5' + StrLeadZero = '07670' + StrComma = '0,26' + StrEngThousand = '1,234.56' + StrEuroThousand = '1.555,83' + StrDot = '1.2' + StrNegInt = '-31' + StrTrailingNeg = '31-' + StrParens = '(123)' + strLocalCurrency = ('{0}123.45' -f (Get-Culture).NumberFormat.CurrencySymbol ) + strOtherCurrency = ('{0}123.45' -f $OtherCurrencySymbol ) + StrE164Phone = '+32 (444) 444 4444' + StrAltPhone1 = '+32 4 4444 444' + StrAltPhone2 = '+3244444444' + StrLeadSpace = ' 123' + StrTrailSpace = '123 ' + Link1 = [uri]"https://github.com/dfinke/ImportExcel" #2,15 + Link2 = "https://github.com/dfinke/ImportExcel" #2, 16 + } | Export-Excel -NoNumberConversion IPAddress, StrLeadZero, StrAltPhone2 -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.Columns | Should be 22 $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 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 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 strings in Cells E2, F2 and R2 (no number conversion) " { + $ws.Cells[2, 5].Value.GetType().name | Should be 'String' + $ws.Cells[2, 6].Value.GetType().name | Should be 'String' + $ws.Cells[2, 18].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 numbers in Cells K2,L2,M2 (diferent Negative integer formats) " { + ($ws.Cells[2, 11].Value -is [valuetype] ) | Should be $true + ($ws.Cells[2, 12].Value -is [valuetype] ) | Should be $true + ($ws.Cells[2, 13].Value -is [valuetype] ) | Should be $true + $ws.Cells[2, 11].Value | Should beLessThan 0 + $ws.Cells[2, 12].Value | Should beLessThan 0 + $ws.Cells[2, 13].Value | Should beLessThan 0 } - - it "Set a hyperlink in Cell N2 " { - $ws.Cells[2, 14].Hyperlink | Should be "https://github.com/dfinke/ImportExcel" + it "Set hyperlinks in Cells U2 and V2 " { + $ws.Cells[2, 21].Hyperlink | Should be "https://github.com/dfinke/ImportExcel" + $ws.Cells[2, 22].Hyperlink | Should be "https://github.com/dfinke/ImportExcel" + } + it "Processed thousands according to local settings (Cells H2 and I2) " { + if ((Get-Culture).NumberFormat.NumberGroupSeparator -EQ ",") { + ($ws.Cells[2, 8].Value -is [valuetype] ) | Should be $true + $ws.Cells[2, 9].Value.GetType().name | Should be 'String' + } + elseif ((Get-Culture).NumberFormat.NumberGroupSeparator -EQ ".") { + ($ws.Cells[2, 9].Value -is [valuetype] ) | Should be $true + $ws.Cells[2, 8].Value.GetType().name | Should be 'String' + } + } + it "Processed local currency as a number and other currency as a string (N2 & O2) " { + ($ws.Cells[2, 14].Value -is [valuetype] ) | Should be $true + $ws.Cells[2, 15].Value.GetType().name | Should be 'String' + } + it "Processed numbers with spaces between digits as strings (P2 & Q2) " { + $ws.Cells[2, 16].Value.GetType().name | Should be 'String' + $ws.Cells[2, 17].Value.GetType().name | Should be 'String' + } + it "Processed numbers leading or trailing speaces as Numbers (S2 & T2) " { + ($ws.Cells[2, 19].Value -is [valuetype] ) | Should be $true + ($ws.Cells[2, 20].Value -is [valuetype] ) | Should be $true } } @@ -438,7 +470,7 @@ Describe ExportExcel { $excel.Workbook.Worksheets[6].Name | Should be "Sheet1" } - it "Cloned 'Sheet1' to 'NewSheet' " { + 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) @@ -686,6 +718,41 @@ Describe ExportExcel { Close-ExcelPackage -ExcelPackage $excel -nosave } + Context " # Awkward multiple tables" { + $path = "$Env:TEMP\test.xlsx" + remove-item -Path $path -ErrorAction SilentlyContinue + $r = Get-ChildItem -path C:\WINDOWS\system32 -File + + "Biggest files" | Export-Excel -Path $path -StartRow 1 -StartColumn 7 + $r | Sort-Object length -Descending | Select-Object -First 14 Name, @{n="Size";e={$_.Length}} | + Export-Excel -Path $path -TableName FileSize -StartRow 2 -StartColumn 7 -TableStyle Medium2 + + $r.extension | Group-Object | Sort-Object -Property count -Descending | Select-Object -First 12 Name, Count | + Export-Excel -Path $path -TableName ExtSize -Title "Frequent Extensions" -TitleSize 11 + + $r | Group-Object -Property extension | Select-Object Name, @{n="Size"; e={($_.group | Measure-Object -property length -sum).sum}} | + Sort-Object -Property size -Descending | Select-Object -First 10 | + Export-Excel -Path $path -TableName ExtCount -Title "Biggest extensions" -TitleSize 11 -StartColumn 4 -AutoSize + + $excel = Open-ExcelPackage -Path $path + $ws = $excel.Workbook.Worksheets[1] + it "Created 3 tables " { + $ws.tables.count | should be 3 + } + it "Created the FileSize table in the right place with the right size and style " { + $ws.Tables["FileSize"].Address.Address | should be "G2:H16" #Insert at row 2, Column 7, 14 rows x 2 columns of data + $ws.Tables["FileSize"].StyleName | should be "TableStyleMedium2" + } + it "Created the ExtSize table in the right place with the right size " { + $ws.Tables["ExtSize"].Address.Address | should be "A2:B14" #tile, then 12 rows x 2 columns of data + } + it "Created the ExtCount table in the right place with the right size " { + $ws.Tables["ExtCount"].Address.Address | should be "D2:E12" #title, then 10 rows x 2 columns of data + } + } + + + ## To do ## More Charts , pivot options & other FreezePanes settings ? ## Style script block diff --git a/__tests__/Join-Worksheet.tests.ps1 b/__tests__/Join-Worksheet.tests.ps1 new file mode 100644 index 0000000..8e8ce0b --- /dev/null +++ b/__tests__/Join-Worksheet.tests.ps1 @@ -0,0 +1,103 @@ +$data1 = ConvertFrom-Csv -InputObject @" +ID,Product,Quantity,Price,Total +12001,Nails,37,3.99,147.63 +12002,Hammer,5,12.10,60.5 +12003,Saw,12,15.37,184.44 +12010,Drill,20,8,160 +12011,Crowbar,7,23.48,164.36 +"@ +$data2 = ConvertFrom-Csv -InputObject @" +ID,Product,Quantity,Price,Total +12001,Nails,53,3.99,211.47 +12002,Hammer,6,12.10,72.60 +12003,Saw,10,15.37,153.70 +12010,Drill,10,8,80 +12012,Pliers,2,14.99,29.98 +"@ +$data3 = ConvertFrom-Csv -InputObject @" +ID,Product,Quantity,Price,Total +12001,Nails,20,3.99,79.80 +12002,Hammer,2,12.10,24.20 +12010,Drill,11,8,88 +12012,Pliers,3,14.99,44.97 +"@ + +Describe "Join Worksheet" { + BeforeAll { + $path = "$Env:TEMP\test.xlsx" + Remove-Item -Path $path -ErrorAction SilentlyContinue + $data1 | Export-Excel -Path $path -WorkSheetname Oxford + $data2 | Export-Excel -Path $path -WorkSheetname Abingdon + $data3 | Export-Excel -Path $path -WorkSheetname Banbury + $ptdef = New-PivotTableDefinition -PivotTableName "Summary" -PivotRows "Store" -PivotColumns "Product" -PivotData @{"Total"="SUM"} -IncludePivotChart -ChartTitle "Sales Breakdown" -ChartType ColumnStacked -ChartColumn 10 + Join-Worksheet -Path $path -WorkSheetName "Total" -Clearsheet -FromLabel "Store" -TableName "Summary" -TableStyle Light1 -AutoSize -BoldTopRow -FreezePane 2,1 -Title "Store Sales Summary" -TitleBold -TitleSize 14 -PivotTableDefinition $ptdef + $excel = Open-ExcelPackage -Path $path + $ws = $excel.Workbook.Worksheets["Total"] + $pt = $excel.Workbook.Worksheets["Summary"].pivottables[0] + $pc = $excel.Workbook.Worksheets["Summary"].Drawings[0] + } + Context "Merge 3 blocks" { + it "Created sheet of the right size with a title and a table " { + $ws.Dimension.Address | Should be "A1:F16" + $ws.Tables[0].Address.Address | Should be "A2:F16" + $ws.cells["A1"].Value | Should be "Store Sales Summary" + $ws.cells["A1"].Style.Font.Size | Should be 14 + $ws.Tables[0].StyleName | Should be "TableStyleLight1" + $ws.cells["A2:F2"].Style.Font.Bold | Should be $True + } + it "Added a from column with the right heading " { + $ws.cells["F2" ].Value | Should be "Store" + $ws.cells["F3" ].Value | Should be "Oxford" + $ws.cells["F8" ].Value | Should be "Abingdon" + $ws.cells["F13"].Value | Should be "Banbury" + } + it "Filled in the data " { + $ws.cells["C3" ].Value | Should be $data1[0].quantity + $ws.cells["C8" ].Value | Should be $data2[0].quantity + $ws.cells["C13"].Value | Should be $data3[0].quantity + } + it "Created the pivot table " { + $pt | Should not beNullOrEmpty + $pt.StyleName | Should be "PivotStyleMedium9" + $pt.RowFields[0].Name | Should be "Store" + $pt.ColumnFields[0].name | Should be "Product" + $pt.DataFields[0].name | Should be "Sum of Total" + $pc.ChartType | Should be "ColumnStacked" + $pc.Title.text | Should be "Sales Breakdown" + } + } + $path = "$env:TEMP\Test.xlsx" + Remove-item -Path $path -ErrorAction SilentlyContinue + Get-WmiObject -Class win32_logicaldisk | + Select-Object -Property DeviceId,VolumeName, Size,Freespace | + Export-Excel -Path $path -WorkSheetname Volumes -NumberFormat "0,000" + Get-NetAdapter | + Select-Object -Property Name,InterfaceDescription,MacAddress,LinkSpeed | + Export-Excel -Path $path -WorkSheetname NetAdapters + + Join-Worksheet -Path $path -HideSource -WorkSheetName Summary -NoHeader -LabelBlocks -AutoSize -Title "Summary" -TitleBold -TitleSize 22 + $excel = Open-ExcelPackage -Path $path + $ws = $excel.Workbook.Worksheets["Summary"] + Context "3 Unlinked blocks" { + it "Hid the source worksheets " { + $excel.Workbook.Worksheets[1].Hidden.tostring() | should be "Hidden" + $excel.Workbook.Worksheets[2].Hidden.tostring() | should be "Hidden" + } + it "Created the Summary sheet with title, and block labels, and copied the correct data " { + $ws.Cells["A1"].Value | should be "Summary" + $ws.Cells["A2"].Value | should be $excel.Workbook.Worksheets[1].name + $ws.Cells["A3"].Value | should be $excel.Workbook.Worksheets[1].Cells["A1"].value + $ws.Cells["A4"].Value | should be $excel.Workbook.Worksheets[1].Cells["A2"].value + $ws.Cells["B4"].Value | should be $excel.Workbook.Worksheets[1].Cells["B2"].value + $nextRow = $excel.Workbook.Worksheets[1].Dimension.Rows + 3 + $ws.Cells["A$NextRow"].Value | should be $excel.Workbook.Worksheets[2].name + $nextRow ++ + $ws.Cells["A$NextRow"].Value | should be $excel.Workbook.Worksheets[2].Cells["A1"].value + $nextRow ++ + $ws.Cells["A$NextRow"].Value | should be $excel.Workbook.Worksheets[2].Cells["A2"].value + $ws.Cells["B$NextRow"].Value | should be $excel.Workbook.Worksheets[2].Cells["B2"].value + } + } + +} + diff --git a/compare-worksheet.ps1 b/compare-worksheet.ps1 index 0eaf1f4..95fa36e 100644 --- a/compare-worksheet.ps1 +++ b/compare-worksheet.ps1 @@ -1,53 +1,53 @@ Function Compare-WorkSheet { <# - .Synopsis - Compares two worksheets with the same name in different files. + .Synopsis + Compares two worksheets with the same name in different files. .Description - This command takes two file names, a worksheet name and a name for a key column. + This command takes two file names, a worksheet name and a name for a key column. It reads the worksheet from each file and decides the column names. - It builds as hashtable of the key column values and the rows they appear in + It builds as hashtable of the key column values and the rows they appear in It then uses PowerShell's compare object command to compare the sheets (explicity checking all column names which have not been excluded) - For the difference rows it adds the row number for the key of that row - we have to add the key after doing the comparison, - otherwise rows will be considered as different simply because they have different row numbers - We also add the name of the file in which the difference occurs. - If -BackgroundColor is specified the difference rows will be changed to that background. - .Example + For the difference rows it adds the row number for the key of that row - we have to add the key after doing the comparison, + otherwise rows will be considered as different simply because they have different row numbers + We also add the name of the file in which the difference occurs. + If -BackgroundColor is specified the difference rows will be changed to that background. + .Example Compare-WorkSheet -Referencefile 'Server56.xlsx' -Differencefile 'Server57.xlsx' -WorkSheetName Products -key IdentifyingNumber -ExcludeProperty Install* | format-table The two workbooks in this example contain the result of redirecting a subset of properties from Get-WmiObject -Class win32_product to Export-Excel - The command compares the "products" pages in the two workbooks, but we don't want to register a differnce if if the software was installed on a - different date or from a different place, so Excluding Install* removes InstallDate and InstallSource. - This data doesn't have a "name" column" so we specify the "IdentifyingNumber" column as the key. - The results will be presented as a table. - .Example - compare-WorkSheet "Server54.xlsx" "Server55.xlsx" -WorkSheetName services -GridView - This time two workbooks contain the result of redirecting Get-WmiObject -Class win32_service to Export-Excel + The command compares the "products" pages in the two workbooks, but we don't want to register a differnce if if the software was installed on a + different date or from a different place, so Excluding Install* removes InstallDate and InstallSource. + This data doesn't have a "name" column" so we specify the "IdentifyingNumber" column as the key. + The results will be presented as a table. + .Example + compare-WorkSheet "Server54.xlsx" "Server55.xlsx" -WorkSheetName services -GridView + This time two workbooks contain the result of redirecting Get-WmiObject -Class win32_service to Export-Excel Here the -Differencefile and -Referencefile parameter switches are assumed , and the default setting for -key ("Name") works for services - This will display the differences between the "services" sheets using a grid view - .Example + This will display the differences between the "services" sheets using a grid view + .Example Compare-WorkSheet 'Server54.xlsx' 'Server55.xlsx' -WorkSheetName Services -BackgroundColor lightGreen - This version of the command outputs the differences between the "services" pages and also highlights any different rows in the spreadsheet files. - .Example + This version of the command outputs the differences between the "services" pages and also highlights any different rows in the spreadsheet files. + .Example Compare-WorkSheet 'Server54.xlsx' 'Server55.xlsx' -WorkSheetName Services -BackgroundColor lightGreen -FontColor Red -Show This builds on the previous example: this time Where two changed rows have the value in the "name" column (the default value for -key), this version adds highlighting of the changed cells in red; and then opens the Excel file. .Example Compare-WorkSheet 'Pester-tests.xlsx' 'Pester-tests.xlsx' -WorkSheetName 'Server1','Server2' -Property "full Description","Executed","Result" -Key "full Description" This time the reference file and the difference file are the same file and two different sheets are used. Because the tests include the - machine name and time the test was run the command specifies a limited set of columns should be used. + machine name and time the test was run the command specifies a limited set of columns should be used. .Example - Compare-WorkSheet 'Server54.xlsx' 'Server55.xlsx' -WorkSheetName general -Startrow 2 -Headername Label,value -Key Label -GridView -ExcludeDifferent - The "General" page has a title and two unlabelled columns with a row forCPU, Memory, Domain, Disk and so on + Compare-WorkSheet 'Server54.xlsx' 'Server55.xlsx' -WorkSheetName general -Startrow 2 -Headername Label,value -Key Label -GridView -ExcludeDifferent + The "General" page has a title and two unlabelled columns with a row forCPU, Memory, Domain, Disk and so on So the command is instructed to starts at row 2 to skip the title and to name the columns: the first is "label" and the Second "Value"; - the label acts as the key. This time we interested the rows which are the same in both sheets, - and the result is displayed using grid view. Note that grid view works best when the number of columns is small. + the label acts as the key. This time we interested the rows which are the same in both sheets, + and the result is displayed using grid view. Note that grid view works best when the number of columns is small. .Example Compare-WorkSheet 'Server1.xlsx' 'Server2.xlsx' -WorkSheetName general -Startrow 2 -Headername Label,value -Key Label -BackgroundColor White -Show -AllDataBackgroundColor LightGray - This version of the previous command lightlights all the cells in lightgray and then sets the changed rows back to white; only + This version of the previous command lightlights all the cells in lightgray and then sets the changed rows back to white; only the unchanged rows are highlighted #> [cmdletbinding(DefaultParameterSetName)] Param( - #First file to compare + #First file to compare [parameter(Mandatory=$true,Position=0)] $Referencefile , #Second file to compare @@ -57,106 +57,106 @@ Function Compare-WorkSheet { $WorkSheetName = "Sheet1", #Properties to include in the DIFF - supports wildcards, default is "*" $Property = "*" , - #Properties to exclude from the the search - supports wildcards + #Properties to exclude from the the search - supports wildcards $ExcludeProperty , #Specifies custom property names to use, instead of the values defined in the column headers of the TopRow. [Parameter(ParameterSetName='B', Mandatory)] - [String[]]$Headername, + [String[]]$Headername, #Automatically generate property names (P1, P2, P3, ..) instead of the using the values the top row of the sheet [Parameter(ParameterSetName='C', Mandatory)] - [switch]$NoHeader, + [switch]$NoHeader, #The row from where we start to import data, all rows above the StartRow are disregarded. By default this is the first row. - [int]$Startrow = 1, - #If specified, highlights all the cells - so you can make Equal cells one colour, and Diff cells another. + [int]$Startrow = 1, + #If specified, highlights all the cells - so you can make Equal cells one colour, and Diff cells another. [System.Drawing.Color]$AllDataBackgroundColor, - #If specified, highlights the DIFF rows + #If specified, highlights the DIFF rows [System.Drawing.Color]$BackgroundColor, - #If specified identifies the tabs which contain DIFF rows (ignored if -backgroundColor is omitted) + #If specified identifies the tabs which contain DIFF rows (ignored if -backgroundColor is omitted) [System.Drawing.Color]$TabColor, - #Name of a column which is unique and will be used to add a row to the DIFF object, default is "Name" + #Name of a column which is unique and will be used to add a row to the DIFF object, default is "Name" $Key = "Name" , - #If specified, highlights the DIFF columns in rows which have the same key. + #If specified, highlights the DIFF columns in rows which have the same key. [System.Drawing.Color]$FontColor, - #If specified opens the Excel workbooks instead of outputting the diff to the console (unless -passthru is also specified) + #If specified opens the Excel workbooks instead of outputting the diff to the console (unless -passthru is also specified) [Switch]$Show, - #If specified, the command tries to the show the DIFF in a Gridview and not on the console. (unless-Passthru is also specified). This Works best with few columns selected, and requires a key + #If specified, the command tries to the show the DIFF in a Gridview and not on the console. (unless-Passthru is also specified). This Works best with few columns selected, and requires a key [switch]$GridView, - #If specified -Passthrough full set of diff data is returned without filtering to the specified properties + #If specified -Passthrough full set of diff data is returned without filtering to the specified properties [Switch]$PassThru, - #If specified the result will include equal rows as well. By default only different rows are returned + #If specified the result will include equal rows as well. By default only different rows are returned [Switch]$IncludeEqual, #If Specified the result includes only the rows where both are equal [Switch]$ExcludeDifferent ) - - #if the filenames don't resolve, give up now. + + #if the filenames don't resolve, give up now. try { $oneFile = ((Resolve-Path -Path $Referencefile -ErrorAction Stop).path -eq (Resolve-Path -Path $Differencefile -ErrorAction Stop).path)} - Catch { Write-Warning -Message "Could not Resolve the filenames." ; return } - - #If we have one file , we mush have two different worksheet names. If we have two files we can a single string or two strings. + Catch { Write-Warning -Message "Could not Resolve the filenames." ; return } + + #If we have one file , we mush have two different worksheet names. If we have two files we can a single string or two strings. if ($onefile -and ( ($WorkSheetName.count -ne 2) -or $WorkSheetName[0] -eq $WorkSheetName[1] ) ) { - Write-Warning -Message "If both the Reference and difference file are the same then worksheet name must provide 2 different names" + Write-Warning -Message "If both the Reference and difference file are the same then worksheet name must provide 2 different names" return } - if ($WorkSheetName.count -eq 2) {$worksheet1 = $WorkSheetName[0] ; $WorkSheet2 = $WorkSheetName[1]} + if ($WorkSheetName.count -eq 2) {$worksheet1 = $WorkSheetName[0] ; $WorkSheet2 = $WorkSheetName[1]} elseif ($WorkSheetName -is [string]) {$worksheet1 = $WorkSheet2 = $WorkSheetName} - else {Write-Warning -Message "You must provide either a single worksheet name or two names." ; return } - - $params= @{ ErrorAction = [System.Management.Automation.ActionPreference]::Stop } + else {Write-Warning -Message "You must provide either a single worksheet name or two names." ; return } + + $params= @{ ErrorAction = [System.Management.Automation.ActionPreference]::Stop } foreach ($p in @("HeaderName","NoHeader","StartRow")) {if ($PSBoundParameters[$p]) {$params[$p] = $PSBoundParameters[$p]}} try { - $Sheet1 = Import-Excel -Path $Referencefile -WorksheetName $WorkSheet1 @params - $Sheet2 = Import-Excel -Path $Differencefile -WorksheetName $WorkSheet2 @Params + $Sheet1 = Import-Excel -Path $Referencefile -WorksheetName $WorkSheet1 @params + $Sheet2 = Import-Excel -Path $Differencefile -WorksheetName $WorkSheet2 @Params } - Catch {Write-Warning -Message "Could not read the worksheet from $Referencefile and/or $Differencefile." ; return } - - #Get Column headings and create a hash table of Name to column letter. + Catch {Write-Warning -Message "Could not read the worksheet from $Referencefile and/or $Differencefile." ; return } + + #Get Column headings and create a hash table of Name to column letter. $headings = $Sheet1[-1].psobject.Properties.name # This preserves the sequence - using get-member would sort them alphabetically! $headings | ForEach-Object -Begin {$columns = @{} ; $i=65 } -Process {$Columns[$_] = [char]($i ++) } - - #Make a list of property headings using the Property (default "*") and ExcludeProperty parameters - if ($Key -eq "Name" -and $NoHeader) {$key = "p1"} - $propList = @() - foreach ($p in $Property) {$propList += ($headings.where({$_ -like $p}) )} - foreach ($p in $ExcludeProperty) {$propList = $propList.where({$_ -notlike $p}) } + + #Make a list of property headings using the Property (default "*") and ExcludeProperty parameters + if ($Key -eq "Name" -and $NoHeader) {$key = "p1"} + $propList = @() + foreach ($p in $Property) {$propList += ($headings.where({$_ -like $p}) )} + foreach ($p in $ExcludeProperty) {$propList = $propList.where({$_ -notlike $p}) } if (($headings -contains $key) -and ($propList -notcontains $Key)) {$propList += $Key} - $propList = $propList | Select-Object -Unique + $propList = $propList | Select-Object -Unique if ($propList.Count -eq 0) {Write-Warning -Message "No Columns are selected with -Property = '$Property' and -excludeProperty = '$ExcludeProperty'." ; return} - #Add RowNumber, Sheetname and file name to every row + #Add RowNumber, Sheetname and file name to every row $FirstDataRow = $startRow + 1 - if ($Headername -or $NoHeader) {$FirstDataRow -- } - $i = $FirstDataRow ; foreach ($row in $Sheet1) {Add-Member -InputObject $row -MemberType NoteProperty -Name "_Row" -Value ($i ++) + if ($Headername -or $NoHeader) {$FirstDataRow -- } + $i = $FirstDataRow ; foreach ($row in $Sheet1) {Add-Member -InputObject $row -MemberType NoteProperty -Name "_Row" -Value ($i ++) Add-Member -InputObject $row -MemberType NoteProperty -Name "_Sheet" -Value $worksheet1 - Add-Member -InputObject $row -MemberType NoteProperty -Name "_File" -Value $Referencefile} - $i = $FirstDataRow ; foreach ($row in $Sheet2) {Add-Member -InputObject $row -MemberType NoteProperty -Name "_Row" -Value ($i ++) + Add-Member -InputObject $row -MemberType NoteProperty -Name "_File" -Value $Referencefile} + $i = $FirstDataRow ; foreach ($row in $Sheet2) {Add-Member -InputObject $row -MemberType NoteProperty -Name "_Row" -Value ($i ++) Add-Member -InputObject $row -MemberType NoteProperty -Name "_Sheet" -Value $worksheet2 - Add-Member -InputObject $row -MemberType NoteProperty -Name "_File" -Value $Differencefile} - - if ($ExcludeDifferent -and -not $IncludeEqual) {$IncludeEqual = $true} - #Do the comparison and add file,sheet and row to the result - these are prefixed with "_" to show they are added the addition will fail if the sheet has these properties so split the operations + Add-Member -InputObject $row -MemberType NoteProperty -Name "_File" -Value $Differencefile} + + if ($ExcludeDifferent -and -not $IncludeEqual) {$IncludeEqual = $true} + #Do the comparison and add file,sheet and row to the result - these are prefixed with "_" to show they are added the addition will fail if the sheet has these properties so split the operations [PSCustomObject[]]$diff = Compare-Object -ReferenceObject $Sheet1 -DifferenceObject $Sheet2 -Property $propList -PassThru -IncludeEqual:$IncludeEqual -ExcludeDifferent:$ExcludeDifferent | Sort-Object -Property "_Row","File" - - #if BackgroundColor was specified, set it on extra or extra or changed rows + + #if BackgroundColor was specified, set it on extra or extra or changed rows if ($diff -and $BackgroundColor) { - #Differences may only exist in one file. So gather the changes for each file; open the file, update each impacted row in the shee, save the file + #Differences may only exist in one file. So gather the changes for each file; open the file, update each impacted row in the shee, save the file $updates = $diff.where({$_.SideIndicator -ne "=="}) | Group-object -Property "_File" foreach ($file in $updates) { try {$xl = Open-ExcelPackage -Path $file.name } - catch {Write-warning -Message "Can't open $($file.Name) for writing." ; return} + catch {Write-warning -Message "Can't open $($file.Name) for writing." ; return} if ($AllDataBackgroundColor) { $file.Group._sheet | Sort-Object -Unique | ForEach-Object { - $ws = $xl.Workbook.Worksheets[$_] + $ws = $xl.Workbook.Worksheets[$_] if ($headerName) {$range = "A" + $startrow + ":" + $ws.dimension.end.address} else {$range = "A" + ($startrow + 1) + ":" + $ws.dimension.end.address} - Set-Format -WorkSheet $ws -BackgroundColor $AllDataBackgroundColor -Range $Range + Set-Format -WorkSheet $ws -BackgroundColor $AllDataBackgroundColor -Range $Range } } foreach ($row in $file.group) { $ws = $xl.Workbook.Worksheets[$row._Sheet] $range = $ws.Dimension -replace "\d+",$row._row - Set-Format -WorkSheet $ws -Range $range -BackgroundColor $BackgroundColor + Set-Format -WorkSheet $ws -Range $range -BackgroundColor $BackgroundColor } if ($TabColor) { foreach ($tab in ($file.group._sheet | Select-Object -Unique)) { @@ -166,12 +166,12 @@ Function Compare-WorkSheet { $xl.save() ; $xl.Stream.Close() ; $xl.Dispose() } } - #if font colour was specified, set it on changed properties where the same key appears in both sheets. + #if font colour was specified, set it on changed properties where the same key appears in both sheets. if ($diff -and $FontColor -and ($propList -contains $Key) ) { - $updates = $diff.where({$_.SideIndicator -ne "=="}) | Group-object -Property $Key | Where-Object {$_.count -eq 2} + $updates = $diff.where({$_.SideIndicator -ne "=="}) | Group-object -Property $Key | Where-Object {$_.count -eq 2} if ($updates) { $XL1 = Open-ExcelPackage -path $Referencefile - if ($oneFile ) {$xl2 = $xl1} + if ($oneFile ) {$xl2 = $xl1} else {$xl2 = Open-ExcelPackage -path $Differencefile } foreach ($u in $updates) { foreach ($p in $propList) { @@ -186,75 +186,75 @@ Function Compare-WorkSheet { if($u.Group[0].$p -ne $u.Group[1].$p ) { Set-Format -WorkSheet $ws1 -Range ($Columns[$p] + $u.Group[0]._Row) -FontColor $FontColor Set-Format -WorkSheet $ws2 -Range ($Columns[$p] + $u.Group[1]._Row) -FontColor $FontColor - } - } + } + } } $xl1.Save() ; $xl1.Stream.Close() ; $xl1.Dispose() if (-not $oneFile) {$xl2.Save() ; $xl2.Stream.Close() ; $xl2.Dispose()} } } - elseif ($diff -and $FontColor) {Write-Warning -Message "To match rows to set changed cells, you must specify -Key and it must match one of the included properties." } + elseif ($diff -and $FontColor) {Write-Warning -Message "To match rows to set changed cells, you must specify -Key and it must match one of the included properties." } #if nothing was found write a message which wont be redirected if (-not $diff) {Write-Host "Comparison of $Referencefile::$worksheet1 and $Differencefile::$WorkSheet2 returned no results." } - if ($show) { - Start-Process -FilePath $Referencefile + if ($show) { + Start-Process -FilePath $Referencefile if (-not $oneFile) { Start-Process -FilePath $Differencefile } - if ($GridView) { Write-Warning -Message "-GridView is ignored when -Show is specified" } - } - elseif ($GridView -and $propList -contains $key) { - - + if ($GridView) { Write-Warning -Message "-GridView is ignored when -Show is specified" } + } + elseif ($GridView -and $propList -contains $key) { + + if ($IncludeEqual -and -not $ExcludeDifferent) { - $GroupedRows = $diff | Group-Object -Property $key + $GroupedRows = $diff | Group-Object -Property $key } - else { #to get the right now numbers on the grid we need to have all the rows. - $GroupedRows = Compare-Object -ReferenceObject $Sheet1 -DifferenceObject $Sheet2 -Property $propList -PassThru -IncludeEqual | - Group-Object -Property $key + else { #to get the right now numbers on the grid we need to have all the rows. + $GroupedRows = Compare-Object -ReferenceObject $Sheet1 -DifferenceObject $Sheet2 -Property $propList -PassThru -IncludeEqual | + Group-Object -Property $key } #Additions, deletions and unchanged rows will give a group of 1; changes will give a group of 2 . - #If one sheet has extra rows we can get a single "==" result from compare, but with the row from the reference sheet - #but the row in the other sheet might so we will look up the row number from the key field build a hash table for that - $Sheet2 | ForEach-Object -Begin {$Rowhash = @{} } -Process {$Rowhash[$_.$key] = $_._row } + #If one sheet has extra rows we can get a single "==" result from compare, but with the row from the reference sheet + #but the row in the other sheet might so we will look up the row number from the key field build a hash table for that + $Sheet2 | ForEach-Object -Begin {$Rowhash = @{} } -Process {$Rowhash[$_.$key] = $_._row } $ExpandedDiff = ForEach ($g in $GroupedRows) { #we're going to create a custom object from a hash table. We want the fields to be ordered - $hash = [ordered]@{} + $hash = [ordered]@{} foreach ($result IN $g.Group) { # if result indicates equal or "in Reference" set the reference side row. If we did that on a previous result keep it. Otherwise set to "blank" - if ($result.sideindicator -ne "=>") {$hash["") {$hash["Row"] = $Rowhash[$g.Name] - #position the key as the next field (only appears once) - $Hash[$key] = $g.Name - #For all the other fields we care about create <=FieldName and/or =>FieldName + #if result is "in reference" and we don't have a matching "in difference" (meaning a change) the lookup will be blank. Which we want. + $hash[">Row"] = $Rowhash[$g.Name] + #position the key as the next field (only appears once) + $Hash[$key] = $g.Name + #For all the other fields we care about create <=FieldName and/or =>FieldName foreach ($p in $propList.Where({$_ -ne $key})) { if ($result.SideIndicator -eq "==") {$hash[("=>$P")] = $hash[("<=$P")] =$result.$P} else {$hash[($result.SideIndicator+$P)] =$result.$P} } - } + } [Pscustomobject]$hash } #Sort by reference row number, and fill in any blanks in the difference-row column - $ExpandedDiff = $ExpandedDiff | Sort-Object -Property "row") {$ExpandedDiff[$i].">row" = $ExpandedDiff[$i-1].">row" } } + $ExpandedDiff = $ExpandedDiff | Sort-Object -Property "row") {$ExpandedDiff[$i].">row" = $ExpandedDiff[$i-1].">row" } } #Sort by difference row number, and fill in any blanks in the reference-row column - $ExpandedDiff = $ExpandedDiff | Sort-Object -Property ">row" - for ($i = 1; $i -lt $ExpandedDiff.Count; $i++) {if (-not $ExpandedDiff[$i]."row" } + $ExpandedDiff = $ExpandedDiff | Sort-Object -Property ">row" + for ($i = 1; $i -lt $ExpandedDiff.Count; $i++) {if (-not $ExpandedDiff[$i]."row" } elseif ( $IncludeEqual) {$ExpandedDiff = $ExpandedDiff | Sort-Object -Property "row" } - else {$ExpandedDiff = $ExpandedDiff.where({$_.side -ne "=="}) | Sort-Object -Property "row" } - $ExpandedDiff | Update-FirstObjectProperties | Out-GridView -Title "Comparing $Referencefile::$worksheet1 (<=) with $Differencefile::$WorkSheet2 (=>)" + else {$ExpandedDiff = $ExpandedDiff.where({$_.side -ne "=="}) | Sort-Object -Property "row" } + $ExpandedDiff | Update-FirstObjectProperties | Out-GridView -Title "Comparing $Referencefile::$worksheet1 (<=) with $Differencefile::$WorkSheet2 (=>)" } - elseif ($GridView ) {Write-Warning -Message "To use -GridView you must specify -Key and it must match one of the included properties." } + elseif ($GridView ) {Write-Warning -Message "To use -GridView you must specify -Key and it must match one of the included properties." } elseif (-not $PassThru) {return ($diff | Select-Object -Property (@(@{n="_Side";e={$_.SideIndicator}},"_File" ,"_Sheet","_Row") + $propList))} if ( $PassThru) {return $diff } } diff --git a/formatting.ps1 b/formatting.ps1 index 5d6d26a..f54c27c 100644 --- a/formatting.ps1 +++ b/formatting.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 "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 , @@ -89,17 +89,17 @@ } 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 + <# + .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 ( #One or more row(s), Column(s) and/or block(s) of cells to format [Parameter(ValueFromPipeline=$true)]