0

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 "[email protected]" -ou "MyOU" -o "MyOrg" -l "MyCity" -st "FDL" -c "USA"

1 Answer 1

1

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 ...
}
2
  • Thank you so much. That did the trick. Also, I like tip about making $body "ordered". Commented Dec 7, 2023 at 13:55
  • 1
    So in further reading and digesting what you wrote. What I initially had tried to do was do an "add" for $body.names, which wasn't working obviously, because it was an array. So the solution was to build a new hashtable, or ordered dictionary as you did, and add it as a whole to the array object instead of piece by piece. Which is where I was getting hung up. This is something I probably would've caught myself had I ever taken a formal PowerShell class. I'm completely self-taught when it comes to PowerShell. Thanks again! Commented Dec 7, 2023 at 14:24

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.