PowerShell では外部プログラムの標準エラー (stderr) の扱いで大きな落とし穴があるので、まとめておく。

  • stderr を redirect して外部プログラムを起動し、 stderr に出力すると一行ごとに ErrorRecord でラップされる
  • stdout への redirect 2>&1 、ファイルへの redirect 2> a.txt$null への redirect 2>$null で ErrorRecord でのラップがされる
  • 外部プログラムの stderr を stdout やファイルに redirect すると、 NativeCommandError と出力される
  • stderr に出力があるとエラーとみなされ $?$false になり、エラーは $Error に記録される。
  • $ErrorActionPreference = "stop" していると、処理が止まる
  • PowerShell スクリプトで 外部プログラムの起動において stderr を redirect していなくても、その PowerShell スクリプトの stderr が redirect されると ErrorRecord でのラップがされる
  • $null への redirect では stderr は捨てられるが、エラーが発生したことは $Error に記録される。

対策としては、$? ではなく $LastExitCode をチェックする。 stderr の元の文字列が見たいときは stdout に redirect して ToString() する *>&1 |% { "$_" }

The Big Book of PowerShell Error Handling という本にも書かれている。

If an external executable writes anything to the StdErr stream, PowerShell sometimes sees this and wraps the text in an ErrorRecord, but this behavior doesn’t seem to be consistent. I’m not sure yet under what conditions these errors will be produced, so I tend to stick with $LASTEXITCODE when I need to tell whether an external command worked or not.

本家でもたくさん議論されていて状況がよくわからない。