DEV Community

Cover image for C# .NET Tools with System.CommandLine
Karen Payne
Karen Payne

Posted on • Edited on

C# .NET Tools with System.CommandLine

Learn how to create .NET tools which can assist with automating task which can be installed globally on a computer.

Skill level

To write a simple .NET tool requires basic understand of the C# language and basics of working with classes. Consider starting with the project CommandArgsConsoleApp1.

For writing robust .NET tool a solid understanding of C# is required along with asynchronous programming and writing events.

What is a global tool?

A .NET tool is a special NuGet package that contains a console application. You can install a tool on your machine in the following ways.

There are over 3,000 preexisting packages to choose from such as dotnet-ef which generates code for a DbContext and entity types for a database. In order for this command to generate an entity type, the database table must have a primary key.

To create a .NET tool there are two NuGet packages

  • Microsoft.Extensions.Configuration.CommandLine package
  • CommandLineParser package

In this article, Microsoft.Extensions.Configuration.CommandLine will be used which is currently considered in preview while CommandLineParser is considered a mature package.

Advise before creating a tool

Rather than try and figure out what to create a tool for, instead consider task done that require several steps that can be done in C#. Next, create a console project without Microsoft.Extensions.Configuration.CommandLine package, get it working perfectly. Next, create a new console project and bring in the code from the first project, now with the Microsoft.Extensions.Configuration.CommandLine package. The alternate is to create one project and simple write the code if you feel comfortable with the basics presented below.

Types of tools

Tools can be utilities such as learning about indexes for tables in a database which includes a user interface.

Here once the tool is executed, a menu appears with database names where the server used is stored in appsettings.json.



{
  "ServerSettings": {
    "Name": ".\\SQLEXPRESS"
  } 
}


Enter fullscreen mode Exit fullscreen mode

Caveat, the base folder for tools are under C:\Users\USER_NAME.dotnet\tools.store. This means to change the server name a developer must remember the installed application location.

user interface for tool

Other tool types may have minimal user interfaces unlike the one above. Most tools will need arguments passed to them and with a well written tool if required arguments are not provided a minimal help screen prompts the user for what arguments are expected. For example, the following tool will report holidays for a specified country and needs the country code. Execute without the code we get.

Holidays without arguments

Given a country code

code passed

Creating a simple tool

Let's start off simple, in this sample, get first and last name.

Create a new Console project, once created double click on the project name in Solution Explorer. Remove current contents in place of the following.

Note
In the section below for installing and uninstalling YourProjectName needs to be replaced with the name of this project. In the supplied example in the GitHub repository the project name is KP_CommandLineBase.



<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net7.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <PackAsTool>true</PackAsTool>
        <ToolCommandName>hello</ToolCommandName>
        <PackageOutputPath>./nupkg</PackageOutputPath>
        <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
        <Version>2.0.0</Version>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
    </ItemGroup>
</Project>


Enter fullscreen mode Exit fullscreen mode

Save the file.

Next, create a folder named Classes, add a class named MainOperation which will be called in Program.Main below.

Add the following code to MainOperations class



internal class MainOperations
{
    public static void MainCommand(string firstName, string lastName)
    {
        Console.WriteLine($"Hello {firstName} {lastName}");
    }
}


Enter fullscreen mode Exit fullscreen mode

Next, open Program.cs and replace current Program code with the following.



internal class Program
{
    static async Task Main(string[] args)
    {
        var firstNameOption = new Option<string>("--first")
        {
            Description = "First name",
            IsRequired = true
        };
        firstNameOption.AddAlias("-f");

        var lastNameOption = new Option<string>("--last")
        {
            Description = "last name",
            IsRequired = true
        };
        lastNameOption.AddAlias("-l");

        RootCommand rootCommand = new("Example for a basic command line tool")
        {
            firstNameOption,
            lastNameOption
        };

        rootCommand.SetHandler(MainOperations.MainCommand, firstNameOption, lastNameOption);

        var commandLineBuilder = new CommandLineBuilder(rootCommand);

        commandLineBuilder.AddMiddleware(async (context, next) =>
        {
            await next(context);
        });

        commandLineBuilder.UseDefaults();
        Parser parser = commandLineBuilder.Build();

        await parser.InvokeAsync(args);

    }
}


