229 votes

Comment exécuter une commande native arbitraire à partir d'une chaîne de caractères ?

Je peux exprimer mon besoin avec le scénario suivant : Écrivez une fonction qui accepte une chaîne de caractères à exécuter comme une commande native.

Ce n'est pas une idée trop farfelue : si vous vous interfacez avec d'autres utilitaires en ligne de commande provenant d'autres parties de l'entreprise qui vous fournissent une commande à exécuter mot pour mot. Comme vous ne contrôlez pas la commande, vous devez accepte toute commande valide en entrée . Ce sont les principaux obstacles que je n'ai pas réussi à surmonter facilement :

  1. La commande peut exécuter un programme se trouvant dans un chemin comportant un espace :

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

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

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

Est-ce qu'il y a tout un moyen propre d'y parvenir ? Je n'ai été capable que de concevoir des solutions de contournement somptueuses et hideuses, mais pour un langage de script, je pense que cela devrait être très simple.

353voto

Joel B Fant Points 14013

Invoke-Expression également connu sous le nom de iex . Ce qui suit fonctionnera sur vos exemples #2 et #3 :

iex $command

Certaines chaînes ne fonctionneront pas telles quelles, comme votre exemple n°1, car l'exe est entre guillemets. Ceci fonctionnera tel quel, car le contenu de la chaîne est exactement comme vous le feriez 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 de & pour le faire fonctionner, comme dans cet exemple, comme exécuté depuis la ligne de commande :

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

Et ensuite dans le script :

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

Il est probable que vous puissiez traiter 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 utiliser soit try{}catch{} peut-être pour des types d'exception/messages spécifiques, ou 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 particuliers, voire aucun, en dehors des deux ci-dessus.

4 votes

L'aliasing est génial. N'oubliez pas que si vous changez de machine ou si vous envoyez ce script à quelqu'un d'autre, cet alias ne sera probablement pas configuré. Préférez les noms complets des fonctions PowerShell.

1 votes

@Doug : La plupart du temps, je fais cela ou j'utilise les alias intégrés (surtout pour la brièveté de la ligne de commande). Le site eval est à moitié plaisantée car c'est ainsi qu'elle est appelée dans de nombreux autres langages de script, et ce n'est pas la première question que j'ai vue où quelqu'un n'avait aucune idée de ce qu'est invoke-expression . Et le cas de l'OP ressemble à un script interne seulement.

0 votes

J'ai essayé, mais cela ne fonctionne pas avec : $command = '" C:\Program Fichiers \Windows Lecteur multimédia \mplayer2.exe " " H:\Audio\Music\Stevie Wonder \Stevie Wonder - Superstition.mp3 "

23voto

Luke Puplett Points 4971

Veuillez également consulter ce rapport de Microsoft Connect qui montre essentiellement à quel point il est difficile d'utiliser PowerShell pour exécuter des commandes shell (oh, quelle ironie).

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

Ils suggèrent d'utiliser --% comme un 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

2 votes

Ah, et Microsoft a cassé l'internet et le lien n'est plus valide.

3 votes

Le lien ci-dessus est sur archive.org à web.archive.org/web/20131122050220/http://…

2 votes

J'adore cet article. Il résume parfaitement ce qu'est "pwsh". no une coquille.

4voto

Droogans Points 2098

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

Je suis finalement tombé sur ce beau 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 que $app est une application interne qui, je le sais, n'aura que deux arguments. Pour des chaînes de désinstallation plus complexes, vous pouvez utiliser la fonction opérateur de jointure . De plus, je viens d'utiliser un hash-map, mais en réalité, vous voudriez probablement utiliser un tableau.

De plus, si plusieurs versions d'une même application sont installées, le désinstalleur les passe toutes en revue en même temps, ce qui perturbe les utilisateurs. MsiExec.exe donc il y a ça aussi.

0 votes

Cela suppose que le chemin de la désinstallation ne comporte pas d'espaces.

3voto

js2010 Points 823

Si vous voulez 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 travaille également avec les objets ApplicationInfo.

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

0 votes

Ceci devrait être la réponse acceptée

1 votes

@YoraiLevi mode argumentaire vs mode expression & $wsl (-split "--install -d Ubuntu-20.04")

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X