All Articles

Creating output handler Crescendo

Back in June I explored the Crescendo module for Powershell to wrap native command line tool into cmdlets. In the blog post, I needed to make a deviation and did not spend time on looking to the output handler.

After the authors of the Crescendo module gave me a tip to use on of the properties in to get the ordinal position, I thought to re-visite my original implementation and expand at least on the queryex options for the sc.exe command line tool. Thus, in this small blog, I’ll show you how you can get the ordinal position and add the output handler.

Creating get function

In the previous blog post, I ended without exploring the option for queryex. Now armed with new knowledge, I was able to get the right ordinal position for the server name. To refresh it, when I was looking into the documentation, it is possible also to give the server name optionally.

queryex-docs

I needed to create the ability to have it run before the OriginalCommandElements where added. That is where the ApplyToExecutable property came in useful. The implementation looks as followed.

{
    "$schema": "https://aka.ms/PowerShell/Crescendo/Schemas/2021-11",
    "Commands": [
        {
            "Verb": "Get",
            "Noun": "ScService",
            "OriginalName": "C:\\Windows\\System32\\sc.exe",
            "OriginalCommandElements": ["queryex"],
            "SupportsShouldProcess": false,
            "ConfirmImpact": "Low",
            "Parameters": [
                {
                    "OriginalName": "",
                    "Name": "ComputerName",
                    "Mandatory": false,
                    "ParameterType": "string",
                    "DefaultParameterSetName": null,
                    "OriginalPosition": 0,
                    "ApplyToExecutable": true,
                    "Position": 0,
                    "Description": "Specify the computer name by UNC path"
                },
                {
                    "OriginalName": "",
                    "Name": "ServiceName",
                    "Mandatory": false,
                    "ParameterType": "string",
                    "OriginalPosition": 1,
                    "Position": 1
                }
            ]
        }
    ]
}

Let’s see it in action when building the module itself.

get-service-remote

In the above screenshot, you can see now that the -ComputerName has been added first, even that the -ServiceName was first. My original idea could have been implemented now.

Adding the output handler

The output from queryex is listed below as an example.

SERVICE_NAME: winrm 
        TYPE               : 20  WIN32_SHARE_PROCESS  
        STATE              : 1  STOPPED 
        WIN32_EXIT_CODE    : 1077  (0x435)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0
        PID                : 0
        FLAGS              : 

I needed to trim the results into an array, so I was able to loop through the results and create a custom object. Sean Wheelers example of his vssadmin module, helped me out with this.

function ParseQuery
{
    param (
        [Parameter(Mandatory = $true)]    
        $CmdResults
    )
    
    $TextBlocks = $CmdResults.Replace(" ", "")
    $Lines = $TextBlocks.Trim()

    $Hash = @{}
    foreach ($Line in $Lines)
    {
        if ($Line -ne '')
        {
            switch -Regex ($Line)
            {
                'DISPLAY_NAME:'
                {
                    $DisplayName = ($Line.Substring(13).Split('_'))[0]
                    $Hash.Add('DisplayName', $DisplayName) 
                }
                'SERVICE_NAME:'
                {
                    $ServiceName = $Line.Substring(13)
                    $Hash.Add('Name', $ServiceName)
                }
                'STATE:'
                {
                    $State = ( Get-Culture ).TextInfo.ToTitleCase($Line.Substring(7).ToLower())
                    $Hash.Add('State', $State)
                
                }
                'PID:'
                {
                    $PI = $Line.Substring(4)
                    $Hash.Add('PID', $PI)
                    
                    [pscustomobject]$hash
                    $hash = [ordered]@{}
                    break
                }
            }
        }
    }
}

When building the output handler, I first wanted to check what the results would be:

cmd-result

From line 12, I can loop through the results, and as I know now where the get the output I need, I just simple use the Substring method to grab the value and dump it into the hash.

cmd-output

You’ll notice when queryex is called with service name, the display name is not returned

I now have a Powershell object to play with if needed.

The full JSON configuration is shown below.

{
    "$schema": "https://aka.ms/PowerShell/Crescendo/Schemas/2021-11",
    "Commands": [
        {
            "Verb": "Get",
            "Noun": "ScService",
            "OriginalName": "C:\\Windows\\System32\\sc.exe",
            "OriginalCommandElements": ["queryex"],
            "SupportsShouldProcess": false,
            "ConfirmImpact": "Low",
            "Parameters": [
                {
                    "OriginalName": "",
                    "Name": "ComputerName",
                    "Mandatory": false,
                    "ParameterType": "string",
                    "DefaultParameterSetName": null,
                    "OriginalPosition": 0,
                    "ApplyToExecutable": true,
                    "Position": 0,
                    "Description": "Specify the computer name by UNC path"
                },
                {
                    "OriginalName": "",
                    "Name": "ServiceName",
                    "Mandatory": false,
                    "ParameterType": "string",
                    "OriginalPosition": 1,
                    "Position": 1
                }
            ],
            "OutputHandlers": [
                {
                    "ParameterSetName": "Default",
                    "Handler": "ParseQuery",
                    "HandlerType": "Function"
                }
            ]
        }
    ]
}

Let’s rebuild the module to see how it looks. When rebuilding the module and adding the output handler, I needed to make sure first that the function is loaded into the session, so Crescendo was able to pick it up and add it. It took me some time to figure this one out.

output-object

Conclusion

It was a small blog post, but wanted to share it how you can create the output handler for the sc.exe command line tool. I just used some methods like Replace and Substring to extract the values I want. This function is primarly for sc.exe, as each native command has it’s own output. That’s it.