From 8b2817261608f6f32ccb9ceacb2e243cdcf38196 Mon Sep 17 00:00:00 2001 From: jhoneill Date: Tue, 9 Jul 2019 19:10:51 +0100 Subject: [PATCH] Refactored copy sheet and added pipe support --- Copy-ExcelWorkSheet.ps1 | 159 +++++++++++++----------- __tests__/Copy-ExcelWorksheet.Tests.ps1 | 62 ++++++--- 2 files changed, 132 insertions(+), 89 deletions(-) diff --git a/Copy-ExcelWorkSheet.ps1 b/Copy-ExcelWorkSheet.ps1 index 801cf66..d6ada62 100644 --- a/Copy-ExcelWorkSheet.ps1 +++ b/Copy-ExcelWorkSheet.ps1 @@ -3,24 +3,27 @@ .SYNOPSIS Copies a worksheet between workbooks or within the same workbook. .DESCRIPTION - Copy-ExcelWorkSheet takes Source and Destination workbook parameters; each can be the path to an XLSx file, an ExcelPackage object or an ExcelWorkbook object. - The Source worksheet is specified by name or number (starting from 1), and the destination worksheet can be explicitly named, - or will follow the name of the source if no name is specified. + Copy-ExcelWorkSheet takes a Source object which is either a worksheet, + or a package, Workbook or path, in which case the source worksheet can be specified + by name or number (starting from 1). + The destination worksheet can be explicitly named, or will follow the name of the source if no name is specified. + The Destination workbook can be given as the path to an XLSx file, an ExcelPackage object or an ExcelWorkbook object. + .EXAMPLE C:\> Copy-ExcelWorkSheet -SourceWorkbook Test1.xlsx -DestinationWorkbook Test2.xlsx This is the simplest version of the command: no source worksheet is specified so Copy-ExcelWorksheet uses the first sheet in the workbook No Destination sheet is specified so the new worksheet will be the same as the one which is being copied. .EXAMPLE - C:\> Copy-ExcelWorkSheet -SourceWorkbook Server1.xlsx -sourceWorksheet "Settings" -DestinationWorkbook Settings.xlsx -DestinationWorkSheet "Server1" + C:\> Copy-ExcelWorkSheet -SourceWorkbook Server1.xlsx -sourceWorksheet "Settings" -DestinationWorkbook Settings.xlsx -DestinationWorksheet "Server1" Here the Settings page from Server1's workbook is copied to the 'Server1" page of a "Settings" workbook. .EXAMPLE C:\> $excel = Open-ExcelPackage .\test.xlsx - C:\> Copy-ExcelWorkSheet -SourceWorkbook $excel -SourceWorkSheet "first" -DestinationWorkbook $excel -Show -DestinationWorkSheet Duplicate + C:\> Copy-ExcelWorkSheet -SourceWorkbook $excel -SourceWorkSheet "first" -DestinationWorkbook $excel -Show -DestinationWorksheet Duplicate This opens the workbook test.xlsx and copies the worksheet named "first" to a new worksheet named "Duplicate", because -Show is specified the file is saved and opened in Excel .EXAMPLE C:\> $excel = Open-ExcelPackage .\test.xlsx - C:\> Copy-ExcelWorkSheet -SourceWorkbook $excel -SourceWorkSheet 1 -DestinationWorkbook $excel -DestinationWorkSheet Duplicate + C:\> Copy-ExcelWorkSheet -SourceWorkbook $excel -SourceWorkSheet 1 -DestinationWorkbook $excel -DestinationWorksheet Duplicate C:\> Close-ExcelPackage $excel This is almost the same as the previous example, except source sheet is specified by position rather than name and because -Show is not specified, so other steps can be carried using the package object, at the end the file is saved by Close-ExcelPackage @@ -29,83 +32,99 @@ [CmdletBinding()] param( #An ExcelWorkbook or ExcelPackage object or the path to an XLSx file where the data is found. - [Parameter(Mandatory = $true)] - $SourceWorkbook, + [Parameter(Mandatory = $true,ValueFromPipeline=$true)] + [Alias('SourceWorkbook')] + $SourceObject, #Name or number (starting from 1) of the worksheet in the source workbook (defaults to 1). $SourceWorkSheet = 1 , #An ExcelWorkbook or ExcelPackage object or the path to an XLSx file where the data should be copied. [Parameter(Mandatory = $true)] $DestinationWorkbook, #Name of the worksheet in the destination workbook; by default the same as the source worksheet's name. If the sheet exists it will be deleted and re-copied. - $DestinationWorkSheet, + $DestinationWorksheet, #if the destination is an excel package or a path, launch excel and open the file on completion. [Switch]$Show ) - #Special case - give the same path for source and destination worksheet - if ($SourceWorkbook -is [System.String] -and $SourceWorkbook -eq $DestinationWorkbook) { - if (-not $DestinationWorkSheet) {Write-Warning -Message "You must specify a destination worksheet name if copying within the same workbook."; return} - else { - Write-Verbose -Message "Copying " - $excel = Open-ExcelPackage -Path $SourceWorkbook - if (-not $excel.Workbook.Worksheets[$Sourceworksheet]) { - Write-Warning -Message "Could not find Worksheet $sourceWorksheet in $sourceWorkbook" - Close-ExcelPackage -ExcelPackage $excel -NoSave - return - } - elseif ($excel.Workbook.Worksheets[$Sourceworksheet].name -eq $DestinationWorkSheet) { - Write-Warning -Message "The destination worksheet name is the same as the source. " - Close-ExcelPackage -ExcelPackage $excel -NoSave - return - } + begin { + #For the case where we are piped multiple sheets, we want to open the destination in the begin and close it in the end. + if ($DestinationWorkbook -is [OfficeOpenXml.ExcelPackage] ) { + if ($Show) {$package2 = $DestinationWorkbook} + $DestinationWorkbook = $DestinationWorkbook.Workbook + } + elseif ($DestinationWorkbook -is [string] -and ($DestinationWorkbook -ne $SourceObject)) { + $package2 = Open-ExcelPackage -Create -Path $DestinationWorkbook + $DestinationWorkbook = $package2.Workbook + } + } + process { + #Special case - given the same path for source and destination worksheet + if ($SourceObject -is [System.String] -and $SourceObject -eq $DestinationWorkbook) { + if (-not $DestinationWorksheet) {Write-Warning -Message "You must specify a destination worksheet name if copying within the same workbook."; return} else { - $null = Add-WorkSheet -ExcelPackage $Excel -WorkSheetname $DestinationWorkSheet -CopySource ($excel.Workbook.Worksheets[$SourceWorkSheet]) - Close-ExcelPackage -ExcelPackage $excel -Show:$Show - return + Write-Verbose -Message "Copying " + $excel = Open-ExcelPackage -Path $SourceObject + if (-not $excel.Workbook.Worksheets[$Sourceworksheet]) { + Write-Warning -Message "Could not find Worksheet $sourceWorksheet in $SourceObject" + Close-ExcelPackage -ExcelPackage $excel -NoSave + return + } + elseif ($excel.Workbook.Worksheets[$Sourceworksheet].name -eq $DestinationWorksheet) { + Write-Warning -Message "The destination worksheet name is the same as the source. " + Close-ExcelPackage -ExcelPackage $excel -NoSave + return + } + else { + $null = Add-WorkSheet -ExcelPackage $excel -WorkSheetname $DestinationWorksheet -CopySource ($excel.Workbook.Worksheets[$SourceWorkSheet]) + Close-ExcelPackage -ExcelPackage $excel -Show:$Show + return + } + } + } + else { + if ($SourceObject -is [OfficeOpenXml.ExcelWorksheet]) {$sourceWs = $SourceObject} + elseif ($SourceObject -is [OfficeOpenXml.ExcelWorkbook]) {$sourceWs = $SourceObject.Worksheets[$SourceWorkSheet]} + elseif ($SourceObject -is [OfficeOpenXml.ExcelPackage] ) {$sourceWs = $SourceObject.Workbook.Worksheets[$SourceWorkSheet]} + else { + $SourceObject = (Resolve-Path $SourceObject).ProviderPath + try { + Write-Verbose "Opening worksheet '$Worksheetname' in Excel workbook '$SourceObject'." + $stream = New-Object -TypeName System.IO.FileStream -ArgumentList $SourceObject, 'Open', 'Read' , 'ReadWrite' + $package1 = New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList $stream + $sourceWs = $Package1.Workbook.Worksheets[$SourceWorkSheet] + } + catch {Write-Warning -Message "Could not open $SourceObject - the error was '$($_.exception.message)' " ; return} + } + if (-not $sourceWs) {Write-Warning -Message "Could not find worksheet '$Sourceworksheet' in the source workbook." ; return} + else { + try { + if ($DestinationWorkbook -isnot [OfficeOpenXml.ExcelWorkbook]) { + Write-Warning "Not a valid workbook" ; return + } + #check if we have a destination sheet name and set one if not. Because we might loop round check $psBoundParameters, not the variable. + if (-not $PSBoundParameters['DestinationWorksheet']) { + #if we are piped files, use the file name without the extension as the destination sheet name, Otherwise use the source sheet name + if ($_ -is [System.IO.FileInfo]) {$DestinationWorksheet = $_.name -replace '\.xlsx$', '' } + else { $DestinationWorksheet = $sourceWs.Name} + } + if ($DestinationWorkbook.Worksheets[$DestinationWorksheet]) { + Write-Verbose "Destination workbook already has a sheet named '$DestinationWorksheet', deleting it." + $DestinationWorkbook.Worksheets.Delete($DestinationWorksheet) + } + Write-Verbose "Copying '$($sourcews.name)' from $($SourceObject) to '$($DestinationWorksheet)' in $($PSBoundParameters['DestinationWorkbook'])" + $null = Add-WorkSheet -ExcelWorkbook $DestinationWorkbook -WorkSheetname $DestinationWorksheet -CopySource $sourceWs + #Leave the destination open but close the source - if we're copying more than one sheet we'll re-open it and live with the inefficiency + if ($stream) {$stream.Close() } + if ($package1) {Close-ExcelPackage -ExcelPackage $package1 -NoSave } + } + catch {Write-Warning -Message "Could not write to sheet '$DestinationWorksheet' in the destination workbook. Error was '$($_.exception.message)'" ; return} } } } - else { - if ($SourceWorkbook -is [OfficeOpenXml.ExcelWorkbook]) {$sourcews = $SourceWorkbook.Worksheets[$SourceWorkSheet]} - elseif ($SourceWorkbook -is [OfficeOpenXml.ExcelPackage] ) {$sourcews = $SourceWorkbook.Workbook.Worksheets[$SourceWorkSheet]} - else { - $SourceWorkbook = (Resolve-Path $SourceWorkbook).ProviderPath - try { - Write-Verbose "Opening worksheet '$Worksheetname' in Excel workbook '$SourceWorkbook'." - $Stream = New-Object -TypeName System.IO.FileStream -ArgumentList $SourceWorkbook, 'Open', 'Read' , 'ReadWrite' - $Package1 = New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList $Stream - $sourceWs = $Package1.Workbook.Worksheets[$SourceWorkSheet] - } - catch {Write-Warning -Message "Could not open $SourceWorkbook" ; return} - } - if (-not $sourceWs) {Write-Warning -Message "Could not find worksheet '$Sourceworksheet' in the source workbook." ; return} - else { - try { - if ($DestinationWorkbook -is [OfficeOpenXml.ExcelWorkbook]) { - $wb = $DestinationWorkbook - } - elseif ($DestinationWorkbook -is [OfficeOpenXml.ExcelPackage] ) { - $wb = $DestinationWorkbook.workbook - if ($show) {$package2 = $DestinationWorkbook} - } - else { - $package2 = Open-ExcelPackage -Create -Path $DestinationWorkbook - $wb = $package2.Workbook - } - if (-not $DestinationWorkSheet) {$DestinationWorkSheet = $SourceWs.Name} - if ($wb.Worksheets[$DestinationWorkSheet]) { - Write-Verbose "Destination workbook already has a sheet named '$DestinationWorkSheet', deleting it." - $wb.Worksheets.Delete($DestinationWorkSheet) - } - Write-Verbose "Copying $($SourceWorkSheet) from $($SourceWorkbook) to $($DestinationWorkSheet) in $($DestinationWorkbook)" - $null = Add-WorkSheet -ExcelWorkbook $wb -WorkSheetname $DestinationWorkSheet -CopySource $sourceWs - if ($Stream) {$Stream.Close() } - if ($package1) {Close-ExcelPackage -ExcelPackage $Package1 -NoSave } - if ($package2) {Close-ExcelPackage -ExcelPackage $Package2 -Show:$show } - if ($show -and $DestinationWorkbook -is [OfficeOpenXml.ExcelWorkbook]) { - Write-Warning -Message "-Show only works if the Destination workbook is given as a file path or an ExcelPackage object." - } - } - catch {Write-Warning -Message "Could not write to sheet '$DestinationWorkSheet' in the destination workbook" ; return} + end { + #OK Now we can close the destination package + if ($package2) {Close-ExcelPackage -ExcelPackage $package2 -Show:$Show } + if ($Show -and -not $package2) { + Write-Warning -Message "-Show only works if the Destination workbook is given as a file path or an ExcelPackage object." } } } \ No newline at end of file diff --git a/__tests__/Copy-ExcelWorksheet.Tests.ps1 b/__tests__/Copy-ExcelWorksheet.Tests.ps1 index a4c00f3..9cb2635 100644 --- a/__tests__/Copy-ExcelWorksheet.Tests.ps1 +++ b/__tests__/Copy-ExcelWorksheet.Tests.ps1 @@ -60,29 +60,29 @@ Describe "Copy-Worksheet" { it "Copied the expected data into the worksheet " { $ws.Cells[2, 1].Value.Gettype().name | Should be 'DateTime' $ws.Cells[2, 2].Formula | Should be 'SUM(F2:G2)' - $ws.Cells[2, 5].Value.GetType().name | Should be 'String' - $ws.Cells[2, 6].Value.GetType().name | Should be 'String' + $ws.Cells[2, 5].Value.GetType().name | Should be 'String' + $ws.Cells[2, 6].Value.GetType().name | Should be 'String' $ws.Cells[2, 18].Value.GetType().name | Should be 'String' - ($ws.Cells[2, 11].Value -is [valuetype] ) | Should be $true - ($ws.Cells[2, 12].Value -is [valuetype] ) | Should be $true - ($ws.Cells[2, 13].Value -is [valuetype] ) | Should be $true - $ws.Cells[2, 11].Value | Should beLessThan 0 - $ws.Cells[2, 12].Value | Should beLessThan 0 - $ws.Cells[2, 13].Value | Should beLessThan 0 + ($ws.Cells[2, 11].Value -is [valuetype] ) | Should be $true + ($ws.Cells[2, 12].Value -is [valuetype] ) | Should be $true + ($ws.Cells[2, 13].Value -is [valuetype] ) | Should be $true + $ws.Cells[2, 11].Value | Should beLessThan 0 + $ws.Cells[2, 12].Value | Should beLessThan 0 + $ws.Cells[2, 13].Value | Should beLessThan 0 if ((Get-Culture).NumberFormat.NumberGroupSeparator -EQ ",") { - ($ws.Cells[2, 8].Value -is [valuetype] ) | Should be $true - $ws.Cells[2, 9].Value.GetType().name | Should be 'String' + ($ws.Cells[2, 8].Value -is [valuetype] ) | Should be $true + $ws.Cells[2, 9].Value.GetType().name | Should be 'String' } elseif ((Get-Culture).NumberFormat.NumberGroupSeparator -EQ ".") { - ($ws.Cells[2, 9].Value -is [valuetype] ) | Should be $true - $ws.Cells[2, 8].Value.GetType().name | Should be 'String' + ($ws.Cells[2, 9].Value -is [valuetype] ) | Should be $true + $ws.Cells[2, 8].Value.GetType().name | Should be 'String' } - ($ws.Cells[2, 14].Value -is [valuetype] ) | Should be $true - $ws.Cells[2, 15].Value.GetType().name | Should be 'String' - $ws.Cells[2, 16].Value.GetType().name | Should be 'String' - $ws.Cells[2, 17].Value.GetType().name | Should be 'String' - ($ws.Cells[2, 19].Value -is [valuetype] ) | Should be $true - ($ws.Cells[2, 20].Value -is [valuetype] ) | Should be $true + ($ws.Cells[2, 14].Value -is [valuetype] ) | Should be $true + $ws.Cells[2, 15].Value.GetType().name | Should be 'String' + $ws.Cells[2, 16].Value.GetType().name | Should be 'String' + $ws.Cells[2, 17].Value.GetType().name | Should be 'String' + ($ws.Cells[2, 19].Value -is [valuetype] ) | Should be $true + ($ws.Cells[2, 20].Value -is [valuetype] ) | Should be $true } } @@ -101,7 +101,7 @@ Describe "Copy-Worksheet" { } } - it "Should copy and remove sheets" { + it "Should copy and remove sheets " { $targetSheets = echo 1.1.2019 1.4.2019 $targetSheets | ForEach-Object { @@ -113,4 +113,28 @@ Describe "Copy-Worksheet" { (Get-ExcelSheetInfo -Path $xlfile ).Count | Should Be 3 } } + + Context "Copy worksheet should support piped input" { + BeforeAll { + $xlfile = "$env:TEMP\reports.xlsx" + $xlfileArchive = "$env:TEMP\reportsArchive.xlsx" + + rm $xlfile -ErrorAction SilentlyContinue + rm $xlfileArchive -ErrorAction SilentlyContinue + + $sheets = echo 1.1.2019 1.2.2019 1.3.2019 1.4.2019 1.5.2019 + + $sheets | ForEach-Object { + "Hello World" | Export-Excel $xlfile -WorksheetName $_ + } + $e = Open-ExcelPackage $xlfile + $e.Workbook.Worksheets | Copy-ExcelWorkSheet -DestinationWorkbook $xlfileArchive + Close-ExcelPackage -NoSave $e + } + it "Should copy sheets piped into the command " { + $excel = Open-ExcelPackage $xlfileArchive + $excel.Workbook.Worksheets.Count | should be 5 + $excel.Workbook.Worksheets['1.3.2019'].Cells['A1'].Value | should be 'Hello World' + } + } } \ No newline at end of file