From 4581c2b3e9ab23d15bdadede872489ab80c00b43 Mon Sep 17 00:00:00 2001 From: dfinke Date: Thu, 4 Jul 2019 11:18:28 -0400 Subject: [PATCH] Added resolve-path and tests. #627; Commented out write-warning --- ImportExcel.psm1 | 59 ++++++++++++++++--------------- __tests__/RelativePath.tests.ps1 | 22 ++++++++++++ __tests__/testRelative.xlsx | Bin 0 -> 8591 bytes 3 files changed, 52 insertions(+), 29 deletions(-) create mode 100644 __tests__/RelativePath.tests.ps1 create mode 100644 __tests__/testRelative.xlsx diff --git a/ImportExcel.psm1 b/ImportExcel.psm1 index 222b162..5868144 100644 --- a/ImportExcel.psm1 +++ b/ImportExcel.psm1 @@ -48,7 +48,7 @@ if ($PSVersionTable.PSVersion.Major -ge 5) { . $PSScriptRoot\plot.ps1 Function New-Plot { - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification='New-Plot does not change system state')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'New-Plot does not change system state')] Param() [PSPlot]::new() @@ -269,7 +269,7 @@ function Import-Excel { [Parameter(ParameterSetName = "PathA", Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline, Position = 0 )] [Parameter(ParameterSetName = "PathB", Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline, Position = 0 )] [Parameter(ParameterSetName = "PathC", Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline, Position = 0 )] - [ValidateScript( {(Test-Path -Path $_ -PathType Leaf) -and ($_ -match '.xls$|.xlsx$|.xlsm$')})] + [ValidateScript( { (Test-Path -Path $_ -PathType Leaf) -and ($_ -match '.xls$|.xlsx$|.xlsm$') })] [String]$Path, [Parameter(ParameterSetName = "PackageA", Mandatory)] [Parameter(ParameterSetName = "PackageB", Mandatory)] @@ -305,7 +305,7 @@ function Import-Excel { .SYNOPSIS Create objects containing the column number and the column name for each of the different header types. #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification="Name would be incorrect, and command is not exported")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = "Name would be incorrect, and command is not exported")] Param ( [Parameter(Mandatory)] [Int[]]$Columns, @@ -318,13 +318,13 @@ function Import-Excel { $i = 0 foreach ($C in $Columns) { $i++ - $C | Select-Object @{N = 'Column'; E = {$_}}, @{N = 'Value'; E = {'P' + $i}} + $C | Select-Object @{N = 'Column'; E = { $_ } }, @{N = 'Value'; E = { 'P' + $i } } } } elseif ($HeaderName) { $i = 0 foreach ($H in $HeaderName) { - $H | Select-Object @{N = 'Column'; E = {$Columns[$i]}}, @{N = 'Value'; E = {$H}} + $H | Select-Object @{N = 'Column'; E = { $Columns[$i] } }, @{N = 'Value'; E = { $H } } $i++ } } @@ -334,7 +334,7 @@ function Import-Excel { } foreach ($C in $Columns) { - $Worksheet.Cells[$StartRow, $C] | Where-Object {$_.Value} | Select-Object @{N = 'Column'; E = {$C}}, Value + $Worksheet.Cells[$StartRow, $C] | Where-Object { $_.Value } | Select-Object @{N = 'Column'; E = { $C } }, Value } } } @@ -346,43 +346,44 @@ function Import-Excel { process { if ($path) { + $Path = (Resolve-Path $Path).ProviderPath $stream = New-Object -TypeName System.IO.FileStream -ArgumentList $Path, 'Open', 'Read', 'ReadWrite' - if ($Password) {$ExcelPackage = New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList $stream , $Password } - else {$ExcelPackage = New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList $stream} + if ($Password) { $ExcelPackage = New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList $stream , $Password } + else { $ExcelPackage = New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList $stream } } try { #Select worksheet if (-not $WorksheetName) { $Worksheet = $ExcelPackage.Workbook.Worksheets[1] } - elseif(-not ($Worksheet = $ExcelPackage.Workbook.Worksheets[$WorkSheetName])) { - throw "Worksheet '$WorksheetName' not found, the workbook only contains the worksheets '$($ExcelPackage.Workbook.Worksheets)'. If you only wish to select the first worksheet, please remove the '-WorksheetName' parameter." ; return + elseif (-not ($Worksheet = $ExcelPackage.Workbook.Worksheets[$WorkSheetName])) { + throw "Worksheet '$WorksheetName' not found, the workbook only contains the worksheets '$($ExcelPackage.Workbook.Worksheets)'. If you only wish to select the first worksheet, please remove the '-WorksheetName' parameter." ; return } 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 (-not $EndRow ) {$EndRow = $Worksheet.Dimension.End.Row } - if (-not $EndColumn) {$EndColumn = $Worksheet.Dimension.End.Column } + if (-not $EndRow ) { $EndRow = $Worksheet.Dimension.End.Row } + if (-not $EndColumn) { $EndColumn = $Worksheet.Dimension.End.Column } $endAddress = [OfficeOpenXml.ExcelAddress]::TranslateFromR1C1("R[$EndRow]C[$EndColumn]", 0, 0) if ($DataOnly) { #If we are using headers startrow will be the header-row so examine data from startRow + 1, - if ($NoHeader) {$range = "A" + ($StartRow ) + ":" + $endAddress } - else {$range = "A" + ($StartRow + 1 ) + ":" + $endAddress } + if ($NoHeader) { $range = "A" + ($StartRow ) + ":" + $endAddress } + else { $range = "A" + ($StartRow + 1 ) + ":" + $endAddress } #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 = @{} + $colHash = @{ } + $rowHash = @{ } foreach ($cell in $Worksheet.Cells[$range]) { - if ($null -ne $cell.Value ) {$colHash[$cell.Start.Column] = 1; $rowHash[$cell.Start.row] = 1 } + if ($null -ne $cell.Value ) { $colHash[$cell.Start.Column] = 1; $rowHash[$cell.Start.row] = 1 } } - $rows = ( $StartRow..$EndRow ).Where( {$rowHash[$_]}) - $columns = ($StartColumn..$EndColumn).Where( {$colHash[$_]}) + $rows = ( $StartRow..$EndRow ).Where( { $rowHash[$_] }) + $columns = ($StartColumn..$EndColumn).Where( { $colHash[$_] }) } else { - $Columns = $StartColumn .. $EndColumn ; if ($StartColumn -gt $EndColumn) {Write-Warning -Message "Selecting columns $StartColumn to $EndColumn might give odd results."} - if ($NoHeader) {$Rows = $StartRow..$EndRow ; if ($StartRow -gt $EndRow) {Write-Warning -Message "Selecting rows $StartRow to $EndRow might give odd results."} } - else {$Rows = (1 + $StartRow)..$EndRow ; if ($StartRow -ge $EndRow) {Write-Warning -Message "Selecting $StartRow as the header with data in $(1+$StartRow) to $EndRow might give odd results."}} + $Columns = $StartColumn .. $EndColumn ; if ($StartColumn -gt $EndColumn) { Write-Warning -Message "Selecting columns $StartColumn to $EndColumn might give odd results." } + if ($NoHeader) { $Rows = $StartRow..$EndRow ; if ($StartRow -gt $EndRow) { Write-Warning -Message "Selecting rows $StartRow to $EndRow might give odd results." } } + else { $Rows = (1 + $StartRow)..$EndRow } # ; if ($StartRow -ge $EndRow) { Write-Warning -Message "Selecting $StartRow as the header with data in $(1+$StartRow) to $EndRow might give odd results." } } } #endregion #region Create property names @@ -402,7 +403,7 @@ function Import-Excel { foreach ($R in $Rows) { #Disabled write-verbose for speed # Write-Verbose "Import row '$R'" - $NewRow = [Ordered]@{} + $NewRow = [Ordered]@{ } foreach ($P in $PropertyNames) { $NewRow[$P.Value] = $Worksheet.Cells[$R, $P.Column].Value @@ -417,7 +418,7 @@ function Import-Excel { } catch { throw "Failed importing the Excel workbook '$Path' with worksheet '$Worksheetname': $_"; return } finally { - if ($Path) {$stream.close(); $ExcelPackage.Dispose() } + if ($Path) { $stream.close(); $ExcelPackage.Dispose() } } } } @@ -463,9 +464,9 @@ function ConvertFrom-ExcelSheet { $xl = New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList $Stream $workbook = $xl.Workbook - $targetSheets = $workbook.Worksheets | Where-Object {$_.Name -like $SheetName} + $targetSheets = $workbook.Worksheets | Where-Object { $_.Name -like $SheetName } - $params = @{} + $PSBoundParameters + $params = @{ } + $PSBoundParameters $params.Remove("OutputPath") $params.Remove("SheetName") $params.Remove('Extension') @@ -496,7 +497,7 @@ function Export-MultipleExcelSheets { [Switch]$AutoSize ) - $parameters = @{} + $PSBoundParameters + $parameters = @{ } + $PSBoundParameters $parameters.Remove("InfoMap") $parameters.Remove("Show") @@ -509,7 +510,7 @@ function Export-MultipleExcelSheets { & $entry.Value | Export-Excel @parameters } - if ($Show) {Invoke-Item $Path} + if ($Show) { Invoke-Item $Path } } Function WorksheetArgumentCompleter { @@ -519,7 +520,7 @@ Function WorksheetArgumentCompleter { $xlpkg = Open-ExcelPackage -ReadOnly -Path $xlPath $WorksheetNames = $xlPkg.Workbook.Worksheets.Name Close-ExcelPackage -nosave -ExcelPackage $xlpkg - $WorksheetNames.where( {$_ -like "*$wordToComplete*"}) | foreach-object { + $WorksheetNames.where( { $_ -like "*$wordToComplete*" }) | foreach-object { New-Object -TypeName System.Management.Automation.CompletionResult -ArgumentList "'$_'", $_ , ([System.Management.Automation.CompletionResultType]::ParameterValue) , $_ } diff --git a/__tests__/RelativePath.tests.ps1 b/__tests__/RelativePath.tests.ps1 new file mode 100644 index 0000000..8fa1e68 --- /dev/null +++ b/__tests__/RelativePath.tests.ps1 @@ -0,0 +1,22 @@ +Describe "Test reading relative paths" { + It "Should read local file" { + $actual = Import-Excel -Path ".\testRelative.xlsx" + $actual | Should Not Be $null + $actual.Count | Should Not Be 1 + } + + It "Should read with pwd" { + $actual = Import-Excel -Path "$pwd\testRelative.xlsx" + $actual | Should Not Be $null + } + + It "Should read with PSScriptRoot" { + $actual = Import-Excel -Path "$PSScriptRoot\testRelative.xlsx" + $actual | Should Not Be $null + } + + It "Should read with just a file name and resolve to cwd" { + $actual = Import-Excel -Path "testRelative.xlsx" + $actual | Should Not Be $null + } +} \ No newline at end of file diff --git a/__tests__/testRelative.xlsx b/__tests__/testRelative.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..14df5849c64e6db3a013349b965dff0bab04c6b7 GIT binary patch literal 8591 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 B-3b5y literal 0 HcmV?d00001