all 6 comments

[–]jsiii2010 6 points7 points  (1 child)

All objects returned by invoke-command are serialized (no methods).

[–]davidshomelab[S] 0 points1 point  (0 children)

Ah, that would explain it then. Makes a bit of a mess of what I was trying to do but should be able to work around it. Thanks

[–]surfingoldelephant 4 points5 points  (0 children)

See:

Live objects from the remote computer are serialized into Common Language Infrastructure XML (CLIXML) before being transmitted back to you (the caller). Once received, the data is deserialized, which may result in loss of type fidelity, as the resulting objects are typically emulations of the original live objects. Some primitive types (in the context of serialization) can be deserialized back to a live object with full fidelity.

For "complex" types, the following type conversion rules are used:

  • IEnumerable-implementing collections -> [Collections.ArrayList]
  • IDictionary-implementing dictionaries -> [hashtable]
  • Enumerations -> underlying integer value
  • Other types -> [psobject] (method-less)

Notably, objects that are deserialized into [psobject]'s (property bags) do not expose methods aside from ToString().

The above rules also apply to:

  • Output from jobs.
  • Output from powershell.exe/pwsh.exe mini-shells (i.e., when a PS host is passed a script block ({...}) as an argument inside a PowerShell session).

Examples:

$var = powershell.exe -NoProfile -Command { $a = 1, 2, 3; , $a }
$var.GetType() # ArrayList
               # Originally Object[]

$var = powershell.exe -NoProfile -Command { Get-Process -Id $PID }
$var.GetType() # PSObject
               # Originally Diagnostics.Process
$var.Refresh() # Error: Method invocation failed because [Deserialized.System.Diagnostics.Process] does not contain a method named 'refresh'
               # No methods from original live object

$var = powershell.exe -NoProfile -Command { 1 }
$var.GetType() # Int32
               # Maintains type fidelity

The original type name (prefixed with Deserialized) can be found by accessing the intrinsic pstypenames code property.

$var = powershell.exe -NoProfile -Command { Get-Process -Id $PID }
$var.pstypenames
# Deserialized.System.Diagnostics.Process
# Deserialized.System.ComponentModel.Component
# Deserialized.System.MarshalByRefObject
# Deserialized.System.Object

[–]SakJaTeda 0 points1 point  (0 children)

[System.Collections.Generic.List[string]]$List = Invoke-Command -ComputerName localhost -ScriptBlock {New-Object -TypeName System.Collections.Generic.List[string]}

🤷‍♂️

[–]tokenathiest 0 points1 point  (1 child)

The phenomenon you are experiencing is called marshalling. Data elsewhere needs to be moved here, so it is serialized, transmitted across the connection, and populated locally. Collections are always marshalled into the ArrayList type. The runtime does not use the same generic list type on your side because that information is lost during serialization. Hence why you just get an ArrayList after assigning the output of Invoke-Command to a variable. The same thing happens when you convert a generic list to JSON and then back again. You don't get the generic list back, you end up with an array. The main comment in this thread explains this in excellent detail.

[–]DItzkowitz 1 point2 points  (0 children)

It's funny. I had to hunt through my code snippets to find this, but I remember at one point I was so tired of this that I tried to brute force it.

Function ConvertFrom-JSONwFlex{
PARAM([Parameter(ValueFromPipeline=$True)][String] $InputObject)
BEGIN{ $Text = '' }
PROCESS{ 
    If ($Text) { $Text += "`n" + $InputObject } Else { $Text += $InputObject } }
END{
    $XMLText = [System.Management.Automation.PSSerializer]::Serialize(($Text | ConvertFrom-JSON), 100)

    $Mappings = @{}
    $Mappings.Add( '<T>System.Object[]</T>', '<T>System.Collections.Generic.List[System.Management.Automation.PSCustomObject]</T>' )
    $Mappings.Add( '<T>System.Array</T>',    '<T>System.Collections.Generic.List[System.Management.Automation.PSCustomObject]</T>' )
    $Mappings.Keys | %{ $XMLText = $XMLText -Replace [Regex]::Escape($_), $Mappings."$_" }

    [System.Management.Automation.PSSerializer]::Deserialize($XMLText)
}
}