- Create a certificate-signed RDP shortcut via Group Policy - Fri, Aug 9 2019
- Monitor web server uptime with a PowerShell script - Tue, Aug 6 2019
- How to build a PowerShell inventory script for Windows Servers - Fri, Aug 2 2019
We can think of PowerShell as automation glue. This language seems to be able to interact with just about every conceivable product. Part of this impartial ability to work with lots of products typically means working with application programming interfaces (APIs). Many services these days have a representational state transfer (REST) API.
PowerShell can easily query these REST APIs using the Invoke-RestMethod command or Invoke-Webrequest. But what then? Lots of APIs return JSON, which is great. But what if I'm going to use this data in other parts of my script? It's possible to carry around a JSON string all day in a script, but it's going to be tough to parse that out and get to the data you need. Instead, it's much better to convert that JSON string to a native hash table.
PowerShell has a native command called ConvertFrom-Json, but it doesn't convert data to a hash table. In simple situations, this may be fine. But I've found when you've got JSON data that contains deeply nested arrays, for example, you'll begin to see some problems. This command does some processing under the hood as well, like performing some escaping I sometimes don't want. So I decided to build a ConvertTo-Hashtable function that takes an object returned from ConvertFrom-Json and converts it to a hash table.
To demonstrate, let's take a JSON string as an example.
$json ='{ "SQLDatabase": [ { "Name": "FOO", "Type": "OnPrem", "Ensure": "Present" } ], "ADUser": [ { "Name": "jjones", "DnsDomainName": "lab.local" } ], "ADGroup": [ { "Name": "mygroup", "Scope": "DomainLocal", "DnsDomainName": "lab.local", "Members": [ { "Name": "jjones", "Type": "User", "DnsDomainName": "lab.local" } ] } ] }'
I'll then assign that JSON data to a variable to create a string and then convert it to a PowerShell PSCustomObject using the ConvertFrom-Json command:
$jsonObj = $json | ConvertFrom-Json
I now have an object I can work with. I'll go ahead and assign the output to the $jsonObj variable. At this point, I want to convert this object to a hash table. If the object just had some simple string properties, this would not be a hard task at all. We'd simply enumerate all the properties of the object and create a new hash table from them.
$hash = @{} foreach ($property in $jsonObj.PSObject.Properties) { $hash[$property.Name] = $property.Value }
We'd be done here if it weren't for the possibility that these properties can be a lot of different types other than just strings. To build a robust conversation function, we need to account for these situations. This is where I'd usually break out each piece of the code and explain it independently. Since this code is so tightly integrated as well as being a recursive function, it will be easier to explain what's going on inside the function comments themselves.
Subscribe to 4sysops newsletter!
function ConvertTo-Hashtable { [CmdletBinding()] [OutputType('hashtable')] param ( [Parameter(ValueFromPipeline)] $InputObject ) process { ## Return null if the input is null. This can happen when calling the function ## recursively and a property is null if ($null -eq $InputObject) { return $null } ## Check if the input is an array or collection. If so, we also need to convert ## those types into hash tables as well. This function will convert all child ## objects into hash tables (if applicable) if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string]) { $collection = @( foreach ($object in $InputObject) { ConvertTo-Hashtable -InputObject $object } ) ## Return the array but don't enumerate it because the object may be pretty complex Write-Output -NoEnumerate $collection } elseif ($InputObject -is [psobject]) { ## If the object has properties that need enumeration ## Convert it to its own hash table and return it $hash = @{} foreach ($property in $InputObject.PSObject.Properties) { $hash[$property.Name] = ConvertTo-Hashtable -InputObject $property.Value } $hash } else { ## If the object isn't an array, collection, or other object, it's already a hash table ## So just return it. $InputObject } } } We can then call this function via pipeline: $json | ConvertFrom-Json | ConvertTo-HashTable
Performing conversions like this can be challenging and will sometimes require lots of trial and error. I created this function in just this way. An object can be in so many variations. Thus, the only true way to create code that doesn't break will be simply to run as much input through it as possible and monitor the results.
Thanks for the explanation and great code. It works perfectly on a json configuration block with nested hashtables and arrays.
A minor correction on the end of the script. The last line should be $jsonString, not $jsonObj
$jsonString | ConvertFrom-Json | ConvertTo-HashTable
Where did you get the $jsonString variable from?
$jsonObj is the json-decoded object, as seen on the article’s line “I now have an object I can work with. I’ll go ahead and assign the output to the $jsonObj variable.”
$jsonString is, basically, the $json variable
You are right, the article was somewhat incomplete and contained errors. I fixed it now. The crucial line for you is the last one:
Hi,
I’m working on Powershell module to manage Grafana application and plan to put it on Github in opensource.
Can I use your code as is by mentioning your name and this url page please ?
Thank you
Yes, you can use the code if you link to this page.
When we conver it back to Json, why it have additional “value” , and “count”?
$jsonObj = $json | ConvertFrom-Json |ConvertTo-Hashtable |ConvertTo-Json
{
“ADGroup”: {
“value”: [
{
“Scope”: “DomainLocal”,
“Name”: “mygroup”,
“Members”: {
“value”: “System.Collections.Hashtable”,
“Count”: 1
},
“DnsDomainName”: “lab.local”
}
],
“Count”: 1
},
“SQLDatabase”: {
“value”: [
{
“Name”: “FOO”,
“Ensure”: “Present”,
“Type”: “OnPrem”
}
],
“Count”: 1
},
“ADUser”: {
“value”: [
{
“DnsDomainName”: “lab.local”,
“Name”: “jjones”
}
],
“Count”: 1
}
}
Thanks for the code Adam, very helpful and nicely documented.
You have to remove all non NoteProperty properties. See https://github.com/PlagueHO/CosmosDB/issues/382
## Convert it to its own hash table and return it
$hash = @{}
foreach ($property in $InputObject.PSObject.Properties) {
if ($property.MemberType -eq "NoteProperty") {
$hash[$property.Name] = ConvertTo-Hashtable -InputObject $property.Value
}
}
$hash
You should skip if it isn't a "NoteProperty" property. These are internal PSObject properties. Specifically add the below.
if ($property.MemberType -eq "NoteProperty") {
"If the object isn't an array, collection, or other object, it's already a hash table", what if one of the values in that hash table contains another `PSCustomObject`?
My output is
Name Value
—- —–
ADGroup {System.Collections.Hashtable}
SQLDatabase {System.Collections.Hashtable}
ADUser {System.Collections.Hashtable}
Why the value is {System.Collections.Hashtable} ????
How to see “Members”: [
{
“Name”: “jjones”,
“Type”: “User”,
“DnsDomainName”: “lab.local” by hashtable ?