Extracting objects from commands that output text

There are many ways to throw a spanner. But if you really must throw it with powershell you’ll want it to result in an object.

The versatility of objects can give much to legacy commands that still linger in the Windows administration world. This short tutorial should teach you to turn most legacy commands into one or more commandlets in some concise code.

Step 1 - Get your command

First you’ll need to find your command, in this case we are going to try to list all CAs in the forest Get-CA this can be done with certutil -dump.

certutil -dump

Entry 0:
  Name:                     `Contoso Issuing Certificate Authority'
  Organizational Unit:      `Contoso IT Services'
  Organization:             `Contoso'
  ...
  Country/region:           `US'
  Config:                   `CAServer.contoso.com\IssuingCertificateAuthority'
  ...
  Short Name:               `Contoso Issuing Certificate Authority'
  Sanitized Short Name:     `Contoso Issuing Certificate Authority'
  Flags:                    `1'
  Web Enrollment Servers: 
  ... Some data ...
CertUtil: -dump command completed successfully.

Step 2 - Design your regex

The output of certutil -dump has two key sections Entry 0: which defines an object delimiter. And Country/region: `US' which is the general format of our name value pairs. '^Entry \d+: is the first regex we will use. ^ represents the start of the line and \d+ represents “one or more digits” the rest is just direct character matching. " (?<variable>[\w\s]+):\s+``(?<value>.*)'" is the second regex. ?<varible> puts the matched value into a property for use [\w\s]+ matches any number of letter or whitepace characters and .* is zero or more of any character. Note that the string part of this line starts with a backtick, to include a backtick you need to escape it so `` represents only one backtick.

Step 3 - Parse your result

This same technique was used to wrap dnscmd before it had native Powershell counterparts.

 #Get the result in a variable
 $result = certutil -dump
 #prepare an array for the objects
    $CAs = @()
    #Parse through the result
    switch -regex ($result){
        '^Entry \d+:'  {
            #When you hit an Entry put the object into the Array and create a new object
            if ($thisEntry){$CAs += $thisEntry}
            $thisEntry = new-object psobject;
        }
        "  (?<variable>[\w\s]+):\s+``(?<value>.*)'"{
            #When you hit a parameter, add it to the object
            $thisEntry | Add-Member -MemberType NoteProperty -Name $matches.variable -value $matches.value -force
        }
    }    
    #If we finish parsing, put the last object in the array
    if ($thisEntry){$CAs += $thisEntry}

Now that $CAs` contains our list of CAs!

Step 4 - Build your function

Wrap your code up in a function. As a bonus I have also included a command to list the published templates on each CA.

function get-CAs {
    $result = certutil -dump
    $CAs = @()
    
    switch -regex ($result){
        '^Entry \d+:'  {
            if ($thisEntry){$CAs += $thisEntry}
            $thisEntry = new-object psobject;
        }
        "  (?<variable>[\w\s]+):\s+``(?<value>.*)'"{
            $thisEntry | Add-Member -MemberType NoteProperty -Name $matches.variable -value $matches.value -force
        }
    }    
    if ($thisEntry){$CAs += $thisEntry}
    return $CAs
}

function get-CATemplatesAvailableToIssue ($CA) {
    if (!$CA -or $CA -notmatch '^[\w\.]+\\\w+$') {
        $CAs = get-CAs
        $CAsToLookup = $CAs | ?{$_.Config -match $CA -or $_.Config -like $CA}
    } else {
        $CAsToLookup = new-object psobject -Property @{Config=$CA}
    }
    foreach ($Ca in $CAsToLookup) {        
        $result = certutil -config $($CA.config) -caTemplates
        switch -regex ($result) {
            '^(?<Name>\w+): (?<DisplayName>.+) -- .+$'{new-object psobject -Property @{Name=$matches.name;DisplayName=$matches.DisplayName;CA=$CA.config}}
        }
    }
}