FileList Case

This commit is contained in:
ili101
2019-08-26 15:45:28 +03:00
parent b27f6bec3c
commit 6650ecd5b8
7 changed files with 982 additions and 15 deletions

View File

@@ -4,8 +4,8 @@ Add-Type -Path "$($PSScriptRoot)\EPPlus.dll"
. $PSScriptRoot\AddDataValidation.ps1
. $PSScriptRoot\Charting.ps1
. $PSScriptRoot\ColorCompletion.ps1
. $PSScriptRoot\ConvertExcelToImageFile.ps1
. $PSScriptRoot\Compare-WorkSheet.ps1
. $PSScriptRoot\ConvertExcelToImageFile.ps1
. $PSScriptRoot\ConvertFromExcelData.ps1
. $PSScriptRoot\ConvertFromExcelToSQLInsert.ps1
. $PSScriptRoot\ConvertToExcelXlsx.ps1
@@ -23,7 +23,7 @@ Add-Type -Path "$($PSScriptRoot)\EPPlus.dll"
. $PSScriptRoot\InferData.ps1
. $PSScriptRoot\Invoke-Sum.ps1
. $PSScriptRoot\Join-Worksheet.ps1
. $PSScriptRoot\Merge-worksheet.ps1
. $PSScriptRoot\Merge-Worksheet.ps1
. $PSScriptRoot\New-ConditionalFormattingIconSet.ps1
. $PSScriptRoot\New-ConditionalText.ps1
. $PSScriptRoot\New-ExcelChart.ps1
@@ -31,8 +31,9 @@ Add-Type -Path "$($PSScriptRoot)\EPPlus.dll"
. $PSScriptRoot\Open-ExcelPackage.ps1
. $PSScriptRoot\Pivot.ps1
. $PSScriptRoot\PivotTable.ps1
#. $PSScriptRoot\Plot.ps1
. $PSScriptRoot\RemoveWorksheet.ps1
. $PSScriptRoot\Send-SqlDataToExcel.ps1
. $PSScriptRoot\Send-SQLDataToExcel.ps1
. $PSScriptRoot\Set-CellStyle.ps1
. $PSScriptRoot\Set-Column.ps1
. $PSScriptRoot\Set-Row.ps1
@@ -45,7 +46,7 @@ Add-Type -Path "$($PSScriptRoot)\EPPlus.dll"
New-Alias -Name Use-ExcelData -Value "ConvertFrom-ExcelData" -Force
if ($PSVersionTable.PSVersion.Major -ge 5) {
. $PSScriptRoot\plot.ps1
. $PSScriptRoot\Plot.ps1
Function New-Plot {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'New-Plot does not change system state')]

View File

