Creating Global Command Line Tools with .Net Core

Dave Cook
Compare the Market
Published in
11 min readOct 31, 2018

--

In this post I will introduce you to a pretty cool addition to the world of .Net: Global Command Line tools. We’ll cover how to install and use them, how to create your own and make them available for others to use via nuget.

My team at work maintain and run a large suite of .Net Microservices. As you’d expect in a setup like this, the code for each service lives in its own repository, and is completely separate from every other service. This obviously has its advantages over the monolith that we all know and love, but it also creates several challenges to overcome. One of those challenges is the additional overhead of keeping everything up to date: for example, if you use a particular package or dependency in all your services then this needs upgrading N times each time a new version is released.

We recently reached the end of a large project at a similar time to .Net Core 2.0 nearing ‘end of life’. So, we were faced with updating all of our services to the latest version…not a particularly difficult task, but repetitive and not much fun either. Plus, while we are doing that, it keeps us from the stuff we really want to do rather than the things that need doing!

However, all of our many services are structured in exactly the same way, were running on the same version of .Net, and all needed updating to the same version. The perfect job for a little automation while I spend some time playing with .Net Global tools! :-)

Enter Automatize: A Global .Net tool for upgrading your .Net Core projects to a new version of the runtime, applying the updates recommended on the migration docs. I won’t talk much about Automatize itself, instead I’ll focus on the tech that powers it. If you are interested, then you can check it out on github or nuget.

The ability to create command line tools in .Net has been around for ages, allowing you to fire off instructions to a dll running locally on your machine and have it do stuff without the need to faff about with any fancy UI. What you may not be aware of though is that .Net Core has raised its game considerably in this area with the release of version 2.1:

Firstly, it’s .Net Core, so your CLI tools will run on not just Windows, but on Linux and Mac setups too — much like the web solutions it creates.
Secondly, making your tools available for others to consume is now super easy, as you can simply publish them on NuGet and … boom, anyone can install your tool right from the command line, with the following:

This should feel nice and familiar if you’ve ever used NPM. That’s because the team at Microsoft who put this together didn’t want us needing to think too hard when switching back and forth between the different tool sets.

The -g or --global options will install the tool for all users, allowing it to be used from any location. It does this by adding the tool to your Path. This is optional, but if you don’t specify it you will need to specify where to install the tool:

Once a tool is installed you have all the options you’d expect for managing your tools:

Replace -g with — tool-path for specific install locations.

Built a tool that you don’t want to share with the world, just your own team? No problem, you can publish it to your private Nuget server and your team can install it from there:

If you are interested in what .Net tools are already out there to get your hands on, then Nate McMasters has a fairly comprehensive list.
Personally, I’ve been using NuKeeper to keep my packages up to date across all my projects (see this post for details).

Enough chatter, how do you make your own Global tool?

For a start you’re going to need the .Net 2.1 SDK installed, but you should be using that for all .Net development now, so that’s no hardship.
Then, since we are talking about command line tools, the next step should really be:

This, as I’m sure you know, creates a new class library! Nothing new here. In order to make this into a global tool though, you’ll need to make a few tweaks to your project file:

Note the <ToolCommandName> and <PackAsTool> elements which are both required when building CLI tools: ‘ToolCommandName’ is the name of your new tool. ‘PackAsTool’ indicates that this package should be built as a tool, instead of a normal library.

And that’s it, you’re good to go. Anything you wanted to do in a console app you can do here too, but with the added ability to install via nuget and run from any location on your machine, on various operating systems.

COOL! So once you’ve built your shiny new tool, how do you get it on nuget?

Well the good news is that there is nothing new to learn here. If you know how to publish libraries to nuget then you already know how to publish your CLI tool.

To recap, you’ll need to modify your project file again in order to enable the creation of a nuget package:

Then use the dotnet pack and dotnet nuget push commands, to create your nuget package, and upload that package onto nuget:

Not bad! However, you are probably thinking that all that editing of project files is a bit of a pain…why doesn’t the template project just include all that stuff for you? I thought that too, so in order to speed up the creation of new tools I created my own global tools template, which does set these defaults. You can install these templates and use them like this:

