¿Cómo se ejecuta un comando nativo arbitrario desde una cadena?

208

Puedo expresar mi necesidad con el siguiente escenario: Escriba una función que acepte una cadena para ejecutarse como un comando nativo.

No es una idea demasiado descabellada: si está interactuando con otras utilidades de línea de comandos de otras partes de la compañía que le proporcionan un comando para ejecutar textualmente. Debido a que no controla el comando, debe aceptar cualquier comando válido como entrada . Estos son los principales problemas que no he podido superar fácilmente:

  1. El comando podría ejecutar un programa que vive en una ruta con un espacio en él:

    $command = '"C:\Program Files\TheProg\Runit.exe" Hello';
  2. El comando puede tener parámetros con espacios en ellos:

    $command = 'echo "hello world!"';
  3. El comando puede tener marcas simples y dobles:

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

¿Hay alguna forma limpia de lograr esto? Solo he sido capaz de idear soluciones fastuosas y feas, pero para un lenguaje de script creo que esto debería ser muy simple.

Johnny Kauffman
fuente

Respuestas:

318

Invoke-Expression, también alias como iex. Lo siguiente funcionará en sus ejemplos # 2 y # 3:

iex $command

Algunas cadenas no se ejecutarán tal cual, como su ejemplo # 1 porque el exe está entre comillas. Esto funcionará tal cual, porque el contenido de la cadena es exactamente como lo ejecutaría directamente desde un símbolo del sistema de Powershell:

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

Sin embargo, si el exe está entre comillas, necesita la ayuda de &ejecutarlo, como en este ejemplo, como se ejecuta desde la línea de comandos:

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

Y luego en el guión:

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

Probablemente, podría manejar casi todos los casos detectando si el primer carácter de la cadena de comando es ", como en esta implementación ingenua:

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

Pero puede encontrar algunos otros casos que deben invocarse de una manera diferente. En ese caso, deberá usar try{}catch{}, quizás para tipos / mensajes de excepción específicos, o examinar la cadena de comandos.

Si siempre recibe rutas absolutas en lugar de rutas relativas, no debería tener muchos casos especiales, si los hay, fuera de los 2 anteriores.

Joel B Fant
fuente
3
El alias es genial. Recuerde, si se muda a otra máquina o envía ese script a otra persona, ese alias probablemente no se configurará. Prefiere los nombres completos de las funciones de PowerShell.
Doug Finke
1
@Doug: La mayoría de las veces hago eso o uso los alias integrados (especialmente por brevedad en la línea de comandos). Las evalcosas son medio bromistas porque así se llama en muchos otros lenguajes de scripting, y esta no es la primera pregunta que he visto sobre alguien que no tenía idea invoke-expression. Y el caso del OP suena solo como guión interno.
Joel B Fant
He intentado esto, pero no funciona con: $ command = '"C: \ Archivos de programa \ Windows Media Player \ mplayer2.exe" "H: \ Audio \ Music \ Stevie Wonder \ Stevie Wonder - Superstition.mp3" '
Johnny Kauffman
3
Puede que tenga que poner un "&" o "." firmar antes del comando real si no es powershell-native, por ejemploInvoke-Expression "& $command"
Torbjörn Bergstedt
¡Torbjörn Bergstedt gana! ¡El extra mágico "&" ha resuelto mi problema! Como resultado, estoy contento y nervioso.
Johnny Kauffman
19

Consulte también este informe de Microsoft Connect sobre, esencialmente, cuán difícil es usar PowerShell para ejecutar comandos de shell (oh, la ironía).

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

Sugieren usarlo --%como una forma de obligar a PowerShell a dejar de intentar interpretar el texto a la derecha.

Por ejemplo:

MSBuild /t:Publish --% /p:TargetDatabaseName="MyDatabase";TargetConnectionString="Data Source=.\;Integrated Security=True" /p:SqlPublishProfilePath="Deploy.publish.xml" Database.sqlproj
Luke Puplett
fuente
1
Ah, y Microsoft rompió Internet y el enlace ya no es válido.
Johan Boulé
1
El enlace de arriba está en archive.org en web.archive.org/web/20131122050220/http://…
mwfearnley
4

La respuesta aceptada no funcionaba para mí cuando intentaba analizar el registro para desinstalar cadenas y ejecutarlas. Resulta que no necesitaba la llamada paraInvoke-Expression después de todo.

Finalmente encontré esta bonita plantilla para ver cómo ejecutar cadenas de desinstalación:

$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
}

Esto funciona para mí, es decir, porque $appes una aplicación interna que sé que solo tendrá dos argumentos. Para cadenas de desinstalación más complejas, es posible que desee utilizar el operador de combinación . Además, acabo de usar un mapa hash, pero en realidad, probablemente quieras usar una matriz.

Además, si tiene instaladas varias versiones de la misma aplicación, este desinstalador las recorrerá todas a la vez, lo cual es confuso MsiExec.exe, por lo que también está eso.

Droogans
fuente
1

Si desea utilizar el operador de llamada, los argumentos pueden ser una matriz almacenada en una variable:

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

El operador de llamada también funciona con objetos ApplicationInfo.

$prog = get-command cmd
$myargs = -split '/c dir /x'
& $prog $myargs
js2010
fuente