Announcing PowerShell Crescendo Preview.1
As a shell, PowerShell is intended to work with native commands in addition to cmdlets. However, native commands have their own unique syntax, sometimes containing many subcommands and parameters/switches, and are often like its own language. Wouldn’t it be great to have an option to leverage your PowerShell skills to use new commands or be even more proficient with native commands you are already using? As a PowerShell user, it would be great if those native commands supported PowerShell features like:
- Clear naming convention –
Verb-Noun
- Consistent parameter naming for similar uses
- Output consisting of objects
- Common method of getting command help
- Easy to work with objects in a pipeline
- Easy to share using Modules
We are pleased to announce the first preview of PowerShell Crescendo, a framework to rapidly develop PowerShell cmdlets for native commands, regardless of platform.
Many of today’s modern native commands are complex, they themselves are mini-shells with their own
mini-language containing sub levels, or sub contexts. If you’ve worked with kubectl
, docker
or
netsh.exe
, you have experienced the complexity of executing and automating these commands.
To make native commands have a more PowerShell-like feel and offer a similar experience, you could
re-write the tool yourself, or if the tool uses REST
, directly call the web APIs using
AutoRest. These options work very well, but require more
development experience and can be harder to maintain over time.
Crescendo provides the tools to easily wrap a native command to gain the benefits of PowerShell
cmdlets. Wrapping native commands into Crescendo cmdlets can provide parameter handling like
prompting for mandatory parameters and tab-completion for parameter values. Crescendo cmdlets
can take the text output from the native application and parse it into objects. The output objects
allow you to take advantage of all the post processing tools such as Sort-Object
, Where-Object
,
etc.
What’s supported
Microsoft.PowerShell.Crescendo 0.4.1 Preview.1 is currently available for download from the PowerShell Gallery
Supported PowerShell versions for authoring a module with Crescendo:
- PowerShell 7.0+
Supported PowerShell versions that can execute a Crescendo module:
- Windows PowerShell 5.1+
- PowerShell 7.0+
What’s next
Next/Future plans for preview.2 will be based on feedback and include a few items under investigation:
- Create cmdlet aliases when generating a module
- Investigate parsing man pages and documentation to improve automatic JSON generation
Installing Crescendo
To create a Crescendo cmdlet, you will need the module Microsoft.PowerShell.Crescendo from the PowerShell Gallery.
Install-Module Microsoft.PowerShell.Crescendo
Documentation
We’ve included the about_Crescendo
and we’ll be adding more documentation in the future.
Make sure to check out the Samples folder in the module.
Get-Help about_Crescendo
To author a Crescendo module, you create a JSON configuration file describing how you want the native command projected as a cmdlet. You need the Microsoft.PowerShell.Crescendo module to build the finished module containing the Crescendo proxy cmdlets. The Microsoft.PowerShell.Crescendo module runs on PowerShell 7+.
To consume a module built with Crescendo, you don’t need to have Microsoft.PowerShell.Crescendo installed. Simply download or install the generated module and you’re good to go. Because a Crescendo-built module is just like any other module, these are supported on Windows PowerShell 5.1 and above. Your work can help others in the community automate native commands. Fortunately, building and sharing Crescendo modules is easy when you publish to the PowerShell Gallery.
For more information about the concepts and design approach, see:
Examples
Creating a new cmdlet
This example builds on a common Linux packaging utility apt
for displaying, installing and
removing software. The properties Verb and Noun define the name of the new cmdlet
Get-InstalledPackage
.
The Crescendo module includes a schema definition file for assistance creating and editing JSON
configuration files. The schema file Microsoft.PowerShell.Crescendo.Schema.json
is included with
the module. In the below example, the schema file is copied to a temporary working directory with
the JSON file under development. The location of the schema file is defined in the JSON
configuration and can be located anywhere.
Common properties defined by the schema:
Verb
: The name of the cmdlet verb (CheckGet-Verb
for valid verb names)Noun
: The name of the cmdlet nounOriginalName
: The original native command name and locationOriginalCommandElements
: Some native commands have additional mandatory switches to execute properly for a given scenarioOutputHandlers
: The output handler captures the string output from the native command and transforms it into structured data (objects). As with other PowerShell cmdlets, this object output may be manipulated further down the pipeline.
Note: The example code contains comments (// <--
) in JSON. They are for
descriptive purposes only and should be removed.
{
"$schema": "./Microsoft.PowerShell.Crescendo.Schema.json", // <-- Schema definition file
"Verb": "Get", // <-- Cmdlet Verb
"Noun":"InstalledPackage", // <-- Cmdlet Noun
"OriginalName": "apt", // <-- Native command
"OriginalCommandElements": ["-q","list","--installed"],
"OutputHandlers": [ // <-- Add string output to an object
{
"ParameterSetName":"Default",
"Handler": "$args[0]| select-object -skip 1 | %{$n,$v,$p,$s = "$_" -split ' '; [pscustomobject]@{ Name = $n -replace '/now'; Version = $v; Architecture = $p; State = $s.Trim('[]') -split ',' } }"
}
]
}
The following example shows how to use the Get-InstalledPackage
we defined in the JSON file.
PS> Get-InstalledPackage | Where-Object { $_.name -match "libc"}
Name Version Architecture State
---- ------- ------------ -----
libc-bin 2.31-0ubuntu9.1 amd64 {installed, local}
libc6 2.31-0ubuntu9.1 amd64 {installed, local}
libcap-ng0 0.7.9-2.1build1 amd64 {installed, local}
libcom-err2 1.45.5-2ubuntu1 amd64 {installed, local}
libcrypt1 1:4.4.10-10ubuntu4 amd64 {installed, local}
PS> Get-InstalledPackage | Group-Object -Property Architecture
Count Name Group
----- ---- -----
10 all {@{Name=adduser; Version=3.118ubuntu2; Architecture=all; State=System.String[}, @{Name=debconf; V…
82 amd64 {@{Name=apt; Version=2.0.2ubuntu0.1; Architecture=amd64; State=System.String[]}, @{Name=base-files…
Example with parameters
This example wraps the Windows command ipconfig.exe
and illustrates how to define parameters. In
addition to the properties in the above example, the schema supports a Parameters
section to wrap
and project descriptively named parameters to the new cmdlet.
Name
: Name of parameter that appears in the new cmdletOriginalName
: Name of original parameter that appears in the native command
{
"$schema" : "./Microsoft.PowerShell.Crescendo.Schema.json",
"Verb": "Get",
"Noun": "IpConfig",
"OriginalName":"c:/windows/system32/ipconfig.exe",
"Description": "This will display the current IP configuration information on Windows",
"Parameters": [ // <-- Parameter definition
{
"Name":"All", // <-- Name of parameter that appears in cmdlet
"OriginalName": "/all", // <-- Name of original parameter that appears in native cmd
"ParameterType": "switch",
"Description": "This switch provides all ip configuration details" // <-- Help parameter
},
{
"Name":"AllCompartments",
"OriginalName": "/allcompartments",
"ParameterType": "switch",
"Description": "This switch provides compartment configuration details"
}
],
"OutputHandlers": [
{
"ParameterSetName": "Default",
"Handler":"param ( $lines )
$post = $false;
foreach($line in $lines | ?{$_.trim()}) {
$LineToCheck = $line | select-string '^[a-z]';
if ( $LineToCheck ) {
if ( $post ) { [pscustomobject]$ht |add-member -pass -typename $oName }
$oName = ($LineToCheck -match 'Configuration') { 'IpConfiguration' } else {'EthernetAdapter'}
$ht = @{};
$post = $true
}
else {
if ( $line -match '^ [a-z]' ) {
$prop,$value = $line -split ' :',2;
$pName = $prop -replace '[ .-]';
$ht[$pName] = $value.Trim()
}
else {
$ht[$pName] = .{$ht[$pName];$line.trim()}
}
}
}
[pscustomobject]$ht | add-member -pass -typename $oName"
}
]
}
Export the proxy module from JSON configuration
Exporting a Crescendo JSON configuration file results in a PowerShell module that can be shared
locally, on GitHub, and on the PowerShell Gallery. Crescendo includes the
Export-CrescendoModule
cmdlet that generates the proxy module from the JSON configuration file.
- The ConfigurationFile parameter should include the name and path to the JSON configuration file.
- The ModuleName parameter contains the name of the exported module.
Export-CrescendoModule -ConfigurationFile .get-ipconfig.crescendo.json -ModuleName Ipconfig.psm1
Using your Crescendo module
Once you have created the module with Crescendo, you can install and import it like any other module. Keep in mind you will need the original native command available wherever you install the Crescendo module.
PS> Import-Module .ipconfig.psm1
PS> Get-IpConfig -All | Select-Object -Property ipv4address, DefaultGateway, subnetmask
ipv4address DefaultGateway subnetmask
----------- -------------- ----------
172.24.112.1 255.255.240.0
192.168.94.2 {fe80::145e:1779:5cfa:c2a5%8, 192.168.94.1} 255.255.255.0
How you can help
Our goal is to make it easier to convert your native commands to PowerShell cmdlets and receive the benefits that PowerShell provides. We value your ideas and feedback and hope you will give Crescendo a try, then stop by our GitHub repository and let us know of any issues or features you would like added.
For more information about PowerShell Crescendo issues and features, see: Crescendo on GitHub
Jason Helmick Program Manager, PowerShell
21 comments
This is a very gratifying new development!
I use a lot of native commands within powershell scripts. My main pain point is error handling as I have to use a lot of $lastexitcode checks. The primary issue I hope powershell could solve is consistent error handling.
Transforming native commands to powershell cmdlets sounds like a good idea however it’s unlikely that it will find good adoption as native CLI commands are more popular and are what you find on Sackoverflow or a web search.
For example I use aws cli commands although there is a powershell module for aws which is less usable. however it’s not intuitive and cli support json output OOB so I can easily parse the results to powershell objects and deal with them.