Sometimes, you, even being a power user of Windows, can miss some things that can make your life easier. You get used to a routine and then, new features come and unfortunately, you don’t use them because you don’t have time to explore and add the features to the routine.

One of these is the task view. To use it, just right-click in the taskbar and select “Show Taskview Button”:

This will add a new button to the taskbar:

I have to admit: it’s not a flashy button and the icon is similar to a film strip, so it’s very easy to miss. But then you click it (or use Windows+Tab), a new world opens:

The window that opens shows you the tasks you are currently working on and you can switch between them. It’s a little different from Alt-Tab, because it shows you the current tasks and the earlier ones you’ve been working.

If you move the scrollbar at the right, you will see the tasks you’ve worked on the past days and you can open them just by clicking in the one you want. Nice, no? If you right-click on a task, a context menu opens, where you can open, remove or clear tasks.

But this is not everything you can do with the Task View: if you take a look at the top of the window, you will see + New Desktop. Yes, you can use virtual desktops in Windows. This is not a new feature, but I’m sure it’s not one of the most used features. You can work with virtual desktops and have programs running in all of them. This is a nice way to separate things. Let’s say you don’t want to be disturbed while working with IM or email. You can add all the communication apps in a second desktop while working in the first one. That will be like a second machine to work. The icons won’t be shown in the taskbar, and if you disable notifications (yes, you can set focus mode by right clicking the notifications icon – see image below), you can work without being disturbed.

When you want a pause, just press Windows+Ctrl+Right or Windows+Ctrl+Left to switch desktops.

To move a window to a different desktop, just open the Task View and drag the window to the desired desktop. Notice that you will only see the windows in the selected desktop. If you want to see what’s in the other desktops you will have to choose the at the top:

Alt-Tab also will show only the windows in the current desktop. That way, you will have multiple machines at once. Nice!

This is a nice feature, I think it’s a great addition to the Windows team, and I’m sure I’ll add it to my routine, it will enhance my organization and productivity a lot!

A very common issue when dealing with console applications is to parse the command line. I am a huge fan of command line applications, especially when I want to add an operation to my DevOps pipeline that is not provided by the tool I’m using, Besides that, there are several applications that don’t need user interaction or any special UI, so I end up creating a lot of console applications. But one problem that always arises is parsing the command line. Adding switches and help to the usage is always a pain and many times I resort to third party utils (CommandLineUtils is one of them, for example).

But this make me resort to non-standard utilities and, many times, I use different libraries, in a way that it poses me a maintenance issue: I need to remember the way that the library is used in order to make changes in the app. Not anymore. It seems that Microsoft has seen this as a problem and has designed its own library: System.CommandLine.

Using System.CommandLine

The first step to use this library is to create a console application. Open Visual Studio and create a new console application. The default application is a very simple one that prints Hello World on the screen.

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");
    }
}

If you want to process the command line arguments, you could do something like this:

static void Main(string[] args)
{
    Console.WriteLine($"Hello {(args.Length > 0 ? args[0] : "World")}!");
}

This program will take the first argument (if it exists) and print a Hello to it:

This is ok, but I’m almost sure that’s not what you want.

If you want to add a switch, you will have  to parse the arguments, check which ones start with a slash or an hyphen, check the following parameter (for something like “–file Myfile.txt”). Not an easy task, especially if there are a lot of switches available.

Then, there is System.CommandLine that comes to the rescue. You must add the System.CommandLine NuGet package to the project. Then, you can add a command handler to add your options and process them, like this:

static async Task Main(string[] args)
{
    var command = new RootCommand
    {
        new Option(new [] {"--verbose", "-v"}),
        new Option("--numbers") { Argument = new Argument<int[]>() }
    };

    command.Handler = CommandHandler.Create(
        (bool verbose, int[] numbers) =>
        {
            if (numbers != null)
            {
                if (verbose)
                    Console.WriteLine($"Adding {string.Join(' ', numbers)}");

                Console.WriteLine(numbers.Sum());
            }
            else
            {
                if (verbose)
                    Console.WriteLine("No numbers to sum");
            }
        });
    await command.InvokeAsync(args);
}

As you can see, there are several parts to the process:

Initially you declare a RootCommand with the options you want. The parameter to the Option constructor has the aliases to the option. The first option is a switch with to aliases, –verbose  and -v. That way, you can enable it using any of the two ways or passing a boolean argument (like in -v false). The second parameter will be an array of ints. This is specified in the generic type of the argument. In our case, we expect to have an array of ints. In this case, if you pass a value that cannot be parsed to an int, you will receive an error:

As you can see in the image above, you get the help and version options for free.

The second part is the handler, a function that will receive the parsed parameters and use them in the program. Finally, you will call the handler with:

await command.InvokeAsync(args);

Once you execute the program, it will parse your command line and will pass the arguments to the handler, where you can use them. In our handler, I am using the verbose switch to show extra info, and I’m summing the numbers. If anything is wrong, like unknown parameters or invalid data, the program will show an error message and the help.

Right now, I’ve used simple parameters, but we can also pass some special ones. As it’s very common to pass file or directory names in the command line, you can accept FileInfo and DirectoryInfo parameters, like in this code:

static async Task Main(string[] args)
{
    var command = new RootCommand
    {
        new Argument<DirectoryInfo>("Directory", 
            () => new DirectoryInfo("."))
            .ExistingOnly()
    };

    command.Handler = CommandHandler.Create(
        (DirectoryInfo directory) =>
        {
            foreach (var file in directory.GetFiles())
            {
               Console.WriteLine($"{file.Name,-40} {file.Length}");
            }
        });
    await command.InvokeAsync(args);
}