@@ -25,8 +25,47 @@ $IncludeFiles = @(
'*.dll',
'*.psd1',
'*.psm1',
'*.ps1',
'BinFolder*'
'AddConditionalFormatting.ps1',
'AddDataValidation.ps1',
'Charting.ps1',
'ColorCompletion.ps1',
'Compare-WorkSheet.ps1',
'ConvertExcelToImageFile.ps1',
'ConvertFromExcelData.ps1',
'ConvertFromExcelToSQLInsert.ps1',
'ConvertToExcelXlsx.ps1',
'Copy-ExcelWorkSheet.ps1',
'Export-Excel.ps1',
'Export-ExcelSheet.ps1',
'Export-StocksToExcel.ps1',
'Get-ExcelColumnName.ps1',
'Get-ExcelSheetInfo.ps1',
'Get-ExcelWorkbookInfo.ps1',
'Get-HtmlTable.ps1',
'Get-Range.ps1',
'Get-XYRange.ps1',
'Import-Html.ps1',
'InferData.ps1',
'Invoke-Sum.ps1',
'Join-Worksheet.ps1',
'Merge-Worksheet.ps1',
'New-ConditionalFormattingIconSet.ps1',
'New-ConditionalText.ps1',
'New-ExcelChart.ps1',
'New-PSItem.ps1',
'Open-ExcelPackage.ps1',
'Pivot.ps1',
'PivotTable.ps1',
'Plot.ps1',
'RemoveWorksheet.ps1',
'Send-SQLDataToExcel.ps1',
'Set-CellStyle.ps1',
'Set-Column.ps1',
'Set-Row.ps1',
'Set-WorkSheetProtection.ps1',
'SetFormat.ps1',
'TrackingUtils.ps1',
'Update-FirstObjectProperties.ps1'
)
$ExcludeFiles = @(
'Install.ps1'

538
Merge-Worksheet.ps1 Normal file
View File

@@ -0,0 +1,538 @@
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* a 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 sheet contains
a list of services. This command creates a worksheet named "54-55" in a
workbook named "services.xlsx" which shows all the services and their
differences, and opens the new workbook 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 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. This version uses
aliases to shorten the parameters, (OutputFileName can be "outFile" and
the Sheet can be"OutSheet"; DifferenceObject & ReferenceObject can be
DiffObject & RefObject respectively).
#>
[cmdletbinding(SupportsShouldProcess=$true)]
Param(
#First Excel file to compare. You can compare two Excel files or two other objects or a reference obhct against a difference file, but not a reference file against an object.
[parameter(ParameterSetName='A',Mandatory=$true,Position=0)] #A = Compare two files default headers
[parameter(ParameterSetName='B',Mandatory=$true,Position=0)] #B = Compare two files user supplied headers
[parameter(ParameterSetName='C',Mandatory=$true,Position=0)] #C = Compare two files headers P1, P2, P3 etc
$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)] #D Compare two objects; E = Compare one object one file that uses default headers
[parameter(ParameterSetName='F',Mandatory=$true,Position=1)] #F = Compare one object one file that uses user supplied headers
[parameter(ParameterSetName='G',Mandatory=$true,Position=1)] #G Compare one object one file that uses headers P1, P2, P3 etc
$Differencefile ,
#Name(s) of Worksheets to compare.
[parameter(ParameterSetName='A',Position=2)] #Applies to all sets EXCEPT D which is two objects (no sheets)
[parameter(ParameterSetName='B',Position=2)]
[parameter(ParameterSetName='C',Position=2)]
[parameter(ParameterSetName='E',Position=2)]
[parameter(ParameterSetName='F',Position=2)]
[parameter(ParameterSetName='G',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')] #Applies to all sets EXCEPT D which is two objects (no sheets, so no start row )
[parameter(ParameterSetName='B')]
[parameter(ParameterSetName='C')]
[parameter(ParameterSetName='E')]
[parameter(ParameterSetName='F')]
[parameter(ParameterSetName='G')]
[int]$Startrow = 1,
#Specifies custom property names to use, instead of the values defined in the column headers of the Start ROw.
[Parameter(ParameterSetName='B',Mandatory=$true)] #Compare object + sheet or 2 sheets with user supplied headers
[Parameter(ParameterSetName='F',Mandatory=$true)]
[String[]]$Headername,
#Automatically generate property names (P1, P2, P3, ..) instead of using the values the top row of the sheet.
[Parameter(ParameterSetName='C',Mandatory=$true)] #Compare object + sheet or 2 sheets with headers of P1, P2, P3 ...
[Parameter(ParameterSetName='G',Mandatory=$true)]
[switch]$NoHeader,
#Reference object to compare if a Worksheet is NOT being used. Reference object can combine with a difference sheet or difference object
[parameter(ParameterSetName='D',Mandatory=$true)]
[parameter(ParameterSetName='E',Mandatory=$true)]
[parameter(ParameterSetName='F',Mandatory=$true)]
[parameter(ParameterSetName='G',Mandatory=$true)]
[Alias('RefObject')]
$ReferenceObject ,
#Difference object to compare if a Worksheet is NOT being used for either half. Can't have a reference sheet and difference object.
[parameter(ParameterSetName='D',Mandatory=$true,Position=1)]
[Alias('DiffObject')]
$DifferenceObject ,
[parameter(ParameterSetName='D',Position=2)]
[parameter(ParameterSetName='E',Position=2)]
[parameter(ParameterSetName='F',Position=2)]
[parameter(ParameterSetName='G',Position=2)]
#If there isn't a filename to use to label data from the "Difference" side, DiffPrefix is used, it defaults to "=>"
$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.
$KeyFontColor = [System.Drawing.Color]::DarkRed ,
#Sets the background color for changed rows.
$ChangeBackgroundColor = [System.Drawing.Color]::Orange,
#Sets the background color for rows in the reference but deleted from the difference sheet.
$DeleteBackgroundColor = [System.Drawing.Color]::LightPink,
#Sets the background color for rows not in the reference but added to the difference sheet.
$AddBackgroundColor = [System.Drawing.Color]::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 the command only outputs to the pipeline).
[switch]$Passthru ,
#If specified, opens the output workbook.
[Switch]$Show
)
#region Read Excel data
if ($Differencefile -is [System.IO.FileInfo]) {$Differencefile = $Differencefile.FullName}
if ($Referencefile -is [System.IO.FileInfo]) {$Referencefile = $Referencefile.FullName}
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 }
foreach ($p in @("HeaderName","NoHeader","StartRow")) {if ($PSBoundParameters[$p]) {$params[$p] = $PSBoundParameters[$p]}}
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$",""
}
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" }
$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
$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
#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 ($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
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 ($DifferenceObject.count -gt $Rowhash.Keys.Count) {
Write-Warning -Message "Difference object has $($DifferenceObject.Count) rows; but only $($Rowhash.keys.count) unique keys"
}
if ($Key -eq '*') {$key = "_ALL"}
#endregion
#We need to know all the properties we've met on the objects we've diffed
$eDiffProps = [ordered]@{}
#When we do a compare object changes will result in two rows so we group them and join them together.
$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.
$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, this must be the second record, so set side to indicate "changed"; if we got two "Same" indicators we may have a classh of keys
if ($hash.Side) {
if ($hash.Side -eq $result.SideIndicator) {Write-Warning -Message "'$keyval' may be a duplicate."}
$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 in 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}
}
}
foreach ($k in $hash.keys) {$eDiffProps[$k] = $true}
[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 + $eDiffProps.keys.where({$_ -notin ($outputProps + @("_row","side","SideIndicator","_ALL" ))})
if ($PassThru -or -not $OutputFile) {return ($expandedDiff | Select-Object -Property $allprops | Sort-Object -Property "_row", "$DiffPrefix Row" ) }
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-ExcelRange -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-ExcelRange -Worksheet $ws -Range $range -BackgroundColor $ChangeBackgroundColor
}
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-ExcelRange -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)
Set-ExcelRange -Worksheet $ws -Range $range -BackgroundColor $AddBackgroundColor
}
Set-ExcelRange -Worksheet $ws -Range ("A" + ($i + 2 )) -BackgroundColor $AddBackgroundColor
}
}
Close-ExcelPackage -ExcelPackage $xl -Show:$Show
}
}
Function Merge-MultipleSheets {
<#
.Synopsis
Merges Worksheets into a single Worksheet with differences marked up.
.Description
The Merge Worksheet command combines two sheets. Merge-MultipleSheets is
designed to merge more than two. 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 "Startmode" from sheets A and C; the result has
column headings "_Row", "Name", "DisplayName", "Startmode", "C-DisplayName",
"C-StartMode", "C-Is" and "C-Row".
Merge-MultipleSheets then calls Merge-Worksheet again passing it the
intermediate result and sheet B, comparing "Name", "Displayname" and
"Start mode" columns on each side, and gets a result with columns "_Row",
"Name", "DisplayName", "Startmode", "B-DisplayName", "B-StartMode", "B-Is",
"B-Row", "C-DisplayName", "C-StartMode", "C-Is" and "C-Row". Any columns on
the "reference" side which are not used in the comparison are added on the
right, which is why we compare the sheets in reverse order.
The "Is" columns hold "Same", "Added", "Removed" or "Changed" and is used for
conditional formatting in the output sheet (these columns are 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, 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.
.Example
dir Server*.xlsx | Merge-MulipleSheets -WorksheetName Services -OutputFile Test2.xlsx -OutputSheetName Services -Show
Here 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
Merge-MulipleSheets -Path hotfixes.xlsx -WorksheetName Serv* -Key hotfixid -OutputFile test2.xlsx -OutputSheetName hotfixes -HideRowNumbers -Show
This time all the servers have written their hotfix information to their own
worksheets in a shared Excel workbook named "Hotfixes.xlsx" (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.
#>
[cmdletbinding()]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification="False positives when initializing variable in begin block")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification="MultipleSheet would be incorrect")]
#[Alias("Merge-MulipleSheets")] #There was a spelling error in the first release. This was there to ensure things didn't break but intelisense gave the alias first.
param (
#Paths to the files to be merged. Files are also accepted
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
$Path ,
#The row from where we start to import data, all rows above the Start row are disregarded. By default this is the first row.
[int]$Startrow = 1,
#Specifies custom property names to use, instead of the values defined in the column headers of the Start row.
[String[]]$Headername,
#If specified, property names will be automatically generated (P1, P2, P3, ..) instead of using the values from the start row.
[switch]$NoHeader,
#Name(s) of Worksheets to compare.
$WorksheetName = "Sheet1",
#File to write output to.
[Alias('OutFile')]
$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 comparison - supports wildcards, default is "*".
$Property = "*" ,
#Properties to exclude from the the comparison - supports wildcards.
$ExcludeProperty ,
#Name of a column which is unique used to pair up rows from the reference and difference sides, 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.
$KeyFontColor = [System.Drawing.Color]::Red,
#Sets the background color for changed rows.
$ChangeBackgroundColor = [System.Drawing.Color]::Orange,
#Sets the background color for rows in the reference but deleted from the difference sheet.
$DeleteBackgroundColor = [System.Drawing.Color]::LightPink,
#Sets the background color for rows not in the reference but added to the difference sheet.
$AddBackgroundColor = [System.Drawing.Color]::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 pipeline).
[switch]$Passthru ,
#If specified, opens the output workbook.
[Switch]$Show
)
begin { $filestoProcess = @() }
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
$WorksheetName = $excel.Workbook.Worksheets.Name.where({$_ -like $WorksheetName})
Close-ExcelPackage -NoSave -ExcelPackage $excel
}
#Merge identically 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}
Write-Progress -Activity "Merging sheets" -CurrentOperation "comparing '$WorksheetName' in $($filestoProcess[-1]) against $($filestoProcess[0]). "
$merged = Merge-Worksheet @params -Referencefile $filestoProcess[0] -Differencefile $filestoProcess[-1]
$nextFileNo = 2
while ($nextFileNo -lt $filestoProcess.count -and $merged) {
Write-Progress -Activity "Merging sheets" -CurrentOperation "comparing '$WorksheetName' in $($filestoProcess[-$nextFileNo]) against $($filestoProcess[0]). "
$merged = Merge-Worksheet @params -ReferenceObject $merged -Differencefile $filestoProcess[-$nextFileNo]
$nextFileNo ++
}
}
#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}
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) {
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.
else { Write-Warning -Message "Need at least two files to process" ; return }
#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 }
$orderByProperties = $merged[0].psobject.properties.where({$_.name -match "row$"}).name
Write-Progress -Activity "Merging sheets" -CurrentOperation "creating output sheet '$OutputSheetName' in $OutputFile"
$excel = $merged | Sort-Object -Property $orderByProperties |
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$"}
if ($filesToProcess.Count -ge 2) {
$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'
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)
while ($sheet.cells[$cellAddr].value -match $prefix) {
$condFormattingParams = @{RuleType='Expression'; BackgroundPattern='Solid'; Worksheet=$sheet; StopIfTrue=$true; 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 '|'
foreach ($cell in $sheet.Cells[($sheet.Dimension.Address -replace "\d+$","1")].Where({$_.value -Notmatch $nameRegex}) ) {
$cell.Value = $refPrefix + $cell.Value
$condFormattingParams = @{RuleType='Expression'; BackgroundPattern='Solid'; Worksheet=$sheet; StopIfTrue=$true; 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 ("AND(" +(($sameChecks -join ",") -replace '<>"Same"','="Changed"') +")" ) -BackgroundColor $ChangeBackgroundColor
}
#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 ",") +")" )
$sheet.view.FreezePanes(2, 3)
}
else {$sheet.view.FreezePanes(2, 2) }
#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 ($HideRowNumbers) {
foreach ($cell in $sheet.Cells[($sheet.Dimension.Address -replace "\d+$","1")].Where({$_.value -match "Row$"}) ) {
$sheet.Column($cell.start.Column).HIDDEN = $true
}
}
if ($Passthru) {$excel}
else {Close-ExcelPackage -ExcelPackage $excel -Show:$Show}
Write-Progress -Activity "Merging sheets" -Completed
}
}

