Announcing PowerShell Crescendo Preview.1

Jason

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 (Check Get-Verb for valid verb names)
  • Noun: The name of the cmdlet noun
  • OriginalName: The original native command name and location
  • OriginalCommandElements: Some native commands have additional mandatory switches to execute properly for a given scenario
  • OutputHandlers: 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 cmdlet
  • OriginalName: 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

Comments are closed. Login to edit/delete your existing comments

  • hspc pc

    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.