Skip to content

karenpayneoregon/command-line-exploration

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

43 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Working with command line (C#)

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

⚜️ Article

Where tools are installed

C:\Users\USER_NAME.dotnet\tools.store

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.

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>

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
  • 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.

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.

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.

Mot all are dotnet tools

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.

See also

Source code

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