PowerShell elimina las comillas dobles de los argumentos de la línea de comandos

Recientemente, he tenido problemas para usar GnuWin32 de PowerShell cuando se trata de comillas dobles.

Tras una investigación adicional, parece que PowerShell está eliminando comillas dobles de los argumentos de la línea de comandos, incluso cuando escapó correctamente.

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" 

Observe que las comillas dobles están allí cuando se pasan al cmdlet echo de PowerShell, pero cuando se pasan como un argumento a echo.exe , las comillas dobles se eliminan a menos que se escape con una barra diagonal inversa (aunque el carácter de escape de PowerShell es un backsick, no una barra invertida).

Esto me parece un error. Si paso las cadenas escapadas correctas a PowerShell, entonces PowerShell debería encargarse de cualquier escape que sea necesario para invocar el comando.

¿Que esta pasando aqui?

Por ahora, la solución es escapar de los argumentos de línea de comando de acuerdo con estas reglas (que parecen ser utilizadas por la llamada a la API CreateProcess que PowerShell usa para invocar archivos .exe):

  • Para pasar una comilla doble, escape con una barra diagonal inversa: \" -> "
  • Para pasar una o más barras invertidas seguidas de una comilla doble, escapa cada barra invertida con otra barra invertida y escapa de la cita: \\\\\" -> \\"
  • Si no se sigue con una comilla doble, no es necesario el escape para las barras diagonales inversas: \\ -> \\

Tenga en cuenta que puede ser necesario seguir escapando de las comillas dobles para evitar las comillas dobles en la cadena escapada API de Windows a PowerShell.

Estos son algunos ejemplos, con 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 "\\" \\ 

Imagino que esto puede convertirse rápidamente en un infierno si necesitas pasar un complicado parámetro de línea de comando. Por supuesto, nada de esto está documentado en la documentación de CreateProcess() o PowerShell.

También tenga en cuenta que esto no es necesario para pasar argumentos con comillas dobles a funciones .NET o cmdlets de PowerShell. Para eso, solo necesita escapar sus comillas dobles a PowerShell.

Es algo conocido :

Es MUY DIFÍCIL pasar parámetros a las aplicaciones que requieren cadenas entre comillas. Hice esta pregunta en IRC con un “cuarto lleno” de expertos de PowerShell, y le tomó hora a alguien encontrar la manera (originalmente comencé a publicar aquí que simplemente no es posible). Esto rompe completamente la capacidad de PowerShell de servir como un shell de propósito general, porque no podemos hacer cosas simples como ejecutar sqlcmd. El trabajo número uno de un shell de comandos debería estar ejecutando aplicaciones de línea de comandos … Como ejemplo, al tratar de usar SqlCmd desde SQL Server 2008, hay un parámetro -v que toma una serie de parámetros name: value. Si el valor tiene espacios en él, debe citarlo …

… no hay una sola forma de escribir una línea de comando para invocar esta aplicación correctamente, por lo que incluso después de dominar las 4 o 5 formas diferentes de citar y escapar cosas, todavía está adivinando qué funcionará cuando … o bien, puede pagar en cmd y terminarlo.

Personalmente evito usar ‘\’ para escapar cosas en PowerShell, porque técnicamente no es un personaje de escape de shell. He obtenido resultados impredecibles con eso. En cadenas de comillas dobles, puede usar "" para obtener una comilla doble incrustada o escapársela con una marca de retroceso:

 PS C:\Users\Droj> "string ""with`" quotes" string "with" quotes 

Lo mismo ocurre con las comillas simples:

 PS C:\Users\Droj> 'string ''with'' quotes' string 'with' quotes 

Lo extraño de enviar parámetros a progtwigs externos es que hay un nivel adicional de evaluación de citas. No sé si esto es un error, pero supongo que no cambiará, porque el comportamiento es el mismo cuando usa Start-Process y pasa los argumentos. Start-Process toma una matriz para los argumentos, lo que hace las cosas un poco más claras, en términos de cuántos argumentos se envían en realidad, pero esos argumentos parecen ser evaluados en un tiempo adicional.

Entonces, si tengo una matriz, puedo configurar los valores de arg para que tengan citas incrustadas:

 PS C:\cygwin\home\Droj> $aa = 'arg="foo"', 'arg=""""bar""""' PS C:\cygwin\home\Droj> echo $aa arg="foo" arg=""""bar"""" 

El argumento ‘barra’ tiene suficiente para cubrir la evaluación extra oculta. Es como si enviara ese valor a un cmdlet entre comillas dobles, luego envíe ese resultado de nuevo con comillas dobles:

 PS C:\cygwin\home\Droj> echo "arg=""""bar""""" # level one arg=""bar"" PS C:\cygwin\home\Droj> echo "arg=""bar""" # hidden level arg="bar" 

Uno esperaría que estos argumentos pasasen a comandos externos tal como están, como a cmdlets como ‘echo’ / ‘write-output’, pero no lo son, debido a ese nivel oculto:

 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" 

No sé la razón exacta para ello, pero el comportamiento es como si hubiera otro paso indocumentado bajo las cubiertas que re-analiza las cadenas. Por ejemplo, obtengo el mismo resultado si envío la matriz a un cmdlet, pero agrego un nivel de análisis haciéndolo a través de invoke-expression :

 PS C:\cygwin\home\Droj> $aa = 'arg="foo"', 'arg=""""bar""""' PS C:\cygwin\home\Droj> iex "echo $aa" arg=foo arg="bar" 

… que es exactamente lo que obtengo cuando envío estos argumentos a mi cygwin ‘echo.exe externo:

 PS C:\cygwin\home\Droj> c:\cygwin\bin\echo 'arg="foo"' 'arg=""""bar""""' arg=foo arg="bar" 

Esto parece estar solucionado en las versiones recientes de PowerShell en el momento de escribir esto, por lo que ya no es algo de lo que preocuparse.

Si aún cree que ve este problema, recuerde que puede estar relacionado con otra cosa, como el progtwig que invoca PowerShell, por lo que si no puede reproducirlo al invocar PowerShell directamente desde un símbolo del sistema o el ISE, debe depurar en otro lugar.

Por ejemplo, encontré esta pregunta al investigar un problema de desaparición de las cotizaciones al ejecutar un archivo de PowerShell desde el código de C # utilizando Process.Start . El problema era realmente C # Process Start necesita Argumentos con comillas dobles: desaparecen

Confiar en el CMD para pagar el problema como se indica en la respuesta aceptada no funcionó para mí, ya que las comillas dobles aún se eliminaron al llamar al ejecutable de CMD.

La mejor solución para mí fue estructurar mi línea de comando como una matriz de cadenas en lugar de una sola cadena completa que contiene todos los argumentos. Luego, simplemente pase esa matriz como argumentos para la invocación binaria:

 $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 

En ese caso, las comillas dobles que rodean los argumentos se pasan correctamente al comando invocado.

Si lo necesita, puede probarlo y depurarlo con EchoArgs de las extensiones de la comunidad de PowerShell.

Puede resolver el problema del comportamiento de PowerShell triplicando todas las comillas dobles:

 PS> .\echo.exe '"""help"""' "help" PS> .\echo.exe '"""help""""""' "help"" 

Este comportamiento está algo documentado en ProcessStartInfo.Argument .

Funciona para ejecutables que no hacen nada divertido con los argumentos mismos. El ejemplo específico de alguien del argumento sqlcmd -v tiene un comportamiento extraño al cotizar en primer lugar, incluso desde el símbolo del sistema.