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.
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.
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:
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.
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.
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.