NB: the templates are still a work in progress and have a few things I’d like to improve.

High-level, that’s all there is to it. However, once you start getting into it you are going to need to start creating a way of mapping your command line instructions in the terminal into Commands and Arguments in your code, or your tool to isn’t going to do anything. At this point I would recommend taking a look at Nate McMaster’s Command Line Utils package on nuget which will really make your life a lot easier, and basically get you seeing something in return for your efforts that bit faster. For a happy bonus, it also makes adding help commands really simple too!

The remainder of this post will focus on the code you’ll need to get your first tool up and running using this package, starting with an introduction to a few key concepts…

Commands, Arguments and Options

A command is an instruction to your tool to execute a particular action. Some familiar examples are git status, dotnet new or npm start the keywords ‘status’, ‘new’ and ‘start’ are all commands to their respective executable.

The command for Automatize is upgrade.

Arguments and options are two different types of parameter that can be sent to a command. Both can be configured as optional or mandatory, (more on that later) and you can use a combination of both if you wish.

Arguments are positional, which is helpful for reducing the amount your user needs to type in order to execute a command, but also means that the order in which they are supplied is important. Options on the other hand are named so can be supplied in any order but must be specified using the familiar dash dash notation, or ‘switches’ like --help. Personally, I prefer options over arguments as I think they are easier to use once you have more than one parameter for your command, and easier to read as the act as metadata documenting what’s going on. We’ll be using a combination in our sample app, and you can make up your own mind.

Building a Command

I’ll assume you’ve already created a new project and have set a ToolCommandName in the project xml file. Once that’s done, to build your own command simply define a new class and create a public OnExecute method that accepts a parameter of type CommandLineApplication and returns an integer:

public async Task<int> OnExecute(CommandLineApplication app)

This is what will executed at run time when you issue your command, so this is where you place the code you want to actually make things happen.

Here is a complete example:

Notice from my two examples that the OnExecute method can be made synchronous or async depending on what you want to do inside your command.

As you can see, there is nothing particularly special about the code above, it’s just a class with a public method. The only thing that is significant is that the method name must be OnExecute. This means that can unit test your commands in the normal way.

Notice that I have injected IConsole into the method to allow for just that, rather than tightly couple the command to System.Console. (IConsole also lives in CommandLineUtils package, along with a useful implementation).

At this point if you run your programme nothing will happen as we still need to add some wire up code to Program.cs in order to parse what you type into your terminal window into a commands, arguments and options:

There are a few things to point out here. First off, on line 3 you’ll notice that I’ve decorated the Program class with a Subcommand attribute. This tells the application that whenever the user runs “appname Hello” on the command line, then the HelloCommand should be executed. The string “Hello” in the first parameter declares what the user needs to type on the command line, but there is no requirement for this string to match the class where the functionality for that command lives, so renaming either will still work, but changing the string value will impact on what your users need to type.

If your tool has multiple commands, then they should all be listed here, using additional Subcommand attributes.

Lines 8–9 this is where the bulk of the work for parsing the user’s command line instructions is done. It does all the hard stuff for you, so you can concentrate on the fun stuff.

Line 13 is where all the magic happens when the CommandLineUtils package executes the user’s request by calling the appropriate command’s OnExecute method.

Lines 22–24 have no significance in what we are doing, but it does make debugging and reading any output that little bit easier by preventing the console window from closing when you command completes, but without being annoying once the code is released.

Testing Your Command

Before you publish your tool on nuget, you are probably going to want to test it out locally, and/or step through the code to debug any issues. To do this in Visual Studio, right click your project file and select ‘properties’. Then go to the ‘Debug’ tab and type whatever your command line instructions would be to the tool into the “application arguments” field. Hitting F5 will now launch your tool, supplying these arguments.

Notice that I’ve not included the name of the tool before the command, that’s because your dll is the only thing being run in this context, so you don’t need it. Remember though, you will need to specify the tool name once it’s installed as a global tool.

