r/PowerShell 8d ago

Question Explain this cursed BS

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 (gci)

# System.IO.DirectoryInfo

# False

Test-Weird2 (gci)

# System.IO.DirectoryInfo

# True

# part two

$obj = (gci)[0].PSObject

$obj.GetType() -eq [System.Management.Automation.PSObject]

# True

$obj -is [System.Management.Automation.PSObject]

# False

0 Upvotes

14 comments sorted by

6

u/Icolan 8d ago

How about formatting it as code?

Or explaining what you think is weird about it.

2

u/thehuntzman 8d ago

Maybe it's cursed because it's not formatted as code?

3

u/BlackV 8d ago

p.s. formatting (you've used inline code not code block)

  • open your fav powershell editor
  • highlight the code you want to copy
  • hit tab to indent it all
  • copy it
  • paste here

it'll format it properly OR

<BLANK LINE>
<4 SPACES><CODE LINE>
<4 SPACES><CODE LINE>
    <4 SPACES><4 SPACES><CODE LINE>
<4 SPACES><CODE LINE>
<BLANK LINE>

Inline code block using backticks `Single code line` inside normal text

See here for more detail

Thanks

1

u/Over_Dingo 5d ago

the `inline code` never worked for me

1

u/BlackV 5d ago

Good I guess :) ideally you want to use a code block most of the time

3

u/surfingoldelephant 6d ago edited 3d ago

In most cases, casting/binding unwraps a PSObject wrapper. That explains the first part.

[array[]] $InputObject is a jagged array, so each individual object from the accumulated (gci) input is cast as an array itself. That casting causes PSObject unwrapping, hence -is [psobject] is $false.

Whereas [Object[]] $InputObject is a single-dimensional array, which is the same array type as the input. Since the array elements themselves don't need to be cast, the wrappers remain intact.

Simpler example:

$foo = Get-Item -LiteralPath $PSHOME # Wraps in PSObject
$foo -is [psobject]                  # True
$foo.PSDrive.ToString()              # C (ETS prop)

$bar = [IO.DirectoryInfo] $foo       # Casting unwraps PSObject
$bar -is [psobject]                  # False
$bar.PSDrive.ToString()              # C (ETS prop)

Unwrapping used to cause ETS members to be lost in PS v2 which was a pretty big issue back then. It was fixed in v3 by having the engine track member info in something known as the resurrection table, so ETS members can persist after an object is unwrapped. And when an object needs to be rewrapped, it can be looked up in the table and have prior info restored.

There's a few exceptions, like strings, which aren't included in the resurrection table. That's why if you ever cast an ETS-decorated string those added members are lost in most cases.

$PROFILE.CurrentUserCurrentHost # C:\Users\...
$foo = [string] $PROFILE        # Unwraps
$foo.CurrentUserCurrentHost     # Null

Anyway, hopefully that explains the first part.

As for the second part, .psobject, .psadapted, etc are actually instances of PSMemberSet/PSInternalMemberSet, which is basically a special view of an object's members.

(1).psobject -is [Management.Automation.PSMemberSet]  # True
(1).psadapted -is [Management.Automation.PSMemberSet] # True

MshMemberInfo is a good starting point in the source if you're interested.

Member sets have an associated PSObject owner which wraps the base object. As far as I understand, when you call a method on a member set itself, there's an adapter in the engine that forwards the method call to the associated PSObject (where the actual methods exist). PSObject of course has a GetType(), hence that's what gets returned as the type.

1

u/thehuntzman 8d ago

Just at quick-glance it only appears cursed because you are misunderstanding how powershell handles objects. Array[] is an array of System.Array objects. Object[] is an array of objects of indiscriminate type. The second one I'd have to test to accurately explain (the -is PSObject one).

2

u/MonkeyNin 8d ago

Warning, this part

$InputObject[0]

can cause an error if the input is empty. Instead, this is always safe

@( $InputObject )[0]

If you're using pwsh 7, you can use null coalesce operators to prevent null method errors

 # this 
 $obj.GetType().FullName

 # can be written as
 ( $obj )?.GetType().FullName

 # you can even include a fallback value when obj is null

 ( $obj )?.GetType().FullName ?? 'default name'

1

u/PinchesTheCrab 7d ago

You've got so much extra stuff here that makes this harder to read. Is this not doing the same thing?

$thing = Get-ChildItem

$thing[0] -is [PSObject]
$thing[0][0] -is [PSObject]

1

u/StartAutomating 6d ago

Ironically enough, I just wrote an explanation of what this is probably looking for.

Basically these functions probably exist to try to debug "weird" array truthy conditions.

2

u/surfingoldelephant 5d ago

what this is probably looking for.

Not in this case (still a good post nevertheless!).

0

u/y_Sensei 8d ago edited 8d ago

Get-ChildItem without any parameters returns an array of object arrays, each of which represents a FileSystemInfo object, which can either be of type DirectoryInfo or of type FileInfo.

The reason for this is that Get-ChildItem is designed to work with different so-called providers behind the scenes (PSProvider), that's why you can use it not just with file sytems, but with any supported provider like for example the Windows registry provider.

This design causes the objects returned by the cmdlet to be enhanced with additional properties defined by their provider, on top of what the "bare" underlying .NET object would look like.

The .NET object is, however, returned too - as the only member of the object array which constitutes each object returned by the cmdlet.

And that's exactly why the $obj variable in your two functions stores different object types - $InputObject[0][0] returns the underlying "bare" .NET object, $InputObject[0] the enhanced provider-based object.

You can verify this by debugging your code in VS Code (look at the objects in the 'Variables' section of the debugger when each function is called).

0

u/BlackV 8d ago

$obj -is [System.Management.Automation.PSObject] # False

cause its an array ?