MISNole
MISNole

Reputation: 1062

Looping Through Files to Run PowerShell Script

I am trying to loop through an unknown number of Excel files in a folder and rename the 1st worksheet in each file to a specific name.

What I have so far is:

Clear-Host
$file = Get-ChildItem -Path "C:\PowerShell\BA" -Name -Include *.xlsx
Write-Output $file

This does list out all of the Excel files in the folder. I am then trying to run one of the commands from the ImportExcel module to rename the first worksheet to "Sheet1".

foreach ($i in $file )
{
$xl= Open-ExcelPackage $file
$sheet1 = $xl.Workbook.Worksheets[1]
$sheet1.Name ="Sheet1"
Close-ExcelPackage $xl
}

But when I run this code, I get the following error for each of the files in the folder:

WARNING: Could not find C:\WINDOWS\system32\11.25.2020_JH_BDX.xlsx 11.25.2020_JH_COV.xlsx 11.25.2020_JH_MISC.xlsx bx_1_coverage_report_2020-11-25 Final V.1 .xlsx bx_2_misc_report_2020-11-25 Final V.1 .xlsx bx_3_bordereau_report_2020-11-25 Final. V.1 .xlsx ic
at_cov_20201126053019.xlsx icat_misc_20201126053024.xlsx
Cannot index into a null array.
At line:8 char:1
+ $sheet1 = $xl.Workbook.Worksheets[1]
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : NullArray
 
Close-ExcelPackage : Cannot bind argument to parameter 'ExcelPackage' because it is null.
At line:10 char:20
+ Close-ExcelPackage $xl
+                    ~~~
    + CategoryInfo          : InvalidData: (:) [Close-ExcelPackage], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Close-ExcelPackage

If I run in 32-bit instead of 64-bit, it looks like it is looking for the files here:

WARNING: Could not find C:\Users\1192643\11.25.2020_JH_BDX.xlsx 11.25.2020_JH_COV.xlsx 11.25.2020_JH_MISC.xlsx bx_1_coverage_report_2020-11-25 Final V.1 .xlsx bx_2_misc_report_2020-11-25 Final V.1 .xlsx bx_3_bordereau_report_2020-11-25 Final. V.1 .xlsx icat_
cov_20201126053019.xlsx icat_misc_20201126053024.xlsx

I'm not sure why it is looking in C:\WINDOWS\system32 or my User director for the Excel files. I have tried to ensure it looks in the correct folder by adding the full path in the foreach block with the following:

foreach ($i in $file )
{
$xl= Open-ExcelPackage "C:\PowerShell\BA\"$file
$sheet1 = $xl.Workbook.Worksheets[1]
$sheet1.Name ="Sheet1"
Close-ExcelPackage $xl
}

But that does not work either. Could anyone help me understand what I am missing here? I am on version 5.1.17763.1490.

Updating the $xl variable to $xl= Open-ExcelPackage $i.fullname gives the following errors - so it seems to have the right path now, but it doesn't like the code. Open-ExcelPackage : Cannot bind argument to parameter 'Path' because it is null. At C:\PowerShell\BA\RenameWorksheet.ps1:23 char:24 + $xl= Open-ExcelPackage $i.fullname + ~~~~~~~~~~~ + CategoryInfo : InvalidData: (:) [Open-ExcelPackage], ParameterBindingValidationException + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Open-ExcelPackage

Cannot index into a null array.
At C:\PowerShell\BA\RenameWorksheet.ps1:24 char:1
+ $sheet1 = $xl.Workbook.Worksheets[1]
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : NullArray
 
The property 'Name' cannot be found on this object. Verify that the property exists and can be set.
At C:\PowerShell\BA\RenameWorksheet.ps1:25 char:1
+ $sheet1.Name ="Sheet1"
+ ~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : PropertyNotFound
 
Close-ExcelPackage : Cannot bind argument to parameter 'ExcelPackage' because it is null.
At C:\PowerShell\BA\RenameWorksheet.ps1:26 char:20
+ Close-ExcelPackage $xl
+                    ~~~
    + CategoryInfo          : InvalidData: (:) [Close-ExcelPackage], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Close-ExcelPackage

To test the code, I was able to update the worksheet name in a specific .xlsx file by using:

$xl= Open-ExcelPackage "C:\PowerShell\BA\11.25.2020_JH_MISC.xlsx"
$sheet1 = $xl.Workbook.Worksheets[1]
$sheet1.Name ="Sheet1"
Close-ExcelPackage $xl

Thanks,

Upvotes: 0

Views: 801

Answers (2)

user2674513
user2674513

Reputation:

When working with PowerShell, there are two things to remember.

  1. PowerShell is object-oriented.
  2. PowerShell uses pipelines.

Here we have a variable and a loop. Both can be eliminated by switching to a pipeline.

Take care to read the help file for Get-ChildItem.

As Doug mentioned, the -Name parameter:

-Name

Gets only the names of the items in the location. The output is a string object that can be sent down the pipeline to other commands. Wildcards are permitted.

Here we go from a FileInfo object to a String. So we lose valuable information like the location of each file: $_.FullName This contributed to the issue with $i.fullname

Also, -Include requires a special wildcard in the -Path.

-Include

Specifies, as a string array, an item or items that this cmdlet includes in the operation. The value of this parameter qualifies the Path parameter. Enter a path element or pattern, such as "*.txt". Wildcard characters are permitted. The Include parameter is effective only when the command includes the contents of an item, such as C:\Windows\*, where the wildcard character specifies the contents of the C:\Windows directory.

