I'm creating a Powershell script to deploy some code and part of the process is to call a command-line compression tool called RAR.EXE to back-up some folders.
I'm attempting to dynamically build out the parameters and then have powershell call the command with the variables but I'm running into trouble. It isn't working...
Run the following script and you should see what I'm talking about. The parameters being passed in as a variable are being mangled. If I pass the entire command + parameters in I get the infamous "is not recognized as a cmdlet..." message.
Thanks for any help!
echo "this should succeed"
& cmd /c echo foo
echo "why does this echo out an additional double quote?"
$param = "/c echo foo"
& cmd "$param"
echo "this does the same"
$param = "/c echo foo"
& cmd $param
echo "escaping the slash doesn't work either..."
$param = "`/c echo foo"
& cmd $param
echo "this fails, but why?"
$cmd = "cmd /c echo foo"
&$cmd
The call operator '&' is unnecessary in this case. It is used to invoke a command in a new scope. This is typically used to invoke a command specified by a string or scriptblock. It also has the side benefit that any variables created in say a PowerShell script are discarded after the command finishes and the scope goes away.
However since the cmd is an EXE it executes in a completely different process. FWIW, you get similar output directly from cmd.exe:
> cmd "/c echo foo"
foo"
So the extra quote on the end is a cmd.exe issue. Typically you need to keep the command separate from the parameters when PowerShell is doing the parsing to invoke the command e.g.
45> & { $foo = "foo" }
46> $foo # Note that $foo wasn't found - it went away with the scope
47> . { $foo = "foo" } # dotting executes in the current scope
48> $foo
foo
The notable exception here is that Invoke-Expression behaves like an "evaluate this string" function. Use with care, especially if the user provides the string. Your day could suck if they provided "ri C:\ -r".
In this case, as others have suggested I would pull the /c out of the string $param string and specify it e.g.:
cmd /c $param
Or use Invoke-Expression but use with care. BTW when you are trying to debug issues with sending arguments to EXE from PowerShell, check out the echoargs utility in PowerShell Community Extensions (http://pscx.codeplex.com). It is very handy:
49> $param = "/c echo foo"
50> echoargs $param
Arg 0 is </c echo foo>
This shows that cmd.exe receives "/c echo foo" as a single argument. "/c" should be a separate argument from "echo foo" (the command to execute).
I have had problems with the & call operator in the past when trying to invoke executable type commands like you are trying. Not sure I understand why. Invoke-Expression however, always seems to work in this context:
PS C:\> $cmd = "cmd /c echo foo"
PS C:\> Invoke-expression $cmd
foo
One other way I found to do this was to create an array of arguments for the command line and use that with the apersand & call operator. Something like this:
$exe = "cmd";
[Array]$params = "/c", "echo", "foo";
& $exe $params;
It's worked well for me.
I originally found this technique here:
http://techstumbler.blogspot.com/2009/12/windows-commands-with-arguments-in.html
Your last example if failing because "&" treats the string as one argument, so it is looking for a program named "cmd /c echo foo.exe". :)
This works:
& $cmd $params
As for the double quote issue, it does seem that cmd does not like the quotes around arguments that PowerShell puts. It gets this:
cmd "/c echo foo"
So I think it treats everything after /c as the exact command, so like so:
echo foo"
Some command line programs and funky parsing of the command line (that is why PowerShell takes over this job for functions and cmdlets). In the case of cmd, I would suggest this:
$param = "echo foo"
& cmd /c $param
it's an artifact of using cmd /c, I think. running
$param = "echo foo"
cmd /c $param
works fine. Unless you have a real code example, it's a bit hard to trouble shoot.
Args are treated differently when they are contained in a String:
PS D:\> echo "1 2 3"
1 2 3
PS D:\> echo 1 2 3
1
2
3
The same results occur when you use a variable for the args:
PS D:\> $param = "1 2 3"
PS D:\> echo $param
1 2 3
The SOLUTION is to use an Array:
PS D:\> $param = @(1,2,3)
PS D:\> echo $param
1
2
3