218
Plot.ps1 Normal file
View File

@@ -0,0 +1,218 @@
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification="False positives")]
param()
class PSPlot {
hidden $path
hidden $pkg
hidden $ws
hidden $chart
PSPlot() {
$this.path=[System.IO.Path]::GetTempFileName() -replace "\.tmp", ".xlsx"
$this.pkg = New-Object OfficeOpenXml.ExcelPackage $this.path
$this.ws=$this.pkg.Workbook.Worksheets.Add("plot")
}
[PSPlot] Plot($yValues) {
$this.NewChart()
$xValues = 0..$yValues.Count
$xCol = 'A'
$yCol = 'B'
$this.AddDataToSheet($xCol,$yCol,'x','y',$xValues,$yValues)
$this.AddSeries($xCol,$yCol,$yValues)
$this.SetChartPosition($yCol)
return $this
}
[PSPlot] Plot($yValues,[string]$options) {
$this.NewChart()
$xValues = 0..$yValues.Count
$xCol = 'A'
$yCol = 'B'
$this.AddDataToSheet($xCol,$yCol,'x','y',$xValues,$yValues)
$this.AddSeries($xCol,$yCol,$yValues)
$this.SetMarkerInfo($options)
$this.SetChartPosition($yCol)
return $this
}
[PSPlot] Plot($xValues,$yValues) {
$this.NewChart()
$xCol = 'A'
$yCol = 'B'
$this.AddDataToSheet($xCol,$yCol,'x','y',$xValues,$yValues)
$this.AddSeries($xCol,$yCol,$yValues)
$this.SetChartPosition($yCol)
return $this
}
[PSPlot] Plot($xValues,$yValues,[string]$options) {
$this.NewChart()
$xCol = 'A'
$yCol = 'B'
$this.AddDataToSheet($xCol,$yCol,'x','y',$xValues,$yValues)
$this.AddSeries($xCol,$yCol,$yValues)
$this.SetMarkerInfo($options)
$this.SetChartPosition($yCol)
return $this
}
[PSPlot] Plot($xValues,$yValues,$x1Values,$y1Values) {
$this.NewChart()
$xCol = 'A'
$yCol = 'B'
$this.AddDataToSheet($xCol,$yCol,'x','y',$xValues,$yValues)
$this.AddSeries($xCol,$yCol,$yValues)
$xCol=$this.GetNextColumnName($yCol)
$yCol=$this.GetNextColumnName($xCol)
$this.AddDataToSheet($xCol,$yCol,'x1','y1',$x1Values,$y1Values)
$this.AddSeries($xCol,$yCol,$y1Values)
$this.SetChartPosition($yCol)
return $this
}
[PSPlot] Plot($xValues,$yValues,$x1Values,$y1Values,$x2Values,$y2Values) {
$this.NewChart()
$xCol = 'A'
$yCol = 'B'
$this.AddDataToSheet($xCol,$yCol,'x','y',$xValues,$yValues)
$this.AddSeries($xCol,$yCol,$yValues)
$xCol=$this.GetNextColumnName($yCol)
$yCol=$this.GetNextColumnName($xCol)
$this.AddDataToSheet($xCol,$yCol,'x1','y1',$x1Values,$y1Values)
$this.AddSeries($xCol,$yCol,$y1Values)
$xCol=$this.GetNextColumnName($yCol)
$yCol=$this.GetNextColumnName($xCol)
$this.AddDataToSheet($xCol,$yCol,'x2','y2',$x2Values,$y2Values)
$this.AddSeries($xCol,$yCol,$y2Values)
$this.SetChartPosition($yCol)
return $this
}
[PSPLot] SetChartPosition($yCol) {
$columnNumber = $this.GetColumnNumber($yCol)+1
$this.chart.SetPosition(1,0,$columnNumber,0)
return $this
}
AddSeries($xCol,$yCol,$yValues) {
$yRange = "{0}2:{0}{1}" -f $yCol,($yValues.Count+1)
$xRange = "{0}2:{0}{1}" -f $xCol,($yValues.Count+1)
$Series=$this.chart.Series.Add($yRange,$xRange)
}
hidden SetMarkerInfo([string]$options) {
$c=$options.Substring(0,1)
$m=$options.Substring(1)
$cmap=@{r='red';g='green';b='blue';i='indigo';v='violet';c='cyan'}
$mmap=@{Ci='Circle';Da='Dash';di='diamond';do='dot';pl='plus';sq='square';tr='triangle'}
$this.chart.Series[0].Marker = $mmap.$m
$this.chart.Series[0].MarkerColor = $cmap.$c
$this.chart.Series[0].MarkerLineColor = $cmap.$c
}
hidden [string]GetNextColumnName($columnName) {
return $this.GetColumnName($this.GetColumnNumber($columnName)+1)
}
hidden [int]GetColumnNumber($columnName) {
$sum=0
$columnName.ToCharArray() |
ForEach-Object {
$sum*=26
$sum+=[char]$_.tostring().toupper()-[char]'A'+1
}
return $sum
}
hidden [string]GetColumnName($columnNumber) {
$dividend = $columnNumber
$columnName = @()
while($dividend -gt 0) {
$modulo = ($dividend - 1) % 26
$columnName += [char](65 + $modulo)
$dividend = [int](($dividend -$modulo)/26)
}
return ($columnName -join '')
}
hidden AddDataToSheet($xColumn,$yColumn,$xHeader,$yHeader,$xValues,$yValues) {
$count=$yValues.Count
$this.ws.Cells["$($xColumn)1"].Value=$xHeader
$this.ws.Cells["$($yColumn)1"].Value=$yHeader
for ($idx= 0; $idx-lt $count; $idx++) {
$row=$idx+2
$this.ws.Cells["$($xColumn)$($row)"].Value=$xValues[$idx]
$this.ws.Cells["$($yColumn)$($row)"].Value=$yValues[$idx]
}
}
hidden NewChart() {
$chartType="XYScatter"
#$chartType="line"
$this.chart=$this.ws.Drawings.AddChart("plot", $chartType)
$this.chart.Title.Text = 'Plot'
$this.chart.Legend.Remove()
$this.SetChartSize(300,300)
}
[PSPlot] SetChartSize([int]$width,[int]$height){
$this.chart.SetSize($width, $height)
return $this
}
[PSPlot] Title($title) {
$this.chart.Title.Text = $title
return $this
}
Show() {
$this.pkg.Save()
$this.pkg.Dispose()
Invoke-Item $this.path
}
}

