Author Posts

July 4, 2016 at 4:44 pm

Hi Folks,

I have to construct a JSON string containing some basics on my ESXi host and it's VMs.

$esxihost = 'esxi-01'
Connect-VIServer $esxihost

Get-VMHost -Name $esxihost | Get-View | 
    Select @{N="host_cpus";E={($_.Hardware.CpuInfo.NumCpuPackages * $_.Hardware.CpuInfo.NumCpuCores)}},
        @{N="host_sn";E={(Get-EsxCli).hardware.platform.get().SerialNumber}},
        @{N="hostname";E={$_.Name}},
        @{N="vms";E={Get-VM |
            Select @{N="memory";E={[string]($_.MemoryMB)}},
                @{N="name";E={$_.Name }}          
        }}| ConvertTo-Json -Depth 2

This is the object before it's converted to JSON

$object | GM

   TypeName: Selected.VMware.Vim.HostSystem

Name        MemberType   Definition                                             
----        ----------   ----------                                             
Equals      Method       bool Equals(System.Object obj)                         
GetHashCode Method       int GetHashCode()                                      
GetType     Method       type GetType()                                         
ToString    Method       string ToString()                                      
hostname    NoteProperty System.String hostname=ESXI-01.company.local
host_cpus   NoteProperty System.Int32 host_cpus=40                              
host_sn     NoteProperty System.String host_sn=8XXXX2                          
vms         NoteProperty System.Object[] vms=                                  

$object | fl

host_cpus : 40
host_sn   : 8XXXXX2
hostname  : ESXI-01.company.local
vms       : {@{memory=16384; name=DC-01}, @{memory=8192; name=test-centos}}

Looks ok, but this is the converted JSON

{
    "host_cpus":  40,
    "host_sn":  "8XXXXX2",
    "hostname":  "ESXI-01.company.local",
    "vms":  {
                "value":  [
                              {
                                  "memory":  "16384",
                                  "name":  "DC-01"
                              },
                              {
                                  "memory":  "8192",
                                  "name":  "test-centos"
                              }
                          ],
                "Count":  2
            }
}

My problem is the Value and Count properties are being added.

The format I need to send the JSON doesn't allow these.

{
    "host_cpus":  40,
    "host_sn":  "8XXXXX2",
    "hostname":  "ESXI-01.company.local",
    "vms":  {
               [
                              {
                                  "memory":  "16384",
                                  "name":  "DC-01"
                              },
                              {
                                  "memory":  "8192",
                                  "name":  "test-centos"
                              }
                          ],
            }
}
  • This topic was modified 2 years, 2 months ago by  Michael Maher. Reason: Removed identifying informtion

July 4, 2016 at 4:49 pm

I forgot to mention, I found this explanation from Trevor Sullivan but I can't get it to work for me.

http://stackoverflow.com/questions/20848507/why-does-powershell-give-different-result-in-one-liner-than-two-liner-when-conve

PowerShell automatically wraps multiple objects into a collection called a PSMemberSet that has a Count property on it. It's basically how PowerShell manages arbitrary arrays of objects. What's happening is that the Count property is getting added to the resulting JSON, yielding the undesirable results that you're seeing.

You can work around this behavior by referencing the SyncRoot property of the PSMemberSet (which implements the ICollection .NET interface), and passing the value of that property to ConvertTo-Json.

  • This reply was modified 2 years, 2 months ago by  Michael Maher. Reason: formatting

July 4, 2016 at 4:51 pm

I suspect asking Trevor might be the best solution. I've pinged him on Twitter, @pcgeek86.

July 4, 2016 at 5:40 pm

What version of PowerShell are you running? With the latest version of WMF5 on Windows 10, I'm not seeing a problem when I tried to reproduce this, but I do remember there being some headaches with the JSON serializer in PSv4:

$json = @'
{
    "host_cpus":  40,
    "host_sn":  "8XXXXX2",
    "hostname":  "ESXI-01.company.local",
    "vms":                 [
                              {
                                  "memory":  "16384",
                                  "name":  "DC-01"
                              },
                              {
                                  "memory":  "8192",
                                  "name":  "test-centos"
                              }
                          ]
            }
'@

$json | ConvertFrom-Json | ConvertTo-Json -Depth 2

< #
{
    "host_cpus":  40,
    "host_sn":  "8XXXXX2",
    "hostname":  "ESXI-01.company.local",
    "vms":  [
                {
                    "memory":  "16384",
                    "name":  "DC-01"
                },
                {
                    "memory":  "8192",
                    "name":  "test-centos"
                }
            ]
}
#>

July 4, 2016 at 5:58 pm

Hi Dave,

I'm on WMF5 but Windows 7 (5.0.10586.117).

I will set-up a W12 R2 or W10 system with WMF5 and see if that works.

Thanks,

Michael

July 5, 2016 at 6:36 pm

Hi Guys,

Yes hit the issue on Windows 2012 R2 and Windows 10.

I involved one of my developer friends and he picked up on how to apply Trevor's recommendation to use syncroot. The issue appears when using Select-Object (which you have not run Dave).

Get-VMHost -Name $esxihost | Get-View | 
    Select @{N="host_cpus";E={($_.Hardware.CpuInfo.NumCpuPackages * $_.Hardware.CpuInfo.NumCpuCores)}},
        @{N="host_sn";E={(Get-EsxCli).hardware.platform.get().SerialNumber}},
        @{N="hostname";E={$_.Name}},
        @{N="vms";E={Get-VM |
            Select @{N="memory";E={[string]($_.MemoryMB)}},
                @{N="name";E={$_.Name }}                
        }}| % { $_.vms = $_.vms.syncroot; $_ } | ConvertTo-Json -Depth 2

Regards,

Michael

  • This reply was modified 2 years, 2 months ago by  Michael Maher. Reason: formatting

July 5, 2016 at 7:15 pm

That is just weird. It's certainly a bug in ConvertTo-Json, but so far I haven't spotted the problem in the decompiled code. In any case, I was at least able to reproduce the problem.

July 6, 2016 at 4:57 am

To expand on Michael's answer:

* The problem is not OS-specific and has been around since PSv3.

* The problem is not ConvertTo-Json; rather, Select-Object creates the problem when creating array-valued properties via a script block that functions as the expression entry of a hashtable-defined property. In short: the values of such properties are treated as a single object rather than as a collection.
Here's a quick demonstration that ConvertTo-Json generally does handle array-valued properties properly:

[pscustomobject] @{ host = 'somehost'; vms = 'vm1', 'vm2' } | ConvertTo-Json

* The workaround is to force such array properties to behave like a regular collection, for which you can use the SyncRoot property, but it's not necessary; redefining the property with its value evaluated via $(...) is sufficient.

* I've posted a generic (albeit non-recursive) workaround in this SO answer. Note that the linked answer focuses on a similar bug in ConvertFrom-Json (note: From-Json, not To-Json), which exhibits the same – presumably buggy – behavior when processing a JSON string that is an array. Also note that I believe Trevor's answer there to be incorrect.