web-dev-qa-db-fra.com

Sortie UTF-8 de PowerShell

J'essaie d'utiliser Process.Start avec les E/S redirigées pour appeler PowerShell.exe avec une chaîne et pour obtenir la sortie, le tout dans UTF-8 . Mais je ne semble pas être capable de faire ce travail.

Ce que j'ai essayé

  • Passage de la commande à exécuter via le paramètre -Command
  • Écriture du script PowerShell en tant que fichier sur disque avec codage UTF-8
  • Écriture du script PowerShell en tant que fichier sur disque avec UTF-8 avec BOM encoding
  • Écrire le script PowerShell sous forme de fichier sur disque avec UTF-16
  • Définition de Console.OutputEncoding dans mon application de console et dans le script PowerShell
  • Définition de $OutputEncoding dans PowerShell
  • Réglage Process.StartInfo.StandardOutputEncoding
  • Tout faire avec Encoding.Unicode au lieu de Encoding.UTF8

Dans tous les cas, lorsque j'inspecte les octets qui me sont attribués, ma chaîne d'origine reçoit des valeurs différentes. J'aimerais vraiment une explication quant à pourquoi cela ne fonctionne pas.

Voici mon code:

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<string> output, Action<string> 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);
    }
}

Mise à jour 1

J'ai trouvé que si je fais ce script:

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

Puis invoquez-le via:

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

La première ligne est corrompue, mais la seconde n'est pas:

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

Cela me suggère que mon code de redirection fonctionne correctement; Lorsque j'utilise Console.WriteLine dans PowerShell, je reçois le format UTF-8, comme je l’attendais.

Cela signifie que les commandes Write-Output et Write-Host de PowerShell doivent faire quelque chose de différent avec la sortie et ne pas simplement appeler Console.WriteLine.

Mise à jour 2

J'ai même essayé ce qui suit pour forcer la page de codes de la console PowerShell à UTF-8, mais Write-Host et Write-Output continuent de produire des résultats cassés tant que [Console]::WriteLine fonctionne.

$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
41
Paul Stovell

C'est un bogue dans .NET. Lorsque PowerShell est lancé, il met en cache le descripteur de sortie (Console.Out). La propriété Encoding de ce rédacteur de texte ne sélectionne pas la propriété ValueOutputEncoding.

Lorsque vous le modifiez depuis PowerShell, la propriété Encoding de l'enregistreur de sortie mis en cache renvoie la valeur mise en cache, de sorte que la sortie est toujours encodée avec l'encodage par défaut.

En guise de solution de contournement, je suggère de ne pas modifier le codage. Il vous sera renvoyé sous forme de chaîne Unicode. Vous pourrez alors gérer vous-même l'encodage.

Exemple de mise en cache:

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
20
LeeHolmes

Pas un expert en encodage, mais après avoir lu ces ...

... il semble assez clair que la variable $ OutputEncoding affecte uniquement les données acheminées vers des applications natives. 

Si vous envoyez un fichier depuis PowerShell vers un fichier, le codage peut être contrôlé par le paramètre -encoding de la cmdlet out-file, par exemple.

 write-output "hello" | fichier "enctest.txt" codant utf8 

Rien d’autre que vous ne puissiez faire sur le front PowerShell alors, mais le post suivant pourrait bien vous aider:.

19
andyb

Définissez le [Console]::OuputEncoding comme encodant ce que vous voulez et imprimez-le avec [Console]::WriteLine

Si la méthode de sortie powershell a un problème, ne l'utilisez pas. Ça fait un peu mal, mais ça marche comme un charme :)

1
jhk

J'ai passé du temps à chercher une solution à mon problème et j'ai pensé que cela pourrait être intéressant. J'ai rencontré un problème en essayant d'automatiser la génération de code à l'aide de PowerShell 3.0 sous Windows 8. La cible IDE était le compilateur Keil utilisant MDK-ARM Essential Toolchain 5.24.1. Un peu différent de OP, car j'utilise PowerShell de manière native lors de l'étape de pré-construction. Quand j'ai essayé d'inclure # le fichier généré, j'ai reçu l'erreur 

erreur fatale: la marque d'ordre d'octet UTF-16 (LE) a été détectée '..\GITVersion.h' mais l'encodage n'est pas supporté

J'ai résolu le problème en modifiant la ligne qui a généré le fichier de sortie à partir de:

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

à:

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