237

I can express my need with the following scenario: Write a function that accepts a string to be run as a native command.

It's not too far fetched of an idea: if you're interfacing with other command-line utilities from elsewhere in the company that supply you with a command to run verbatim. Because you don't control the command, you need to accept any valid command as input. These are the main hiccups I've been unable to easily overcome:

  1. The command might execute a program living in a path with a space in it:

    $command = '"C:\Program Files\TheProg\Runit.exe" Hello';
    
  2. The command may have parameters with spaces in them:

    $command = 'echo "hello world!"';
    
  3. The command might have both single and double ticks:

    $command = "echo `"it`'s`"";
    

Is there any clean way of accomplishing this? I've only been able to devise lavish and ugly workarounds, but for a scripting language I feel like this should be dead simple.

SteveC
  • 15,808
  • 23
  • 102
  • 173
Johnny Kauffman
  • 3,613
  • 3
  • 21
  • 22

4 Answers4

363

Invoke-Expression, also aliased as iex. The following will work on your examples #2 and #3:

iex $command

Some strings won't run as-is, such as your example #1 because the exe is in quotes. This will work as-is, because the contents of the string are exactly how you would run it straight from a Powershell command prompt:

$command = 'C:\somepath\someexe.exe somearg'
iex $command

However, if the exe is in quotes, you need the help of & to get it running, as in this example, as run from the commandline:

>> &"C:\Program Files\Some Product\SomeExe.exe" "C:\some other path\file.ext"

And then in the script:

$command = '"C:\Program Files\Some Product\SomeExe.exe" "C:\some other path\file.ext"'
iex "& $command"

Likely, you could handle nearly all cases by detecting if the first character of the command string is ", like in this naive implementation:

function myeval($command) {
    if ($command[0] -eq '"') { iex "& $command" }
    else { iex $command }
}

But you may find some other cases that have to be invoked in a different way. In that case, you will need to either use try{}catch{}, perhaps for specific exception types/messages, or examine the command string.

If you always receive absolute paths instead of relative paths, you shouldn't have many special cases, if any, outside of the 2 above.

dubs
  • 6,511
  • 4
  • 19
  • 35
Joel B Fant
  • 24,406
  • 4
  • 66
  • 67
  • 4
    Aliasing is great. Remember, if you move to another machine or send that script to someone else that alias will probably not be setup. Prefer the full names of the PowerShell functions. – Doug Finke Jun 14 '11 at 01:17
  • 1
    @Doug: Most of the time I do that or use the built-in aliases (especially for brevity on the commandline). The `eval` things is half-joking because that's what it is called in so many other scripting languages, and this isn't the first question I've seen where someone had no idea about `invoke-expression`. And the OP's case sounds like in-house script only. – Joel B Fant Jun 14 '11 at 01:25
  • I've tried this, but it does not work with: $command = '"C:\Program Files\Windows Media Player\mplayer2.exe" "H:\Audio\Music\Stevie Wonder\Stevie Wonder - Superstition.mp3"' – Johnny Kauffman Jun 14 '11 at 04:40
  • 5
    You may have to put an "&" or "." sign before the actual command if it isn't powershell-native, e.g. `Invoke-Expression "& $command"` – Torbjörn Bergstedt Jun 14 '11 at 11:09
  • Torbjörn Bergstedt wins! The magical extra "&" has solved my issue! As a result, I'm both happy and flustered. – Johnny Kauffman Jun 14 '11 at 13:55
  • Yep, just flustered at the time I lost. By the way, the "if" statement that would check for a beginning quote is unnecessary, as far as I can tell. – Johnny Kauffman Jun 14 '11 at 14:30
  • Ok. I was just thinking there may be cases where throwing the `&` in prevents it from running, whereas if it begins with a quote it's almost certainly a quoted path to an exe. – Joel B Fant Jun 14 '11 at 14:43
  • Note that with the `iex "& $command"` syntax, the "last operation status" automatic variable (`$?`) returns whether or not `iex` was successful in *invoking the command*. If you want the exit code (`ERRORLEVEL`), you'd want to use `$LASTEXITCODE` (when `& $command` is invoked directly, it is automatically translated into `$?`). – Ohad Schneider Sep 10 '15 at 14:33
  • 1
    I could not get the above to work for me the way I needed. I ended up using & cmd /c $command which did not require modifying quotes in $command – Stephen Lee Parker Feb 06 '19 at 16:31
  • I don't agree with this solution. I think it gives a bad incentive to people on what to use in this scenario. `&` is the [call operator](https://ss64.com/ps/call.html), and it works by itself without the need of `Invoke-Expression`. You could to this: `& "C:\Program Files\Some Product\SomeExe.exe" @(arguments)`. `Invoke-Expression` is used when you want to run Strings as **POWERSHELL** code. From docs: `The Invoke-Expression cmdlet evaluates or runs a specified string as a command and returns the results of the expression or command.` Basically in your example, you used & in your string... – Vivere Apr 05 '21 at 06:49
  • The iex "& $cmd" version worked for me too - thank you so much - but even after poring over the MS docs for [the call operator](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_operators?view=powershell-5.1#call-operator-) and [Invoke-Expression](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-expression?view=powershell-5.1) I couldn't for the life of me explain why it works, and why both the iex and the & were necessary. – Rob Aug 08 '22 at 22:47
23

Please also see this Microsoft Connect report on essentially, how blummin' difficult it is to use PowerShell to run shell commands (oh, the irony).

http://connect.microsoft.com/PowerShell/feedback/details/376207/

They suggest using --% as a way to force PowerShell to stop trying to interpret the text to the right.

For example:

MSBuild /t:Publish --% /p:TargetDatabaseName="MyDatabase";TargetConnectionString="Data Source=.\;Integrated Security=True" /p:SqlPublishProfilePath="Deploy.publish.xml" Database.sqlproj
Luke Puplett
  • 42,091
  • 47
  • 181
  • 266
6

If you want to use the call operator, the arguments can be an array stored in a variable:

$prog = 'c:\windows\system32\cmd.exe'
$myargs = '/c','dir','/x'
& $prog $myargs

The call operator works with ApplicationInfo objects too.

$prog = get-command cmd
$myargs = -split '/c dir /x'
& $prog $myargs
js2010
  • 23,033
  • 6
  • 64
  • 66
  • 1
    @YoraiLevi argument mode vs expression mode `& $wsl (-split "--install -d Ubuntu-20.04")` – js2010 Nov 29 '21 at 00:06
4

The accepted answer wasn't working for me when trying to parse the registry for uninstall strings, and execute them. Turns out I didn't need the call to Invoke-Expression after all.

I finally came across this nice template for seeing how to execute uninstall strings:

$path = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
$app = 'MyApp'
$apps= @{}
Get-ChildItem $path | 
    Where-Object -FilterScript {$_.getvalue('DisplayName') -like $app} | 
    ForEach-Object -process {$apps.Set_Item(
        $_.getvalue('UninstallString'),
        $_.getvalue('DisplayName'))
    }

foreach ($uninstall_string in $apps.GetEnumerator()) {
    $uninstall_app, $uninstall_arg = $uninstall_string.name.split(' ')
    & $uninstall_app $uninstall_arg
}

This works for me, namely because $app is an in house application that I know will only have two arguments. For more complex uninstall strings you may want to use the join operator. Also, I just used a hash-map, but really, you'd probably want to use an array.

Also, if you do have multiple versions of the same application installed, this uninstaller will cycle through them all at once, which confuses MsiExec.exe, so there's that too.

yurisich
  • 6,991
  • 7
  • 42
  • 63