web-dev-qa-db-fra.com

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

Je peux exprimer mon besoin avec le scénario suivant: Écrivez une fonction qui accepte qu'une chaîne soit exécutée en tant que commande native.

L’idée n’est pas exagérée: si vous vous connectez à d’autres utilitaires de ligne de commande provenant d’autres fournisseurs de la société, vous recevrez une commande permettant d’exécuter mot pour mot. Comme vous ne contrôlez pas la commande, vous devez accepter toute commande valide en tant qu'entrée . Ce sont les principaux hoquets que je n'ai pas pu surmonter facilement:

  1. La commande peut exécuter un programme résidant 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`"";
    

Existe-t-il une manière propre de réaliser cela? Je n’ai pu que concevoir des solutions de contournement somptueuses et laides, mais pour un langage de script, j’ai le sentiment que cela devrait être extrêmement simple.

191
Johnny Kauffman

Invoke-Expression, alias également comme iex. Ce qui suit va travailler sur vos exemples n ° 2 et n ° 3:

iex $command

Certaines chaînes ne fonctionneront pas telles quelles, comme votre exemple n ° 1, car l'exécutable est entre guillemets. Cela fonctionnera tel quel, car le contenu de la chaîne correspond exactement à la manière dont 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 de & pour l'exécuter, comme dans cet exemple, à 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"

Probablement, vous pourriez 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.

290
Joel B Fant

Veuillez également consulter ce rapport Microsoft Connect sur, en gros, à quel point il est difficile d’utiliser PowerShell pour exécuter des commandes Shell (oh, ironie).

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

Ils suggèrent d'utiliser --% comme moyen de forcer PowerShell à cesser 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
17
Luke Puplett

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

Je suis finalement tombé sur ceci 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, à ma connaissance, ne comportera que deux arguments. Pour les chaînes de désinstallation plus complexes, vous pouvez utiliser le opérateur de jointure . En outre, je viens d'utiliser une carte de hachage, mais en réalité, 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 parcourera toutes en même temps, ce qui déroutera MsiExec.exe, donc il y a aussi cela.

4
Droogans