web-dev-qa-db-ja.com

PowerShellからのUTF-8出力

リダイレクトされたI/OでProcess.Startを使用して、文字列でPowerShell.exeを呼び出し、出力を取得しようとしています。すべて TF-8 です。しかし、私はこの仕事をすることができないようです。

私が試したもの:

  • -Commandパラメーターを介して実行するコマンドを渡す
  • PowerShellスクリプトをUTF-8エンコーディングでディスクにファイルとして書き込む
  • PowerShellスクリプトを BOM encodingを使用してUTF-8でディスクにファイルとして書き込む
  • PowerShellスクリプトをファイルとしてディスクにUTF-16で書き込む
  • コンソールアプリケーションとPowerShellスクリプトの両方でConsole.OutputEncodingを設定する
  • PowerShellで$OutputEncodingを設定する
  • Process.StartInfo.StandardOutputEncodingの設定
  • Encoding.Unicodeの代わりにEncoding.UTF8ですべてを行う

どの場合でも、与えられたバイトを調べると、元の文字列とは異なる値を取得します。これがうまくいかない理由についての説明が本当に欲しいです。

ここに私のコードがあります:

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);
    }
}

アップデート1

このスクリプトを作成すると、次のことがわかりました。

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

次に、以下を介して呼び出します。

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

最初の行は破損していますが、2番目の行は破損していません。

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

これは、リダイレクトコードがすべて正常に機能していることを示唆しています。 PowerShellでConsole.WriteLineを使用すると、期待どおりにUTF-8が取得されます。

これは、単にWrite-Outputを呼び出すのではなく、PowerShellのWrite-HostコマンドとConsole.WriteLineコマンドが出力で異なる処理を行う必要があることを意味します。

更新2

PowerShellコンソールのコードページを強制的にUTF-8にするために次のことも試みましたが、Write-HostWrite-Outputは壊れた結果を生成し続けますが、[Console]::WriteLineは機能します。

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

これは.NETのバグです。 PowerShellが起動すると、出力ハンドル(Console.Out)がキャッシュされます。そのテキストライターのEncodingプロパティは、StandardOutputEncodingプロパティの値を取得しません。

PowerShell内から変更すると、キャッシュされた出力ライターのEncodingプロパティはキャッシュされた値を返すため、出力は引き続きデフォルトのエンコードでエンコードされます。

回避策として、エンコードを変更しないことをお勧めします。 Unicode文字列として返されます。この時点で、エンコードを自分で管理できます。

キャッシングの例:

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

エンコーディングの専門家ではなく、これらを読んだ後...

... $ OutputEncoding変数がネイティブアプリケーションにパイプされたデータにのみ影響することはかなり明らかです。

PowerShellを使用してファイルに送信する場合、エンコードは-encodingコマンドレットのout-fileパラメーターによって制御できます。

 write-output "hello" | out-file "enctest.txt" -enuting utf8 

PowerShellのフロントでは他にできることは何もありませんが、次の投稿が役立ちます。

19
andyb

[Console]::OuputEncodingを任意のエンコードとして設定し、[Console]::WriteLineで出力します。

Powershell ouputメソッドに問題がある場合は、使用しないでください。それは少し悪い感じですが、魅力のように動作します:)

2
jhk

私の問題の解決に時間を費やし、それが興味があるかもしれないと思った。 Windows 8でPowerShell 3.0を使用してコード生成を自動化しようとして問題が発生しました。ターゲットIDEは、MDK-ARM Essential Toolchain 5.24.1を使用するKeilコンパイラーでした。ビルド前のステップでPowerShellをネイティブに使用しているため、OPとは少し異なります。生成されたファイルを#includeしようとしたときに、エラーが表示されました

致命的なエラー:UTF-16(LE)バイトオーダーマークは '..\GITVersion.h'を検出しましたが、エンコードはサポートされていません

出力ファイルを生成する行を次のように変更することで問題を解決しました。

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

に:

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