New Puppet Bolt PowerShell Cmdlets
Tags: puppet, puppetbolt, devops
Published: 2020-09-02 20:57:48

[Puppet Bolt](https://puppet.com/docs/bolt/latest/bolt.html) now ships (as of version 2.20.0) with PowerShell native [cmdlets](https://docs.microsoft.com/en-us/powershell/scripting/developer/cmdlet/cmdlet-overview) that are auto-generated from Bolt source code with correct PowerShell [verb-noun](https://docs.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands.) semantics, familiar PowerShell parameter [conventions](https://docs.microsoft.com/en-us/powershell/scripting/developer/cmdlet/strongly-encouraged-development-guidelines?view=powershell-7#parameter-design-guidelines-sd03), and identical Bolt [help documentation](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_comment_based_help). All of these features work together to surface Bolt commands in a discoverable and understandable manner and enable using powerful PowerShell language features with Bolt.

## What to do

How do you find out what tool to use, if you don't know what tools are available? One of the first things you learn when using PowerShell is how to get help using `Get-Command` and `Get-Help`. These are two very powerful cmdlets that enable intuitive discovery of the available tooling and how to use the chosen tool.

`Get-Command` is a versatile tool that can find any PowerShell command on the system using a variety of filters, and returns information on both the command and where the command comes from. Previously we only shipped the `bolt` command with Bolt:

```
PS > Get-Command bolt

CommandType     Name  Version    Source
-----------     ----  -------    ------
Function        bolt  2.23.0     PuppetBolt
```

This doesn't do much for discoverability and requires you to run the `bolt` command to find out what it can do. If we instead start to surface Bolt commands as PowerShell cmdlets, we can start to take advantage of both `Get-Command` and PowerShell's cmdlet conventions to increase discoverability:

```
PS > Get-Command *bolt*

CommandType     Name                          Version    Source
-----------     ----                          -------    ------
Function        bolt                          2.23.0     PuppetBolt
Function        Convert-BoltPlan              2.23.0     PuppetBolt
Function        Get-BoltGroup                 2.23.0     PuppetBolt
Function        Get-BoltInventory             2.23.0     PuppetBolt
Function        Get-BoltPlan                  2.23.0     PuppetBolt
Function        Get-BoltPuppetfileModules     2.23.0     PuppetBolt
Function        Get-BoltTask                  2.23.0     PuppetBolt
Function        Install-BoltPuppetfile        2.23.0     PuppetBolt
Function        Invoke-BoltApply              2.23.0     PuppetBolt
Function        Invoke-BoltCommand            2.23.0     PuppetBolt
Function        Invoke-BoltPlan               2.23.0     PuppetBolt
Function        Invoke-BoltScript             2.23.0     PuppetBolt
Function        Invoke-BoltTask               2.23.0     PuppetBolt
Function        New-BoltPlan                  2.23.0     PuppetBolt
Function        New-BoltProject               2.23.0     PuppetBolt
Function        New-BoltSecretKey             2.23.0     PuppetBolt
Function        Protect-BoltSecret            2.23.0     PuppetBolt
Function        Receive-BoltFile              2.23.0     PuppetBolt
Function        Register-BoltPuppetfileTypes  2.23.0     PuppetBolt
Function        Send-BoltFile                 2.23.0     PuppetBolt
Function        Unprotect-BoltSecret          2.23.0     PuppetBolt
Function        Update-BoltProject            2.23.0     PuppetBolt
```

Now we can see a large list of commands using PowerShell's verb-noun syntax. Even if you aren't familiar with PowerShell's verb-noun conventions, reading the list of commands gives you an immediate general idea of what's possible with Bolt. There's `plans` and `scripts`, `tasks` and `projects`, concepts like invoking commands and getting information from the system. Scanning the list gets you immediate information you can use to choose what tool to complete your task.

If you're familiar with PowerShell's verb-noun conventions, you can start to drill down and quickly decide what command will do what you want. For example, you may want to find out how to get a list of Bolt tasks on the system. You know that PowerShell's verb-noun conventions dictate that all commands that return information use `Get` as the verb, and that the noun should contain the word related to the information returned, `Task`. Using this information, you run `Get-Command` and tell it to search the `PuppetBolt` module for all commands that use the verb `Get` and have a noun with the word `task` in it using a wildcard:

```
PS > Get-Command -Module PuppetBolt -Verb Get -Noun *task*

CommandType     Name          Version    Source
-----------     ----          -------    ------
Function        Get-BoltTask  2.23.0     PuppetBolt
```

With `Get-Command` we found `Get-BoltTask` quickly in one attempt, whereas we would have to call `bolt --help`, then `bolt task --help` to find the same information. We didn't really need to read documentation to know which command to run, we already knew from PowerShell's conventions that any `Get` command would return a one or more items and that a command with a noun `BoltTask` returns information about tasks.

All of this is great for figuring out which command we need to run, but how do we know how to use it ? In the next section we'll explore using `Get-Help` for this purpose, but before we do we can use `Get-Command` one last time to find out some quick bits of syntax:

```
PS > Get-Command Get-BoltTask -Syntax

Get-BoltTask [[-Name] <string>] [-LogLevel <string>] [-Modulepath <string>] [-Project <string>] [-Configfile <string>] [-Filter <string>] [-Format <string>] [-Version] [<CommonParameters>]
```

The output returned by `Get-Command` calls back to the opening paragraphs of this post. We used Bolt source code to generate these PowerShell cmdlets, so each cmdlet is populated with the parameters and help text for each Bolt command. Since they are formatted using PowerShell conventions, commands like `Get-Command` and `Get-Help` can parse these new Bolt PowerShell cmdlets and return structured information. When we ran `Get-Command Get-BoltTask -Syntax`, PowerShell parsed the Bolt cmdlets and returned structured parameter information for us. This is immensely powerful for command discovery.

## Getting Help

We now know the command we want to run, but how do we find out how to run it? We can use `Get-Help` for that. While we can get far with a few conventions using `Get-Command`, the cmdlet `Get-Help` really shines in helping us understand how to use them. Since `Get-BoltTask` is a PowerShell cmdlet and contains PowerShell comment based help, we can use `Get-Help` to read more detailed information about how to use it:

```
PS > Get-Help Get-BoltTask

NAME
    Get-BoltTask

SYNOPSIS
    bolt task show [task] [options]


SYNTAX
    Get-BoltTask [[-Name] <String>] [-LogLevel <String>] [-Modulepath <String>] [-Project
    <String>] [-Configfile <String>] [-Filter <String>] [-Format <String>] [-Version]
    [<CommonParameters>]


DESCRIPTION
    Show available tasks and task documentation. Omitting the name of a task will display a
    list of tasks available in the Bolt project. Providing the name of a task will display
    detailed documentation for the task, including a list of available parameters.


RELATED LINKS
    https://puppet.com/products/bolt
    https://puppet.com/docs/bolt/latest/bolt_command_reference.html

REMARKS
    To see the examples, type: "get-help Get-BoltTask -examples".
    For more information, type: "get-help Get-BoltTask -detailed".
    For technical information, type: "get-help Get-BoltTask -full".
    For online help, type: "get-help Get-BoltTask -online"
```

We get formatted help immediatly with one command using `Get-Help`. Looking through the output using `Get-Help Get-BoltTask -Detailed`, you can see each Bolt PowerShell cmdlet contains all the Bolt help text for each command and parameter:

```
PARAMETERS
    -Name <String>
        The task to show

    -LogLevel <String>
        Set the log level for the console. Available options are
        debug, info, notice, warn, error, fatal, any.

    -Modulepath <String>
        List of directories containing modules, separated by ';'
        Directories are case-sensitive

    -Project <String>
        Specify what project to load config from (default: autodiscovered from current working
        dir)

    -Configfile <String>
        Specify where to load config from (default: ~/.puppetlabs/bolt/bolt.yaml).
        Directory containing bolt.yaml will be used as the project directory.
```

This shows that the generated the PowerShell cmdlets have **typed** parameters. Parameters that accept strings are cast to `[string]`, parameters that accept a range of values now have `[ValidateSet()]`, and so on. Most of Bolt's parameters translate naturally to PowerShell conventions. We spent a lot of time making sure these translation fit PowerShell semantics. A small list of considerations for parameters follows:

- Required parameters are detected and set as `Mandatory`
- Detects when Validation attributes can be applied at the PowerShell level
- Detects common parameter sets for commands
- Added new `name` parameters to commands like `command`, `task`, or `plan` and set as `Mandatory` and `positional`
- Boolean parameters (`--[no]-ssl-verify`) are collapsed into one PowerShell `switch` parameter `--SslVerify`

All of this shows up in the `Get-Help` output, using commands you're familiar with, without having to page through several pages of `bolt command run --help` to find out what parameter will do what.

## Using what you have learned

Putting all this effort into making these Bolt commands and parameters have PowerShell types and validation pays dividends not only with the PowerShell help system, but also enables some very powerful ways to invoke Bolt commands.

We can see how by using what we've covered so far in a real world example. Let's say we want to get the status of the Print Spooler service on a remote target. Using `bolt --help`, then `bolt task --help`, then `bolt task show`, then `bolt task show service` we can see that we need to run `bolt task run -t localhost service name=spooler action=status` in order to check the status of the Print Spooler.

That was around 3 commands and alot of reading through pages of parameters to find out what to do. If the params we want to pass to the `service` task become complicated, we can use JSON instead but that is yet another line to write.

If we remember that the new Bolt PowerShell cmdlets follow PowerShell verb-noun conventions, we can use the same discovery process we saw earlier in this post to find the command to use. We know that `Invoke` is the PowerShell verb convention for executing something and we know `BoltTask` is the noun we need by applying PowerShell noun conventions. Without having to think about it much or run a series of commands we know to use `Invoke-BoltTask` to execute tasks. To find out how to use `Invoke-BoltTask` we run `Get-Help Invoke-BoltTask -detailed` and see the list of parameters needed to run the command and come up with:

```
PS > Invoke-BoltTask -Targets localhost -Name service -Params @{ name = 'spooler'; action = 'status' }
Started on localhost...
Finished on localhost:
  {
    "status": "running",
    "enabled": "true"
  }
Successful on 1 target: localhost
Ran on 1 target in 9.24 sec
```

This shows the power of using PowerShell language constructs to make passing input to Bolt easier. The `-params` parameter accepts a PowerShell hash and converts it under the covers to a JSON string to pass to Bolt. This means we can get as simple or as complicated as we want defining it and PowerShell will handle the complexity of sending it to Bolt.

We can take it one step further and use PowerShell parameter splatting inside a PowerShell script, so that we can reuse the same data for multiple commands in our scripts:

```
PS > gc .\getserviceinfo.ps1
$spoolerParams = @{
  Name = 'service'
  Params = @{
    name = 'spooler'
    action = 'status'
  }
  Targets = 'localhost'
}
Invoke-BoltTask @spoolerParams
PS > .\getserviceinfo.ps1
Started on localhost...
Finished on localhost:
  {
    "status": "running",
    "enabled": "true"
  }
Successful on 1 target: localhost
Ran on 1 target in 9.68 sec
```

## Wrapping Up

We think taking the time and effort to surface Bolt commands as native PowerShell cmdlets is proven effective by the ease of use demonstrated above. Using some simple scenarios we have shown how PowerShell cmdlets increase discoverability of our toolset and make it easier to understand how to use it. Then we covered how the PowerShell language also makes it easier to express complicated parameter sets to Bolt and enables powerful scripting scenarios.