Search code examples
powershelltype-conversionpowershell-4.0powershell-cmdlet

Powershell - Cannot convert value of type "System.Management.Automation.PSCustomObject" to type "System.Management.Automation.PSCustomObject"


I am working with Powershell 4 under Windows 7 and I have cmdlet defined like this:

 Function Transfer-File {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        [System.Management.Automation.PSCustomObject]
        $source,

        # other parameters removed for brevity
    )

    # actual logic does not matter 

  }

I am calling it like this:

$hasError = Transfer-File -source $source -destination $cfg.Destination -logfile $logFile

$source is obtained by parsing a JSON file and looks like this:

@{Path=some path string here; Pack=False}

Write-Host $source.GetType() outputs:

System.Management.Automation.PSCustomObject

I obtain the following error:

Cannot convert the "@{Path=some path string here; Pack=False}" value of type "System.Management.Automation.PSCustomObject" to type "System.Management.Automation.PSCustomObject".

In a desperate trial to solve it empirically I replace System.Management.Automation.PSCustomObject with psobject and it seems to work fine.

Question: Why does my System.Management.Automation.PSCustomObject does not seem to fit System.Management.Automation.PSCustomObject from the cmdlet prototype?


Complete code for object creation:

### Configuration ###
$cfg = New-Object –TypeName PSObject
$cfg | Add-Member -MemberType NoteProperty –Name Sources –Value @()

# get configuration from json file
$jsonContent = Get-Content .\config.json |  Out-String
$jsonObj = ConvertFrom-Json $jsonContent
$cfg.Sources = $jsonObj.Sources

Foreach ($source in $cfg.Sources)
{
    # Write-Host $source.GetType()
    $hasError = Transfer-File -source $source -destination $cfg.Destination -logfile $logFile
}

Solution

  • As @VivekKumarSingh's comment suggests, this is a bug related to the accelerator.

    This will not work:

    # Error
    $y = [System.Management.Automation.PSCustomObject]@{Path='some path string here'; Pack=$False}
    

    And this will not work:

    $y = [PSCustomObject]@{Path='some path string here'; Pack=$False}
    
    function t ([System.Management.Automation.PSCustomObject]$x) { $x }
    
    # Error
    t $y
    

    This will work:

    $y = [PSCustomObject]@{Path='some path string here'; Pack=$False}
    
    function t ([PSCustomObject]$x) { $x }
    
    t $y
    

    However, the above will not type cast anything to a PSCustomObject, because that type inherits from Object:

    PS> function t ([PSCustomObject]$x) { $x.GetType().FullName }
    PS> t 5
    System.Int32
    PS> t 'asdf'
    System.String
    PS> t $(Get-ChildItem)
    System.Object[]
    PS> t $(Get-Item .)
    System.IO.DirectoryInfo
    

    This will also work, but I'm not entirely sure if it's 100% equivalent.

    function t ([System.Management.Automation.PSObject]$x) { $x }
    

    The reason I'm not sure is because of this weirdness:

    PS> $a = New-Object -TypeName System.Management.Automation.PSObject -Property @{Path='some path string here'; Pack=$False}
    PS> $a.GetType().FullName
    System.Management.Automation.PSCustomObject
    
    PS> $a = New-Object -TypeName System.Management.Automation.PSCustomObject -Property @{Path='some path string here'; Pack=$False}
    New-Object : A constructor was not found. Cannot find an appropriate constructor for type System.Management.Automation.PSCustomObject.
    
    PS> $a = [PSCustomObject]@{Path='some path string here'; Pack=$False}
    PS> $a.GetType().FullName
    System.Management.Automation.PSCustomObject
    
    PS> $a = [System.Management.Automation.PSObject]@{Path='some path string here'; Pack=$False}
    PS> $a.GetType().FullName
    System.Collections.Hashtable
    
    PS> $a = [PSObject]@{Path='some path string here'; Pack=$False}
    PS> $a.GetType().FullName
    System.Collections.Hashtable
    

    As far as I know, [PSCustomObject] and [PSObject] are both accelerators for System.Management.Automation.PSObject. System.Management.Automation.PSCustomObject is just an implementation detail they added to make [PSCustomObject] possible, but, unfortunately, the way that everything works feels a bit inconsistent. I'm not entirely clear on what the [PSObject] type accelerator is for, however. It appears to do nothing, but it also certainly exists:

    PS> ([System.Management.Automation.Cmdlet]).Assembly.GetType('System.Management.Automation.TypeAccelerators')::Get.ContainsKey('psobject')
    True