Tidying of case, parameter clarity, removal of aliasess.

Added timeout to send-SqlDataToExcel
Added Merge WorkSheet
Fixed bugs in Compare-Worksheet
This commit is contained in:
jhoneill
2018-05-26 22:24:36 +01:00
parent 3f1ea599e3
commit 39a68e71c4
13 changed files with 586 additions and 329 deletions

View File

@@ -85,11 +85,12 @@
[switch]$StrikeThru
)
#Allow add 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) {
if ($Address -and -not $WorkSheet -and -not $Range) {
$WorkSheet = $Address.Worksheet[0]
$Range = $Address.Address
}
If ($ThreeIconsSet) {$rule = $WorkSheet.ConditionalFormatting.AddThreeIconSet($Range , $ThreeIconsSet)}
if ($rule -eq "Databar" -and -not $databarColor) {Write-Warning -Message "-DatabarColor must be specified for the Databar rule type" }
if ( $ThreeIconsSet) {$rule = $WorkSheet.ConditionalFormatting.AddThreeIconSet($Range , $ThreeIconsSet)}
elseif ($FourIconsSet) {$rule = $WorkSheet.ConditionalFormatting.AddFourIconSet( $Range , $FourIconsSet) }
elseif ($FiveIconsSet) {$rule = $WorkSheet.ConditionalFormatting.AddFiveIconSet( $Range , $IconType) }
elseif ($DataBarColor) {$rule = $WorkSheet.ConditionalFormatting.AddDatabar( $Range , $DataBarColor) }

View File

