'ConvertTo-Json on VMWare objects doesn't work
In powershell while converting VM objects to json , ($json = ConvertTo-Json $vm -Compress)
i am getting "An item with the same key has already been added" exception.
PS SQLSERVER:\> C:\Users\admin\Desktop\inventory.ps1
ConvertTo-Json : An item with the same key has already been added.
At C:\Users\huradmin\Desktop\inventory.ps1:68 char:31
+ if($vm -ne $null){$json = ConvertTo-Json $vm -Compress; insertToElasticSearc ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [ConvertTo-Json], ArgumentException
+ FullyQualifiedErrorId : System.ArgumentException,Microsoft.PowerShell.Commands.ConvertToJsonCommand
insertToElasticSearch : Cannot bind argument to parameter 'json' because it is null.
At C:\Users\admin\Desktop\inventory.ps1:68 char:89
+ ... icSearch -json $json -info:$true -Verbose:$true}
+ ~~~~~
+ CategoryInfo : InvalidData: (:) [insertToElasticSearch], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,insertToElasticSearch
getVMHosts function returns a list of VM guests. Please find my code below.
function getVMHosts{
Param(
[Parameter(Mandatory=$True,Position=1)]
[string]$vcenter,
[Parameter(Mandatory=$False)]
[switch]$info=$false
)
try
{
Write-Verbose "$(get-date -Format "dd/MM/yyyy HH:mm") - Function:$($MyInvocation.MyCommand) - Importing VMWare modules" -verbose:$info
Get-Module -ListAvailable -Name "VMware.*" | Import-Module
Write-Verbose "$(get-date -Format "dd/MM/yyyy HH:mm") - Function:$($MyInvocation.MyCommand) - Connecting to Vcenter:$vcenter" -verbose:$info
[void]::$(Connect-VIServer -Server $vcenter -ErrorAction SilentlyContinue)
Write-Verbose "$(get-date -Format "dd/MM/yyyy HH:mm") - Function:$($MyInvocation.MyCommand) - Getting Data center servers" -verbose:$info
$DCs = Get-Datacenter
$VMs = $null
foreach($dc in $DCs)
{
Write-Verbose "$(get-date -Format "dd/MM/yyyy HH:mm") - Function:$($MyInvocation.MyCommand) - Getting VM servers for Data Center:$dc" -verbose:$info
$VMs=$VMs+ $(Get-Datacenter -Name $dc.Name | Get-VM -Verbose:$info| Select PowerState,Name, NumCpu,MemoryMB,GuestId,VMHost, @{N="IP Address";E={@($_.guest.IPAddress[0])}})
}
Write-Verbose "$(get-date -Format "dd/MM/yyyy HH:mm") - Function:$($MyInvocation.MyCommand) - Disconnecting from VCenter:$vcenter" -verbose:$info
Disconnect-VIServer -Server $vcenter -ErrorAction SilentlyContinue -Confirm:$false
Write-Verbose "$(get-date -Format "dd/MM/yyyy HH:mm") - Function:$($MyInvocation.MyCommand) - Returning VM Lists" -verbose:$info
return $VMs
}
catch
{
$errorMessage = "$($_.Exception.Message)`n$(($_|select -ExpandProperty invocationinfo).PositionMessage)"
Write-Warning -Message "Catched an exception in Function:$($MyInvocation.MyCommand)`n$errorMessage" -Verbose:$true
}
}
$vmHosts = getVMHosts -vcenter "vcenter"
$counter = 0
foreach($vm in $vmHosts)
{
if($vm -ne $null){$json = ConvertTo-Json $vm -Compress;insertToElasticSearch json $json -info:$true -Verbose:$true}
}
Solution 1:[1]
Try ConvertTo-JSON -Depth 1
. Sounds like there are properties in the object that have the same name.
Solution 2:[2]
I don't have VCenter to verify the script, but I refactored yours a bit to make it more powershell-ly.
Notes:
CmdletBinding gives you -Verbose and other features
Any object not set to a variable is output to the pipeline by default
Return does not do what most developers would expect
function getVMHosts{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True,Position=1)]
[string]$vcenter,
)
try
{
Write-Verbose "$(get-date -Format "dd/MM/yyyy HH:mm") - Function:$($MyInvocation.MyCommand) - Importing VMWare modules"
Get-Module -ListAvailable -Name "VMware.*" | Import-Module
Write-Verbose "$(get-date -Format "dd/MM/yyyy HH:mm") - Function:$($MyInvocation.MyCommand) - Connecting to Vcenter:$vcenter"
[void]$(Connect-VIServer -Server $vcenter -ErrorAction SilentlyContinue)
Write-Verbose "$(get-date -Format "dd/MM/yyyy HH:mm") - Function:$($MyInvocation.MyCommand) - Getting Data center servers"
Get-Datacenter |
ForEach-Object {
Write-Verbose "$(get-date -Format "dd/MM/yyyy HH:mm") - Function:$($MyInvocation.MyCommand) - Getting VM servers for Data Center:$_"
Get-Datacenter -Name $_.Name |
Get-VM -Verbose:$Verbose|
Select PowerState, Name, NumCpu, MemoryMB, GuestId, VMHost, @{N="IP Address";E={@($_.guest.IPAddress[0])}}
}
Write-Verbose "$(get-date -Format "dd/MM/yyyy HH:mm") - Function:$($MyInvocation.MyCommand) - Disconnecting from VCenter:$vcenter"
[void]Disconnect-VIServer -Server $vcenter -ErrorAction SilentlyContinue -Confirm:$false
}
catch
{
$errorMessage = "$($_.Exception.Message)`n$(($_|select -ExpandProperty invocationinfo).PositionMessage)"
Write-Warning -Message "Exception caught in Function:$($MyInvocation.MyCommand)`n$errorMessage"
}
}
getVMHosts -vcenter "vcenter" |
ForEach-Object {
$json = ConvertTo-Json $_ -Compress;
insertToElasticSearch json $json -info:$true -Verbose:$true
}
}
Solution 3:[3]
As noam states there are objects in there causing this. Extract the base case as an example
get-vm <insertexamplevmname> | Select PowerState, Name, NumCpu, MemoryMB, GuestId, VMHost, @{N="IP Address";E={@($_.guest.IPAddress[0])}} | convertto-json -Depth 1
You will see that VMHost isn't just the name of the host it is running on but the actual host object which also has a Name property just like the VM has a Name.
So what you probably want is to extract the VMHost name as you have done for the IP addresses from the guest object.
get-vm <insertexamplevmname> | Select PowerState, Name, NumCpu, MemoryMB, GuestId, @{N="Hostname";E={@($_.VMhost.Name)}}, @{N="IP Address";E={@($_.guest.IPAddress[0])}} | convertto-json
Solution 4:[4]
After some fiddling, it appears to be a bug with convertto-json when the get-vm statement returns a single vm object. If more than one vm object is returned, convertto-json works. You can test yourself, replacing vm1 and vm2 with valid vm names:
get-vm -name 'vm1' | convertto-json -depth 1## fail
get-vm -name @('vm1') | convertto-json -depth 1 ## fail
get-vm -name 'vm2' | convertto-json -depth 1 ## fail
get-vm -name @('vm2') | convertto-json -depth 1 ## fail
get-vm -name @('vm1','vm2') | convertto-json -depth 1 ## success
get-vm -name @('vm2','vm1') | convertto-json -depth 1 ## success
One hackaround would be to ensure get-vm always returns two vms by including a known vm, then ignoring the known vm json element. Not recommending this solution, but may help someone in a bind.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | noam |
Solution 2 | Eris |
Solution 3 | Ken |
Solution 4 | General Grievance |