Friday, April 15, 2016

.NET parsing command line arguments differently from CMD.EXE

Cracked command line screen 3D I came upon a StackOverflow question today that sounded plain silly to me. In it someone was complaining that parsing a command line like "x\" -y returns a single argument, not two. And that is ridiculous, since the operating system doesn't do that.

Just to prove myself right (because I love being right) I created a batch file that displays all command line parameters:
SET counter=1
IF "%1"=="" EXIT
ECHO %counter% %1
SET /A counter=counter+1
GOTO loop

I ran the batch file with the command line supplied in the SO question: -p -z "E:\" -v "f:\" -r -o -s –c and the result was
1 -p
2 -z
3 "E:\"
4 -v
5 "f:\"
6 -r
7 -o
8 -s
9 –c
See? I was right! The command line is properly parsed. But just to be sure, I created a .NET console application:
static void Main(string[] args)
    for (var i=0; i<args.Length; i++)
        Console.WriteLine(i + "  " + args[i]);
and the result was... different!
0  -p
1  -z
2  E:\
3  -v
4  f:" -r -o -s -c
Never would I have imagined that .NET console applications would do different things from system applications, especially since they are both made by Microsoft.

Investigating the problem, I found a page that explained the rules of parsing command line arguments. It was for C++, but it applied perfectly. Apparently the caret (^) is treated just as any other character (not like by the operating system that treats it as an escape character) and a quote preceded by a backslash is considered an ordinary character as well (not like the operating system that ignores it).

So, how do I get the command line the same way the operating system does? First of all I need the raw unprocessed command line. I get that using Environment.CommandLine. Then I need to split it. Of course, I can make my own code, but I want to use the same stuff as the operating system - in this case Windows, we are not discussing .NET Core or Mono here - so I will be "Pinvoking" the Windows CommandLineToArgvW function.

And, of course, it didn't work. What I did here was basically recreating the Environment.GetCommandLineArgs method, which returns the exact same result as the args array!

Now that I had already gone through the rabbit hole, I got to this very informative link about it: How Command Line Parameters Are Parsed. In it, you may find the very aptly named chapter Everyone Parses Differently which shows that there are even differences between how C and VB programs parse the command line.

Bottom line: while you may be able to get the command line from the Environment class, there is no "standard" for parsing command line arguments, instead each compiler and operating system version implements their own. Consequently, I suggest that the only way to be consistent in how you parse command line arguments is to parse them yourself.