There are a lot of administrative tasks you cannot do with PowerShell alone, especially on Unix platforms like macOS or Linux. For day-to-day tasks, it is not a problem, you will use native applications and return to PowerShell when needed. But what if you need to integrate these native applications in a more complex PowerShell solution.
There are several approaches for that, check this blog post.
But recently Microsoft announced a new module, Crescendo, to better support these native commands, see this blog post here.
This module, Crescendo, is a framework. It let you create a cmdlet with a Verb-Noum naming convention, PowerShell parameters mapped to the command parameters, and output the result as an object.
To install the module, you must use PowerShell 7.X
Install-Module Microsoft.PowerShell.Crescendo
Now that the module is installed you can start to create your first cmdlet with it. For that, you need a JSON configuration file to describe how to use the target command, parameters, output, …
Let's take the same example I used to illustrate how to create a Kubectl plugin with PowerShell, listing pods by namespace.
The command is
kubectl get pods -o json
It lists pods information of the Kubernetes cluster you are connected to.
On this command, you need to add a parameter to select a specific namespace, and if the parameter is not present, list pods from the default namespace.
The command looks like this:
kubectl get pods -o json –namespace XXX
The corresponding JSON configuration file look like:
{
"$schema": "https://raw.githubusercontent.com/PowerShell/Crescendo/master/Microsoft.PowerShell.Crescendo/src/Microsoft.PowerShell.Crescendo.Schema.json",
"Verb": "Get",
"Noun":"k8spods",
"OriginalName": "kubectl",
"OriginalCommandElements": [
"get",
"pods",
"-o",
"json"
],
"Parameters": [
{
"Name":"namespace",
"OriginalName": "--namespace",
"ParameterType": "string",
"Description": "Namespace name",
"DefaultValue": "default"
}
]
}
The $Schema section is optional, but it will help you to write your configuration file in Visual Studio Code. You can use the local file (on macOs, $HOME/.local/share/powershell/Modules/Microsoft.PowerShell.Crescendo//Microsoft.PowerShell.Crescendo.Schema.json) or you can use the online version in GitHub (https://raw.githubusercontent.com/PowerShell/Crescendo/master/Microsoft.PowerShell.Crescendo/src/Microsoft.PowerShell.Crescendo.Schema.json).
The verb section will design your cmdlet Verb. You need to choose a valid PowerShell verb. You can use this command to get the list of valid verbs
Get-verb
Noun is the second part of the cmdlet you want to create; you can choose anything you want.
The originalName is the name (or the path) to the command without any parameter.
If the command needs some default parameters to work, you will need to use the OriginalCommandElements. These parameters do not need to be part of your cmdlet but must be present during the execution of the command. It's an array.
And finally, parameters, it's an array of JSON objects. Each object will define a parameter in the cmdlet. You will need to provide a name, the name of the parameter in the PowerShell cmdlet, originalName, the corresponding name for the native command.
Other properties can help you to design the parameter:
- Description, it will be added to the comment-based help of the function
- DefaultValue, a default value for the parameter
- ParameterType, any PowerShell value (String, Int, …) or Switch if the parameter doesn't need a value.
- Mandatory, true, or false if you want to make the parameter mandatory
This configuration file is enough to create a cmdlet. It will create a function with parameters from the Parameters section and create a wraper for the native command.
To create the cmdlet:
Export-CrescendoModule -ConfigurationFile ./pods.json -ModuleName pods.psm1
This will create the module file in the current directory (if the file exists, the cmdlet will raise an error).
You can use the cmdlet by using
Import-Module ./pods1.psm1
You can now use the cmdlet
Get-k8spods -namespace MyNameSpace
But if you use it you will have the direct output of the kubectl command. Can we do better and output a custom PowerShell object instead?
In the original post, the output from kubectl was converted from JSON data and retrieve only pod name, status (or phase), restart count, start DateTime, image, namespace node name, and node type.
You need to modify the output of the cmdlet. Until now, the configuration file is only related to how to run the command and create the cmdlet inputs. To modify the output, you need to add an outputHandlers property. It's an array of objects where each element contains two required properties, ParameterSetName, the name of the parameterSet associated with the output, and Handler the code to execute for the output.
The Handler property contains the code to execute against the native command output. The output as an argument. You can use the first element of the $args array ($args[0]) or use the param keyword with any parameter name.
Even if JSON doesn't support multiline string by default, PowerShell supports it, but it's not the case with Visual Studio Code and you can get some warnings you can ignore while editing your configuration file.
{
"$schema": "https://raw.githubusercontent.com/PowerShell/Crescendo/master/Microsoft.PowerShell.Crescendo/src/Microsoft.PowerShell.Crescendo.Schema.json",
"Verb": "Get",
"Noun":"pods2",
"OriginalName": "kubectl",
"OriginalCommandElements": [
"get",
"pods",
"-o",
"json"
],
"Parameters": [
{
"Name":"namespace",
"OriginalName": "--namespace",
"ParameterType": "string",
"Description": "Namespace name",
"DefaultValue": "default",
"Mandatory": true
}
],
"OutputHandlers": [
{
"ParameterSetName": "Default",
"Handler":"param ( $kubectlOutput )
$Podslist = $kubectlOutput | convertfrom-Json
$PodsArrayList = [System.Collections.ArrayList]::new()
foreach ($pod in $Podslist.items) {
[void]$PodsArrayList.Add([pscustomobject]@{
PodName = $pod.metadata.name
Status = $pod.status.phase
restartCount = $pod.status.containerStatuses[0].restartCount
StartTime = $pod.status.startTime
image = $pod.status.containerStatuses[0].image
Node = $pod.spec.nodeName
NodeType = $pod.spec.nodeSelector
NameSpace = $pod.metadata.namespace
})
}
$PodsArrayList
"
}
]
}
The code in the Handler property converts the output from kubectl to a PowerShell object. A loop extract needed values and add them to a custom object before returning this object.
The cmdlet is almost complete, but the kubectl command also accepts a --all-namespace. This switch excludes the --namespace parameter.
You need to add the parameter in the parameters section. To exclude the --namespace in the cmdlet you will need to add a parameterSetName property to each parameter.
"Parameters": [
{
"Name":"namespace",
"OriginalName": "--namespace",
"ParameterType": "string",
"Description": "Namespace name",
"DefaultValue": "default",
"ParameterSetName": [
"default"
]
},
{
"Name":"allNameSpace",
"OriginalName": "--all-namespace",
"ParameterType": "switch",
"Description": "to get all namespace",
"ParameterSetName": [
"allnamespace"
]
}
]
The first parameter, namespace, has been modified to add a parameter set name, default. The second parameter, allnamespace, is bind to the –all-namespaces switch, the parameter type is switch and the parameter is added to the allnamespace parameter set.
There is another thing to do. In the handler section, each object must have a parameterSetName property. This implies we need to create another item for the allnamespace paramterSet.
{
"$schema": "https://raw.githubusercontent.com/PowerShell/Crescendo/master/Microsoft.PowerShell.Crescendo/src/Microsoft.PowerShell.Crescendo.Schema.json",
"Verb": "Get",
"Noun":"pods3",
"OriginalName": "kubectl",
"OriginalCommandElements": [
"get",
"pods",
"-o",
"json"
],
"Parameters": [
{
"Name":"namespace",
"OriginalName": "--namespace",
"ParameterType": "string",
"Description": "Namespace name",
"DefaultValue": "default",
"ParameterSetName": [
"default"
]
},
{
"Name":"allnamespace",
"OriginalName": "--all-namespaces",
"ParameterType": "switch",
"Description": "to get all namespace",
"ParameterSetName": [
"allnamespace"
]
}
],
"OutputHandlers": [
{
"ParameterSetName": "Default",
"Handler":"param ( $kubectlOutput )
$Podslist = $kubectlOutput | convertfrom-Json
$PodsArrayList = [System.Collections.ArrayList]::new()
foreach ($pod in $Podslist.items) {
[void]$PodsArrayList.Add([pscustomobject]@{
PodName = $pod.metadata.name
Status = $pod.status.phase
restartCount = $pod.status.containerStatuses[0].restartCount
StartTime = $pod.status.startTime
image = $pod.status.containerStatuses[0].image
Node = $pod.spec.nodeName
NodeType = $pod.spec.nodeSelector
NameSpace = $pod.metadata.namespace
})
}
$PodsArrayList
"
},
{
"ParameterSetName": "allnamespace",
"Handler":"param ( $kubectlOutput )
$Podslist = $kubectlOutput | convertfrom-Json
$PodsArrayList = [System.Collections.ArrayList]::new()
foreach ($pod in $Podslist.items) {
[void]$PodsArrayList.Add([pscustomobject]@{
PodName = $pod.metadata.name
Status = $pod.status.phase
restartCount = $pod.status.containerStatuses[0].restartCount
StartTime = $pod.status.startTime
image = $pod.status.containerStatuses[0].image
Node = $pod.spec.nodeName
NodeType = $pod.spec.nodeSelector
NameSpace = $pod.metadata.namespace
})
}
$PodsArrayList
"
}
]
}
The module created by the Crescendo framework is only a PSM1 file with one function. You may want to create a full module with it or integrate the created function into your project.
The Crescendo framework is still in preview and you can find bugs and limitations. You can view the project and open issue on the project's GitHub pages
Top comments (1)
Very interesting. I saw a demo of crescendo I had immediately thought to kubectl use case.