Lately, I’ve become obsessed with automating local development environment tasks, especially when it comes to onboarding, bootstrapping, and managing local machine environments for various projects. When something like a web application starts to add external dependencies like services running in docker-compose, quickly that project’s onboarding steps can get quite lengthy. Managing issues around onboarding steps can become a pain, and troubleshooting them, especially when those tasks are expected to be run across operating systems, can be very difficult and time consuming. PowerShell Core, being cross-platform and JIT runnable, offers a friendly scripting experience for creating command-based command line interfaces for whatever application you need.
Prerequisites
- Install PowerShell Core on your operating system of choice: https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell
- All the examples can also be found here. Download/clone this repo to follow along with each step: https://github.com/kavun/ps-cli
Goal
Let’s build a script with these commands:
.\cli.ps1 up
.\cli.ps1 down
.\cli.ps1 build
.\cli.ps1 test
.\cli.ps1 migrate <name>
.\cli.ps1 ip
Validate the commands
To enable these ad-hoc command names (without the default -
prefix that PowerShell params normally require), we can use a combination of ValidateSet
and Parameter(Position=0)
attributes on the script’s first param
.
This ensures that when you pass an incorrect command or if you exclude the command, you get a detailed error message.
Handle the commands
Now we need to handle the commands and run functions for each one. For this, we’ll use a switch
.
Now, when a command is passed, the associated function is run.
Support Get-Help
This is great, but it’s not immediately known what commands are available without opening up the .ps1
file and reading it. We could add a command help
and then spit out some help content with Write-Host "help string"
, but there’s a better way. PowerShell scripts can hook into the Get-Help
command to provide structured help documentation for the whole script. Let’s do both!
Let’s look at what we changed:
- Added
<# #>
help content at the top of the file. And added.SYNOPSIS
and.DESCRIPTION
sections. - Removed
Mandatory=$True
from the$Command
param, so that we can show the help content when you call the script with no params. - Added the
help
command, which ends up callingGet-Help $PSCommandPath
This will show help for all 4 of these commands. The goal here is to make finding help foolproof.
.\3-help.ps1
.\3-help.ps1 help
.\3-help.ps1 -?
Get-Help .\3-help.ps1
Handle nested commands
What we have is great for simple one-liners, but eventually we’ll want to pass params to the commands and also support nested commands.
Let’s look at what we did:
- We used
ValueFromRemainingArguments
to capture any trailing params in the$Rest
param and passed those along to the inner functions. - We used
ValidateSet
on an inner function’s param, just like we did on the script’s params. - Added help docs to one of the inner functions.
These are just examples, but you can see the range of possibilities that open up when we start nesting commands and forwarding params. Here’s what this example looks like in use:
Resources
- Scripts from examples: https://github.com/kavun/ps-cli
ValidateSet
: https://docs.microsoft.com/en-us/powershell/scripting/developer/cmdlet/validateset-attribute-declarationValueFromRemainingArguments
: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions_advanced_parameters#valuefromremainingarguments-argument- about_Comment_Based_Help (
<# #>
): https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_comment_based_help