Search code examples
powershellobjecttypeshashtable

How come I can create objects with [pscustomobject] but not [System.Management.Automation.PSCustomObject]?


How come I can create objects with the type accelerator [pscustomobject] but not its fullname, [System.Management.Automation.PSCustomObject]? Is there some magical constructor I'm not accessing?

$a = [pscustomobject]@{name='joe'}
$a.gettype().fullname
System.Management.Automation.PSCustomObject

[System.Management.Automation.PSCustomObject]@{name='joe'}
InvalidArgument: Cannot convert the "System.Collections.Hashtable" value of type "System.Collections.Hashtable" to type "System.Management.Automation.PSCustomObject".

Or I can try [System.Management.Automation.PSObject], but I just get a hashtable:

[psobject].Assembly.GetType('System.Management.Automation.TypeAccelerators')::get.pscustomobject.fullname
System.Management.Automation.PSObject

[System.Management.Automation.PSObject]@{name='joe'}                                                     

Name                           Value
----                           -----
name                           joe

Inspired by this thread: https://powershell.org/forums/topic/type-accelerator


Solution

  • Is there some magical constructor I'm not accessing?

    No, there's magic sauce in the compiler - whenever the PowerShell compiler sees a cast expression where the right-hand side is a dictionary and the type literal has the exact name pscustomobject, it'll treat the dictionary or hashtable (whether literal or not) as an ordered dictionary and convert it to a PSObject.

    From VisitConvertExpression in Compiler.cs:

    var typeName = convertExpressionAst.Type.TypeName;
    var hashTableAst = convertExpressionAst.Child as HashtableAst;
    Expression childExpr = null;
    if (hashTableAst != null)
    {
        var temp = NewTemp(typeof(OrderedDictionary), "orderedDictionary");
        if (typeName.FullName.Equals(LanguagePrimitives.OrderedAttribute, StringComparison.OrdinalIgnoreCase))
        {
            return Expression.Block(
                typeof(OrderedDictionary),
                new[] { temp },
                BuildHashtable(hashTableAst.KeyValuePairs, temp, ordered: true));
        }
    
        if (typeName.FullName.Equals("PSCustomObject", StringComparison.OrdinalIgnoreCase))
        {
            // pure laziness here - we should construct the PSObject directly.  Instead, we're relying on the conversion
            // to create the PSObject from an OrderedDictionary.
            childExpr = Expression.Block(
                typeof(OrderedDictionary),
                new[] { temp },
                BuildHashtable(hashTableAst.KeyValuePairs, temp, ordered: true));
        }
    }
    

    Notice how this is also how [ordered]@{Key='Value'} results in an OrderedDictionary