• subscribe
November 28, 2009 12:00 AM

Solving PowerShell Argument Input Errors with Wrapper Scripts

Here are 3 solutions for correctly passing strings with embedded spaces into PowerShell scripts
Windows IT Pro
InstantDoc ID #103174
Downloads
103174.zip

As a security measure, Windows PowerShell doesn't create an association between the script extension .ps1 and the PowerShell command shell. The result is that clicking on a file with the extension of .ps1 doesn't cause it to execute, and you can't just drag and drop files onto a PowerShell script icon for easy execution. You can create a Windows shortcut or a Cmd.exe batch file that wraps up the process of invoking PowerShell explicitly for a script. This workaround doesn't represent a security problem per se, because it doesn't create the vulnerabilities that a file-type association does. However, due to the way arguments are parsed and passed on by Windows, arguments that need to be enclosed in double quotes (e.g., they include spaces) aren't passed correctly to PowerShell scripts.

Fortunately, there are several ways around this problem; the most common ways involve a batch-file wrapper, which I refer to as a wrapper script for simplicity. I'll explain the problem and the basic workarounds as well as some points that might be important if you need to go further than I do here.

The Problem: Stripping Double Quotes
The easiest way to communicate the problem is to use an example. We start out with a simple PowerShell script, echodemo.ps1, which looks like this:

#echodemo.ps1
$i = 0;
foreach($arg in $args)
{$i++; "$i $arg"}

All this script does is loop through each of the arguments passed to it, writing an argument counter and the argument for each one. For ease of testing, I have the script saved in the same folder as a couple of files that have spaces in their names. As Figure 1 shows, when I run the script with the filenames as arguments, the script treats each quoted string as a separate argument.

PowerShell lets you specify command-line options when you start PowerShell, and by using the Command option, you can explicitly tell PowerShell to run a script or an internal command. You can see the detailed Help for PowerShell's command-line options by entering the following command at either a Cmd.exe or PowerShell.exe prompt:

powershell -? 

We want to run commands that need command-line parameters passed into PowerShell. According to the PowerShell 1.0 Help documentation, we just need to use

powershell -Command 

followed by a script block. This works fine—as long as there are no quotes needed for filenames or other parameters passed into PowerShell. If this sounds confusing, don't worry: In fact, it's simpler to run commands than the Help information indicates.

We're aided in the search for a solution by the fact that Cmd.exe command shell scripts have a special generic placeholder variable, %*, that substitutes in all unused arguments, putting quotes around them as needed. However, this process doesn't correctly run all commands in PowerShell. Listing 1 shows several Cmd.exe wrapper scripts I wrote to demonstrate how %* variable expansion works. These examples will let us explore what happens when you create a Cmd.exe wrapper script for running a PowerShell script that takes multiple quoted arguments.

Figure 2 shows the output from the first four scripts in Listing 1. The output from wrapper0 clearly shows that %* expansion substitutes the filenames, with quotes as needed. The wrapper1 script doesn't cause echodemo.ps1 to run at all; instead, it appears that PowerShell interprets everything within the script block brackets as a single string, and PowerShell simply echoes back what it received. In wrapper2, I include the PowerShell invocation operator, &, and quote the entire string. Now echodemo.ps1 runs, but the quotes are stripped from the quoted arguments. As a result, each word of each filename is interpreted as a separate argument. In wrapper3, I make a last-ditch attempt, which I knew would fail, quoting the argument expansion term %*. This method concatenates all of the command-line arguments into a single long argument.

So how do we pass data containing embedded spaces into PowerShell? We actually have our choice of solutions, and that's what the rest of this article is about.

Solution 1: PowerShell 2.0's File Parameter
PowerShell 2.0, which is in its Community Technology Preview (CTP) stage as I write, provides an alternative string parsing technique for running a script. You can use -File instead of -Command, followed by the path to the script and the arguments you wish to use with the script, and the arguments will be parsed correctly. So with PowerShell 2.0, we can use a batch-file wrapper such as the following command, which is also shown as wrapper4.cmd in Listing 1:

PowerShell -File echodemo.ps1 %* 

PowerShell passes the arguments following the script path directly to the script as the pre-parsed $args array. The result is that each quoted argument appears as a separate argument. Of course, this method works only if you have PowerShell 2.0 installed. You can download the CTP3 version from the Microsoft Download Center at www.microsoft.com/downloads/details.aspx?FamilyID=c913aeab-d7b4-4bb1-a958-ee6d7fe307bc.

However, telling someone they need to upgrade is one of the top 10 most irritating responses to a technical problem, so let's explore how you solve this problem with PowerShell 1.0.

Solution 2: Turn Arguments into PowerShell 1.0 Input
To increase PowerShell's flexibility, you can use a hyphen (-) following the Command parameter to tell PowerShell to interpret input as command text to execute. In a batch file, this technique means you can pipe anything, including the output of an echo command, into PowerShell and it will be received without any further parsing or quote-stripping by Windows or Cmd.exe. The wrapper5.cmd batch file shown in Listing 1 uses this technique to make PowerShell correctly interpret quoted arguments.

By the way, PowerShell automatically exits after it finishes the script when you use wrapper5.cmd. This behavior is desirable if you're using the script to accomplish a task directly. However, if you want to inspect the output from the PowerShell script, you'll want to use PowerShell's NoExit parameter, as shown in wrapper6.cmd in Listing 1.

This technique also works in the PowerShell 2.0 CTP releases, so it should be fully forward-compatible. However, it still has one basic flaw. Many of PowerShell's special characters are used in variant but regrettably common filenames. Names that contain parentheses in particular can cause PowerShell to attempt evaluating the content as an embedded command. To solve such problems, we need a better way to handle passing commands to PowerShell. We need to process items so that PowerShell unambiguously treats them as file and folder names as well. Therefore, we'll need a stronger solution than a batch-file wrapper. Our last solution uses VBScript to correctly pass arguments to PowerShell.



ARTICLE TOOLS

Comments
    There are no comments to display. Be the first one!
You must log on before posting a comment.

Are you a new visitor? Register Here