Here, you will have only one argument, a DirectoryInfo. If it isn’t passed, the second parameter in the Argument constructor is the function that will get a default argument to the handler, a DirectoryInfo pointing to the current directory. Then, it will list all the files in the selected directory. One interesting thing is the ExistingOnly method. It will ensure that the directory exists. If it doesn’t exist, an error will be generated:

If the class has a constructor receiving a string, you can also use it as an argument, like in the following code:

static async Task Main(string[] args)
{
    var command = new RootCommand
    {
        new Argument<StreamReader>("stream")
    };

    command.Handler = CommandHandler.Create(
        (StreamReader stream) =>
        {
            var fileContent = stream.ReadToEnd();
            Console.WriteLine(fileContent);
        });
    await command.InvokeAsync(args);
}

In this case, the argument is a StreamReader, that is created directly from a file name. Then, I use the ReadToEnd method to read the file into a string, and then show the contents in the console.

You can also pass a complex class and minimize the number of parameters that are passed to the handler.

All this is really fine, we now have a great method to parse the command line, but it’s a little clumsy: create a command, then declare a handler to process the commands and then call the InvokeAsync method to process the parameters and execute the code. Wouldn’t it be better to have something easier to parse the command line? Well, there is. Microsoft went further and created DragonFruit – with it, you can add your parameters directly in the Main method.

Instead of adding the System.CommandLine NuGet package, you must add the System.CommandLine.DragonFruit package and, all of a sudden, your Main method can receive the data you want as parameters. For example, the first program will turn into:

static void Main(bool verbose, int[] numbers)
{
    if (numbers != null)
    {
        if (verbose)
            Console.WriteLine($"Adding {string.Join(' ', numbers)}");

        Console.WriteLine(numbers.Sum());
    }
    else
    {
        if (verbose)
            Console.WriteLine("No numbers to sum");
    }
}

If you run it, you will have something like this:

If you notice the code, it is the same as the handler. What Microsoft is doing, under the hood, is to hide all this boilerplate from you and calling your main program with the parameters you are defining. If you want to make a parameter an argument, with no option, you should name it as argumentargs or arguments:

static void Main(bool verbose, int[] args)
{
    if (args != null)
    {
        if (verbose)
            Console.WriteLine($"Adding {string.Join(' ', args)}");

        Console.WriteLine(args.Sum());
    }
    else
    {
        if (verbose)
            Console.WriteLine("No numbers to sum");
    }
}

 

You can also define default values with the exactly same way you would do with other methods:

static void Main(int number = 10, bool verbose = false)
{
    var primes = GetPrimesLessThan(number);
    Console.WriteLine($"Found {primes.Length} primes less than {number}");
    Console.WriteLine($"Last prime last than {number} is {primes.Last()}");
    if (verbose)
    {
        Console.WriteLine($"Primes: {string.Join(' ',primes)}");
    }
}

private static int[] GetPrimesLessThan(int maxValue)
{
    if (maxValue <= 1)
        return new int[0];
    ;
    var primeArray = Enumerable.Range(0, maxValue).ToArray();
    var sizeOfArray = primeArray.Length;

    primeArray[0] = primeArray[1] = 0;

    for (int i = 2; i < Math.Sqrt(sizeOfArray - 1) + 1; i++)
    {
        if (primeArray[i] <= 0) continue;
        for (var j = 2 * i; j < sizeOfArray; j += i)
            primeArray[j] = 0;
    }

    return primeArray.Where(n => n > 0).ToArray();
}

If you want more help in your app, you can add xml comments in the app, they will be used when the help is requested:

/// <summary>
/// Lists files of the selected directory.
/// </summary>
/// <param name="argument">The directory name</param>
static void Main(DirectoryInfo argument)
{
    argument ??= new DirectoryInfo(".");
    foreach (var file in argument.GetFiles())
    {
        Console.WriteLine($"{file.Name,-40} {file.Length}");
    }
}

As you can see, there are a lot of options for command line processing, and DragonFruit makes it easier to process them. But that is not everything, the same team has also made some enhancements to make use of the new Ansi terminal features, in System.CommandLine.Rendering. For example, if you want to list the files in a table, you can use code like this:

static void Main(InvocationContext context, DirectoryInfo argument= null)
{
    argument ??= new DirectoryInfo(".");
    var consoleRenderer = new ConsoleRenderer(
        context.Console,
        context.BindingContext.OutputMode(),
        true);

    var tableView = new TableView<FileInfo>
    {
        Items = argument.EnumerateFiles().ToList()
    };

    tableView.AddColumn(f => f.Name, "Name");

    tableView.AddColumn(f => f.LastWriteTime, "Modified");

    tableView.AddColumn(f => f.Length, "Size");

    var screen = new ScreenView(consoleRenderer, context.Console) { Child = tableView };
    screen.Render();
}

As you can see, the table is formatted for you. You may have noticed another thing: the first parameter, context, is not entered by the user. This is because you can have some variables injected for you by DragonFruit.

There are a lot of possibilities with System.CommandLine and this is a very welcome addition. I really liked it and, although it’s still on preview, it’s very nice and I’m sure I’ll use it a lot. And you, what do you think?

All the source code for this article is at https://github.com/bsonnino/CommandLine