Récemment, j'ai eu quelques difficultés à utiliser GnuWin32 à partir de PowerShell chaque fois que des guillemets doubles sont impliqués.
Après une enquête plus approfondie, il apparaît que PowerShell supprime les guillemets des arguments de ligne de commande, même lorsqu'ils sont correctement échappés.
PS C:\Documents and Settings\Nick> echo '"hello"'
"hello"
PS C:\Documents and Settings\Nick> echo.exe '"hello"'
hello
PS C:\Documents and Settings\Nick> echo.exe '\"hello\"'
"hello"
Notez que les guillemets doubles sont présents lorsqu'ils sont passés à la commande echo cmdlet de PowerShell, mais lorsqu'ils sont passés en tant qu'argument à echo.exe , ils sont supprimés sauf s'ils sont échappés avec une barre oblique inversée (même si le caractère d'échappement de PowerShell est un backtick, pas un backslash).
Cela semble être un bug pour moi. Si je transmets les chaînes échappées correctes à PowerShell, PowerShell doit alors prendre en charge tout ce qui est nécessaire pour échapper, quelle que soit la manière dont il appelle la commande.
Qu'est-ce qui se passe ici?
Pour le moment, le correctif consiste à échapper aux arguments de ligne de commande conformément à ces règles (qui semblent être utilisées par l'appel d'API CreateProcess
utilisé par PowerShell pour appeler les fichiers .exe):
\"
-> "
\\\\\"
-> \\"
\\
-> \\
Notez qu'il peut être nécessaire d'échapper davantage les guillemets doubles pour échapper les guillemets doubles dans la chaîne d'échappement de l'API Windows vers PowerShell.
Voici quelques exemples, avec echo.exe de GnuWin32:
PS C:\Documents and Settings\Nick> echo.exe "\`""
"
PS C:\Documents and Settings\Nick> echo.exe "\\\\\`""
\\"
PS C:\Documents and Settings\Nick> echo.exe "\\"
\\
J'imagine que cela peut rapidement devenir un enfer si vous devez passer un paramètre de ligne de commande compliqué. Bien entendu, rien de tout cela n’est documenté dans la documentation CreateProcess()
ou PowerShell.
Notez également qu'il n'est pas nécessaire de transmettre des arguments avec des guillemets doubles aux fonctions .NET ou aux applets de commande PowerShell. Pour cela, il vous suffit d'échapper vos guillemets à PowerShell.
C'est une chose connue :
Il est TRES TROP DIFFICILE de passer des paramètres aux applications nécessitant des chaînes entre guillemets. J'ai posé cette question dans IRC avec un "personnel qualifié" d'experts de PowerShell, et il a fallu une heure pour que quelqu'un trouve un moyen (j'ai commencé à poster ici que c'est tout simplement impossible). Cela brise complètement la capacité de PowerShell de servir de shell à usage général, car nous ne pouvons pas faire des choses simples comme exécuter sqlcmd. Le travail numéro un d'une commande doit exécuter des applications de ligne de commande ... Par exemple, si vous essayez d'utiliser SqlCmd à partir de SQL Server 2008, il existe un paramètre -v qui prend une série de paramètres name: value. Si la valeur contient des espaces, vous devez la citer ...
... il n'y a pas qu'un seul moyen d'écrire une ligne de commande pour appeler cette application correctement, donc même après avoir maîtrisé les 4 ou 5 manières différentes de citer et d'échapper à des choses, vous devez toujours deviner laquelle fonctionnera quand ... ou, vous pouvez simplement Shell à cmd, et se faire avec elle.
Personnellement, j’évite d’utiliser «\» pour échapper à des choses dans PowerShell, car ce n’est techniquement pas un personnage d’échappement Shell. J'ai eu des résultats imprévisibles avec. Dans les chaînes entre guillemets, vous pouvez utiliser ""
pour obtenir une double citation incorporée, ou l'échapper avec une coche arrière:
PS C:\Users\Droj> "string ""with`" quotes"
string "with" quotes
Il en va de même pour les guillemets simples:
PS C:\Users\Droj> 'string ''with'' quotes'
string 'with' quotes
Ce qui est étrange avec l'envoi de paramètres à des programmes externes, c'est qu'il existe un niveau supplémentaire d'évaluation des devis. Je ne sais pas s'il s'agit d'un bogue, mais j'imagine que cela ne sera pas modifié, car le comportement est le même lorsque vous utilisez Start-Process et transmettez des arguments. Start-Process prend un tableau pour les arguments, ce qui rend les choses un peu plus claires, en termes de nombre d'arguments réellement envoyés, mais ces arguments semblent être évalués une fois de plus.
Ainsi, si j’ai un tableau, je peux définir les valeurs arg pour avoir des guillemets incorporés:
PS C:\cygwin\home\Droj> $aa = 'arg="foo"', 'arg=""""bar""""'
PS C:\cygwin\home\Droj> echo $aa
arg="foo"
arg=""""bar""""
L'argument 'bar' suffit à couvrir l'évaluation supplémentaire cachée. C'est comme si j'envoyais cette valeur à une applet de commande entre guillemets, puis renvoyer ce résultat entre guillemets:
PS C:\cygwin\home\Droj> echo "arg=""""bar""""" # level one
arg=""bar""
PS C:\cygwin\home\Droj> echo "arg=""bar""" # hidden level
arg="bar"
On pourrait s'attendre à ce que ces arguments soient passés tels quels aux commandes externes, comme aux applets de commande telles que 'echo'/'write-output', mais ils ne le sont pas, à cause de ce niveau caché:
PS C:\cygwin\home\Droj> $aa = 'arg="foo"', 'arg=""""bar""""'
PS C:\cygwin\home\Droj> start c:\cygwin\bin\echo $aa -nonew -wait
arg=foo arg="bar"
Je ne connais pas la raison exacte pour cela, mais le comportement est comme s'il y avait une autre étape non documentée prise sous les couvertures qui réanalyse les chaînes. Par exemple, j'obtiens le même résultat si j'envoie le tableau à une cmdlet, mais j'ajoute un niveau d'analyse en le faisant via invoke-expression
:
PS C:\cygwin\home\Droj> $aa = 'arg="foo"', 'arg=""""bar""""'
PS C:\cygwin\home\Droj> iex "echo $aa"
arg=foo
arg="bar"
... ce qui est exactement ce que je reçois quand j'envoie ces arguments à mon cygwin externe 'echo.exe':
PS C:\cygwin\home\Droj> c:\cygwin\bin\echo 'arg="foo"' 'arg=""""bar""""'
arg=foo arg="bar"
Compter sur CMD pour que Shell résolve le problème, comme indiqué dans la réponse acceptée, ne m'a pas fonctionné car les guillemets étaient toujours supprimés lors de l'appel de l'exécutable de CMD.
La bonne solution pour moi était de structurer ma ligne de commande sous la forme d'un tableau de chaînes plutôt que d'une chaîne complète contenant tous les arguments. Ensuite, passez simplement ce tableau en tant qu'arguments pour l'invocation binaire:
$args = New-Object System.Collections.ArrayList
$args.Add("-U") | Out-Null
$args.Add($cred.UserName) | Out-Null
$args.Add("-P") | Out-Null
$args.Add("""$($cred.Password)""")
$args.Add("-i") | Out-Null
$args.Add("""$SqlScriptPath""") | Out-Null
& SQLCMD $args
Dans ce cas, les doubles guillemets entourant les arguments sont correctement passés à la commande invoquée.
Si vous avez besoin, vous pouvez le tester et le déboguer avec EchoArgs à partir de Extensions de la communauté PowerShell
Cela semble être résolu dans les versions récentes de PowerShell au moment de la rédaction de cet article, de sorte que ce n’est plus un sujet de préoccupation.
Si vous pensez toujours que vous rencontrez ce problème, rappelez-vous qu'il peut être lié à quelque chose d'autre, tel que le programme qui appelle PowerShell. Par conséquent, si vous ne pouvez pas le reproduire lorsque vous appelez PowerShell directement à partir d'une invite de commande ou de l'ISE, vous devez effectuer un débogage ailleurs.
Par exemple, j'ai trouvé cette question lors de la recherche d'un problème de disparition de guillemets lors de l'exécution d'un script PowerShell à partir de code C # à l'aide de Process.Start
. Le problème était en fait Le processus C # a besoin de démarrer Arguments avec guillemets doubles - ils disparaissent