PowerShell -is operator always matching PSCustomObject when used in ForEach-Object

485 Views Asked by At

Using PowerShell Core 6.1 on Mac. It appears that piping an array to ForEach-Object is modifying or wrapping each element such that the -is operator considers all of them to be PSCustomObjects.

Let me demonstrate:

Set up an array of four items of different types (use of JSON because that's where the data is coming from in my real use case):

$a = '[4, "Hi", {}, true]' | ConvertFrom-Json

Iterate the list by index and determine which ones are PSCustomObjects:

0..3 | ForEach-Object { 
    $v = $a[$_]
    $t = $v.GetType().FullName
    $is = $v -is [PSCustomObject]
    "$t - $is"
}

The output (for me) is exactly what I would expect:

System.Int64 - False
System.String - False
System.Management.Automation.PSCustomObject - True
System.Boolean - False

But if I just pipe the array to ForEach-Object:

$a | ForEach-Object { 
    $v = $_
    $t = $v.GetType().FullName
    $is = $v -is [PSCustomObject]
    "$t - $is"
}

Now the output claims that all four are PSCustomObjects:

System.Int64 - True
System.String - True
System.Management.Automation.PSCustomObject - True
System.Boolean - True

Could anyone explain what is happening here?

1

There are 1 best solutions below

0
On BEST ANSWER

PetSerAl, as he frequently does, has provided the crucial pointer in a comment:

Piping objects to ForEach-Object wraps them in a [psobject] instance (as reflected in $_ / $PSItem), which causes -is [pscustomobject] / -is [psobject] to return $True for any input object, because - confusingly - [pscustomobject] is the same as [psobject]: they're both type accelerators for [System.Management.Automation.PSObject] - against what one would expect,[pscustomobject] is not short for [System.Management.Automation.PSCustomObject].

Therefore, test the input objects for being instances of [System.Management.Automation.PSCustomObject] rather than [pscustomobject]:

$a | ForEach-Object {
  $_ -is [System.Management.Automation.PSCustomObject]
}

Note that if you use a foreach loop, even -is [pscustomobject] would work, because the objects being enumerated are then not wrapped in an extra [psobject] instance:

foreach ($element in $a) {
  $element -is [pscustomobject]
}

This works, because even a bona fide [System.Management.Automation.PSCustomObject] is technically also a [System.Management.Automation.PSObject] behind the scenes.