Why does PowerShell unwrap PSObject wrappers differently for array[] and object[]?
Asked by
MellowPine47
I'm trying to understand two related PowerShell behaviors. These functions receive the output of Get-ChildItem:
function Test-Weird1 {
param([array[]]$InputObject)
$obj = $InputObject[0][0]
$obj.GetType().FullName
($obj -is [PSObject])
}
function Test-Weird2 {
param([object[]]$InputObject)
$obj = $InputObject[0]
$obj.GetType().FullName
($obj -is [PSObject])
}
Test-Weird1 (Get-ChildItem) returns System.IO.DirectoryInfo and False, while Test-Weird2 (Get-ChildItem) returns System.IO.DirectoryInfo and True.
There is also this behavior:
$obj = (Get-ChildItem)[0].PSObject
$obj.GetType() -eq [System.Management.Automation.PSObject] # True
$obj -is [System.Management.Automation.PSObject] # False
Why does the first parameter type change the indexing and PSObject result, and why does the PSObject property report that it has the PSObject type while the -is operator says otherwise?
Answers
The first difference comes from the parameter conversion. `[array[]]` means an array whose elements are themselves arrays, so the input is coerced into a jagged array. `$InputObject[0][0]` reaches the underlying .NET `DirectoryInfo` object. During that conversion, PowerShell removes the `PSObject` wrapper, which is why `-is [PSObject]` is false.
`[object[]]` is just a one-dimensional object array. In that case, `$InputObject[0]` is the original PowerShell object, including its wrapper, so `-is [PSObject]` is true. The wrapperโs base object is still a `System.IO.DirectoryInfo`, which explains the type name returned by `GetType()`.
PowerShell normally preserves extended type system members when it unwraps and later rewraps objects, although some typesโstrings are a common exampleโcan lose added members during conversion.
Reply to this answer
The `.PSObject` member is not simply the same thing as asking whether the value itself is a `PSObject`. It exposes an object view represented by a member-set object, essentially a PowerShell inspection interface around the original value.
So in this example, `$obj.GetType()` reports the runtime type of that view, `System.Management.Automation.PSObject`, but the `-is` check tests the value in the other direction and does not consider the member-set view to be an instance of `PSObject`. In practice, `.PSObject`, `.PSAdapted`, and related intrinsic members are special PowerShell member sets rather than ordinary properties containing a normal PSObject wrapper.
Reply to this answer
One extra edge case: indexing an empty result can leave you with `$null`, so `$InputObject[0].GetType()` may fail. If you need a consistently safe first-element lookup, use `@($InputObject)[0]`. In PowerShell 7, a null-conditional call such as `($obj)?.GetType().FullName` can also avoid calling a method on `$null`.
Reply to this answer
Add your answer
Answers are reviewed before publication.
Related Discussions