views:

853

answers:

3

Can someone explain why this script throws an exception?

$byteArray = @(1,2,3)
write-Output ( "{0:X}{1:X}{2:X}" -f $byteArray )
write-Output ( $byteArray.Length -ge 3 )
write-Output ( "{0:X}{1:X}{2:X}" -f $byteArray )

Basically, I am creating an array of numbers, formatting the array and then checking its length and formatting it again.

The first format succeeds, but the second format throws an exception.

123
True
--------------------------------------------------------------------------------
POWERSHELL EXCEPTION 
EXCEPTION TYPE:System.Management.Automation.RuntimeException
MESSAGE:Error formatting a string: Index (zero based) must be greater than or equal to zero and less than the size of the argument list..
POSITION:
At line:4 char:36
+ write-Output ( "{0:X}{1:X}{2:X}" -f  <<<< $byteArray )
--------------------------------------------------------------------------------
+3  A: 

That's definitely weird. As a workaround you can use

"{0:X}{1:X}{2:X}" -f @($byteArray)

which seems to work even after accessing members of $byteArray.

Another possible workaround might be to save the formatted string to a variable and re-use it.

As for why it doesn't work after accessing the Length property I have no idea.

Joey
Thanks. It's the same work-around I have been using. It seems if you copy the array to a new variable you can use that variable for all your formatting calls and no exception will be thrown.
kervin
Somehow the member access changes something in the array which triggers this behavior with -f. Maybe one of the Powershell devs knows what's going on.
Joey
+1  A: 

Wow, that's pretty fascinating. I've played around with this for a few minutes on PowerShell V2 and i can't find a hard and fast reason about why this is happenning.

But that won't stop me from speculating :)

The problem is that the -f command is really expecting an array of objects. What's clearly happening in the problem line is that it's interpreting $byteArray as a single element vs. an array.

But why does it work the first time? My suspicion is that the array is lazy evaluated. Up until you actually call a method on the array type, it is merely an alias into the pipeline . By some fluke it works in the first case because it's just indexing into the existing pipe line or arguments. Once you call .Length, it gels the pipeline into an object and hence later calls correctly interpret it as an array.

Again, this is mostly just speculation. I highly advise you to file a bug on connect as this smells like a bug.

JaredPar
+3  A: 

To add to the puzzle:

PS > $a = @(1,2,3)
PS > $b = $a
PS > [object]::ReferenceEquals($a, $b)
True
PS > $a.Length
3
PS > [object]::ReferenceEquals($a, $b)
True
PS > "{0:X}{1:X}{2:X}" -f $a
Error formatting a string: Index (zero based) must be greater than or equal to zero and less than the size of the argum
ent list..
At line:1 char:21
+ "{0:X}{1:X}{2:X}" -f  <<<< $a
PS > "{0:X}{1:X}{2:X}" -f $b
123
PS > $b.GetLength(0)
3
PS > "{0:X}{1:X}{2:X}" -f $b
123
PS > [object]::ReferenceEquals($a, $b)
True

I tend to agree with Jared that it's a quirk of the -f operator seeing the variable as an object rather than an array, supported in part by this:

PS > $a = @(1,2,3)
PS > "{0:X}{1:X}{2:X}" -f $a
123
PS > "{0:X}{1:X}{2:X}" -f $a.PSObject
Error formatting a string: Index (zero based) must be greater than or equal to zero and less than the size of the argum
ent list..
At line:1 char:21

If the underlying object isn't acceptable as a parameter, then there must be something special about how $a is originally stored that makes -f happy. But that still doesn't explain why calling GetLength() doesn't affect $b's "arrayness" in the way that Length (and Rank) seem to.

As others have noted, using @() does seem to work consistently.

dahlbyk