From 7de956b3437fe80de508cf2dcf533cb284ca5313 Mon Sep 17 00:00:00 2001 From: jhoneill Date: Sat, 14 Jul 2018 23:40:50 +0100 Subject: [PATCH] Fixed column name handling in merge-multipleWorksheets. More tests --- AddConditionalFormatting.ps1 | 8 +- ImportExcel.psm1 | 3 +- Join-Worksheet.ps1 | 78 ++--- Merge-worksheet.ps1 | 464 ++++++++++++------------- New-ExcelChart.ps1 | 30 +- Open-ExcelPackage.ps1 | 38 +-- README.md | 61 ++-- Send-SqlDataToExcel.ps1 | 140 ++++---- Set-Column.ps1 | 4 +- __tests__/Compare-WorkSheet.tests.ps1 | 474 +++++++++++++++----------- compare-worksheet.ps1 | 234 ++++++------- 11 files changed, 812 insertions(+), 722 deletions(-) diff --git a/AddConditionalFormatting.ps1 b/AddConditionalFormatting.ps1 index db18fc6..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. @@ -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/ImportExcel.psm1 b/ImportExcel.psm1 index a81259e..c214007 100644 --- a/ImportExcel.psm1 +++ b/ImportExcel.psm1 @@ -11,6 +11,7 @@ . $PSScriptRoot\Copy-ExcelWorkSheet.ps1 . $PSScriptRoot\Export-Excel.ps1 . $PSScriptRoot\Export-ExcelSheet.ps1 + . $PSScriptRoot\Get-ExcelColumnName.ps1 . $PSScriptRoot\Get-ExcelSheetInfo.ps1 . $PSScriptRoot\Get-ExcelWorkbookInfo.ps1 . $PSScriptRoot\Get-HtmlTable.ps1 @@ -369,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 0779f29..8786d1e 100644 --- a/Join-Worksheet.ps1 +++ b/Join-Worksheet.ps1 @@ -1,11 +1,11 @@ -function Join-Worksheet { +function Join-Worksheet { [CmdletBinding(DefaultParameterSetName = 'Default')] <# .SYNOPSIS Combines data on all the sheets in an Excel worksheet onto a single sheet. .DESCRIPTION Join worksheet can work in two main ways: - Either Combining data which has the same layout from many pages into one, or combining pages which have nothing in common. + Either Combining data which has the same layout from many pages into one, or combining pages which have nothing in common. In the former case the header row is copied from the first sheet and, by default, each row of data is labelled with the name of the sheet it came from. In the latter case -NoHeader is specified, and each copied block can have the sheet it came from placed above it as a title. .EXAMPLE @@ -14,37 +14,37 @@ Export-Excel -Path .\test.xlsx -WorkSheetname $computerName -AutoSize } $ptDef =New-PivotTableDefinition -PivotTableName "Pivot1" -SourceWorkSheet "Combined" -PivotRows "Status" -PivotFilter "MachineName" -PivotData @{Status='Count'} -IncludePivotChart -ChartType BarClustered3D - Join-Worksheet -Path .\test.xlsx -WorkSheetName combined -FromLabel "MachineName" -HideSource -AutoSize -FreezeTopRow -BoldTopRow -PivotTableDefinition $pt -Show + Join-Worksheet -Path .\test.xlsx -WorkSheetName combined -FromLabel "MachineName" -HideSource -AutoSize -FreezeTopRow -BoldTopRow -PivotTableDefinition $pt -Show The foreach command gets the services running on four servers and exports each to its own page in Test.xlsx. $PtDef= creates a defintion for a single Pivot table. - The Join-Worksheet command uses the same file and merges the results onto a sheet named "Combined". It sets a column header of "Machinename", - this column will contain the name of the sheet the data was copied from; after copying the data to the sheet "combined", the other sheets will be hidden. - Join-Worksheet finishes by calling export-Excel to AutoSize cells, freeze the top row and make it bold and add the Pivot table. + The Join-Worksheet command uses the same file and merges the results onto a sheet named "Combined". It sets a column header of "Machinename", + this column will contain the name of the sheet the data was copied from; after copying the data to the sheet "combined", the other sheets will be hidden. + Join-Worksheet finishes by calling export-Excel to AutoSize cells, freeze the top row and make it bold and add the Pivot table. .EXAMPLE - Get-WmiObject -Class win32_logicaldisk | select -Property DeviceId,VolumeName, Size,Freespace | + Get-WmiObject -Class win32_logicaldisk | select -Property DeviceId,VolumeName, Size,Freespace | Export-Excel -Path "$env:computerName.xlsx" -WorkSheetname Volumes -NumberFormat "0,000" - Get-NetAdapter | Select-Object Name,InterfaceDescription,MacAddress,LinkSpeed | + Get-NetAdapter | Select-Object Name,InterfaceDescription,MacAddress,LinkSpeed | Export-Excel -Path "$env:COMPUTERNAME.xlsx" -WorkSheetname NetAdapter 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 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. #> param ( # Path to a new or existing .XLSX file. [Parameter(ParameterSetName = "Default", Position = 0)] [Parameter(ParameterSetName = "Table" , Position = 0)] - [String]$Path , + [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 = "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', + $WorkSheetName = 'Combined', # If specified any pre-existing target for the joined data will be deleted and re-created; otherwise data will be appended on this sheet. [switch]$Clearsheet, #Join-Worksheet assumes each sheet has identical headers and the headers should be copied to the target sheet, unless -NoHeader is specified. @@ -53,7 +53,7 @@ $FromLabel = "From" , #If specified, the copied blocks of data will have the name of the sheet they were copied from inserted above them as a title. [switch]$LabelBlocks, - #Sizes the width of the Excel column to the maximum width needed to display all the containing data in that cell. + #Sizes the width of the Excel column to the maximum width needed to display all the containing data in that cell. [Switch]$AutoSize, #Freezes headers etc. in the top row. [Switch]$FreezeTopRow, @@ -69,7 +69,7 @@ [Switch]$AutoFilter, #Makes the top Row boldface. [Switch]$BoldTopRow, - #If Specified hides the sheets that the data is copied from. + #If Specified hides the sheets that the data is copied from. [switch]$HideSource, #Text of a title to be placed in Cell A1. [String]$Title, @@ -80,7 +80,7 @@ #Sets the title in boldface type. [Switch]$TitleBold, #Sets the point size for the title. - [Int]$TitleSize = 22, + [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]$PivotTableDefinition, #A hashtable containing ChartType, Title, NoLegend, ShowCategory, ShowPercent, Yrange, Xrange and SeriesHeader for one or more [non-pivot] charts. @@ -113,18 +113,18 @@ [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. + #If specified, an object representing the unsaved Excel package will be returned, it then needs to be saved. [switch]$PassThru ) - #region get target worksheet, select it and move it to the end. + #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 - $row = (1 + $destinationSheet.Dimension.End.Row ) - #endregion + $row = (1 + $destinationSheet.Dimension.End.Row ) + #endregion #region Setup title and header rows #Title parameters work as they do in Export-Excel . @@ -139,7 +139,7 @@ } elseif ($TitleBackgroundColor) { Write-Warning "Title Background Color ignored. You must set the TitleFillPattern parameter to a value other than 'None'. Try 'Solid'." } $row = 2 - } + } if (-not $noHeader) { #Assume every row has titles in row 1, copy row 1 from first sheet to new sheet. @@ -148,48 +148,48 @@ if ($FromLabel ) { #Add a column which says where the data comes from. $fromColumn = ($destinationSheet.Dimension.Columns + 1) - $destinationSheet.Cells[$row, $fromColumn].Value = $FromLabel + $destinationSheet.Cells[$row, $fromColumn].Value = $FromLabel } - $row += 1 + $row += 1 } #endregion foreach ($i in 1..($ExcelPackage.Workbook.Worksheets.Count - 1) ) { - $sourceWorksheet = $ExcelPackage.Workbook.Worksheets[$i] - #Assume row one is titles, so data itself starts at A2. + $sourceWorksheet = $ExcelPackage.Workbook.Worksheets[$i] + #Assume row one is titles, so data itself starts at A2. if ($NoHeader) {$sourceRange = $sourceWorksheet.Dimension.Address} - else {$sourceRange = $sourceWorksheet.Dimension.Address -replace "A1:", "A2:"} - #Position insertion point/ + else {$sourceRange = $sourceWorksheet.Dimension.Address -replace "A1:", "A2:"} + #Position insertion point/ $destinationSheet.Select("A$row") if ($LabelBlocks) { $destinationSheet.Cells[$row, 1].value = $sourceWorksheet.Name $destinationSheet.Cells[$row, 1].Style.Font.Bold = $true $destinationSheet.Cells[$row, 1].Style.Font.Size += 2 - $row += 1 + $row += 1 } $destinationSheet.Select("A$row") - - #And finally we're ready to copy the data. + + #And finally we're ready to copy the data. $sourceWorksheet.Cells[$sourceRange].Copy($destinationSheet.SelectedRange) - #Fill in column saying where data came from. + #Fill in column saying where data came from. if ($fromColumn) { $row..$destinationSheet.Dimension.Rows | ForEach-Object {$destinationSheet.Cells[$_, $fromColumn].Value = $sourceWorksheet.Name} } - #Update where next insertion will go. - $row = $destinationSheet.Dimension.Rows + 1 - if ($HideSource) {$sourceWorksheet.Hidden = [OfficeOpenXml.eWorkSheetHidden]::Hidden} + #Update where next insertion will go. + $row = $destinationSheet.Dimension.Rows + 1 + if ($HideSource) {$sourceWorksheet.Hidden = [OfficeOpenXml.eWorkSheetHidden]::Hidden} } - - #We accept a bunch of parameters work to pass on to Export-excel ( Autosize, Autofilter, boldtopRow Freeze ); if we have any of those call export-excel otherwise close the package here. + + #We accept a bunch of parameters work to pass on to Export-excel ( Autosize, Autofilter, boldtopRow Freeze ); if we have any of those call export-excel otherwise close the package here. $params = @{} + $PSBoundParameters 'Path', 'Clearsheet', 'NoHeader', 'FromLabel', 'LabelBlocks', 'HideSource', - 'Title', 'TitleFillPattern', 'TitleBackgroundColor', 'TitleBold', 'TitleSize' | ForEach-Object {[void]$params.Remove($_)} + '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 + Export-Excel @Params } else { - Close-ExcelPackage -ExcelPackage $ExcelPackage + Close-ExcelPackage -ExcelPackage $ExcelPackage $ExcelPackage.Dispose() $ExcelPackage = $null } diff --git a/Merge-worksheet.ps1 b/Merge-worksheet.ps1 index 6d239eb..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,183 +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" } - $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 + 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 + $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 @@ -264,213 +264,215 @@ } elseif ( $expandedDiff[$i].side -eq "<=" ) { $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 + $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) { $rangeR1C1 = "R[{0}]C[{1}]:R[{0}]C[{2}]" -f ($i + 2 ) , $FirstDiffColNo , $lastDiffColNo - $range = [OfficeOpenXml.ExcelAddress]::TranslateFromR1C1($rangeR1C1,0,0) + $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 868b43e..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, @@ -21,26 +21,26 @@ function New-ExcelChartDefinition { $SeriesHeader, [Switch]$TitleBold, [Int]$TitleSize , - [String]$XAxisTitleText, + [String]$XAxisTitleText, [Switch]$XAxisTitleBold, $XAxisTitleSize , [string]$XAxisNumberformat, - $XMajorUnit, - $XMinorUnit, + $XMajorUnit, + $XMinorUnit, $XMaxValue, $XMinValue, [OfficeOpenXml.Drawing.Chart.eAxisPosition]$XAxisPosition , - [String]$YAxisTitleText, + [String]$YAxisTitleText, [Switch]$YAxisTitleBold, $YAxisTitleSize, [string]$YAxisNumberformat, - $YMajorUnit, - $YMinorUnit, + $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 @@ -57,22 +57,22 @@ function New-ExcelChartDefinition { ShowCategory = $ShowCategory -as [Boolean] ShowPercent = $ShowPercent -as [Boolean] TitleBold = $TitleBold -as [Boolean] - TitleSize = $TitleSize + TitleSize = $TitleSize SeriesHeader = $SeriesHeader XAxisTitleText = $XAxisTitleText XAxisTitleBold = $XAxisTitleBold -as [Boolean] - XAxisTitleSize = $XAxisTitleSize + XAxisTitleSize = $XAxisTitleSize XAxisNumberformat = $XAxisNumberformat - XMajorUnit = $XMajorUnit + XMajorUnit = $XMajorUnit XMinorUnit = $XMinorUnit XMaxValue = $XMaxValue XMinValue = $XMinValue XAxisPosition = $XAxisPosition - YAxisTitleText = $YAxisTitleText - YAxisTitleBold = $YAxisTitleBold -as [Boolean] + YAxisTitleText = $YAxisTitleText + YAxisTitleBold = $YAxisTitleBold -as [Boolean] YAxisTitleSize = $YAxisTitleSize - YAxisNumberformat = $YAxisNumberformat - YMajorUnit = $YMajorUnit + YAxisNumberformat = $YAxisNumberformat + YMajorUnit = $YMajorUnit YMinorUnit = $YMinorUnit YMaxValue = $YMaxValue YMinValue = $YMinValue 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 53902c7..3c51951 100644 --- a/README.md +++ b/README.md @@ -31,57 +31,58 @@ 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 11th July 18 -- Moved chart creatation 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 +# 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 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 (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 -- 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) +- 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 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 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 ths release. At it's simplest it copies all the data in Worksheet A to the end of Worksheet B -- Add-Worksheet + 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 ths release. At it's simplest it copies all the data in Worksheet A to the end of Worksheet B +- Add-Worksheet - I have moved this from ImportExcel.psm1 to ExportExcel.ps1 and it now can move a new worksheet to the right place, and can copy an existing worksheet (from the same or a different workbook) to a new one, and I set the Set return-type to aid intellisense -- New-PivotTableDefinition - - Now Supports `-PivotFilter` and `-PivotDataToColumn`, `-ChartHeight/width` `-ChartRow/Column`, `-ChartRow/ColumnPixelOffset` parameters -- Set-Format - - Fixed a bug where the `-address` parameter had to be named, although the examples in `export-excel` help showed it working by position (which works now. ) -- Export-Excel - - I've done some re-factoring +- New-PivotTableDefinition + - Now Supports `-PivotFilter` and `-PivotDataToColumn`, `-ChartHeight/width` `-ChartRow/Column`, `-ChartRow/ColumnPixelOffset` parameters +- Set-Format + - Fixed a bug where the `-address` parameter had to be named, although the examples in `export-excel` help showed it working by position (which works now. ) +- Export-Excel + - I've done some re-factoring 1. I "flattened out" small "called-once" functions , add-title, convert-toNumber and Stop-ExcelProcess. - 2. It now uses Add-Worksheet, Open-ExcelPackage and Add-ConditionalFormat instead of duplicating their functionality. + 2. It now uses Add-Worksheet, Open-ExcelPackage and Add-ConditionalFormat instead of duplicating their functionality. 3. I've moved the PivotTable functionality (which was doubled up) out to a new function "Add-PivotTable" which supports some extra parameters PivotFilter and PivotDataToColumn, ChartHeight/width ChartRow/Column, ChartRow/ColumnPixelOffsets. - 4. I've made the try{} catch{} blocks cover smaller blocks of code to give a better idea where a failure happend, some of these now Warn instead of throwing - I'd rather save the data with warnings than throw it away because we can't add a chart. Along with this I've added some extra write-verbose messages + 4. I've made the try{} catch{} blocks cover smaller blocks of code to give a better idea where a failure happend, some of these now Warn instead of throwing - I'd rather save the data with warnings than throw it away because we can't add a chart. Along with this I've added some extra write-verbose messages - Bad column-names specified for Pivots now generate warnings instead of throwing. - Fixed issues when pivottables / charts already exist and an export tries to create them again. - Fixed issue where AutoNamedRange, NamedRange, and TableName do not work when appending to a sheet which already contains the range(s) / table - - Fixed issue where AutoNamedRange may try to create ranges with an illegal name. + - Fixed issue where AutoNamedRange may try to create ranges with an illegal name. - Added check for illegal characters in RangeName or Table Name (replace them with "_"), changed tablename validation to allow spaces and applied same validation to RangeName - - Fixed a bug where BoldTopRow is always bolds row 1 even if the export is told to start at a lower row. - - Fixed a bug where titles throw pivot table creation out of alignment. - - Fixed a bug where Append can overwrite the last rows of data if the initial export had blank rows at the top of the sheet. + - Fixed a bug where BoldTopRow is always bolds row 1 even if the export is told to start at a lower row. + - Fixed a bug where titles throw pivot table creation out of alignment. + - Fixed a bug where Append can overwrite the last rows of data if the initial export had blank rows at the top of the sheet. - Removed the need to specify a fill type when specifying a title background color - - Added MoveToStart, MoveToEnd, MoveBefore and MoveAfter Parameters - these go straight through to Add worksheet - - Added "NoScriptOrAliasProperties" "DisplayPropertySet" switches (names subject to change) - combined with ExcludeProperty these are a quick way to reduce the data exported (and speed things up) - - Added PivotTableName Switch (in line with 5.0.1 release) + - Added MoveToStart, MoveToEnd, MoveBefore and MoveAfter Parameters - these go straight through to Add worksheet + - Added "NoScriptOrAliasProperties" "DisplayPropertySet" switches (names subject to change) - combined with ExcludeProperty these are a quick way to reduce the data exported (and speed things up) + - Added PivotTableName Switch (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 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. + - Commented out the write verbose statements even if verbose is silenced they cause a significiant performance impact and if it's on they will cause a flood of messages. + - Re-ordered the choices in the switch and added an option to say "If it is numeric already post it as is" + - Added an option to only set the number format if doesn't match the default for the sheet. - Export-Excel Pester Tests - I have converted examples 1-9, 11 and 13 from Export-Excel help into tests and have added some additional tests, and extra parameters to the example command to ge better test coverage. The test so far has 184 "should" conditions grouped as 58 "IT" statements; but is still a work in progress. - Compare-Worksheet pester tests diff --git a/Send-SqlDataToExcel.ps1 b/Send-SqlDataToExcel.ps1 index 44e8437..30eb821 100644 --- a/Send-SqlDataToExcel.ps1 +++ b/Send-SqlDataToExcel.ps1 @@ -1,27 +1,27 @@ Function Send-SQLDataToExcel { <# - .SYNOPSIS + .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. + 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 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 + 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 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 + 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. + Override the default query time of 30 seconds. .PARAMETER Path Path to a new or existing .XLSX file. .PARAMETER WorkSheetName @@ -43,7 +43,7 @@ .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" + 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 @@ -57,7 +57,7 @@ .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) + The type for Pivot chart (one of Excel's defined chart types) .PARAMETER NoLegend Exclude the legend from the pivot chart. .PARAMETER ShowCategory @@ -66,9 +66,9 @@ 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. + 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 @@ -88,9 +88,9 @@ .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 + 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 + 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 @@ -108,52 +108,52 @@ .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. + 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" + 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. .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 + 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 + .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 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 ) + 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 + 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. + 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 - - 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" + 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 - 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 + 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, - [Parameter(ParameterSetName="ExistingSession",Mandatory=$true)] + $Connection, + [Parameter(ParameterSetName="ExistingSession",Mandatory=$true)] [System.Data.Common.DbConnection]$Session, [Parameter(ParameterSetName="SQLConnection",Mandatory=$true)] [switch]$MsSQLserver, @@ -161,14 +161,14 @@ [String]$DataBase, [Parameter(ParameterSetName="SQLConnection", Mandatory=$true)] [Parameter(ParameterSetName="ODBCConnection",Mandatory=$true)] - [Parameter(ParameterSetName="ExistingSession",Mandatory=$true)] - [string]$SQL, + [Parameter(ParameterSetName="ExistingSession",Mandatory=$true)] + [string]$SQL, [int]$QueryTimeout, - [Parameter(ParameterSetName="Pre-FetchedData",Mandatory=$true)] + [Parameter(ParameterSetName="Pre-FetchedData",Mandatory=$true)] [System.Data.DataTable]$DataTable, - $Path, - [String]$WorkSheetname = 'Sheet1', - [Switch]$KillExcel, + $Path, + [String]$WorkSheetname = 'Sheet1', + [Switch]$KillExcel, [Switch]$Show, [String]$Title, [OfficeOpenXml.Style.ExcelFillStyle]$TitleFillPattern = 'None', @@ -182,8 +182,8 @@ [String[]]$PivotColumns, $PivotData, [String[]]$PivotFilter, - [Switch]$PivotDataToColumn, - [Switch]$NoTotalsInPivot, + [Switch]$PivotDataToColumn, + [Switch]$NoTotalsInPivot, [Switch]$IncludePivotChart, [OfficeOpenXml.Drawing.Chart.eChartType]$ChartType = 'Pie', [Switch]$NoLegend, @@ -203,7 +203,7 @@ [Switch]$Barchart, [Switch]$PieChart, [Switch]$LineChart , - [Switch]$ColumnChart , + [Switch]$ColumnChart , [Object[]]$ExcelChartDefinition, [Switch]$AutoNameRange, [Object[]]$ConditionalFormat, @@ -214,18 +214,18 @@ [Switch]$ReturnRange, [Switch]$Passthru ) - + if ($KillExcel) { Get-Process excel -ErrorAction Ignore | Stop-Process 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 -and $Connection) { - if ($Connection -notmatch "=") {$Connection = "server=$Connection;trusted_connection=true;timeout=60"} + 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 ($Session.State -ne 'Open') {$Session.Open()} if ($DataBase) {$Session.ChangeDatabase($DataBase) } } elseif ($Connection) { @@ -233,36 +233,36 @@ } 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") { + #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 ) + 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 + #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)" + Write-Verbose -Message "Query returned $rowCount row(s)" } if ($DataTable.Rows) { - #ExportExcel user a -NoHeader parameter so that's what we use here, but needs to be the other way around. + #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 + 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 + Export-Excel -ExcelPackage $excelPackage @PSBoundParameters } - else {Write-Warning -Message "No Data to insert."} + 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() } + if ($Connection) {$Session.close() } } diff --git a/Set-Column.ps1 b/Set-Column.ps1 index 02e3081..76e1d6c 100644 --- a/Set-Column.ps1 +++ b/Set-Column.ps1 @@ -26,7 +26,7 @@ $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 + #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)] @@ -79,7 +79,7 @@ [float]$Width, #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. + #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. diff --git a/__tests__/Compare-WorkSheet.tests.ps1 b/__tests__/Compare-WorkSheet.tests.ps1 index 286190d..be2cc8c 100644 --- a/__tests__/Compare-WorkSheet.tests.ps1 +++ b/__tests__/Compare-WorkSheet.tests.ps1 @@ -1,250 +1,336 @@ -#Requires -Modules Pester - -# $here = Split-Path -Parent $MyInvocation.MyCommand.Path -# Import-Module $here -Force -Verbose +#Requires -Modules Pester Import-Module $PSScriptRoot\..\ImportExcel.psd1 -Force Describe "Compare Worksheet" { - - Remove-Item -Path "$env:temp\server*.xlsx" - [System.Collections.ArrayList]$s = get-service | Select-Object -Property * - - $s | Export-Excel -Path $env:temp\server1.xlsx - - #$s is a zero based array, excel rows are 1 based and excel has a header row so Excel rows will be 2 + index in $s - $row4Displayname = $s[2].DisplayName - $s[2].DisplayName = "Changed from the orginal" - - $d = $s[-1] | Select-Object -Property * - $d.DisplayName = "Dummy Service" - $d.Name = "Dummy" - $s.Insert(3,$d) - - $row6Name = $s[5].name - $s.RemoveAt(5) - - $s | Export-Excel -Path $env:temp\server2.xlsx - #Assume default worksheet name, (sheet1) and column header for key ("name") - $comp = compare-WorkSheet "$env:temp\Server1.xlsx" "$env:temp\Server2.xlsx" | Sort-Object -Property _row, _file - Context "Simple comparison output" { + BeforeAll { + Remove-Item -Path "$env:temp\server*.xlsx" + [System.Collections.ArrayList]$s = get-service | Select-Object -Property * + $s | Export-Excel -Path $env:temp\server1.xlsx + #$s is a zero based array, excel rows are 1 based and excel has a header row so Excel rows will be 2 + index in $s + $row4Displayname = $s[2].DisplayName + $s[2].DisplayName = "Changed from the orginal" + $d = $s[-1] | Select-Object -Property * + $d.DisplayName = "Dummy Service" + $d.Name = "Dummy" + $s.Insert(3,$d) + $row6Name = $s[5].name + $s.RemoveAt(5) + $s | Export-Excel -Path $env:temp\server2.xlsx + #Assume default worksheet name, (sheet1) and column header for key ("name") + $comp = compare-WorkSheet "$env:temp\Server1.xlsx" "$env:temp\Server2.xlsx" | Sort-Object -Property _row, _file + } it "Found the right number of differences " { - $comp | should not beNullOrEmpty - $comp.Count | should be 4 + $comp | should not beNullOrEmpty + $comp.Count | should be 4 } it "Found the data row with a changed property " { - $comp | should not beNullOrEmpty - $comp[0]._Side | should not be $comp[1]._Side - $comp[0]._Row | should be 4 - $comp[1]._Row | should be 4 - $comp[1].Name | should be $comp[0].Name - $comp[0].DisplayName | should be $row4Displayname - $comp[1].DisplayName | should be "Changed from the orginal" + $comp | should not beNullOrEmpty + $comp[0]._Side | should not be $comp[1]._Side + $comp[0]._Row | should be 4 + $comp[1]._Row | should be 4 + $comp[1].Name | should be $comp[0].Name + $comp[0].DisplayName | should be $row4Displayname + $comp[1].DisplayName | should be "Changed from the orginal" } it "Found the inserted data row " { - $comp | should not beNullOrEmpty - $comp[2]._Side | should be '=>' - $comp[2]._Row | should be 5 - $comp[2].Name | should be "Dummy" + $comp | should not beNullOrEmpty + $comp[2]._Side | should be '=>' + $comp[2]._Row | should be 5 + $comp[2].Name | should be "Dummy" } it "Found the deleted data row " { - $comp | should not beNullOrEmpty - $comp[3]._Side | should be '<=' - $comp[3]._Row | should be 6 - $comp[3].Name | should be $row6Name + $comp | should not beNullOrEmpty + $comp[3]._Side | should be '<=' + $comp[3]._Row | should be 6 + $comp[3].Name | should be $row6Name } } - $null = compare-WorkSheet "$env:temp\Server1.xlsx" "$env:temp\Server2.xlsx" -BackgroundColor LightGreen - $xl1 = Open-ExcelPackage -Path "$env:temp\Server1.xlsx" - $xl2 = Open-ExcelPackage -Path "$env:temp\Server2.xlsx" - $s1Sheet = $xl1.Workbook.Worksheets[1] - $s2Sheet = $xl2.Workbook.Worksheets[1] - Context "Setting the background to highlight different rows" { - it "set the background on the right rows " { - $s1Sheet.Cells["4:4"].Style.Fill.BackgroundColor.Rgb | should be "FF90EE90" - $s1Sheet.Cells["6:6"].Style.Fill.BackgroundColor.Rgb | should be "FF90EE90" - $s2Sheet.Cells["4:4"].Style.Fill.BackgroundColor.Rgb | should be "FF90EE90" - $s2Sheet.Cells["5:5"].Style.Fill.BackgroundColor.Rgb | should be "FF90EE90" + BeforeAll { + $null = compare-WorkSheet "$env:temp\Server1.xlsx" "$env:temp\Server2.xlsx" -BackgroundColor LightGreen + $xl1 = Open-ExcelPackage -Path "$env:temp\Server1.xlsx" + $xl2 = Open-ExcelPackage -Path "$env:temp\Server2.xlsx" + $s1Sheet = $xl1.Workbook.Worksheets[1] + $s2Sheet = $xl2.Workbook.Worksheets[1] + } + it "Set the background on the right rows " { + $s1Sheet.Cells["4:4"].Style.Fill.BackgroundColor.Rgb | should be "FF90EE90" + $s1Sheet.Cells["6:6"].Style.Fill.BackgroundColor.Rgb | should be "FF90EE90" + $s2Sheet.Cells["4:4"].Style.Fill.BackgroundColor.Rgb | should be "FF90EE90" + $s2Sheet.Cells["5:5"].Style.Fill.BackgroundColor.Rgb | should be "FF90EE90" } it "Didn't set other cells " { - $s1Sheet.Cells["3:3"].Style.Fill.BackgroundColor.Rgb | should not be "FF90EE90" - $s1Sheet.Cells["F4"].Style.Font.Color.Rgb | should beNullOrEmpty - $s2Sheet.Cells["F4"].Style.Font.Color.Rgb | should beNullOrEmpty - $s2Sheet.Cells["3:3"].Style.Fill.BackgroundColor.Rgb | should not be "FF90EE90" + $s1Sheet.Cells["3:3"].Style.Fill.BackgroundColor.Rgb | should not be "FF90EE90" + $s1Sheet.Cells["F4"].Style.Font.Color.Rgb | should beNullOrEmpty + $s2Sheet.Cells["F4"].Style.Font.Color.Rgb | should beNullOrEmpty + $s2Sheet.Cells["3:3"].Style.Fill.BackgroundColor.Rgb | should not be "FF90EE90" + } + AfterAll { + Close-ExcelPackage -ExcelPackage $xl1 -NoSave + Close-ExcelPackage -ExcelPackage $xl2 -NoSave } } - Close-ExcelPackage -ExcelPackage $xl1 -NoSave - Close-ExcelPackage -ExcelPackage $xl2 -NoSave - $null = compare-WorkSheet "$env:temp\Server1.xlsx" "$env:temp\Server2.xlsx" -AllDataBackgroundColor white -BackgroundColor LightGreen -FontColor DarkRed - $xl1 = Open-ExcelPackage -Path "$env:temp\Server1.xlsx" - $xl2 = Open-ExcelPackage -Path "$env:temp\Server2.xlsx" - $s1Sheet = $xl1.Workbook.Worksheets[1] - $s2Sheet = $xl2.Workbook.Worksheets[1] - Context "Setting the forgound to highlight changed properties" { + BeforeAll { + $null = compare-WorkSheet "$env:temp\Server1.xlsx" "$env:temp\Server2.xlsx" -AllDataBackgroundColor white -BackgroundColor LightGreen -FontColor DarkRed + $xl1 = Open-ExcelPackage -Path "$env:temp\Server1.xlsx" + $xl2 = Open-ExcelPackage -Path "$env:temp\Server2.xlsx" + $s1Sheet = $xl1.Workbook.Worksheets[1] + $s2Sheet = $xl2.Workbook.Worksheets[1] + } it "Added foreground colour to the right cells " { - $s1Sheet.Cells["4:4"].Style.Fill.BackgroundColor.Rgb | should be "FF90EE90" - $s1Sheet.Cells["6:6"].Style.Fill.BackgroundColor.Rgb | should be "FF90EE90" - $s2Sheet.Cells["4:4"].Style.Fill.BackgroundColor.Rgb | should be "FF90EE90" - $s2Sheet.Cells["5:5"].Style.Fill.BackgroundColor.Rgb | should be "FF90EE90" - # $s1Sheet.Cells["F4"].Style.Font.Color.Rgb | should be "FF8B0000" - $s2Sheet.Cells["F4"].Style.Font.Color.Rgb | should be "FF8B0000" + $s1Sheet.Cells["4:4"].Style.Fill.BackgroundColor.Rgb | should be "FF90EE90" + $s1Sheet.Cells["6:6"].Style.Fill.BackgroundColor.Rgb | should be "FF90EE90" + $s2Sheet.Cells["4:4"].Style.Fill.BackgroundColor.Rgb | should be "FF90EE90" + $s2Sheet.Cells["5:5"].Style.Fill.BackgroundColor.Rgb | should be "FF90EE90" + # $s1Sheet.Cells["F4"].Style.Font.Color.Rgb | should be "FF8B0000" + $s2Sheet.Cells["F4"].Style.Font.Color.Rgb | should be "FF8B0000" } it "Didn't set the foreground on other cells " { - $s1Sheet.Cells["F5"].Style.Font.Color.Rgb | should beNullOrEmpty - $s2Sheet.Cells["F5"].Style.Font.Color.Rgb | should beNullOrEmpty - $s1Sheet.Cells["G4"].Style.Font.Color.Rgb | should beNullOrEmpty - $s2Sheet.Cells["G4"].Style.Font.Color.Rgb | should beNullOrEmpty + $s1Sheet.Cells["F5"].Style.Font.Color.Rgb | should beNullOrEmpty + $s2Sheet.Cells["F5"].Style.Font.Color.Rgb | should beNullOrEmpty + $s1Sheet.Cells["G4"].Style.Font.Color.Rgb | should beNullOrEmpty + $s2Sheet.Cells["G4"].Style.Font.Color.Rgb | should beNullOrEmpty } + AfterAll { + Close-ExcelPackage -ExcelPackage $xl1 -NoSave + Close-ExcelPackage -ExcelPackage $xl2 -NoSave + } } - Close-ExcelPackage -ExcelPackage $xl1 -NoSave - Close-ExcelPackage -ExcelPackage $xl2 -NoSave - - [System.Collections.ArrayList]$s = get-service | Select-Object -Property * -ExcludeProperty Name - - $s | Export-Excel -Path $env:temp\server1.xlsx -WorkSheetname Server1 - - #$s is a zero based array, excel rows are 1 based and excel has a header row so Excel rows will be 2 + index in $s - $row4Displayname = $s[2].DisplayName - $s[2].DisplayName = "Changed from the orginal" - - $d = $s[-1] | Select-Object -Property * - $d.DisplayName = "Dummy Service" - $d.ServiceName = "Dummy" - $s.Insert(3,$d) - - $row6Name = $s[5].ServiceName - $s.RemoveAt(5) - - $s[10].ServiceType = "Changed should not matter" - - $s | Select-Object -Property ServiceName, DisplayName, StartType, ServiceType | Export-Excel -Path $env:temp\server2.xlsx -WorkSheetname server2 - #Assume default worksheet name, (sheet1) and column header for key ("name") - $comp = compare-WorkSheet "$env:temp\Server1.xlsx" "$env:temp\Server2.xlsx" -WorkSheetName Server1,Server2 -Key ServiceName -Property DisplayName,StartType -AllDataBackgroundColor AliceBlue -BackgroundColor White -FontColor Red | Sort-Object _row,_file - $xl1 = Open-ExcelPackage -Path "$env:temp\Server1.xlsx" - $xl2 = Open-ExcelPackage -Path "$env:temp\Server2.xlsx" - - $s1Sheet = $xl1.Workbook.Worksheets["server1"] - $s2Sheet = $xl2.Workbook.Worksheets["server2"] Context "More complex comparison: output check and different worksheet names " { + BeforeAll { + [System.Collections.ArrayList]$s = get-service | Select-Object -Property * -ExcludeProperty Name + $s | Export-Excel -Path $env:temp\server1.xlsx -WorkSheetname Server1 + #$s is a zero based array, excel rows are 1 based and excel has a header row so Excel rows will be 2 + index in $s + $row4Displayname = $s[2].DisplayName + $s[2].DisplayName = "Changed from the orginal" + $d = $s[-1] | Select-Object -Property * + $d.DisplayName = "Dummy Service" + $d.ServiceName = "Dummy" + $s.Insert(3,$d) + $row6Name = $s[5].ServiceName + $s.RemoveAt(5) + $s[10].ServiceType = "Changed should not matter" + $s | Select-Object -Property ServiceName, DisplayName, StartType, ServiceType | Export-Excel -Path $env:temp\server2.xlsx -WorkSheetname server2 + #Assume default worksheet name, (sheet1) and column header for key ("name") + $comp = compare-WorkSheet "$env:temp\Server1.xlsx" "$env:temp\Server2.xlsx" -WorkSheetName Server1,Server2 -Key ServiceName -Property DisplayName,StartType -AllDataBackgroundColor AliceBlue -BackgroundColor White -FontColor Red | Sort-Object _row,_file + $xl1 = Open-ExcelPackage -Path "$env:temp\Server1.xlsx" + $xl2 = Open-ExcelPackage -Path "$env:temp\Server2.xlsx" + $s1Sheet = $xl1.Workbook.Worksheets["server1"] + $s2Sheet = $xl2.Workbook.Worksheets["server2"] + } it "Found the right number of differences " { - $comp | should not beNullOrEmpty - $comp.Count | should be 4 + $comp | should not beNullOrEmpty + $comp.Count | should be 4 } it "Found the data row with a changed property " { - $comp | should not beNullOrEmpty - $comp[0]._Side | should not be $comp[1]._Side - $comp[0]._Row | should be 4 - $comp[1]._Row | should be 4 - $comp[1].ServiceName | should be $comp[0].ServiceName - $comp[0].DisplayName | should be $row4Displayname - $comp[1].DisplayName | should be "Changed from the orginal" + $comp | should not beNullOrEmpty + $comp[0]._Side | should not be $comp[1]._Side + $comp[0]._Row | should be 4 + $comp[1]._Row | should be 4 + $comp[1].ServiceName | should be $comp[0].ServiceName + $comp[0].DisplayName | should be $row4Displayname + $comp[1].DisplayName | should be "Changed from the orginal" } it "Found the inserted data row " { - $comp | should not beNullOrEmpty - $comp[2]._Side | should be '=>' - $comp[2]._Row | should be 5 - $comp[2].ServiceName | should be "Dummy" + $comp | should not beNullOrEmpty + $comp[2]._Side | should be '=>' + $comp[2]._Row | should be 5 + $comp[2].ServiceName | should be "Dummy" } it "Found the deleted data row " { - $comp | should not beNullOrEmpty - $comp[3]._Side | should be '<=' - $comp[3]._Row | should be 6 - $comp[3].ServiceName | should be $row6Name + $comp | should not beNullOrEmpty + $comp[3]._Side | should be '<=' + $comp[3]._Row | should be 6 + $comp[3].ServiceName | should be $row6Name } + it "Set the background on the right rows " { + $s1Sheet.Cells["4:4"].Style.Fill.BackgroundColor.Rgb | should be "FFFFFFFF" + $s1Sheet.Cells["6:6"].Style.Fill.BackgroundColor.Rgb | Should be "FFFFFFFF" + $s2Sheet.Cells["4:4"].Style.Fill.BackgroundColor.Rgb | Should be "FFFFFFFF" + $s2Sheet.Cells["5:5"].Style.Fill.BackgroundColor.Rgb | Should be "FFFFFFFF" - it "set the background on the right rows " { - $s1Sheet.Cells["4:4"].Style.Fill.BackgroundColor.Rgb | should be "FFFFFFFF" - $s1Sheet.Cells["6:6"].Style.Fill.BackgroundColor.Rgb | should be "FFFFFFFF" - $s2Sheet.Cells["4:4"].Style.Fill.BackgroundColor.Rgb | should be "FFFFFFFF" - $s2Sheet.Cells["5:5"].Style.Fill.BackgroundColor.Rgb | should be "FFFFFFFF" - - $s1Sheet.Cells["E4"].Style.Font.Color.Rgb | should be "FFFF0000" - $s2Sheet.Cells["E4"].Style.Font.Color.Rgb | should be "FFFF0000" + $s1Sheet.Cells["E4"].Style.Font.Color.Rgb | Should be "FFFF0000" + $s2Sheet.Cells["E4"].Style.Font.Color.Rgb | Should be "FFFF0000" } it "Didn't set other cells " { - $s1Sheet.Cells["3:3"].Style.Fill.BackgroundColor.Rgb | should not be "FFFFFFFF" - $s2Sheet.Cells["3:3"].Style.Fill.BackgroundColor.Rgb | should not be "FFFFFFFF" - $s1Sheet.Cells["E5"].Style.Font.Color.Rgb | should beNullOrEmpty - $s2Sheet.Cells["E5"].Style.Font.Color.Rgb | should beNullOrEmpty - $s1Sheet.Cells["F4"].Style.Font.Color.Rgb | should beNullOrEmpty - $s2Sheet.Cells["F4"].Style.Font.Color.Rgb | should beNullOrEmpty + $s1Sheet.Cells["3:3"].Style.Fill.BackgroundColor.Rgb | Should not be "FFFFFFFF" + $s2Sheet.Cells["3:3"].Style.Fill.BackgroundColor.Rgb | Should not be "FFFFFFFF" + $s1Sheet.Cells["E5"].Style.Font.Color.Rgb | Should beNullOrEmpty + $s2Sheet.Cells["E5"].Style.Font.Color.Rgb | Should beNullOrEmpty + $s1Sheet.Cells["F4"].Style.Font.Color.Rgb | Should beNullOrEmpty + $s2Sheet.Cells["F4"].Style.Font.Color.Rgb | Should beNullOrEmpty + } + AfterAll { + Close-ExcelPackage -ExcelPackage $xl1 -NoSave -Show + Close-ExcelPackage -ExcelPackage $xl2 -NoSave -Show } - } - Close-ExcelPackage -ExcelPackage $xl1 -NoSave -Show - Close-ExcelPackage -ExcelPackage $xl2 -NoSave -Show - - } Describe "Merge Worksheet" { - - Remove-Item -Path "$env:temp\server*.xlsx" , "$env:temp\Combined*.xlsx" -ErrorAction SilentlyContinue - [System.Collections.ArrayList]$s = get-service | Select-Object -Property * - - $s | Export-Excel -Path $env:temp\server1.xlsx - - #$s is a zero based array, excel rows are 1 based and excel has a header row so Excel rows will be 2 + index in $s - $s[2].DisplayName = "Changed from the orginal" - - $d = $s[-1] | Select-Object -Property * - $d.DisplayName = "Dummy Service" - $d.Name = "Dummy" - $s.Insert(3,$d) - - $s.RemoveAt(5) - - $s | Export-Excel -Path $env:temp\server2.xlsx - #Assume default worksheet name, (sheet1) and column header for key ("name") - Merge-Worksheet -Referencefile "$env:temp\server1.xlsx" -Differencefile "$env:temp\Server2.xlsx" -OutputFile "$env:temp\combined1.xlsx" -Property name,displayname,startType -Key name - $excel = Open-ExcelPackage -Path "$env:temp\combined1.xlsx" - $ws = $excel.Workbook.Worksheets["sheet1"] Context "Merge with 3 properties" { - it "Created a worksheet with the correct headings " { - $ws | should not beNullOrEmpty - $ws.Cells[ 1,1].Value | should be "name" - $ws.Cells[ 1,2].Value | should be "DisplayName" - $ws.Cells[ 1,3].Value | should be "StartType" - $ws.Cells[ 1,4].Value | should be "Server2 DisplayName" - $ws.Cells[ 1,5].Value | should be "Server2 StartType" + BeforeAll { + Remove-Item -Path "$env:temp\server*.xlsx" , "$env:temp\Combined*.xlsx" -ErrorAction SilentlyContinue + [System.Collections.ArrayList]$s = get-service | Select-Object -Property * + + $s | Export-Excel -Path $env:temp\server1.xlsx + + #$s is a zero based array, excel rows are 1 based and excel has a header row so Excel rows will be 2 + index in $s + $s[2].DisplayName = "Changed from the orginal" + + $d = $s[-1] | Select-Object -Property * + $d.DisplayName = "Dummy Service" + $d.Name = "Dummy" + $s.Insert(3,$d) + + $s.RemoveAt(5) + + $s | Export-Excel -Path $env:temp\server2.xlsx + #Assume default worksheet name, (sheet1) and column header for key ("name") + Merge-Worksheet -Referencefile "$env:temp\server1.xlsx" -Differencefile "$env:temp\Server2.xlsx" -OutputFile "$env:temp\combined1.xlsx" -Property name,displayname,startType -Key name + $excel = Open-ExcelPackage -Path "$env:temp\combined1.xlsx" + $ws = $excel.Workbook.Worksheets["sheet1"] } - it "Joined the two sheets correctly " { - $ws.Cells[ 2,2].Value | should be $ws.Cells[ 2,4].Value - $ws.Cells[ 2,3].Value | should be $ws.Cells[ 2,5].Value - $ws.cells[ 4,4].value | should be "Changed from the orginal" - $ws.cells[ 5,1].value | should be "Dummy" - $ws.cells[ 5,2].value | should beNullOrEmpty - $ws.cells[ 5,3].value | should beNullOrEmpty - $ws.cells[ 5,4].value | should be "Dummy Service" - $ws.cells[ 7,4].value | should beNullOrEmpty - $ws.cells[ 7,5].value | should beNullOrEmpty - $ws.Cells[12,2].Value | should be $ws.Cells[12,4].Value - $ws.Cells[12,3].Value | should be $ws.Cells[12,5].Value + it "Created a worksheet with the correct headings " { + $ws | should not beNullOrEmpty + $ws.Cells[ 1,1].Value | Should be "name" + $ws.Cells[ 1,2].Value | Should be "DisplayName" + $ws.Cells[ 1,3].Value | Should be "StartType" + $ws.Cells[ 1,4].Value | Should be "Server2 DisplayName" + $ws.Cells[ 1,5].Value | Should be "Server2 StartType" } - it "highlighted the keys in the added / deleted / changed rows " { - $ws.cells[4,1].Style.font.color.rgb | should be "FF8b0000" - $ws.cells[5,1].Style.font.color.rgb | should be "FF8b0000" - $ws.cells[7,1].Style.font.color.rgb | should be "FF8b0000" + it "Joined the two sheets correctly " { + $ws.Cells[ 2,2].Value | Should be $ws.Cells[ 2,4].Value + $ws.Cells[ 2,3].Value | Should be $ws.Cells[ 2,5].Value + $ws.cells[ 4,4].value | Should be "Changed from the orginal" + $ws.cells[ 5,1].value | Should be "Dummy" + $ws.cells[ 5,2].value | Should beNullOrEmpty + $ws.cells[ 5,3].value | Should beNullOrEmpty + $ws.cells[ 5,4].value | Should be "Dummy Service" + $ws.cells[ 7,4].value | Should beNullOrEmpty + $ws.cells[ 7,5].value | Should beNullOrEmpty + $ws.Cells[12,2].Value | Should be $ws.Cells[12,4].Value + $ws.Cells[12,3].Value | Should be $ws.Cells[12,5].Value } - it "Set the background for the added / deleted /changed rows " { - $ws.cells["A3:E3"].style.Fill.BackgroundColor.Rgb | should beNullOrEmpty - $ws.cells["A4:E4"].style.Fill.BackgroundColor.Rgb | should be "FFFFA500" - $ws.cells["A5" ].style.Fill.BackgroundColor.Rgb | should be "FF98FB98" - $ws.cells["B5:C5"].style.Fill.BackgroundColor.rgb | should beNullOrEmpty - $ws.cells["D5:E5"].style.Fill.BackgroundColor.Rgb | should be "FF98FB98" - $ws.cells["A7:C7"].style.Fill.BackgroundColor.Rgb | should be "FFFFB6C1" - $ws.cells["D7:E7"].style.Fill.BackgroundColor.rgb | should beNullOrEmpty + it "Highlighted the keys in the added / deleted / changed rows " { + $ws.cells[4,1].Style.font.color.rgb | Should be "FF8b0000" + $ws.cells[5,1].Style.font.color.rgb | Should be "FF8b0000" + $ws.cells[7,1].Style.font.color.rgb | Should be "FF8b0000" + } + it "Set the background for the added / deleted /changed rows " { + $ws.cells["A3:E3"].style.Fill.BackgroundColor.Rgb | Should beNullOrEmpty + $ws.cells["A4:E4"].style.Fill.BackgroundColor.Rgb | Should be "FFFFA500" + $ws.cells["A5" ].style.Fill.BackgroundColor.Rgb | Should be "FF98FB98" + $ws.cells["B5:C5"].style.Fill.BackgroundColor.rgb | Should beNullOrEmpty + $ws.cells["D5:E5"].style.Fill.BackgroundColor.Rgb | Should be "FF98FB98" + $ws.cells["A7:C7"].style.Fill.BackgroundColor.Rgb | Should be "FFFFB6C1" + $ws.cells["D7:E7"].style.Fill.BackgroundColor.rgb | Should beNullOrEmpty } } Context "Wider data set" { it "Copes with more columns beyond Z in the Output sheet " { - { Merge-Worksheet -Referencefile "$env:temp\server1.xlsx" -Differencefile "$env:temp\Server2.xlsx" -OutputFile "$env:temp\combined2.xlsx" } | should not throw + { Merge-Worksheet -Referencefile "$env:temp\server1.xlsx" -Differencefile "$env:temp\Server2.xlsx" -OutputFile "$env:temp\combined2.xlsx" } | Should not throw + } + } +} +Describe "Merge Multiple sheets" { + Context "Merge 3 sheets with 3 properties" { + BeforeAll { + Remove-Item -Path "$env:temp\server*.xlsx" , "$env:temp\Combined*.xlsx" -ErrorAction SilentlyContinue + [System.Collections.ArrayList]$s = get-service | Select-Object -Property Name,DisplayName,StartType + $s | Export-Excel -Path $env:temp\server1.xlsx + + #$s is a zero based array, excel rows are 1 based and excel has a header row so Excel rows will be 2 + index in $s + $row4Displayname = $s[2].DisplayName + $s[2].DisplayName = "Changed from the orginal" + + $d = $s[-1] | Select-Object -Property * + $d.DisplayName = "Dummy Service" + $d.Name = "Dummy" + $s.Insert(3,$d) + + $s.RemoveAt(5) + + $s | Export-Excel -Path $env:temp\server2.xlsx + + $s[2].displayname = $row4Displayname + + $d = $s[-1] | Select-Object -Property * + $d.DisplayName = "Second Service" + $d.Name = "Service2" + $s.Insert(6,$d) + $s.RemoveAt(8) + + $s | Export-Excel -Path $env:temp\server3.xlsx + + Merge-MultipleSheets -Path "$env:temp\server1.xlsx", "$env:temp\Server2.xlsx","$env:temp\Server3.xlsx" -OutputFile "$env:temp\combined3.xlsx" -Property name,displayname,startType -Key name + $excel = Open-ExcelPackage -Path "$env:temp\combined3.xlsx" + $ws = $excel.Workbook.Worksheets["sheet1"] + + } + it "Created a worksheet with the correct headings " { + $ws | Should not beNullOrEmpty + $ws.Cells[ 1,2 ].Value | Should be "name" + $ws.Cells[ 1,3 ].Value | Should be "Server1 DisplayName" + $ws.Cells[ 1,4 ].Value | Should be "Server1 StartType" + $ws.Cells[ 1,5 ].Value | Should be "Server2 DisplayName" + $ws.Cells[ 1,6 ].Value | Should be "Server2 StartType" + $ws.Column(7).hidden | Should be $true + $ws.Cells[ 1,8].Value | Should be "Server2 Row" + $ws.Cells[ 1,9 ].Value | Should be "Server3 DisplayName" + $ws.Cells[ 1,10].Value | Should be "Server3 StartType" + $ws.Column(11).hidden | Should be $true + $ws.Cells[ 1,12].Value | Should be "Server3 Row" + } + it "Joined the three sheets correctly " { + $ws.Cells[ 2,3 ].Value | Should be $ws.Cells[ 2,5 ].Value + $ws.Cells[ 2,4 ].Value | Should be $ws.Cells[ 2,6 ].Value + $ws.Cells[ 2,5 ].Value | Should be $ws.Cells[ 2,9 ].Value + $ws.Cells[ 2,6 ].Value | Should be $ws.Cells[ 2,10].Value + $ws.cells[ 4,5 ].value | Should be "Changed from the orginal" + $ws.cells[ 4,9 ].value | Should be $ws.Cells[ 4,3 ].Value + $ws.cells[ 5,2 ].value | Should be "Dummy" + $ws.cells[ 5,3 ].value | Should beNullOrEmpty + $ws.cells[ 5,4 ].value | Should beNullOrEmpty + $ws.cells[ 5,5 ].value | Should be "Dummy Service" + $ws.cells[ 5,8 ].value | Should be ($ws.cells[ 4,1].value +1) + $ws.cells[ 5,9 ].value | Should be $ws.cells[ 5,5 ].value + $ws.cells[ 7,5 ].value | Should beNullOrEmpty + $ws.cells[ 7,6 ].value | Should beNullOrEmpty + $ws.cells[ 7,9 ].value | Should beNullOrEmpty + $ws.cells[ 7,10].value | Should beNullOrEmpty + $ws.cells[ 8,3 ].value | Should beNullOrEmpty + $ws.cells[ 8,4 ].value | Should beNullOrEmpty + $ws.cells[ 8,5 ].value | Should beNullOrEmpty + $ws.cells[ 8,6 ].value | Should beNullOrEmpty + $ws.cells[11,9 ].value | Should beNullOrEmpty + $ws.cells[11,10].value | Should beNullOrEmpty + $ws.Cells[12,3 ].Value | Should be $ws.Cells[12,5].Value + $ws.Cells[12,4 ].Value | Should be $ws.Cells[12,6].Value + $ws.Cells[12,9 ].Value | Should be $ws.Cells[12,5].Value + $ws.Cells[12,10].Value | Should be $ws.Cells[12,6].Value + } + it "Creared Conditional formatting rules " { + $cf=$ws.ConditionalFormatting + $cf.Count | Should be 15 + $cf[14].Address.Address | Should be 'B2:B1048576' + $cf[14].Type | Should be 'Expression' + $cf[14].Formula | Should be 'OR(G2<>"Same",K2<>"Same")' + $cf[14].Style.Font.Color.Color.Name | Should be "FFFF0000" + $cf[13].Address.Address | Should be 'D2:D1048576' + $cf[13].Type | Should be 'Expression' + $cf[13].Formula | Should be 'OR(G2="Added",K2="Added")' + $cf[13].Style.Fill.BackgroundColor.Color.Name | Should be 'ffffb6c1' + $cf[ 0].Address.Address | Should be 'F1:F1048576' + $cf[ 0].Type | Should be 'Expression' + $cf[ 0].Formula | Should be 'G1="Added"' + $cf[ 0].Style.Fill.BackgroundColor.Color.Name | Should be 'ffffa500' } } } \ No newline at end of file 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 } }