admin管理员组

文章数量:1225020

I saw some code like below:

$ht1 = @{ 
            age=13
            course="Children Fund"
        }

$var1 = [PSObject]$ht1

$json = $var1 | ConvertTo-Json

From what I know, [PSObject] is a type casting, and the $var1.GetType() will still return "HashTable", but I think such type casting are wrapping the ht1 as the PSObject documentation said, is this reasoning right ? It looks after it wrapped the ht1 but the type of $var1 will be still HashTable, I think this is the magic of PowerShell implementation, am I right ?

If that is just a wrapping benefit it brings when doing explicit type casting

[PSObject]$ht1

I wonder is there some other benefits it brings when doing such explicit type casting ? And is such type casting really needed when passing the $var1 to the downstream pipeline part ConvertTo-Json cmdlet ?

I saw some code like below:

$ht1 = @{ 
            age=13
            course="Children Fund"
        }

$var1 = [PSObject]$ht1

$json = $var1 | ConvertTo-Json

From what I know, [PSObject] is a type casting, and the $var1.GetType() will still return "HashTable", but I think such type casting are wrapping the ht1 as the PSObject documentation said, is this reasoning right ? It looks after it wrapped the ht1 but the type of $var1 will be still HashTable, I think this is the magic of PowerShell implementation, am I right ?

If that is just a wrapping benefit it brings when doing explicit type casting

[PSObject]$ht1

I wonder is there some other benefits it brings when doing such explicit type casting ? And is such type casting really needed when passing the $var1 to the downstream pipeline part ConvertTo-Json cmdlet ?

Share Improve this question asked Feb 5 at 21:00 IcyBrkIcyBrk 1,2701 gold badge13 silver badges22 bronze badges 1
  • 2 Passing a hashtable as-is to ConvertTo-Json would work just fine whiteout the need to wrapping it in a PSObject. Unsure where you got that it was required – Santiago Squarzon Commented Feb 5 at 21:07
Add a comment  | 

2 Answers 2

Reset to default 3

Why Use [PSObject] Type Casting

Generally, do NOT use [psobject] casts: In most cases, it is at best an unnecessary, virtual no-op and at worst results in unexpected behavior.

  • [psobject] is a meant-to-be-invisible helper type used behind the scenes to transparently wrap instances of (other) .NET types.

    • It is for this reason that a [psobject] instance that wraps an instance of a .NET type acts just like the latter, as you've observed, and it is only a -is [psobject] test that reveals whether you're dealing with a [psobject]-wrapped object or not.
  • Thus, a [psobject] cast is - in most cases - a no-op.[1]

    • Unfortunately, however, the implementation of this conceptually transparent wrapper is leaky, resulting in situationally different behavior if such a wrapper is present: for details and a list of such situations, see GitHub issue #5579.

    • However, implicitly created [psobject] wrappers cannot be avoided:

      • Notably, all objects output to the success stream by binary cmdlets are [psobject]-wrapped (which implies that objects created with New-Object are wrapped, unlike ones created with the intrinsic, static ::new() method). Also, objects received via the pipeline that bind to an untyped aka [object]-typed parameter are wrapped, whether or not they are received by a binary cmdlet or a PowerShell function or script block; additionally, many built-in cmdlets explicitly type their "untyped" parameters as [psobject].
        See this answer for details.

What DOES make sense is to cast a hashtable or its ordered variant ([ordered]) to [pscustomobject] rather than [psobject], in order to construct a [pscustomobject] instance, PowerShell's "property bag" type (i.e., an object with dynamically defined properties):

  • Note that, technically, [psobject] and [pscustomobject] refer to the same type, System.Management.Automation.PSObject, but only [pscustomobject] (meaningfully) supports casting from (ordered) hashtables, most typically in the form of a literal definition, where a hashtable literal is (seemingly) cast to [pscustomobject], e.g.
    [pscustomobject] @{ Foo = 1; Bar = 2 }.
    Note that this is syntactic sugar that results in direct construction of a [pscustomobject] instance, with the entry-definition order preserved (constructing a hashtable in isolation would not preserve the definition order, as is the case if you cast from a hashtable created beforehand, in a separate step).

    • Pitfalls:

      • Casting instance of types other than (ordered) hashtables to [pscustomobject] unfortunately behaves like casting to [psobject], and is therefore almost always pointless, for the reasons explained above. GitHub issue #20756 suggests preventing such counterintuitive, pointless casts.

      • It would make sense to more generally support casting instances of all System.Collections.IDictionary-implementing types to [pscustomobject], not just (ordered) hashtables, which is currently not the case; GitHub issue #20753 is an feature request to that effect.

  • In PowerShell terms, a [pscustomobject] "property bag" is a [psobject] instance that (conceptually) has no base object,[2] i.e. it doesn't wrap another object; rather it is composed solely of ETS (Extended Type System) members.


