Salida UTF-8 de PowerShell

Estoy tratando de usar Process.Start con E / S redirigida para llamar a PowerShell.exe con una cadena, y para recuperar la salida, todo en UTF-8 . Pero parece que no puedo hacer que esto funcione.

Lo que he intentado:

  • Pasar el comando para ejecutar a través del parámetro -Command
  • Escribir el script de PowerShell como un archivo en el disco con encoding UTF-8
  • Escribir el script de PowerShell como un archivo en el disco con UTF-8 con encoding BOM
  • Escribir el script de PowerShell como un archivo en el disco con UTF-16
  • Configuración de Console.OutputEncoding tanto en mi aplicación de consola como en la secuencia de comandos de PowerShell
  • Estableciendo $OutputEncoding en PowerShell
  • Configuración de Process.StartInfo.StandardOutputEncoding
  • Haciendo todo con Encoding.Unicode lugar de Encoding.UTF8

En todos los casos, cuando inspecciono los bytes que me dan, obtengo valores diferentes a mi cadena original. Realmente me encantaría una explicación de por qué esto no funciona.

Aquí está mi código:

 static void Main(string[] args) { DumpBytes("Héllo"); ExecuteCommand("PowerShell.exe", "-Command \"$OutputEncoding = [System.Text.Encoding]::UTF8 ; Write-Output 'Héllo';\"", Environment.CurrentDirectory, DumpBytes, DumpBytes); Console.ReadLine(); } static void DumpBytes(string text) { Console.Write(text + " " + string.Join(",", Encoding.UTF8.GetBytes(text).Select(b => b.ToString("X")))); Console.WriteLine(); } static int ExecuteCommand(string executable, string arguments, string workingDirectory, Action output, Action error) { try { using (var process = new Process()) { process.StartInfo.FileName = executable; process.StartInfo.Arguments = arguments; process.StartInfo.WorkingDirectory = workingDirectory; process.StartInfo.UseShellExecute = false; process.StartInfo.CreateNoWindow = true; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.StartInfo.StandardOutputEncoding = Encoding.UTF8; process.StartInfo.StandardErrorEncoding = Encoding.UTF8; using (var outputWaitHandle = new AutoResetEvent(false)) using (var errorWaitHandle = new AutoResetEvent(false)) { process.OutputDataReceived += (sender, e) => { if (e.Data == null) { outputWaitHandle.Set(); } else { output(e.Data); } }; process.ErrorDataReceived += (sender, e) => { if (e.Data == null) { errorWaitHandle.Set(); } else { error(e.Data); } }; process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); process.WaitForExit(); outputWaitHandle.WaitOne(); errorWaitHandle.WaitOne(); return process.ExitCode; } } } catch (Exception ex) { throw new Exception(string.Format("Error when attempting to execute {0}: {1}", executable, ex.Message), ex); } } 

Actualización 1

Descubrí que si hago este script:

 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 Write-Host "Héllo!" [Console]::WriteLine("Héllo") 

Luego invocarlo a través de:

 ExecuteCommand("PowerShell.exe", "-File C:\\Users\\Paul\\Desktop\\Foo.ps1", Environment.CurrentDirectory, DumpBytes, DumpBytes); 

La primera línea está dañada, pero la segunda no es:

 H?llo! 48,EF,BF,BD,6C,6C,6F,21 Héllo 48,C3,A9,6C,6C,6F 

Esto me sugiere que mi código de redireccionamiento funciona bien; cuando uso Console.WriteLine en PowerShell obtengo UTF-8 como esperaba.

Esto significa que los comandos Write-Output y Write-Host PowerShell deben hacer algo diferente con la salida y no simplemente llamar a Console.WriteLine .

Actualización 2

Incluso he intentado lo siguiente para forzar la página de códigos de la consola PowerShell a UTF-8, pero Write-Host y Write-Output continúan produciendo resultados rotos mientras [Console]::WriteLine funciona.

 $sig = @' [DllImport("kernel32.dll")] public static extern bool SetConsoleCP(uint wCodePageID); [DllImport("kernel32.dll")] public static extern bool SetConsoleOutputCP(uint wCodePageID); '@ $type = Add-Type -MemberDefinition $sig -Name Win32Utils -Namespace Foo -PassThru $type::SetConsoleCP(65001) $type::SetConsoleOutputCP(65001) Write-Host "Héllo!" & chcp # Tells us 65001 (UTF-8) is being used 

Este es un error en .NET. Cuando se inicia PowerShell, almacena en caché el manejador de salida (Console.Out). La propiedad de Codificación de ese escritor de texto no recoge el valor de la propiedad StandardOutputEncoding.

Cuando lo cambia desde PowerShell, la propiedad Codificación del escritor de salida en caché devuelve el valor en caché, por lo que la salida sigue codificada con la encoding predeterminada.

Como solución alternativa, sugeriría no cambiar la encoding. Se le devolverá como una cadena Unicode, en cuyo punto puede administrar la encoding usted mismo.

Ejemplo de almacenamiento en caché:

 102 [C:\Users\leeholm] >> $r1 = [Console]::Out 103 [C:\Users\leeholm] >> $r1 Encoding FormatProvider -------- -------------- System.Text.SBCSCodePageEncoding en-US 104 [C:\Users\leeholm] >> [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 105 [C:\Users\leeholm] >> $r1 Encoding FormatProvider -------- -------------- System.Text.SBCSCodePageEncoding en-US 

No es un experto en encoding, pero después de leer estos …

… parece bastante claro que la variable $ OutputEncoding solo afecta los datos transmitidos a las aplicaciones nativas.

Si se envía a un archivo desde PowerShell, la encoding se puede controlar mediante el parámetro -encoding en el cmdlet out-file por ejemplo

 write-output "hola" |  fuera de archivo "enctest.txt" -encoding utf8

No hay nada más que pueda hacer en el frente de PowerShell, pero la siguiente publicación puede ayudarle:

Establezca [Console]::OuputEncoding como encoding lo que desee e imprima con [Console]::WriteLine .

Si el método de salida de PowerShell tiene un problema, entonces no lo use. Se siente un poco mal, pero funciona como un encanto 🙂

Pasé algún tiempo trabajando en una solución a mi problema y pensé que podría ser de interés. Me encontré con un problema al tratar de automatizar la generación de código utilizando PowerShell 3.0 en Windows 8. El IDE de destino era el comstackdor Keil que utilizaba MDK-ARM Essential Toolchain 5.24.1. Un poco diferente de OP, ya que estoy usando PowerShell de forma nativa durante el paso previo a la comstackción. Cuando traté de #incluir el archivo generado, recibí el error

error fatal: marca de orden de byte UTF-16 (LE) detectada ‘.. \ GITVersion.h’ pero la encoding no es compatible

Resolví el problema cambiando la línea que generó el archivo de salida desde:

 out-file -FilePath GITVersion.h -InputObject $result 

a:

 out-file -FilePath GITVersion.h -Encoding ascii -InputObject $result