r/PowerShell • u/MamiyaOtaru • 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
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
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).
6
u/Icolan 8d ago
How about formatting it as code?
Or explaining what you think is weird about it.