View File

@@ -1,7 +0,0 @@
$p = @{
Name = "ImportExcel"
NuGetApiKey = $NuGetApiKey
ReleaseNote = "Add NumberFormat parameter"
}
Publish-Module @p

175
Send-SQLDataToExcel.ps1 Normal file
View File

@@ -0,0 +1,175 @@
Function Send-SQLDataToExcel {
<#
.SYNOPSIS
Inserts a DataTable - returned by a SQL query - into an ExcelSheet
.DESCRIPTION
This command takes a SQL statement and run it against a database connection; for the connection it accepts either
* an object representing a session with a SQL server or ODBC database, or
* a connection string to make a session (if -MSSQLServer is specified it uses the SQL Native client,
and -Connection can be a server name instead of a detailed connection string. Without this switch it uses ODBC)
The command takes all the parameters of Export-Excel, except for -InputObject (alias TargetData); after
fetching the data it calls Export-Excel with the data as the value of InputParameter and whichever of
Export-Excel's parameters it was passed; for details of these parameters see the help for Export-Excel.
.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
A database connection string to be used to create a database session; either
* A Data source name written in the form DSN=ODBC_Data_Source_Name, or
* A full ODBC or SQL Native Client Connection string, or
* The name of a SQL server.
.PARAMETER MSSQLServer
Specifies the connection string is for SQL server, not ODBC.
.PARAMETER SQL
The SQL query to run against the session which was passed in -Session or set up from -Connection.
.PARAMETER Database
Switches to a specific database on a SQL server.
.PARAMETER QueryTimeout
Override the default query time of 30 seconds.
.PARAMETER DataTable
A System.Data.DataTable object containing the data to be inserted into the spreadsheet without running a query.
This remains supported to avoid breaking older scripts, but if you have a DataTable object you can pass the it
into Export-Excel using -InputObject.
.EXAMPLE
C:\> Send-SQLDataToExcel -MsSQLserver -Connection localhost -SQL "select name,type,type_desc from [master].[sys].[all_objects]" -Path .\temp.xlsx -WorkSheetname master -AutoSize -FreezeTopRow -AutoFilter -BoldTopRow
Connects to the local SQL server and selects 3 columns from [Sys].[all_objects] and exports then to a sheet named master with some basic header management
.EXAMPLE
C:\> $dbPath = 'C:\Users\James\Documents\Database1.accdb'
C:\> $Connection = "Driver={Microsoft Access Driver (*.mdb, *.accdb)};Dbq=$dbPath;"
C:\> $SQL="SELECT top 25 Name,Length From TestData ORDER BY Length DESC"
C:\> Send-SQLDataToExcel -Connection $connection -SQL $sql -path .\demo1.xlsx -WorkSheetname "Sizes" -AutoSize
This creates an ODBC connection string to read from an Access file and a SQL Statement to extracts data from it,
and sends the resulting data to a new worksheet
.EXAMPLE
C:\> $dbPath = 'C:\users\James\Documents\f1Results.xlsx'
C:\> $Connection = "Driver={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};Dbq=$dbPath;"
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:\> Send-SQLDataToExcel -Connection $connection -SQL $sql -path .\demo2.xlsx -WorkSheetname "Winners" -AutoSize -AutoNameRange -ConditionalFormat @{DataBarColor="Blue"; Range="Wins"}
Similar to the previous example this creates a connection string, this time for an Excel file, and runs
a SQL statement to get a list of motor-racing results, outputting the resulting data to a new spreadsheet.
The spreadsheet is formatted and a data bar added to show make the drivers' wins clearer.
(the F1 results database is available from https://1drv.ms/x/s!AhfYu7-CJv4ehNdZWxJE9LMAX_N5sg )
.EXAMPLE
C:\> $dbPath = 'C:\users\James\Documents\f1Results.xlsx'
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:\> $null = Get-SQL -Session F1 -excel -Connection $dbPath -sql $sql -OutputVariable Table
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 PowerShell gallery with Install-Module -Name GetSQL -
note the function is Get-SQL the module is GetSQL without the "-" )
Get-SQL simplify making database connections and building /submitting SQL statements.
Here Get-SQL 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 command is equivalent to running
C:\> Export-Excel -inputObject $Table -Path ".\demo3.xlsx" -WorkSheetname Gpwinners -autosize -TableName winners -TableStyle Light6 -show
This is quicker than using
C:\> Get-SQL <parameters> | export-excel -ExcludeProperty rowerror,rowstate,table,itemarray,haserrors <parameters>
(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 -ClearSheet -autosize -ColumnChart
Like the previous example, this uses Get-SQL (download from the gallery with Install-Module -Name GetSQL).
It uses the database session which Get-SQL created, rather than an ODBC connection string.
The Session parameter can either be a object (as shown here), or the name used by Get-SQL ("F1" in this case).
Here the data is presented as a quick chart.
.EXAMPLE
C:\> Send-SQLDataToExcel -path .\demo4.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
.Link
Export-Excel
#>
[CmdletBinding(DefaultParameterSetName="none")]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '', Justification="Allowed to use DBSessions Global variable from GETSQL Module")]
param (
[Parameter(ParameterSetName="SQLConnection", Mandatory=$true)]
[Parameter(ParameterSetName="ODBCConnection", Mandatory=$true)]
$Connection,
[Parameter(ParameterSetName="ExistingSession", Mandatory=$true)]
$Session,
[Parameter(ParameterSetName="SQLConnection", Mandatory=$true)]
[switch]$MsSQLserver,
[Parameter(ParameterSetName="SQLConnection")]
[String]$DataBase,
[Parameter(ParameterSetName="SQLConnection", Mandatory=$true)]
[Parameter(ParameterSetName="ODBCConnection", Mandatory=$true)]
[Parameter(ParameterSetName="ExistingSession", Mandatory=$true)]
[string]$SQL,
[int]$QueryTimeout,
[Parameter(ParameterSetName="Pre-FetchedData", Mandatory=$true)]
[System.Data.DataTable]$DataTable
)
#Import the parameters from Export-Excel, we will pass InputObject, and we have the common parameters so exclude those,
#and re-write the [Parmameter] attribute on each one to avoid parameterSetName here competing with the settings in Export excel.
#The down side of this that impossible parameter combinations won't be filtered out and need to be caught later.
DynamicParam {
$ParameterAttribute = "System.Management.Automation.ParameterAttribute"
$RuntimeDefinedParam = "System.Management.Automation.RuntimeDefinedParameter"
$paramDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary
$attributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute]
$attributeCollection.Add((New-Object -TypeName $ParameterAttribute -Property @{ ParameterSetName = "__AllParameterSets" ;Mandatory = $false}))
foreach ($P in (Get-Command -Name Export-Excel).Parameters.values.where({$_.name -notmatch 'Verbose|Debug|Action$|Variable$|Buffer$|TargetData$|InputObject$'})) {
$paramDictionary.Add($p.Name, (New-Object -TypeName $RuntimeDefinedParam -ArgumentList $p.name, $p.ParameterType, $attributeCollection ) )
}
return $paramDictionary
}
process {
#Dynamic params mean we can get passed parameter combination Export-Excel will reject, so throw here, rather than get data and then have Export-Excel error.
if ($PSBoundParameters.Path -and $PSBoundParameters.ExcelPackage) {
throw 'Parameter error: you cannot specify both a path and an Excel Package.'
return
}
if ($PSBoundParameters.AutoFilter -and ($PSBoundParameters.TableName -or $PSBoundParameters.TableStyle)) {
Write-Warning "Tables are automatically auto-filtered, -AutoFilter will be ignored"
$null = $PSBoundParameters.Remove('AutoFilter')
}
#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"}
$Session = New-Object -TypeName System.Data.SqlClient.SqlConnection -ArgumentList $Connection
if ($Session.State -ne 'Open') {$Session.Open()}
if ($DataBase) {$Session.ChangeDatabase($DataBase) }
}
elseif ($Connection) {
$Session = New-Object -TypeName System.Data.Odbc.OdbcConnection -ArgumentList $Connection ; $Session.ConnectionTimeout = 30
}
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 -is [String] -and $Global:DbSessions[$Session]) {$Session = $Global:DbSessions[$Session]}
if ($Session.GetType().name -match "SqlConnection") {
$dataAdapter = New-Object -TypeName System.Data.SqlClient.SqlDataAdapter -ArgumentList (
New-Object -TypeName System.Data.SqlClient.SqlCommand -ArgumentList $SQL, $Session)
}
else {
$dataAdapter = New-Object -TypeName System.Data.Odbc.OdbcDataAdapter -ArgumentList (
New-Object -TypeName System.Data.Odbc.OdbcCommand -ArgumentList $SQL, $Session )
}
if ($QueryTimeout) {$dataAdapter.SelectCommand.CommandTimeout = $QueryTimeout}
#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)"
}
if ($DataTable.Rows.Count) {
#Call export-excel removing parameters which relate to the SQL query, and keeping the rest.
'Connection' , 'Database' , 'Session' , 'MsSQLserver' , 'SQL' , 'DataTable' , 'QueryTimeout' | ForEach-Object {$null = $PSBoundParameters.Remove($_) }
Export-Excel @PSBoundParameters -InputObject $DataTable
}
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() }
}
}

View File

@@ -2,17 +2,18 @@
*.psd1
*.psm1
AddConditionalFormatting.ps1
AddDataValidation.ps1
Charting.ps1
ColorCompletion.ps1
Compare-Worksheet.ps1
Compare-WorkSheet.ps1
ConvertExcelToImageFile.ps1
ConvertFromExcelData.ps1
ConvertFromExcelToSQLInsert.ps1
ConvertToExcelXlsx.ps1
Copy-ExcelWorkSheet.ps1
Export-Charts.ps1
Export-Excel.ps1
Export-ExcelSheet.ps1
Export-StocksToExcel.ps1
Get-ExcelColumnName.ps1
Get-ExcelSheetInfo.ps1
Get-ExcelWorkbookInfo.ps1
@@ -32,10 +33,12 @@ Open-ExcelPackage.ps1
Pivot.ps1
PivotTable.ps1
Plot.ps1
RemoveWorksheet.ps1
Send-SQLDataToExcel.ps1
Set-CellStyle.ps1
Set-Column.ps1
Set-Row.ps1
Set-WorkSheetProtection.ps1
SetFormat.ps1
TrackingUtils.ps1
Update-FirstObjectProperties.ps1