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

#scripting #powershell

#сценарии #powershell

Вопрос:

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

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

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

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

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

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

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

Ответ №1:

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

 iex $command
  

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

 $command = 'C:somepathsomeexe.exe somearg'
iex $command
  

Однако, если exe-файл заключен в кавычки, вам понадобится помощь amp; , чтобы запустить его, как в этом примере, при запуске из командной строки:

 >> amp;"C:Program FilesSome ProductSomeExe.exe" "C:some other pathfile.ext"
  

И затем в сценарии:

 $command = '"C:Program FilesSome ProductSomeExe.exe" "C:some other pathfile.ext"'
iex "amp; $command"
  

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

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

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

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

Комментарии:

1. Наложение псевдонимов — это здорово. Помните, что если вы перейдете на другую машину или отправите этот скрипт кому-то другому, этот псевдоним, вероятно, не будет настроен. Предпочитайте полные имена функций PowerShell.

2. @Doug: Большую часть времени я делаю это или использую встроенные псевдонимы (особенно для краткости в командной строке). eval Это полушутя, потому что так это называется во многих других языках сценариев, и это не первый вопрос, который я видел, о котором кто-то понятия не имел invoke-expression . И случай OP звучит как только внутренний скрипт.

3. Я пробовал это, но это не работает с: $command = ‘»C:Program Файлы Windows Media Playermplayer2.exe » «H:AudioMusicStevie Wonder Стиви Уандер — Суеверие.mp3″‘

4. Возможно, вам придется поставить знак «amp;» или «.» перед фактической командой, если она не является встроенной для powershell, например Invoke-Expression "amp; $command"

5. Я не смог заставить вышеуказанное работать для меня так, как мне было нужно. В итоге я использовал amp; cmd / c $ command, которая не требовала изменения кавычек в $ command

Ответ №2:

Пожалуйста, также ознакомьтесь с этим отчетом 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 отключила Интернет, и ссылка больше не действительна.

2. Приведенная выше ссылка включена archive.org в web.archive.org/web/20131122050220/http ://…

3. Мне нравится эта статья. Полностью подводит итог тому, что «pwsh» не является оболочкой.

Ответ №3:

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

 $prog = 'c:windowssystem32cmd.exe'
$myargs = '/c','dir','/x'
amp; $prog $myargs
  

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

 $prog = get-command cmd
$myargs = -split '/c dir /x'
amp; $prog $myargs
  

Комментарии:

1. @YoraiLevi режим аргументации против режима выражения amp; $wsl (-split "--install -d Ubuntu-20.04")

Ответ №4:

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

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

 $path = 'HKLM:SOFTWAREWow6432NodeMicrosoftWindowsCurrentVersionUninstall'
$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(' ')
    amp; $uninstall_app $uninstall_arg
}
  

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

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

Комментарии:

1. Предполагается, что путь к удалению не содержит пробелов.