Learn how to create .NET tools which can assist with automating task which can be installed globally on a computer.
⚜️ Article
C:\Users\USER_NAME.dotnet\tools.store
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.
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
In this article, Microsoft.Extensions.Configuration.CommandLine will be used which is currently considered in preview while CommandLineParser is considered a mature package.
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.
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>
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}");
}
}
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);
}
}
- 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. -l Payne
- For first name
- 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
- 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
- dotnet tool install --global --add-source ./nupkg
- Enter the following to uninstall
- dotnet tool uninstall -g
YourProjectName
to uninstall the tool.
- dotnet tool uninstall -g
After installation
You can invoke the tool using the following command: hello
Tool 'YourProjectName' (version '2.0.0') was successfully installed.
Run the tool (hello is defined in the .csproj file ToolCommandName
)
hello -f Karen -l Payne
After uninstall
Tool 'YourProjectName' (version '2.0.0') was successfully uninstalled.
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.
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. |
There are several projects that are here as developer tools that are simply console projects yet there are plenty of dotnet tool projects too and some that are not that could be.
- System.CommandLine overview
- command-line-api
- How to manage .NET tools
- Mark of the web remover A utility to remove mark of the web from a folder recursively to all sub-folders.
Clone the following GitHub repository which was created with Microsoft VS2022 using C#11.