Jak wykonać dowolne natywne polecenie z łańcucha?

208

Mogę wyrazić moją potrzebę w następującym scenariuszu: Napisz funkcję, która akceptuje ciąg znaków jako polecenie rodzime.

Pomysł nie jest zbyt daleko idący: jeśli łączysz się z innymi narzędziami wiersza poleceń z innych miejsc w firmie, które dostarczają ci polecenia do uruchamiania dosłownie. Ponieważ nie kontrolujesz polecenia, musisz zaakceptować każde prawidłowe polecenie jako dane wejściowe . Oto główne czkawki, których nie byłem w stanie łatwo pokonać:

  1. Polecenie może uruchomić program żyjący na ścieżce ze spacją:

    $command = '"C:\Program Files\TheProg\Runit.exe" Hello';
  2. Polecenie może zawierać parametry ze spacjami:

    $command = 'echo "hello world!"';
  3. Polecenie może mieć zarówno pojedyncze, jak i podwójne tiki:

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

Czy jest jakiś czysty sposób na osiągnięcie tego? Byłem w stanie wymyślić obfite i brzydkie obejścia, ale dla języka skryptowego uważam, że to powinno być bardzo proste.

Johnny Kauffman
źródło

Odpowiedzi:

318

Invoke-Expression, również aliasowany jako iex. Poniższe będą działać na przykładach 2 i 3:

iex $command

Niektóre ciągi nie będą działać tak, jak są, takie jak twój przykład nr 1, ponieważ exe jest w cudzysłowach. Działa to tak, jak jest, ponieważ zawartość ciągu jest dokładnie taka, jak w przypadku uruchomienia go bezpośrednio z wiersza polecenia programu Powershell:

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

Jeśli jednak exe jest w cudzysłowach, potrzebujesz pomocy, &aby uruchomić go, jak w tym przykładzie, uruchamiany z wiersza poleceń:

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

A potem w skrypcie:

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

Prawdopodobnie możesz obsłużyć prawie wszystkie przypadki, wykrywając, czy pierwszym znakiem ciągu poleceń jest ", jak w tej naiwnej implementacji:

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

Ale możesz znaleźć inne przypadki, które należy wywołać w inny sposób. W takim przypadku konieczne będzie użycie try{}catch{}, być może określonych typów wyjątków / komunikatów, lub sprawdzenie ciągu poleceń.

Jeśli zawsze otrzymujesz ścieżki bezwzględne zamiast ścieżek względnych, nie powinieneś mieć wielu specjalnych przypadków, jeśli w ogóle, poza 2 powyżej.

Joel B Fant
źródło
3
Aliasing jest świetny. Pamiętaj, że jeśli przeniesiesz się na inną maszynę lub wyślesz ten skrypt do kogoś innego, alias prawdopodobnie nie zostanie skonfigurowany. Preferuj pełne nazwy funkcji PowerShell.
Doug Finke
1
@Doug: Zazwyczaj robię to lub używam wbudowanych aliasów (szczególnie dla zwięzłości w linii poleceń). Chodzi o evalto, że na wpół żartuje, ponieważ tak to się nazywa w wielu innych językach skryptowych i nie jest to pierwsze pytanie, jakie widziałem, gdy ktoś nie miał pojęcia invoke-expression. A sprawa OP brzmi jak wewnętrzny skrypt.
Joel B Fant
Próbowałem tego, ale nie działa z: $ command = '"C: \ Program Files \ Windows Media Player \ mplayer2.exe" "H: \ Audio \ Music \ Stevie Wonder \ Stevie Wonder - Superstition.mp3" '
Johnny Kauffman
3
Może być konieczne wstawienie „&” lub „.” podpisać przed właściwym poleceniem, jeśli nie jest ono natywne dla PowerShell, np.Invoke-Expression "& $command"
Torbjörn Bergstedt
Torbjörn Bergstedt wygrywa! Magiczne dodatkowe „&” rozwiązało mój problem! W rezultacie jestem zarówno szczęśliwy, jak i zdenerwowany.
Johnny Kauffman
19

Zapoznaj się także z tym raportem Microsoft Connect, na temat tego, jak bardzo trudno jest używać programu PowerShell do uruchamiania poleceń powłoki (o ironio).

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

Sugerują użycie --%jako sposobu zmuszenia PowerShell do zaprzestania próby interpretacji tekstu po prawej stronie.

Na przykład:

MSBuild /t:Publish --% /p:TargetDatabaseName="MyDatabase";TargetConnectionString="Data Source=.\;Integrated Security=True" /p:SqlPublishProfilePath="Deploy.publish.xml" Database.sqlproj
Luke Puplett
źródło
1
Ach, a Microsoft zepsuł Internet, a link nie jest już ważny.
Johan Boulé
1
Powyższy
mwfearnley
4

Przyjęta odpowiedź nie działała dla mnie podczas próby przeanalizowania rejestru pod kątem ciągów deinstalacji i wykonania ich. Okazuje się, że mimo wszystko nie potrzebowałem telefonu Invoke-Expression.

W końcu natknąłem się na ten fajny szablon do sprawdzania, jak wykonać ciąg deinstalacyjny:

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

Działa to dla mnie, ponieważ $appjest aplikacją wewnętrzną, o której wiem, że ma tylko dwa argumenty. W przypadku bardziej złożonych ciągów deinstalacji możesz użyć operatora łączyć . Użyłem też mapy skrótów, ale tak naprawdę prawdopodobnie będziesz chciał użyć tablicy.

Ponadto, jeśli masz zainstalowanych wiele wersji tej samej aplikacji, ten deinstalator będzie je przeglądał wszystkie naraz, co myli MsiExec.exe, więc i to.

Droogany
źródło
1

Jeśli chcesz użyć operatora wywołania, argumentami może być tablica przechowywana w zmiennej:

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

Operator połączenia działa również z obiektami ApplicationInfo.

$prog = get-command cmd
$myargs = -split '/c dir /x'
& $prog $myargs
js2010
źródło