Enter fullscreen mode Exit fullscreen mode
  • firstNameOption and lastNameOpen of type Option define expected parameters.
    • For first name --first or -f e.g. -f Karen
    • For last name --last or -l e.g. -f Payne
    • RootCommand rootCommand represents the main action that the application performs
  • rootCommand.SetHandler defines, the method to handle actions passed which in this case are firstNameOption and lastNameOption
  • var commandLineBuilder = new CommandLineBuilder(rootCommand); enables composition of command line configurations.
  • commandLineBuilder.AddMiddleware(async (context, next) ... see How to use middleware in System.CommandLine
  • commandLineBuilder.UseDefaults();
  • Parser parser = commandLineBuilder.Build();
  • await parser.InvokeAsync(args); parses and invokes the given command

Install/uninstall

  • Build the project
  • Open a command prompt or PowerShell to the root of this project
  • Enter the following to install
    • dotnet tool install --global --add-source ./nupkg YourProjectName
  • Enter the following to uninstall
    • dotnet tool uninstall -g YourProjectName to uninstall the tool.

After installation



You can invoke the tool using the following command: hello
Tool 'YourProjectName' (version '2.0.0') was successfully installed.


Enter fullscreen mode Exit fullscreen mode

Run the tool (hello is defined in the .csproj file ToolCommandName)



hello -f Karen -l Payne


Enter fullscreen mode Exit fullscreen mode

After uninstall



Tool 'YourProjectName' (version '2.0.0') was successfully uninstalled.

Enter fullscreen mode Exit fullscreen mode




Samples as tools

There are two projects that are practical examples for .NET tools.

The first is Holidays which gets all holidays for the current year by two character country code. Once installed, pass -c XX where 'XX' is a country code. This makes a call to https://date.nager.at/api/v3/publicholidays/ to get holidays for the current year and country.

The second DirectoryCount expects -d followed by a folder name which when executed returns total folder and file count of the folder passed. If the user does not have permissons the exception is caught and presented, if the folder does not exists, that is captured also.

Included projects

Are listed below, the project CommandArgsConsoleAppHelp showcases how to override the default help which can better assist users in the case where the default help does not fully convey usage e.g. a parameter is –name which expects first and last name. They may not know to wrap as follows –name “Karen Payne” while a better approach would be –first Karen –last Payne.

Project Description
KP_CommandLineBase A very basic tool example to accept first and last name, both required to display to the console.
CommandArgsConsoleApp1 This project performs the same operations as in the project CommandArgsConsoleApp2 but not taking full advantage of the package System.CommandLine.
CommandArgsConsoleApp2 A base template for working with System.CommandLine which uses SeriLog sink to write to a SQL-Server database.
CommandArgsConsoleAppHelp Demonstrates how to override the help section for working with System.CommandLine.
CommandArgsConsoleSubCommands Shows how to use verbs and commands to read a file in chunks and display to the screen. This project is based off a Microsoft code sample and enhanced.
DirectoryCount This project demonstrates how to get a folder and file count recursively for a folder name passed. If the user lacks proper permissions an exception is caught and thrown.
Holidays This project shows holidays by two letter country code for the current year and is setup to run as a dot net tool with instructions in its read me file.
Login Simple example for hidden password input and a few other nice things using NuGet package Spectre.Console.
SqlServerColumnDescriptions Example which reads column descriptions from SQL-Server database tables if present. Note the server name is hard wired but easy to change or see SqlServerIndices project which uses appsettings.json to get the server name. In the class for data operations, there are mirror methods, one conventional and the other with Dapper.
SqlServerIndices An example cut from base code in SqlServerColumnDescriptions to get all indexes if present from a SQL-Server database. Unlike SqlServerColumnDescriptions project, server name is in appsettings.json
On the last two projects another idea for setting server name is to setup an argument e.g. --servername someserver. Checkout CommandArgsConsoleApp1 project for how this can be done.
Shortcuts A simple example which reads Visual Studio and Resharper shortcuts in a table. The idea is to provide an easy way to get at shortcuts that are rarely used thus easy to forget. I have this setup in Visual Studio as an external tool. Shortcuts are stored in a json file.
UpdateBootstrapApp Used to update BootStrap in a new ASP.NET Core/Razor Pages project.

See also

Source code

Clone the following GitHub repository which was created with Microsoft VS2022 using C#11.

Top comments (3)

Collapse
 
vaishnavravi33 profile image
Ravindra Vairagi

Nice article to learn

Collapse
 
offpepe profile image
Alan Albuquerque Ferreira Lopes

nice article, thanks!

Collapse
 
dotmake profile image
DotMakeBuild

Good and detailed article. What if you could accomplish most of this with few lines of code? I created a library for the purpose, it uses a source generator to convert your classes to CLI commands and properties to CLI options or CLI arguments by using attributes. I hope you check it out:
github.com/dotmake-build/command-line