@@ -14,6 +14,10 @@ if (Get-Command -Name register-argumentCompleter -ErrorAction SilentlyContinue)
Register-ArgumentCompleter -CommandName Compare-Worksheet -ParameterName BackgroundColor -ScriptBlock $Function:ColorCompletion
Register-ArgumentCompleter -CommandName Compare-Worksheet -ParameterName FontColor -ScriptBlock $Function:ColorCompletion
Register-ArgumentCompleter -CommandName Compare-Worksheet -ParameterName TabColor -ScriptBlock $Function:ColorCompletion
Register-ArgumentCompleter -CommandName Merge-Worksheet -ParameterName AddBackgroundColor -ScriptBlock $Function:ColorCompletion
Register-ArgumentCompleter -CommandName Merge-Worksheet -ParameterName ChangeBackgroundColor -ScriptBlock $Function:ColorCompletion
Register-ArgumentCompleter -CommandName Merge-Worksheet ` -ParameterName DeleteBackgroundColor -ScriptBlock $Function:ColorCompletion
Register-ArgumentCompleter -CommandName Merge-Worksheet -ParameterName KeyFontColor -ScriptBlock $Function:ColorCompletion
Register-ArgumentCompleter -CommandName Set-Format -ParameterName BackgroundColor -ScriptBlock $Function:ColorCompletion
Register-ArgumentCompleter -CommandName Set-Format -ParameterName FontColor -ScriptBlock $Function:ColorCompletion
Register-ArgumentCompleter -CommandName Set-Format -ParameterName PatternColor -ScriptBlock $Function:ColorCompletion

View File

@@ -1,4 +1,4 @@
 ipmo C:\Users\mcp\Documents\GitHub\ImportExcel\ImportExcel.psd1 -Force -Verbose
 Import-Module -name ImportExcel -Force -Verbose
$sql = @"
SELECT rootfile.baseName , rootfile.extension , Image.fileWidth AS width , image.fileHeight AS height ,

View File

@@ -18,7 +18,7 @@
.PARAMETER TargetData
Data to insert onto the worksheet - this is often provided from the pipeline.
.PARAMETER ExcludeProperty
Speficies properties which may exist in the target data but should not be placed on the worksheet
Specifies properties which may exist in the target data but should not be placed on the worksheet
.PARAMETER Title
Text of a title to be placed in Cell A1
.PARAMETER TitleBold
@@ -32,9 +32,9 @@
.PARAMETER IncludePivotTable
Adds a Pivot table using the data in the worksheet
.PARAMETER PivotRows
Name(s) columns from the spreadhseet which will prvoide the row name(s) in the pivot table
Name(s) columns from the spreadhseet which will provide the row name(s) in the pivot table
.PARAMETER PivotColumns
Name(s) columns from the spreadhseet which will prvoide the Column name(s) in the pivot table
Name(s) columns from the spreadhseet which will provide the Column name(s) in the pivot table
.PARAMETER PivotData
Hash table in the form ColumnName = Average|Count|CountNums|Max|Min|Product|None|StdDev|StdDevP|Sum|Var|VarP to provide the data in the Pivot table
.PARAMETER PivotTableDefinition,
@@ -62,7 +62,7 @@
.PARAMETER TableStyle
Selects the style for the named table - defaults to 'Medium6'
.PARAMETER ExcelChartDefinition
A hash table containing ChartType, Title, NoLegend, ShowCategory, ShowPecent, Yrange, Xrange and SeriesHeader for one or more [non-pivot] charts
A hash table containing ChartType, Title, NoLegend, ShowCategory, ShowPercent, Yrange, Xrange and SeriesHeader for one or more [non-pivot] charts
.PARAMETER HideSheet
Name(s) of Sheet(s) to hide in the workbook
.PARAMETER KillExcel

View File

@@ -19,7 +19,7 @@ function Export-ExcelSheet {
$xl = New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList $Path
$workbook = $xl.Workbook
$targetSheets = $workbook.Worksheets | Where {$_.Name -Match $SheetName}
$targetSheets = $workbook.Worksheets | Where-Object {$_.Name -Match $SheetName}
$params = @{} + $PSBoundParameters
$params.Remove("OutputPath")

View File

@@ -14,7 +14,7 @@ Param (
#Path to the Excel file whose chars we will export.
$Path = "C:\Users\public\Documents\stats.xlsx",
#If specified, output file objects representing the image files.
[switch]$passthru,
[switch]$Passthru,
#Format to write - JPG by default
[ValidateSet("JPG","PNG","GIF")]
$OutputType = "JPG",
@@ -23,15 +23,14 @@ Param (
)
#if no output folder was specified, set destination to the folder where the Excel file came from
if (-not $Destination) {$Destination = Split-Path -Path $path -Parent }
if (-not $Destination) {$Destination = Split-Path -Path $Path -Parent }
#Call up Excel and tell it to open the file.
try { $excelApp = New-Object -ComObject "Excel.Application" }
catch { Write-Warning "Could not start Excel application - which usually means it is not installed." ; return }
try { $excelWorkBook = $excelApp.Workbooks.Open($path) }
catch { Write-Warning "Could not start Excel application - which usually means it is not installed." ; return }
try { $excelWorkBook = $excelApp.Workbooks.Open($Path) }
catch { Write-Warning -Message "Could not Open $Path." ; return }
#For each worksheet, for each chart, jump to the chart, create a filename of "WorksheetName_ChartTitle.jpg", and export the file.
foreach ($excelWorkSheet in $excelWorkBook.Worksheets) {
@@ -41,11 +40,12 @@ foreach ($excelWorkSheet in $excelWorkBook.Worksheets) {
$excelApp.Goto($excelchart.TopLeftCell,$true)
$imagePath = Join-Path -Path $Destination -ChildPath ($excelWorkSheet.Name + "_" + ($excelchart.Chart.ChartTitle.Text -split "\s\d\d:\d\d,")[0] + ".$OutputType")
if ( $excelchart.Chart.Export($imagePath, $OutputType, $false) ) { # Export returs true/false for success/failure
if ($passThru) {Get-Item -Path $imagePath } # when succesful return a file object (-passthru) or print a verbose message, write warning for any failures
if ($Passthru) {Get-Item -Path $imagePath } # when succesful return a file object (-Passthru) or print a verbose message, write warning for any failures
else {Write-Verbose -Message "Exported $imagePath"}
}
else {Write-Warning -Message "Failure exporting $imagePath" }
}
}
$excelApp.DisplayAlerts = $false
$excelWorkBook.Close($false,$null,$null)
$excelApp.Quit()

View File

@@ -4,7 +4,7 @@
RootModule = 'ImportExcel.psm1'
# Version number of this module.
ModuleVersion = '4.0.13'
ModuleVersion = '4.0.14'
# ID used to uniquely identify this module
GUID = '60dd4136-feff-401a-ba27-a84458c57ede'

View File

@@ -1,43 +1,44 @@
Add-Type -Path "$($PSScriptRoot)\EPPlus.dll"
. $PSScriptRoot\AddConditionalFormatting.ps1
. $PSScriptRoot\Charting.ps1
. $PSScriptRoot\ColorCompletion.ps1
. $PSScriptRoot\ConvertExcelToImageFile.ps1
. $PSScriptRoot\Compare-WorkSheet.ps1
. $PSScriptRoot\ConvertFromExcelData.ps1
. $PSScriptRoot\ConvertFromExcelToSQLInsert.ps1
. $PSScriptRoot\ConvertToExcelXlsx.ps1
. $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
. $PSScriptRoot\Get-Range.ps1
. $PSScriptRoot\Get-XYRange.ps1
. $PSScriptRoot\Import-Html.ps1
. $PSScriptRoot\InferData.ps1
. $PSScriptRoot\Invoke-Sum.ps1
. $PSScriptRoot\New-ConditionalFormattingIconSet.ps1
. $PSScriptRoot\New-ConditionalText.ps1
. $PSScriptRoot\New-ExcelChart.ps1
. $PSScriptRoot\New-PSItem.ps1
. $PSScriptRoot\Open-ExcelPackage.ps1
. $PSScriptRoot\Pivot.ps1
. $PSScriptRoot\Send-SQLDataToExcel.ps1
. $PSScriptRoot\Set-CellStyle.ps1
. $PSScriptRoot\Set-Column.ps1
. $PSScriptRoot\Set-Row.ps1
. $PSScriptRoot\SetFormat.ps1
. $PSScriptRoot\TrackingUtils.ps1
. $PSScriptRoot\Update-FirstObjectProperties.ps1
#region import everything we need
Add-Type -Path "$($PSScriptRoot)\EPPlus.dll"
. $PSScriptRoot\AddConditionalFormatting.ps1
. $PSScriptRoot\Charting.ps1
. $PSScriptRoot\ColorCompletion.ps1
. $PSScriptRoot\ConvertExcelToImageFile.ps1
. $PSScriptRoot\Compare-WorkSheet.ps1
. $PSScriptRoot\ConvertFromExcelData.ps1
. $PSScriptRoot\ConvertFromExcelToSQLInsert.ps1
. $PSScriptRoot\ConvertToExcelXlsx.ps1
. $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
. $PSScriptRoot\Get-Range.ps1
. $PSScriptRoot\Get-XYRange.ps1
. $PSScriptRoot\Import-Html.ps1
. $PSScriptRoot\InferData.ps1
. $PSScriptRoot\Invoke-Sum.ps1
. $PSScriptRoot\Merge-Worksheet.ps1
. $PSScriptRoot\New-ConditionalFormattingIconSet.ps1
. $PSScriptRoot\New-ConditionalText.ps1
. $PSScriptRoot\New-ExcelChart.ps1
. $PSScriptRoot\New-PSItem.ps1
. $PSScriptRoot\Open-ExcelPackage.ps1
. $PSScriptRoot\Pivot.ps1
. $PSScriptRoot\Send-SQLDataToExcel.ps1
. $PSScriptRoot\Set-CellStyle.ps1
. $PSScriptRoot\Set-Column.ps1
. $PSScriptRoot\Set-Row.ps1
. $PSScriptRoot\SetFormat.ps1
. $PSScriptRoot\TrackingUtils.ps1
. $PSScriptRoot\Update-FirstObjectProperties.ps1
New-Alias -Name Use-ExcelData -Value "ConvertFrom-ExcelData" -Force
New-Alias -Name Use-ExcelData -Value "ConvertFrom-ExcelData" -Force
if ($PSVersionTable.PSVersion.Major -ge 5) {
if ($PSVersionTable.PSVersion.Major -ge 5) {
. $PSScriptRoot\Plot.ps1
Function New-Plot {
@@ -46,12 +47,12 @@ if ($PSVersionTable.PSVersion.Major -ge 5) {
[PSPlot]::new()
}
}
else {
}
else {
Write-Warning 'PowerShell 5 is required for plot.ps1'
Write-Warning 'PowerShell Excel is ready, except for that functionality'
}
}
#endregion
Function Import-Excel {
<#
.SYNOPSIS
@@ -209,7 +210,7 @@ Function Import-Excel {
Notice that empty rows and empty columns are not imported.
.EXAMPLE
.EXAMPLE
Import data from an Excel worksheet. One object is created for each row. The property names are provided with the -HeaderName parameter. The import will start from row 2 and empty columns and rows are not imported.
----------------------------------------------------------
@@ -236,6 +237,7 @@ Function Import-Excel {
#>
[CmdLetBinding(DefaultParameterSetName)]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword","")]
Param (
[Alias('FullName')]
[Parameter(ValueFromPipelineByPropertyName, ValueFromPipeline, Position=0, Mandatory)]
@@ -251,34 +253,13 @@ Function Import-Excel {
[Switch]$NoHeader,
[Alias('HeaderRow','TopRow')]
[ValidateRange(1, 9999)]
[Int]$StartRow,
[Int]$StartRow = 1,
[Switch]$DataOnly,
[ValidateNotNullOrEmpty()]
[String]$Password
)
Begin {
Function Add-Property {
<#
.SYNOPSIS
Add the property name and value to the hashtable that will create a new object for each row.
#>
Param (
[Parameter(Mandatory)]
[String]$Name,
$Value
)
Try {
$NewRow.$Name = $Value
Write-Verbose "Import cell '$($Worksheet.Cells[$R, $P.Column].Address)' with property name '$Name' and value '$Value'"
}
Catch {
throw "Failed adding the property name '$Name' with value '$Value': $_"
}
}
$sw = [System.Diagnostics.Stopwatch]::StartNew()
Function Get-PropertyNames {
<#
.SYNOPSIS
@@ -313,7 +294,7 @@ Function Import-Excel {
}
foreach ($C in $Columns) {
$Worksheet.Cells[$StartRow,$C] | where {$_.Value} | Select-Object @{N='Column'; E={$C}}, Value
$Worksheet.Cells[$StartRow,$C] | Where-Object {$_.Value} | Select-Object @{N='Column'; E={$C}}, Value
}
}
}
@@ -356,56 +337,40 @@ Function Import-Excel {
$Worksheet = $Excel.Workbook.Worksheets | Select-Object -First 1
}
#endregion
#region Set the top row
if (((-not ($NoHeader -or $HeaderName)) -and ($StartRow -eq 0))) {
$StartRow = 1
}
#endregion
if (-not ($AllCells = $Worksheet.Cells | where {($_.Start.Row -ge $StartRow)})) {
Write-Warning "Worksheet '$WorksheetName' in workbook '$Path' is empty after StartRow '$StartRow'"
}
else {
Write-Debug $sw.Elapsed.TotalMilliseconds
#region Get rows and columns
#If we are doing dataonly it is quicker to work out which rows to ignore before processing the cells.
if ($DataOnly) {
$CellsWithValues = $AllCells | where {$_.Value}
$Columns = $CellsWithValues.Start.Column | Sort-Object -Unique
$Rows = $CellsWithValues.Start.Row | Sort-Object -Unique
#If we are using headers startrow will be the headerrow so examine data from startRow + 1,
if ($NoHeader) {$range = "A" + ($StartRow ) + ":" + $Worksheet.Dimension.End.Address }
else {$range = "A" + ($StartRow + 1 ) + ":" + $Worksheet.Dimension.End.Address }
#We're going to look at every cell and build 2 hash tables holding rows & columns which contain data.
#Want to Avoid 'select unique' operations & large Sorts, becuse time time taken increases with square
#of number of items (PS uses heapsort at large size). Instead keep a list of what we have seen,
#using Hash tables: "we've seen it" is all we need, no need to worry about "seen it before" / "Seen it many times"
$colHash = @{}
$rowHash = @{}
foreach ($cell in $Worksheet.Cells[$range]) {
if ($cell.Value -ne $null) {$colHash[$cell.Start.Column]=1; $rowHash[$cell.Start.row]=1 }
}
$rows = ($StartRow..($Worksheet.Dimension.End.Row)).Where({$rowHash[$_]})
$columns = (1..($Worksheet.Dimension.End.Column) ).Where({$colHash[$_]})
}
else {
$LastColumn = $AllCells.Start.Column | Sort-Object -Unique | Select-Object -Last 1
$Columns = 1..$LastColumn
$LastRow = $AllCells.Start.Row | Sort-Object -Unique | Select-Object -Last 1
$Rows = $StartRow..$LastRow | where {($_ -ge $StartRow) -and ($_ -gt 0)}
$Columns = ($Worksheet.Dimension.Start.Column)..($Worksheet.Dimension.End.Column)
if ($NoHeader) {$Rows = ( $StartRow)..($Worksheet.Dimension.End.Row) }
else {$Rows = (1 + $StartRow)..($Worksheet.Dimension.End.Row) }
}
#endregion
#region Create property names
if ((-not $Columns) -or (-not ($PropertyNames = Get-PropertyNames -Columns $Columns -StartRow $StartRow))) {
throw "No column headers found on top row '$StartRow'. If column headers in the worksheet are not a requirement then please use the '-NoHeader' or '-HeaderName' parameter."
}
if ($Duplicates = $PropertyNames | Group-Object Value | where Count -GE 2) {
if ($Duplicates = $PropertyNames | Group-Object Value | Where-Object Count -GE 2) {
throw "Duplicate column headers found on row '$StartRow' in columns '$($Duplicates.Group.Column)'. Column headers must be unique, if this is not a requirement please use the '-NoHeader' or '-HeaderName' parameter."
}
#endregion
#region Filter out rows with data in columns that don't have a column header
if ($DataOnly -and (-not $NoHeader)) {
$Rows = $CellsWithValues.Start | where {$PropertyNames.Column -contains $_.Column} |
Sort-Object Row -Unique | Select-Object -ExpandProperty Row
}
#endregion
#region Filter out the top row when it contains column headers
if (-not ($NoHeader -or $HeaderName)) {
$Rows = $Rows | where {$_ -gt $StartRow}
}
#endregion
Write-Debug $sw.Elapsed.TotalMilliseconds
if (-not $Rows) {
Write-Warning "Worksheet '$WorksheetName' in workbook '$Path' contains no data in the rows after top row '$StartRow'"
}
@@ -416,14 +381,15 @@ Function Import-Excel {
$NewRow = [Ordered]@{}
foreach ($P in $PropertyNames) {
Add-Property -Name $P.Value -Value $Worksheet.Cells[$R, $P.Column].Value
$NewRow[$P.Value] = $Worksheet.Cells[$R, $P.Column].Value
Write-Verbose "Import cell '$($Worksheet.Cells[$R, $P.Column].Address)' with property name '$($p.Value)' and value '$($Worksheet.Cells[$R, $P.Column].Value)'."
}
[PSCustomObject]$NewRow
}
#endregion
}
}
Write-Debug $sw.Elapsed.TotalMilliseconds
}
Catch {
throw "Failed importing the Excel workbook '$Path' with worksheet '$Worksheetname': $_"
@@ -499,7 +465,7 @@ function ConvertFrom-ExcelSheet {
$xl = New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList $stream
$workbook = $xl.Workbook
$targetSheets = $workbook.Worksheets | Where {$_.Name -like $SheetName}
$targetSheets = $workbook.Worksheets | Where-Object {$_.Name -like $SheetName}
$params = @{} + $PSBoundParameters
$params.Remove("OutputPath")
@@ -522,6 +488,7 @@ function ConvertFrom-ExcelSheet {
}
function Export-MultipleExcelSheets {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword","")]
param(
[Parameter(Mandatory=$true)]
$Path,

View File

@@ -25,6 +25,7 @@ Begin {
'AddConditionalFormatting.ps1',
'Charting.ps1',
'ColorCompletion.ps1',
'Compare-Worksheet.ps1',
'ConvertFromExcelData.ps1',
'ConvertFromExcelToSQLInsert.ps1',
'ConvertExcelToImageFile.ps1',

275
Merge-worksheet.ps1 Normal file
View File

@@ -0,0 +1,275 @@
Function Merge-Worksheet {
<#
.Synopsis
Merges two worksheets (or other objects) into a single worksheet with differences marked up.
.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
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 and 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.
.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.
Files which are added or deleted or have changedd size will be highlighed in the output sheet. Changes to dates or other attributes will be ignored
.Example
merge-worksheet -Outf .\dummy.xlsx -RefO (dir .\ImportExcel\4.0.7) -DiffO (dir .\ImportExcel\4.0.8') -Pr Length -WhatIf -Passthru | Out-GridView
This time no file is written because -WhatIf is specified, and -Passthru causes the results to go Out-Gridview. This version uses aliases to shorten the parameters,
(OutputFileName can be "outFile" and the sheet "OutSheet" : DifferenceObject & RefeenceObject can be DiffObject & RefObject)
#>
[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 ,
#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,
#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,
#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,
[parameter(ParameterSetName='D',Mandatory=$true)]
[parameter(ParameterSetName='E',Mandatory=$true)]
[Alias('RefObject')]
$ReferenceObject ,
[parameter(ParameterSetName='D',Mandatory=$true,Position=1)]
[Alias('DiffObject')]
$DifferenceObject ,
[parameter(ParameterSetName='D',Position=2)]
$DiffPrefix = "=>" ,
#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)]
[Alias('OutSheet')]
$OutputSheetName = "Sheet1",
#Properties to include in the DIFF - supports wildcards, default is "*".
$Property = "*" ,
#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.
[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.
[switch]$HideEqual ,
#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.
[Switch]$Show
)
#region Read Excel data
if ($Referencefile -and $Differencefile) {
#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.
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"
return
}
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 }
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
}
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 ;}
try {$DifferenceObject = Import-Excel @Params }
Catch {Write-Warning -Message "Could not read the worksheet '$WorkSheetName' from $Differencefile::$WorkSheetName." ; return }
$DiffPrefix = (Split-Path -Path $Differencefile -Leaf) -replace "\.xlsx$",""
if ($NoHeader) {$firstDataRow = $Startrow } else {$firstDataRow = $Startrow + 1}
}
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 = @()
$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"}
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}) }
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 " + ")
#Build the list of the properties to output, in order.
$diffpart = @()
$refpart = @()
foreach ($p in $proplist.Where({$key -ne $_}) ) {$refPart += $p ; $diffPart += "$DiffPrefix $p" }
#Last reference column will be A if there the only one property (which might be the key), B if there are two properties, C if there are 3 etc
$lastRefCol = [char](64 + $propList.count)
#First difference column will be the next one (we'll trap the case of only having the key later)
$FirstDiffCol = [char](65 + $propList.count)
if ($key -ne '*') {
$outputProps = @($key) + $refpart + $diffpart
#If we are using a single column as the key, don't duplicate it, so the last difference column will be A if there is one property, C if there are two, E if there are 3
$lastDiffCol = [char](63 + 2 * $propList.count)
}
else {
$outputProps = @( ) + $refpart + $diffpart
#If we not using a single column as a key all columns are duplicated so, the Last difference column will be B if there is one property, D if there are two, F if there are 3
$lastDiffCol = [char](64 + 2 * $propList.count)
}
#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
#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 = @{}
$rowNo = $firstDataRow
foreach ($row in $ReferenceObject) {
if ($row._row -eq $null) {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
foreach ($row in $DifferenceObject) {
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
}
else {$Rowhash[$row.$key] = $rowNo }
$rowNo ++
}
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.
$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]@{}
foreach ($result in $_.Group) {
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 ($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'
}
else {
$hash["$DiffPrefix is"] = 'Changed'
}
}
'<=' { $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
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)
{$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" ))})
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
}
elseif ( $HideEqual ) {$ws.row($i+2).hidden = $true }
if ( $expandedDiff[$i].side -eq "<>" ) {
$range = $ws.Dimension -replace "\d+", ($i + 2 )
Set-Format -WorkSheet $ws -Range $range -BackgroundColor $ChangeBackgroundColor
}
elseif ( $expandedDiff[$i].side -eq "<=" ) {
$range = "A" + ($i + 2 ) + ":" + $lastRefCol + ($i + 2 )
Set-Format -WorkSheet $ws -Range $range -BackgroundColor $DeleteBackgroundColor
}
elseif ( $expandedDiff[$i].side -eq "=>" ) {
if ($propList.count -gt 1) {
$range = $FirstDiffCol + ($i + 2 ) + ":" + $lastDiffCol + ($i + 2 )
Set-Format -WorkSheet $ws -Range $range -BackgroundColor $AddBackgroundColor
}
Set-Format -WorkSheet $ws -Range ("A" + ($i + 2 )) -BackgroundColor $AddBackgroundColor
}
}
Close-ExcelPackage -ExcelPackage $xl -Show:$Show
}
}

View File

@@ -33,6 +33,9 @@ iex (new-object System.Net.WebClient).DownloadString('https://raw.github.com/dfi
# What's new
- [James O'Neill](https://twitter.com/jamesoneill) added `Compare-Worksheet`
- Compares two worksheets with the same name in different files.
#### 4/22/2018
Thanks to the community yet again
- [ili101](https://github.com/ili101) for fixes and features

View File

@@ -1,5 +1,7 @@
Function Send-SQLDataToExcel {
<#
[CmdLetBinding()]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword","")]
<#
.Synopsis
Runs a SQL query and inserts the results into an ExcelSheet, more efficiently than sending it via Export-Excel
.Description
@@ -23,7 +25,7 @@
#>
#>
param (
#Database connection string; either DSN=ODBC_Data_Source_Name, a full odbc or SQL Connection string, or the name of a SQL server
[Parameter(ParameterSetName="SQLConnection", Mandatory=$true)]
@@ -41,6 +43,9 @@
#The SQL query to run
[Parameter(Mandatory=$true)]
[string]$SQL,
#Override the default query time of 30 seconds.
[int]$QueryTimeout,
#File name for the Excel File
$Path,
[String]$WorkSheetname = 'Sheet1',
[Switch]$KillExcel,
@@ -92,9 +97,9 @@
#We were either given a session object or a connection string (with, optionally a MSSQLServer parameter)
# If we got -MSSQLServer, create a SQL connection, if we didn't but we got -Connection create an ODBC connection
if ($MsSQLserver) {
if ($connection -notmatch "=") {$Connection = "server=$Connection;trusted_connection=true;timeout=60"}
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) {
@@ -102,30 +107,31 @@
}
#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") {
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)
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
$dataTable = New-Object -TypeName System.Data.DataTable
$rowCount = $dataAdapter.fill($dataTable)
Write-Verbose "Query returned $rowcount row(s)"
Write-Verbose -Message "Query returned $rowCount row(s)"
#ExportExcel user a -NoHeader parameter so that's what we use here, but needs to be the other way around.
$PrintHeaders = -not $NoHeader
$printHeaders = -not $NoHeader
if ($Title) {$r = $StartRow +1 }
else {$r = $StartRow}
#Get our Excel sheet and fill it with the data
$excelPackage = Export-Excel -Path $Path -WorkSheetname $WorkSheetname -PassThru
$excelPackage.Workbook.Worksheets[$WorkSheetname].Cells[$r,$StartColumn].LoadFromDataTable($dataTable, $PrintHeaders ) | Out-Null
$excelPackage.Workbook.Worksheets[$WorkSheetname].Cells[$r,$StartColumn].LoadFromDataTable($dataTable, $printHeaders ) | Out-Null
#Call export-excel with any parameters which don't relate to the SQL query
"Connection", "Database" , "Session", "MsSQLserver", "Destination" , "sql" ,"Path" | foreach-object {$null = $PSBoundParameters.Remove($_) }
"Connection", "Database" , "Session", "MsSQLserver", "Destination" , "SQL" ,"Path" | ForEach-Object {$null = $PSBoundParameters.Remove($_) }
Export-Excel -ExcelPackage $excelPackage @PSBoundParameters
#If we were not passed a session close the session we created.

View File

@@ -45,7 +45,7 @@
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)]
[cmdletbinding(DefaultParameterSetName)]
Param(
#First file to compare
[parameter(Mandatory=$true,Position=0)]
@@ -135,7 +135,7 @@
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
$diff = Compare-Object -ReferenceObject $Sheet1 -DifferenceObject $Sheet2 -Property $propList -PassThru -IncludeEqual:$IncludeEqual -ExcludeDifferent:$ExcludeDifferent |
[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
@@ -168,7 +168,7 @@
}
#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 {$_.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}