Why cant I execute a commandlet with arguments from a string in powershell?

Tag: powershell Author: zczcgame Date: 2009-07-30

In windows powershell, I am trying to store a move command in a string and then execute it. Can someone tell me why this doesn't work?

PS C:\Temp\> dir
    Directory: Microsoft.PowerShell.Core\FileSystem::C:\Temp

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---         8/14/2009   8:05 PM       2596 sa.csproj
-a---         8/15/2009  10:42 AM          0 test.ps1


PS C:\Temp> $str = "mv sa.csproj sb.csproj"
PS C:\Temp> &$str
The term 'mv sa.csproj sb.csproj' is not recognized as a cmdlet, function, operable program, or script file. Verify the
 term and try again.
At line:1 char:2
+ &$ <<<< str
PS C:\Temp>

I get this error when storing any command with arguments. How do I overcome this limitation?

Best Answer

From the help (about_Operators):

&  Call operator
  Description: Runs a command, script, or script block. Because the call
  operator does not parse, it cannot interpret command parameters.

You can use a script block instead of a string:

$s = { mv sa.csproj sb.csproj }
& $s

Or you can use Invoke-Expression:

Invoke-Expression $str

or

iex $str

In contrast to &, Invoke-Expression does parse the string contents, so you can put anything in there, not just a single command.

comments:

Thanks, do you think you can explain (or link to) an explanation of what the & character does?
& is for executing a single command, script or scriptblock, just like the help states. It doesn't do any parsing so it only accepts a single argument which then is assumed to be an invokable command. If you want to add parameters, then either wrap the call in a script or script block or use Invoke-Expression. You can find the help page describing the operators by typing help about_operators in Powershell.

Other Answer1

All of Johannes suggestions are spot on but I want to make folks aware that in V2 there is another option. You put the command in a string as before but you put the parameters in a hashtable and invoke using & e.g.:

$cmd = 'mv'
$params = @{Path = 'log.txt'; Destination = 'log.bak'}
&$cmd @params

This uses a new V2 feature called splatting. It is kind of like using a response file for a command but you put the parameters in a hashtable instead of a file.

Note: you can also put the parameters in an array which will plug in the parameters "by positional" rather than by name e.g.:

$cmd = "cpi"
$params = ('log.txt', 'log.bak')
&$cmd @params

It may not exactly fit the OP's problem but it is a trick worth knowing about.

Other Answer2

I had to answer a similar question a while back about invoking an external program after building up a list of arguments:

Checked and it looks like using . $program $argString or & $program $argString does not work if $argString contains multiple arguments. The . and & operators just do a direct call, passing the entire $argString as a single argument.

Instead of using [string]$argString and doing $argString += "-aaa `"b b b`"", you could change it to [string[]]$argArray and use $argArray += @('-aaa', 'b b b'), which will work and will automatically escape strings as necessary.

You can also use $process = [Diagnostics.Process]::Start( $program, $argString ). This will make it easier to track execution progress, but harder to retrieve any text output.

Or you can put everything in a string and use Invoke-Expression "$program $argString", which will parse the entire string as a command and execute it correctly.

As Keith mentioned, splatting is another good option in PowerShell 2.