Как вы выполняете произвольную нативную команду из строки?

208

Я могу выразить свою потребность в следующем сценарии: Напишите функцию, которая принимает строку для запуска как собственную команду.

Идея не слишком далека: если вы взаимодействуете с другими утилитами командной строки из другой части компании, которые предоставляют вам команду для дословного запуска. Поскольку вы не управляете командой, вам необходимо принять любую допустимую команду в качестве ввода . Вот основные икоты, которые я не смог легко преодолеть:

  1. Команда может выполнить программу, находящуюся в пути с пробелом в ней:

    $command = '"C:\Program Files\TheProg\Runit.exe" Hello';
  2. Команда может иметь параметры с пробелами в них:

    $command = 'echo "hello world!"';
  3. Команда может иметь одинарные и двойные галочки:

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

Есть ли какой-нибудь чистый способ сделать это? Я только смог придумать обильные и безобразные обходные пути, но для языка сценариев я чувствую, что это должно быть очень просто.

Джонни Кауфман
источник

Ответы:

318

Invoke-Expressionтакже псевдоним как iex. Следующее будет работать на ваших примерах № 2 и № 3:

iex $command

Некоторые строки не будут работать как есть, например, ваш пример # 1, потому что exe находится в кавычках. Это будет работать как есть, потому что содержимое строки точно так же, как вы запускаете ее прямо из командной строки Powershell:

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

Однако, если exe находится в кавычках, вам нужна помощь, &чтобы запустить его, как в этом примере, как запуск из командной строки:

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

И тогда в сценарии:

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

Вероятно, вы могли бы обработать почти все случаи, обнаружив, является ли первый символ командной строки ", как в этой наивной реализации:

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

Но вы можете найти некоторые другие случаи, которые должны вызываться по-другому. В этом случае вам нужно будет либо использовать try{}catch{}, возможно, для определенных типов исключений / сообщений, либо изучить командную строку.

Если вы всегда получаете абсолютные пути, а не относительные, у вас не должно быть особых случаев, если они есть, кроме 2 выше.

Джоэл Б Фант
источник
3
Псевдоним великолепен. Помните, что если вы переместитесь на другую машину или отправите этот скрипт кому-то еще, этот псевдоним, вероятно, не будет установлен. Предпочитайте полные имена функций PowerShell.
Дуг Финке
1
@Doug: Большую часть времени я делаю это или использую встроенные псевдонимы (особенно для краткости в командной строке). Все evalнаполовину шутит, потому что так его называют во многих других языках сценариев, и это не первый вопрос, о котором я не знал invoke-expression. И случай ОП звучит как внутренний сценарий.
Джоэл Б. Фэнт
Я пробовал это, но он не работает с: $ command = '"C: \ Program Files \ Windows Media Player \ mplayer2.exe" "H: \ Audio \ Music \ Stevie Wonder \ Stevie Wonder - Superstition.mp3" '
Джонни Кауфман
3
Возможно, вам придется поставить «&» или «.» подписать перед фактической командой, если она не является родной PowerShell, например,Invoke-Expression "& $command"
Torbjörn Bergstedt
Торбьерн Бергштедт побеждает! Волшебный дополнительный «&» решил мою проблему! В результате я и счастлив и взволнован.
Джонни Кауфман
19

Пожалуйста, ознакомьтесь также с отчетом Microsoft Connect о том, насколько сложно использовать PowerShell для запуска команд оболочки (о, ирония судьбы).

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

Они предлагают использовать --% как способ заставить PowerShell прекратить попытки интерпретировать текст вправо.

Например:

MSBuild /t:Publish --% /p:TargetDatabaseName="MyDatabase";TargetConnectionString="Data Source=.\;Integrated Security=True" /p:SqlPublishProfilePath="Deploy.publish.xml" Database.sqlproj
Люк Пуплетт
источник
1
Ах, и Microsoft сломала интернет, и ссылка больше не действительна.
Йохан Буле
1
Выше ссылка находится на archive.org на web.archive.org/web/20131122050220/http://…
mwfearnley
4

Принятый ответ не работал для меня при попытке проанализировать реестр на наличие строк удаления и выполнить их. Оказывается, мне не нужно было звонить в Invoke-Expressionконце концов.

Я наконец-то наткнулся на этот красивый шаблон, чтобы увидеть, как выполнять строки удаления:

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

Это работает для меня, а именно потому, $appчто я знаю, что внутреннее приложение будет иметь только два аргумента. Для более сложных строк деинсталляции вы можете использовать оператор соединения . Кроме того, я просто использовал хеш-карту, но на самом деле вы, вероятно, захотите использовать массив.

Кроме того, если у вас установлено несколько версий одного и того же приложения, этот деинсталлятор будет циклически повторять их все одновременно, что сбивает с толку MsiExec.exe, так что есть и это.

Droogans
источник
1

Если вы хотите использовать оператор вызова, аргументы могут быть массивом, хранящимся в переменной:

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

Оператор вызова также работает с объектами ApplicationInfo.

$prog = get-command cmd
$myargs = -split '/c dir /x'
& $prog $myargs
js2010
источник