しばたテックブログ

気分で書いている技術ブログです。

PowerShellのError output streamは標準エラー出力から何を受け取っているのか?

前のエントリたちのさらに続き。これで最後です。

stknohg.hatenablog.jp

stknohg.hatenablog.jp

stknohg.hatenablog.jp

最後に標準エラー出力からError output streamに対してどういったデータが流れているか確認してみます。

検証プログラム

今回も検証プログラムOutput2.exeを用意します。

using System;
using System.Text;

namespace Output2
{
    class Program
    {
        static void Main(string[] args)
        {                        
            // 標準エラー出力に渡すバイト列を随時変えていく
            var Buffer = System.Text.Encoding.ASCII.GetBytes("Error!");

            var StdErr = Console.OpenStandardError();
            StdErr.Write(Buffer, 0, Buffer.Length);
        }
    }
}

基本的に前回の検証プログラムOutput.exeと同様で、Bufferに設定したバイト列を標準エラー出力に書き出します。
本エントリではこのBufferの部分を随時変えたOutput2.exeを用意して検証を進めていきます。

検証の前に

今回は標準エラー出力、Error output streamにデータを流すのでPowerShellから取得するに一工夫する必要があります。

単純に$Output2 = .\Output2.exeとしても下の図の様になってしまい$Output2$nullになってしまいます。

f:id:stknohg:20150628163028p:plain

そこで$Output2 = .\Output2.exe 2>&1の様に2>&1してやると$Output2に値を設定することができます。

f:id:stknohg:20150628163336p:plain

これをふまえて次に進んでいきます。

PowerShellのError output streamは標準エラー出力から何を受け取っているのか?

まずは、Bufferの部分を

var Buffer = System.Text.Encoding.ASCII.GetBytes("Error!");

としたASCIIコードでError!(45 72 72 6F 72 21)を出力した結果を見てみます。

# [Console]::OutputEncoding = [Text.Encoding]::Default
# var Buffer = System.Text.Encoding.ASCII.GetBytes("Hello!");
PS C:\Temp> $Output2 = .\Output2.exe 2>&1
PS C:\Temp> $Output2
.\Output2.exe : Error!
発生場所 行:1 文字:12
+ $Output2 = .\Output2.exe 2>&1
+            ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (Error!:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

PS C:\Temp> $Output2.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     ErrorRecord                              System.Object


PS C:\Temp> $Output2.TargetObject
Error!
PS C:\Temp> $Output2.Exception.Message
Error!

今回はError output streamにデータが渡されるので最終的なデータの型がErrorRecord型になっています。
標準エラー出力に出力した部分はTargetObjectプロパティや、Exception.Messageプロパティに設定される様です。

前回同様にGetPipeline.exeを使って外部プロセス間でパイプラインを繋ぐとどうなるかというと、

f:id:stknohg:20150628164425p:plain

となりかなり差が出てしまっています。
これは単純に.\Output2.exe 2>&1を実行してコンソールに出力される

.\Output2.exe : Error!
発生場所 行:1 文字:1
+ .\Output2.exe 2>&1
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (Error!:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

の部分がパイプラインで渡されているためです。

続けて、Bufferの部分を

var Buffer = System.Text.Encoding.Default.GetBytes("あ");

としたMS932のあ(82 A0)を出力した結果を見てみます。

最初に[Console]::OutputEncoding = [Text.Encoding]::Defaultの場合、

# [Console]::OutputEncoding = [Text.Encoding]::Default
# var Buffer = System.Text.Encoding.Default.GetBytes("あ");
PS C:\Temp> $Output2 = .\Output2.exe 2>&1
PS C:\Temp> $Output2
.\Output2.exe : あ
発生場所 行:1 文字:12
+ $Output2 = .\Output2.exe 2>&1
+            ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (あ:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

PS C:\Temp> $Output2.TargetObject
あ
PS C:\Temp> $Output2.Exception.Message
あ

次に[Console]::OutputEncoding = [Text.Encoding]::ASCIIの場合、

# var Buffer = System.Text.Encoding.Default.GetBytes("あ");
PS C:\Temp> [Console]::OutputEncoding = [Text.Encoding]::ASCII
PS C:\Temp> $Output2 = .\Output2.exe 2>&1
PS C:\Temp> $Output2
.\Output2.exe : ??
???? ?:1 ??:12
+ $Output2 = .\Output2.exe 2>&1
+            ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (??:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

PS C:\Temp> $Output2.TargetObject
??
PS C:\Temp> $Output2.Exception.Message
??

となり、標準出力→Standard output streamの時と同様に標準エラー出力→Error output streamにデータを渡す際も[Console]::OutputEncodingでエンコードされた文字列にしている様です。

補足

前回同様、適当なバイナリデータを渡しても[Console]::OutputEncodingでエンコードした文字列扱いとなります。

改行ありのデータ

var Buffer = System.Text.Encoding.Default.GetBytes("1行目\n2行目");

を渡すと、

# [Console]::OutputEncoding = [Text.Encoding]::Default
# var Buffer = System.Text.Encoding.Default.GetBytes("1行目\n2行目");
PS C:\Temp> $Output2 = .\Output2.exe 2>&1
PS C:\Temp> $Output2
.\Output2.exe : 1行目
発生場所 行:1 文字:12
+ $Output2 = .\Output2.exe 2>&1
+            ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (1行目:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

2行目
PS C:\Temp> $Output2.Count
2
PS C:\Temp> $Output2.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array


PS C:\Temp> $Output2[0].GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     ErrorRecord                              System.Object


PS C:\Temp> $Output2[1].GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     ErrorRecord                              System.Object

となりErrorRecord[]が渡されていることがわかります。