Get-ChildItem won't produce any output unless you follow this pattern or (for some reason) include the -Name parameter. This might be why you started using the -Name parameter in the first place.

Final Answer

Get-ChildItem -Path "C:\PowerShell\BA\*" -Include "*.xlsx" | 
  ForEach-Object { 
    $xl = Open-ExcelPackage $_.FullName
    $sheet1 = $xl.Workbook.Worksheets[1]
    $sheet1.Name ="Sheet1"
    Close-ExcelPackage $xl
  }

If you have a large number of files and are able to install PowerShell 7, it might be possible to use the -Parallel parameter to speed up processing.

Get-ChildItem -Path "C:\PowerShell\BA\*" -Include "*.xlsx" | 
  ForEach-Object -Parallel { 
    $xl = Open-ExcelPackage $_.FullName
    $sheet1 = $xl.Workbook.Worksheets[1]
    $sheet1.Name ="Sheet1"
    Close-ExcelPackage $xl
  } -ThrottleLimit 5

If you're not a local admin, it is possible to get PowerShell 7 from the Store.

Interpreting Error Messages

Either C:\WINDOWS\system32\ or $env:USERPROFILE is the default working directory when PowerShell is loaded.

Looking at our path:

C:\WINDOWS\system32\11.25.2020_JH_BDX.xlsx

We have one default directory and one custom file.

This is an indication that we're only providing the file name to our function. E.g. $_.Name

In our case, the root cause of this error is Get-ChildItem -Name which produces only file names as strings. It would be necessary to qualify those file names per Walter. Or to remove -Name and use the FullName property of FileInfo per Doug / Itchydon.


Open-ExcelPackage : Cannot bind argument to parameter 'Path' because it is null.
$xl= Open-ExcelPackage $i.fullname

Here there are two clues.

One, we just changed

$xl= Open-ExcelPackage $file

to

$xl= Open-ExcelPackage $i.fullname

So, $i.fullname must be the issue.

Two, we can trace the logic of the error message backward.

it is null -> parameter 'Path' -> Open-ExcelPackage Cannot bind -> Open-ExcelPackage $i.fullname

The two most likely possibilities are that $i or $i.FullName never existed in the first place.

If we check, we see that $i has no FullName property.

Debugging

Set a breakpoint for the $xl= Open-ExcelPackage $i.fullname line in your script file.

For example,

1  # C:\users\michael\desktop\vim\demo 1.ps1
2  $file = Get-ChildItem -Path "C:\users\michael\desktop\vim" -Name -Include *.out
3  
4  
5  foreach ($i in $file )
6  {
7  $xl= Open-ExcelPackage $i.fullname
8  $sheet1 = $xl.Workbook.Worksheets[1]
9  $sheet1.Name ="Sheet1"
10 Close-ExcelPackage $xl
11 }
> # from the shell
> Set-PSBreakpoint -Script '.\demo 1.ps1' -Line 7

  ID Script            Line Command           Variable         Action
  -- ------            ---- -------           --------         ------
   0 demo 1.ps1           7

> & '.\demo 1.ps1'
Entering debug mode. Use h or ? for help.

Hit Line breakpoint on 'C:\Users\Michael\desktop\vim\demo 1.ps1:7'

At C:\Users\Michael\desktop\vim\demo 1.ps1:7 char:1
+ $xl= Open-ExcelPackage $i.fullname
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

> $i | gm

   TypeName: System.String

Name             MemberType            Definition
----             ----------            ----------
Clone            Method                System.Object Clone(), System.Object ...
CompareTo        Method                int CompareTo(System.Object value), i...
Contains         Method                bool Contains(string value)
...

> # We have a string. 

> $i | Get-Member | Where-Object { $_.name -match "^f" }
> # No match for properties starting with the letter 'f'. 
> # So, strings don't have a 'FullName' property. 
> q # to quit the debugger

Cannot bind argument to parameter 'Path' ...
Cannot index into a null array.
The property 'Name' cannot be found on this object...
Close-ExcelPackage : Cannot bind argument ...

Oftentimes resolving the first error will either fix the code or make the larger issue apparent.

For example, we know that $sheet1 = $xl.Workbook.Worksheets[1] (which depends on $xl being defined) won't work if $xl= Open-ExcelPackage $i.fullname didn't work.

There isn't much point in looking at the second, third, fourth, etc errors until the first one is resolved.

Pipelines

The two things to understand about pipelines are that:

  1. Cmdlets are connected with pipes | (ASCII 124) and
  2. An automatic variable is used to represent the current object being processed: $_

This gives us the basic tools to connect cmdlets cmdlet | cmdlet and to refer to the properties of the current object being processed ForEach-Object { $_.FullName }

Upvotes: 0

Walter Mitty
Walter Mitty

Reputation: 18950

Don't reference $file inside the loop. If you are looking for a handle on one of the files, use $i, because that's the name you chose in the setup of your foreach. Note that I have put the $i inside the quoted string.

foreach ($i in $file )
{
$xl= Open-ExcelPackage "C:\PowerShell\BA\$i"
$sheet1 = $xl.Workbook.Worksheets[1]
$sheet1.Name ="Sheet1"
Close-ExcelPackage $xl
}

Upvotes: 1

Related Questions