Solve Problems with External Command Lines in PowerShell
Summary: Microsoft Scripting Guy Ed Wilson discusses problems creating external command arguments using Windows PowerShell.
Hey, Scripting Guy! I am using an external program that takes a –o command-line parameter followed by a path location. The program permits no space between the –o parameter and the supplied path. For example, Windows PowerShell sees an unclosed quote when I type the following command:
.\7za.exe x -o”C:\TARGET\P F” C:\TEMP\ProgramFiles.zip
I need to escape the second (but not the first) double-quote for Windows PowerShell to parse the line correctly. Here is the revised command, with the escape character marked in yellow:
.\7za.exe x -o”C:\TARGET\P F`” C:\TEMP\ProgramFiles.zip
This command looks strange, and I’m confused as to why this is.
Microsoft Scripting Guy Ed Wilson here. One of the things I do not enjoy about Windows PowerShell is the problem inherent in attempting to create a workable command line for complex external utilities. On occasion, I have spent several hours attempting to derive a workable command line for a single program. It is not just a Windows PowerShell problem; I have spent several hours attempting to figure out acceptable syntax for command-line utilities in the old command-prompt days as well. A quick survey of some of my friends reveals it is not just me.
Windows PowerShell exacerbates the problem of command-line argument parsing because Windows PowerShell has its own syntax parser that must run before it passes control over to the external program.
One thing to keep in mind is that external programs, such as 7-Zip can define their own rules for parsing arguments – and this includes their own quoting rules and escape characters. (In fact, 7-Zip defines its own rules for interpreting wildcard characters as well.) To make matters worse, at times we expect Windows PowerShell to execute commands, parse Windows PowerShell paths, update variables, and a host of other things before actually passing the resulting hodgepodge to the external utility for parsing.
Not all is lost. A couple of things can help make sense on the Windows PowerShell side. One of my favorite tools comes with the PowerShell Community Extension Project (PSCX). I have written several times about the PSCX, and recently, I helped the Scripting Wife add the command to import the PSCX into her Windows PowerShell profile. One tool from that project that is useful to me when dealing with complex command lines is the EchoArgs.exe program. When the PSCX is installed, the installer copies the EchoArgs.exe utility to the installation directory. The tool is extremely easy to use. A quick example illustrates how to use EchoArgs.exe to see how Windows PowerShell will parse a command line.
Suppose I want to use the nbtstat.exe program, but I am not sure how Windows PowerShell will parse the command line, and determine the arguments and the values supplied to the arguments to the command. The command line is shown here:
nbtstat -S 2
To use the EchoArgs.exe program, I simply copy the arguments from the command line and paste them after the EchoArgs program. This is shown here, along with the associated output:
PS C:\> EchoArgs.exe -S 2
Arg 0 is <-S>
Arg 1 is <2>
In the preceding example, it is clear that Windows PowerShell sees the first argument as –S and the second argument as 2.
If I have the situation when a value needs to be calculated and I am not certain how Windows PowerShell will handle the argument, I can use EchoArgs to demystify the command line as well. This is shown here where I assign a value of 5 to the $a variable, and then perform a calculation to compute the value I wish to pass to the –S argument:
PS C:\> $a = 5
PS C:\> EchoArgs.exe -S ($a-3)
Arg 0 is <-S>
Arg 1 is <2>
Based upon the preceding, I feel confident that the following command would work (and it does):
PS C:\> $a = 5
PS C:\> nbtstat -S ($a-3)
AM, the figure that follows illustrates using the EchoArgs utility to parse the arguments from the command line you supplied.
As you can see, the EchoArgs utility is pretty slick and easy to use. Another way to see how Windows PowerShell will parse a command line is to ask it. That is right—by using the Windows PowerShell tokenizer (I wrote several articles about using the tokenizer), it is possible to see exactly how Windows PowerShell will interpret a command line.
To begin with, I will parse the same easy nbtstat command I used with the EchoArgs utility. This will allow for a comparison between the two methods. The following command uses the tokenize static method from the psparser .NET Framework class. The first parameter is the command to parse. The [ref]$null is a requirement of the Tokenize command.
[management.automation.psparser]::Tokenize(‘nbtstat -S 3’, [ref]$null)
The command and associated output are shown in the following figure.
The output from the tokenize static method is rather extensive. The good thing is that it breaks everything into its role within the command—and not simply arg 0 and arg 1 as returned from the EchoArgs utility. The downside is that it is a bit more typing.
But wait! This is Windows PowerShell, and a quick function can save lots of typing. I created the Get-Args function to make it easy to use the tokenize static method. The complete Get-Args function is shown here:
} #end function Get-Args
To use the Get-Args function, I run the function to load it into memory, and then I supply the command line to the function. This technique is shown in the following figure.
When I call the function, I pass the entire command inside of literal quotation marks. The output is the same as the output received earlier when I used the tokenize method directly. The command and associated output are shown in the following figure.
One cool thing is that I can parse your original command line, the one that was interpreted as incomplete, by using this method. As shown in the following figure, the Get-Args function easily parses the command. The other thing I like about this method is that I do not have to separate the arguments from the command. I simply copy the entire line.
Well, AM, I hope this discussion helps you solve the mystery of the funky command line. I invite you to join me tomorrow for more cool Windows PowerShell stuff.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at firstname.lastname@example.org, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy