Thursday, December 24, 2009

Powershell like command line arguments in a C# Console Application

Source Code: http://www.box.net/shared/5l587aig4k

Here at work, we use Powershell to script some automated integration tests. Sometimes, simple scripts aren't enough, and we need to resort to writing Cmdlets. For the purpose of this blog post, all you really need to know about Cmdlets is that they're basically a C# class that's invoked from the Powershell command line. What I thought was really cool with Cmdlets, is the way it deals with command-line arguments.

Basically, there's a ParameterAttribute that you put on your properties, and those properties then get set from the command line. Here's a simple example:



public class MyCmdlet : Cmdlet
{
[Parameter]
public int IntArg { get; set; }
}



Then from the Powershell command line you would do something like this:

PS>MyCmdlet -IntArg 100

And then within your Cmdlet class, your IntArg property will automatically be set to 100.

It then dawned on me, we have many Console apps at work that each have their own way of parsing the arguments. Now I know there are many command line parsers out there, but I thought, wouldn't be be cool if we can have the kind of syntax for invoking Console applications just like Powershell does.

I won't explain all of the code, you can download it at the bottom of this post, and dig in yourself, but I'll just highlight a few points. Firstly, this is how you can use this library:

Say you have an application, that you need to pass in different characteristics of a person. Things like, Name, Age, Height and Email Address, you'd want to be able to invoke the application like this:

MyConsoleApp.exe -Name "Alex Friedman" -Age 27 -Height 69.5 -EmailAddress "a.friedman07@gmail.com"

First you'd need to create a Person class to represent all the command line arguments:



internal class Person
{
[Parameter]
public string Name { get; set; }

[Parameter]
public int Age { get; set; }

[Parameter]
public double Height { get; set; }

[Parameter(Mandatory=true)]
public string EmailAddress { get; set; }
}


Then, in your main method, you'd do this:



ArgsSetter<Person> argsSetter = new ArgsSetter<Person>(args);
Person person = argsSetter.BuildParameterObject();


And that's it. Your person object will have all the properties set. The thing to note here is that this library can deal with all primitive types including strings. Remember, all arguments come in as a string, even the number 27 for example gets passed in as the string representation of "27", yet it gets converted for you automatically to an int.

A few other things to note, is the error handling. I didn't implement extensive validation in this library. What I did implement was two things. First, there's an IValidator interface that does some error checking for fatal errors; errors that would prevent from even attempting to parse. I provide a DefaultValidator that checks that there are an even number of elements in the args array. You can think of the command line arguments as a Key-Value pair; the key being the argument name, and the value being the argument value. Therefore, if there's an odd number of args, something isn't right. Also, it makes sure to check that arguments begin with a "-". If that's not enough, you can implement your own IValidator.

The second part of error handling, is to make sure that the values are correct, and that the Mandatory arguments have been provided. To get that information you do something like this:



foreach (var error in setter.Errors)
{
Console.WriteLine("Paramname: {0}, Error: {1}", error.ArgumentName, error.ErrorType);
}


The ArgsSetter class has an Errors collection that gives you all the errors that occurred.

Here's a link to the source code. It contains the actual library, as well as some unit tests:
http://www.box.net/shared/5l587aig4k

That's pretty much all there is to it. Feel free to download the source code and mess with it. I'd like to stress the point that there's alot more that can be done with this, and if you do use this and add something, feel free to drop me an email. Also, any constructive criticism is always welcome. Enjoy!