Як ви можете виконувати довільну нативну команду з рядка?


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. І справа ОП звучить як власний сценарій.
Joel B Fant

Я спробував це, але це не працює з: $ command = '"C: \ Program Files \ Windows Media Player \ mplayer2.exe" "H: \ Audio \ Music \ Stevie Wonder \ Stevie Wonder - Superstition.mp3" '
Джонні Кауффман

3
Можливо, вам доведеться поставити "&" або "." підпишіться перед фактичною командою, якщо вона не є власною, але, наприклад,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 на веб-
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, тож є і це.


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.