[1] Since PowerShell v3, [psobject] wrappers are actually no longer necessary for decorating .NET instances with ETS (Extended Type System) members due to use of resurrection tables, with two exceptions: [string] instances and .NET value type instances (such as numbers), for which the resurrection tables cannot be used. Because of the ephemeral nature of such wrappers, decorating instances of these types is generally best avoided. If you want to decorate them nonetheless, use Add-Member with the -PassThru switch and save the resulting [psobject] instance, to which the new member is directly attached (rather than via the resurrection tables); e.g., $decoratedString = 'Decorate me' | Add-Member -PassThru Foo 42; $decoratedString.Foo

[2] Technically, a placeholder is used as the base object, which is a singleton of the otherwise unused System.Management.Automation.PSCustomObject type. Confusingly, is not the same as [pscustomobject]. Because a property-bag [psobject] == [pscustomobject] still technically behaves like a transparent wrapper, calling .GetType() on it therefore reports System.Management.Automation.PSCustomObject as its type, and to detect such an instance you must use -is [System.Management.Automation.PScustomObject], not -is [pscustomobject] - see GitHub issue #11921 for a discussion of this counterintuitive behavior.
Also, even when spelling out [System.Management.Automation.PScustomObject] there's an outright bug with respect to the -as operator - see GitHub issue #4343

I wonder is there some other benefits it brings when doing such explicit type casting ?

None that I can think of other than the string example shown below.

And is such type casting really needed when passing the $var1 to the downstream pipeline part ConvertTo-Json cmdlet ?

For the question being asked, you really don't need to wrap your hashtable for ConvertTo-Json to work correctly.

You're correct in that PSObject serves the purpose of an invisible wrapper. The .NET doc already describes it perfectly:

Wraps an object providing alternate views of the available members and ways to extend them. Members can be methods, properties, parameterized properties, etc.

Those members referred to in the description that could be added to a PSObject can be:

[psobject].Assembly.GetTypes() |
    Where-Object { $_.IsSubclassOf([System.Management.Automation.PSMemberInfo]) }

As for alternative views they're probably referring to adding the special PSTypeName property and then assigning a formatting or extended type data, see for example: How can I set left/right column justification in Powershell's "format-table".

They could also refer to adding a PSMemberSet:

$object = [pscustomobject]@{
    foo = 1
    bar = 2
    baz = 3
}

$propertySet = [System.Management.Automation.PSPropertySet]::new(
    'DefaultDisplayPropertySet', [string[]] ('foo', 'bar'))

$object.PSObject.Members.Add(
    [System.Management.Automation.PSMemberSet]::new(
        'PSStandardMembers',
        [System.Management.Automation.PSPropertySet[]] $propertySet))

# $object now hides `baz` property in the default display
$object

There is usually no need to wrap an object in PSObject manually, however you can do so if you want to extend that object. For example for a string, if you wrap it you could add a new property to it:

$string = [psobject] "a string"
$string.PSObject.Properties.Add([psnoteproperty]::new('NewProperty', 'hi'))
$string             # still shows the actual value
$string.NewProperty # but now has a hidden property

PSObject is also the base for creating PSCustomObject:

[pscustomobject]@{ NewProperty = 'hi' }

Which is essentially a PSObject with PSNoteProperty added to it:

$acustomObject = [psobject]::new()
$acustomObject.PSObject.Properties.Add([psnoteproperty]::new('NewProperty', 'hi'))
$acustomObject

本文标签: powershellWhy Use PSObject Type CastingStack Overflow