JoelK
JoelK

Reputation: 11

PowerShell Array list assignment and Where-Object fails when Where-Object returns one item. Works with 2+ items

Long time searcher, first time poster. :-)

When piping an array list to Where-Object and assigning it back to another array list, a conversion error is generated when the result of the Where-Object is a single item. But the same command succeeds when two or more items are returned. Is this a PowerShell bug or am I missing something?

Why does this fail?

PS C:\> [System.Collections.ArrayList]$AL1 = @(1,2,3)
PS C:\> [System.Collections.ArrayList]$AL2 = $AL1 | Where-Object {$_ -ge 3}
Cannot convert the "3" value of type "System.Int32" to type "System.Collections.ArrayList".
At line:1 char:1
+ [System.Collections.ArrayList]$AL2 = $AL1 | Where-Object {$_ -ge 3}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : MetadataError: (:) [], ArgumentTransformationMetadataException
    + FullyQualifiedErrorId : RuntimeException

But if the result of the Where-Object is two or more items it does not fail.

PS C:\> [System.Collections.ArrayList]$AL1 = @(1,2,3)
PS C:\> [System.Collections.ArrayList]$AL2 = $AL1 | Where-Object {$_ -ge 2}
PS C:\> $AL2
2
3
PS C:\>

Also the assignment succeeds if you first create the second array list via New-Object.

PS C:\> $AL3 = New-Object System.Collections.ArrayList
PS C:\> $AL3 = $AL1 | Where-Object {$_ -ge 3}
PS C:\> $AL3
3

Tested on PSVersion 5.1.19041.1682 and core 7.0.7

Upvotes: 1

Views: 574

Answers (1)

Santiago Squarzon
Santiago Squarzon

Reputation: 60668

Yes this is expected, PowerShell enumerates all output and, since one item results from the expression:

$AL1 | Where-Object { $_ -ge 3 }

Its trying to coerce an int to the type System.Collections.ArrayList, however only types implementing IEnumerable can be coerced to it, hence the error:

PS \> [System.Collections.ArrayList] 3

InvalidArgument: Cannot convert the "3" value of type "System.Int32" to type "System.Collections.ArrayList".

You have three easy workarounds:

  1. Don't constrain your variable to be of the type System.Collections.ArrayList, let PowerShell dynamically assign the resulting type.
  2. Wrap your expression with the Array subexpression operator @( ), so, no matter how many elements results from the expression, the type will always be an IEnumerable:
[System.Collections.ArrayList] $AL1 = @(1, 2, 3)
[System.Collections.ArrayList] $AL2 = @($AL1 | Where-Object { $_ -ge 3 })
  1. Use the .Where method instead which always returns Collection`1 (an IEnumerable):
[System.Collections.ArrayList] $AL1 = @(1, 2, 3)
[System.Collections.ArrayList] $AL2 = $AL1.Where{ $_ -ge 3 }

Other workarounds are possible either using Write-Output -NoEnumerate or Comma operator ,:

[System.Collections.ArrayList] $AL1 = @(1, 2, 3)
[System.Collections.ArrayList] $AL2 = Write-Output ($AL1 | Where-Object { $_ -ge 3 }) -NoEnumerate
[System.Collections.ArrayList] $AL2 = , ($AL1 | Where-Object { $_ -ge 3 })

As for:

Also the assignment succeeds if you first create the second array list via New-Object.

Correct, you're not constraining the variable in this case:

$AL3 = New-Object System.Collections.ArrayList
$AL3 = $AL1 | Where-Object { $_ -ge 3 }
$AL3.GetType() # => Int32, no longer an ArrayList

For more info, see relevant documentation: about Variables.

Upvotes: 2

Related Questions