Search code examples
powershell

Why does ConvertTo-Json behave differently when accepting pipeline input?


I'm trying to understand differences in ConvertTo-Json behaviour when calling the Cmdlet directly using the -InputObject parameter, and when passing objects in the pipeline. Please see the below code:

# Convert output to Json, pipe to the Cmdlet
$jsonString = Get-PhysicalDisk | ConvertTo-Json
$disks = $jsonString | ConvertFrom-Json
$disks.Size
# Output: 1024209543168

All OK, seems normal

$jsonString = ConvertTo-Json -InputObject (Get-PhysicalDisk)
$disks = $jsonString | ConvertFrom-Json
$disks.Size
# No output

$null -eq $disks.Size
# True

Why is there a difference when using the InputObject parameter in this way?

Using PowerShell 5.1, Windows Server 2019


Solution

  • You're seeing a bug, affecting both Windows PowerShell (the legacy, ships-with-Windows, Windows-only edition of PowerShell whose latest and last version is 5.1) and PowerShell (Core) 7 (the modern, cross-platform, install-on-demand edition) as of v7.4.x:

    • Unless Get-PhysicalDisk's output objects are [psobject]-wrapped, ConvertTo-Json doesn't serialize their ETS (Extended Type System) properties.

      • As such, this bug is yet another manifestation of the problem discussed in GitHub issue #5579, namely that the presence or absence of the meant-to-be-invisible helper type [psobject] causes changes in behavior.

      • The bug at hand affects all CDXML-based cmdlets that do not emit [psobject] wrapped objects themselves, and has been reported in GitHub issue #24554.

    • In the pipeline, this [psobject]-wrapping happens implicitly, whereas it doesn't when you pass the results of a call as an argument.

      • Note, however, that many cmdlets themselves perform this wrapping of their output objects, such as Get-Date or Get-ChildItem, so that their output is [psobject]-wrapped even when passed by argument.

    Workaround:

    By piping the output to Write-Output, which simply relays it, [psobject] wrapping is ensured:

    $jsonString = ConvertTo-Json -InputObject (Get-PhysicalDisk | Write-Output)