2

I have what may be an odd issue. I've got a Powershell script that's supposed to watch a directory for files, then move and rename them. It checks the output directory to see if a file with that name already exists with the form "Trip ID X Receipts Batch Y.pdf" (original output from the web form will always be that Y=1) and if it does replace Y with whatever the highest existing number of Y for other files with Trip ID X is. If there isn't one already, it'll just stay that Y=1. It does this successfully except on the second match, where instead of 2 Y will equal a number that varies depending on the file. This seems to be the file size in bytes plus 1. Example results of the script (from copy/pasting the same source file into the watched directory):

Trip ID 7 Receipts Batch 1.pdf

Trip ID 7 Receipts Batch 126973.pdf

Trip ID 7 Receipts Batch 3.pdf

Trip ID 7 Receipts Batch 4.pdf

The relevant portion of my code is here:

$REFile = "Trip ID " + $TripID + " Receipts Batch "
$TripIDCheck = "Trip ID " + $TripID                    
                
$TripFileCount = Get-ChildItem $destination |Where-Object {$_.Name -match $TripIDCheck}
$BatchCount = $TripFileCount.GetUpperBound(0) + 1
                     
$destinationRegEx = $destination + $REFile + $BatchCount + ".pdf"
                    
Move-Item -Path $path -Destination $destinationRegEx -Force

For counting the number of items in the array, I've used what you see above as well as $TripFileCount.Length, and $TripFileCount.Count. They all behave the same, seemingly taking the file, examining its size, setting the Y value for the second item to that, but then treating the third, fourth, etc. items as expected. For the life of me, I can't figure out what's going on. Have any of you ever seen something like this?

Edit: Trying to force $TripFileCount as an array with

$TripFileCount = @(Get-ChildItem $destination |Where-Object {$_.Name -match $TripIDCheck})

doesn't work either. It still does this.

Community
  • 1
  • 1
  • 4
    When there is one file in the destination, Get-ChildItem will return a single thing, not an array. The thing will be a `FileInfo`, and that will throw an exception `Method invocation failed because [System.IO.FileInfo] does not contain a method named 'GetUpperBound'`. So I'm thinking that a) you use $BatchCount somewhere else, and b) you have errors silenced somehow. BatchCount is not updated here, and is using a value from earlier in the script. Is that possible? – TessellatingHeckler May 26 '17 at 21:31
  • 1
    Still, figure it out with a debugger - run in ISE or VS Code, step through the code line by line, and see where $BatchCount is being set to the filesize... – TessellatingHeckler May 26 '17 at 21:32
  • That's actually where I declare $BatchCount. In fact, I just now thought to try stopping Powershell ISE from running the script between placing the file into the watched directory and it worked as planned. Oddly, placing my initial debugging lines where I'd set a variable to itself for $TripFIleCount and $BatchCount (so I could analyze their values with breakpoints) made it work again. Specifically, $TripFileCount = $TripFileCount is what fixes it. Is it that putting that line in there makes $TripFIleCount set itself to an array no matter what's in it? – Nick Zachariasen May 26 '17 at 22:04
  • That line, setting a variable to itself, should ... do nothing at all. Your description is exactly as if you were doing `$TripFileCount = Get-ChildItem ...; $TripFileCount.Length + 1`. Any chance you have two versions of the script, and are not running the same one you're editing? – TessellatingHeckler May 26 '17 at 23:18

1 Answers1

2

As TessellatingHeckler states, your symptom indeed suggests that you're not accounting for the fact that cmdlets such as Get-ChildItem do not always return an array, but may return a single, scalar item (or no items at all).

Therefore, you cannot blindly invoke methods / properties such as .GetUpperBound() or .Length on such a cmdlet's output. There are two workarounds:

  • Use array subexpression operator @(...) to ensure that the enclosed command's output is treated as an array, even if only a single object is returned or none at all.

  • In PSv3+, use the .Count property even on a single object or $null to implicitly treat them as if they were an array.

The following streamlined solution uses the .Count property on the output from the Get-ChildItem call, which works as intended in all 3 scenarios: Get-ChildItem matches nothing, 1 file, or multiple files.

$prefix = "Trip ID $TripID Receipts Batch "
$suffix = '.pdf'

$pattern = "$prefix*$suffix"

$count = (Get-ChildItem $destination -Filter $pattern).Count

Move-Item -Path $path -Destination (Join-Path $destination "$prefix$($count+1)$suffix")

Note:

  • If you're using PowerShell v2, then prepend @ to (...). @(...), the array subexpression operator, ensures that the output from the enclosed command is always treated as an array, even if it comprise just 1 object or none at all.

  • In PowerShell v3 and above, this behavior is conveniently implicit, although there are caveats - see this answer of mine.

mklement0
  • 382,024
  • 64
  • 607
  • 775