Run your app by pressing F5 to launch, and you should see the following output:

You just built your first tool :-)

Adding input parameters

Now let’s write a command that will count down how many days to go until a given date, supplying the date as an Option in either of the following formats — — date 25/12/2018 or -d 25/12/2018

Add the following class to your project:

On lines 5–8 we have declared two public properties: EventName and Date. EventName is a normal property, so can’t be used as a parameter for our command. However, the Date property can as it is decorated with the Option attribute. Now add the SubCommand attribute to your Program.cs and update the command line arguments in your project file to either Until --date “25/12/2018” or Until -d 25/12/2018. Relaunch your app now and you should see a countdown to Christmas.

The CommandLineUtils package has automatically added both the long and short hand switch behaviours for you, using the name of the Date property to determine default names for the parameter(s). This is case sensitive, so supplying arguments of Until --Date “25/12/2018” (Uppercase D) will result in an “Unrecognized option” error message.

What if you have more than one property with the same name, or you want to specify something other than the defaults? No problem, you can overwrite the default behaviour by supplying LongName and/or ShortName arguments to the Option attribute.

I’d actually recommend doing this anyway, as it’s easy to casually refactor and rename your arguments…which also changes the switch values your users need to send to your commands.

Now let’s update the code to describe the date in our countdown message using an Argument:

Simply decorate the public property with the Argument attribute, supplying an integer that represents the position where this argument should appear in the argument collection. The collection is zero based, this parameter is required, and needs to take into account all Arguments and Options for the Command, not just the Arguments.

So, what does this change mean for our application arguments field under project properties and how we execute the Command once installed? Well, as we have declared that the eventname should appear in position 0 we now need call the Until Command like this:

Until “Kate’s Birthday” -w 20/06/2019

Notice that I’ve had to surround the value of eventname in speech marks. Without these the CommandLineUtil package would interpret the two separate words as different parameters and fail to match any matching commands, resulting in an error: Unrecognized command or argument ‘Birthday’.

Making input parameters Required

I mentioned earlier, both Options and Arguments can be either optional or mandatory, and the examples up until here have all been optional. Making an Argument mandatory simply requires decorating the property in question with a Required attribute like this:

With this attribute applied, calling the command without the EventName argument will result in the error message: The EventName field is required.

Simple. Options are a bit more complicated though as they offer, well… more options! Instead of the Required attribute you need to specify the CommandOptionType parameter on the Option attribute, which accepts one of four values:

SingleValue- The default. Once an option is specified on the command line, its value is required….but specifying the option is still optional. This is as close to required as it’s possible to make an Option without writing your own validation code or defaulting the Property.

The remaining CommandOptionType settings effectively make an optional Option…

NoValue- this effectively makes the parameter into a flag which is simply considered as specified or absent.

SingleOrNoValue- A special hybrid of the previous two options.

MultipleValues - Allows you to specify the option multiple times with multiple values.

Here’s an example of an updated UntilCommand with a few Options configured:

Test this by updating your application arguments like so:

Until “Kate’s Birthday” -d “20/06/2019” -m “Happy Birthday” -m “Many Happy Returns”

The docs on Arguments are Options are very good for when you need to know more.

Help Commands

No command line tool would be complete without some way of giving your users some help commands, and the CommandLineUtils package helps you with that too.

Out of the box if you type --help, then all of your available Commands will be listed, along with further details of how to get more detailed help with each of those commands. If you then type [commandName] --help you will then see a list of all of the parameters available for that Command.

You can expend the help that is returned in each of these outputs by supplying the description argument to the Command, Argument and Option attributes:

Summary

You should now be able to quickly build your own command line tools using .Net Core, publish them to nuget, and install them on multiple operating systems. Once installed you’ll be able to start firing off commands to your tool, either manually or from with automated scripts. Either way, hopefully you can automate a few mundane tasks and free up your time for working on something else.

The code example we have been working through is available on github.

Happy coding.

--

--

Dave Cook
Compare the Market

Software Engineer, snowboarder, Archer & tired father...does not wear tights for any of these things! www.github.com/dcook-net