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

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

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

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

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

    $command = 'echo "hello world!"';
    
  • Команда может иметь как одиночные, так и двойные тики:

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

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

Ответ 1

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 выше.

Ответ 2

Также см. отчет Microsoft Connect о том, как blummin 'трудно использовать 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

Ответ 3

Принятый ответ не работал у меня при попытке проанализировать реестр для удаления строк и выполнить их. Оказывается, мне вообще не нужен вызов 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 - это встроенное приложение, которое, как я знаю, будет иметь только два аргумента. Для более сложных удаляемых строк вы можете использовать join operator. Кроме того, я просто использовал хэш-карту, но на самом деле вы, вероятно, захотите использовать массив.

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