Search code examples
arraysjsonpowershellhashtable

Working with Powershell Arrays,HashTables, and JSON


I'm currently working on a project developing PowerShell commands to administer a device my company makes. The device responds to all sorts of different APIs for all its functions.

I'm currently having an issue converting a group of strings into the proper format so that when I ConvertTo-JSON it comes out right.

I have gotten it to work, but I had to do some string manipulation at the end. I'm sure there's a way to do it right, but I can't find the solution. It's not the prettiest solution, but it currently works. I was hoping someone had another idea, or simply I missed something.

The problem is with the Subject section of the CSR.

My API wants:

    "names": [
        {
            "C": string,
            "L": string,
            "O": string,
            "OU": string,
            "ST": string
        }

And I can't seem to get it to function "cleanly"

Here is the schema my device is looking for for the API call.

{
    "cn": string,
    "algorithm": string,
    "dnsNames": [
        string
    ],
    "emailAddresses": [
        string
    ],
    "encryptionAlgo": string,
    "ipAddresses": [
        string
    ],
    "name": string,
    "names": [
        {
            "C": string,
            "L": string,
            "O": string,
            "OU": string,
            "ST": string
        }
    ],
    "password": string,
    "privateKeyBytes": string,
    "size": integer
}

Here's my function:

function New-ConnectionCSR {
param (
    [Parameter(Mandatory=$true)] [string] $name,
    [Parameter()] [string] $cn=$name,
    [Parameter()] [string] $algorithm="RSA",
    [Parameter()] [int] $size=2048,
    [Parameter()] [string[]] $dnsNames,
    [Parameter()] [string[]] $ipAddresses,
    [Parameter()] [string[]] $emailAddresses,
    [Parameter()] [Alias('organization','org')] [string] $o="",
    [Parameter()] [Alias('oganizationalunit')] [string] $ou="",
    [Parameter()] [Alias('location','locality')] [string] $l="",
    [Parameter()] [Alias('state')] [string] $st="",
    [Parameter()] [Alias('country')] [string] $c=""
    )

    Write-Debug "Start: $($MyInvocation.MyCommand.Name)"

    # Mandatory Parameters
    $body=@{
        
        "name"          = $name
        "cn"            = $cn
        "algorithm"     = $algorithm
        "names"         = @()

    }

    #Optional Parameters
    if($size){$body.Add('size',$size)}
    if($dnsNames){
        $dnsNames = $dnsNames.Split(",")
        $body.Add('dnsNames',$dnsNames)
    }
    if($ipAddresses){
        $ipAddresses = $ipAddresses.Split(",")
        $body.Add('ipAddresses',$ipAddresses)
    }
    if($emailAddresses){
        $emailAddresses = $emailAddresses.Split(",")
        $body.Add('emailAddresses',$emailAddresses)
    }
    if($ou -or $o -or $l -or $st -or $c){
        $names = @()
        if($ou){ $names += ('"ou":"' + $ou + '"') }
        if($o){ $names += ('"o":"' + $o + '"') }
        if($l){ $names += ('"l":"' + $l + '"') }
        if($st){ $names += ('"st":"' + $st + '"') }   
        if($c){ $names += ('"c":"' + $c + '"') }
               
        $body.names += "{ $(($names -join ",")) }"
    }

    $jsonBody = ($body | ConvertTo-Json).Replace('\"','"')
    $jsonBody = $jsonBody.Replace('"{','{')
    $jsonBody = $jsonBody.Replace('}"','}')
    
    Write-Debug "JSON Body:`n$($jsonBody)"
}

**Note the string manipulation at the end. That's what I don't want. **

After that last line, I do all the API calls, but the code should work as is as a function and spit out the JSON Body.

Lastly, here's the command I use to invoke:

New-ConnectionCSR -name "MyConnectionCert" -cn "mydevice.local" -size 2048 -dnsNames "mydevice.contoso.com,mydevice.contoso.local" -ipAddresses "10.0.0.1" -emailAddresses "support@contoso.com" -ou "MyOU" -o "MyOrg" -l "MyCity" -st "FDL" -c "USA"

Solution

  • If you change the middle block of your function to this it should work...

    ...
    
        if($ou -or $o -or $l -or $st -or $c){
            $dn = [ordered] @{}
            if($c)  { $dn.C  = $c  }
            if($l)  { $dn.L  = $l  }
            if($o)  { $dn.O  = $o  }
            if($ou) { $dn.OU = $ou }
            if($st) { $dn.ST = $st }
    
            $body.names = @( $dn )
        }
    
        $jsonBody = $body | ConvertTo-Json
    
    ...
    

    Instead of building an array of strings you build an (ordered) dictionary of key-value pairs - PowerShell will happily convert this cleanly to json without any further work required...

    ...
      "names": [
        {
          "C": "USA"
          "L": "MyCity",
          "O": "MyOrg",
          "OU": "MyOU",
          "ST": "FDL"
        }
      ],
    ...
    

    (I used $dn for the temporary variable as it looks like you're building a Distinguished Name for an X509 certificate...)


    Btw, if you want to control the order of the root object properties you can do this to make $body an ordered dictionary as well, instead of a hashtable - that way your json properties will be emitted in a predictable order:

    $body = [ordered] @{
       ... etc ...
    }