Comment exécutez-vous une commande native arbitraire à partir d'une chaîne?

208

Je peux exprimer mon besoin avec le scénario suivant: Écrire une fonction qui accepte une chaîne à exécuter en tant que commande native.

Ce n'est pas trop tiré par les cheveux d'une idée: si vous vous connectez à d'autres utilitaires de ligne de commande ailleurs dans l'entreprise qui vous fournissent une commande pour exécuter mot pour mot. Comme vous ne contrôlez pas la commande, vous devez accepter toute commande valide en entrée . Ce sont les principaux hoquets que j'ai été incapable de surmonter facilement:

  1. La commande peut exécuter un programme vivant dans un chemin contenant un espace:

    $command = '"C:\Program Files\TheProg\Runit.exe" Hello';
  2. La commande peut avoir des paramètres avec des espaces:

    $command = 'echo "hello world!"';
  3. La commande peut avoir des ticks simples et doubles:

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

Y a-t-il un moyen propre d'accomplir cela? Je n'ai pu concevoir que des solutions de contournement somptueuses et laides, mais pour un langage de script, je pense que cela devrait être simple.

Johnny Kauffman
la source

Réponses:

318

Invoke-Expression, alias également iex. Les éléments suivants fonctionneront sur vos exemples # 2 et # 3:

iex $command

Certaines chaînes ne s'exécuteront pas telles quelles, comme votre exemple n ° 1 car l'exe est entre guillemets. Cela fonctionnera tel quel, car le contenu de la chaîne est exactement comme vous l'exécuteriez directement à partir d'une invite de commande Powershell:

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

Cependant, si l'exe est entre guillemets, vous avez besoin de l'aide &pour le faire fonctionner, comme dans cet exemple, comme exécuté à partir de la ligne de commande:

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

Et puis dans le script:

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

Vous pourriez probablement gérer presque tous les cas en détectant si le premier caractère de la chaîne de commande est ", comme dans cette implémentation naïve:

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

Mais vous pouvez trouver d'autres cas qui doivent être invoqués d'une manière différente. Dans ce cas, vous devrez soit utiliser try{}catch{}, peut-être pour des types / messages d'exception spécifiques, soit examiner la chaîne de commande.

Si vous recevez toujours des chemins absolus au lieu de chemins relatifs, vous ne devriez pas avoir beaucoup de cas spéciaux, le cas échéant, en dehors des 2 ci-dessus.

Joel B Fant
la source
3
Le repliement est génial. N'oubliez pas que si vous passez sur une autre machine ou envoyez ce script à quelqu'un d'autre, cet alias ne sera probablement pas configuré. Préférez les noms complets des fonctions PowerShell.
Doug Finke
1
@Doug: La plupart du temps, je fais ça ou j'utilise les alias intégrés (surtout pour plus de brièveté sur la ligne de commande). Les evalchoses plaisantent à moitié parce que c'est ce qu'on appelle dans de nombreux autres langages de script, et ce n'est pas la première question que j'ai vue sur laquelle quelqu'un n'avait aucune idée invoke-expression. Et le boîtier de l'OP ne ressemble qu'à un script interne.
Joel B Fant
J'ai essayé cela, mais cela ne fonctionne pas avec: $ command = '"C: \ Program Files \ Windows Media Player \ mplayer2.exe" "H: \ Audio \ Music \ Stevie Wonder \ Stevie Wonder - Superstition.mp3" '
Johnny Kauffman
3
Vous devrez peut-être mettre un "&" ou "." signer avant la commande réelle si elle n'est pas native de PowerShell, par exempleInvoke-Expression "& $command"
Torbjörn Bergstedt
Torbjörn Bergstedt gagne! L'extra magique "&" a résolu mon problème! En conséquence, je suis à la fois heureux et troublé.
Johnny Kauffman
19

Veuillez également consulter ce rapport de Microsoft Connect sur essentiellement la difficulté de l'utilisation de PowerShell pour exécuter des commandes shell (oh, l'ironie).

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

Ils suggèrent d'utiliser --%comme moyen de forcer PowerShell à arrêter d'essayer d'interpréter le texte à droite.

Par exemple:

MSBuild /t:Publish --% /p:TargetDatabaseName="MyDatabase";TargetConnectionString="Data Source=.\;Integrated Security=True" /p:SqlPublishProfilePath="Deploy.publish.xml" Database.sqlproj
Luke Puplett
la source
1
Ah, et Microsoft a cassé Internet et le lien n'est plus valide.
Johan Boulé
1
Le lien ci-dessus se trouve sur archive.org à web.archive.org/web/20131122050220/http://…
mwfearnley
4

La réponse acceptée ne fonctionnait pas pour moi lorsque j'essayais d'analyser le registre pour désinstaller des chaînes et de les exécuter. Il s'avère que je n'avais pas besoin de l'appel Invoke-Expressionaprès tout.

Je suis finalement tombé sur ce joli modèle pour voir comment exécuter les chaînes de désinstallation:

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

Cela fonctionne pour moi, notamment parce qu'il $apps'agit d'une application interne qui, je le sais, n'aura que deux arguments. Pour des chaînes de désinstallation plus complexes, vous pouvez utiliser l' opérateur de jointure . De plus, je viens d'utiliser une table de hachage, mais vraiment, vous voudrez probablement utiliser un tableau.

De plus, si vous avez plusieurs versions de la même application installées, ce programme de désinstallation les parcourra toutes à la fois, ce qui est déroutant MsiExec.exe, il y a donc cela aussi.

Droogans
la source
1

Si vous souhaitez utiliser l'opérateur d'appel, les arguments peuvent être un tableau stocké dans une variable:

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

L'opérateur d'appel fonctionne également avec les objets ApplicationInfo.

$prog = get-command cmd
$myargs = -split '/c dir /x'
& $prog $myargs
js2010
la source