[dev] PowerShell から msbuild

普通に Visual Studio の作った csproj なんかを使ったバッチ処理PowerShell でやっていて、msbuild を呼び出していたのだが一部のビルド処理が正常終了しなくてあれれ?って状態になった。
PowerShell 上で msbuild に渡しているコマンドラインを表示してコマンドプロンプトからたたくと正常に終了するので、PowerShell からの呼び出しだけが正常にいかないということになる。
PowerShell は外部プログラムの呼び出し時に引数リストにあるオブジェクトを文字列に展開する。その際に、一般的な Win32 アプリケーションが期待するコマンドライン解析に適合するように、その文字列を加工する。

1つは、オブジェクトの個数と引数の個数が合致するような展開を行う。CMD ではバッチ処理環境変数を展開後に文字列として処理されるので、

SET A=foo bar
SET B=baz
program %A% %B%

のような内容は、2つの引数ではなく3つの引数として扱われることになる。少し上にも書いているが、これは「期待するコマンドライン解析」の話でしかない。Win32 ではコマンドライン引数は1つの文字列として与えられるので、引数をどのように解析してどのように処理するかはプログラム次第だ。とはいえ、代表的なプログラム言語であったC言語では main() の引数に分解された引数をとったりするものだから、デファクトスタンダード的な解析が存在することとなった。


もう1つ、PowerShell はパス区切り文字を \ と / の両方が使えるが、一般的な外部プログラムは \ しか使えないということがある。このため、PowerShell から渡された相対パスまたは絶対パスが実在する場合に / 区切り形式のパスが \ 区切りに変換される。今回の問題の原因になった機能がこれだ。

.NET Framework のビルドルールでは、\ 区切りでパス指定をする場所、/ 区切りでパスを指定する場所、どっちでもいい場所が決まっている。このため、コマンドラインからパスを指定するプロパティを設定しようとすると、すべてが \ 区切りになってしまって、正常に動かないというオチだった。*1

対策としては、

  • Microsoft.Build.Engine を New-Object して使う
  • 外部ファイルにパラメータを書き出して、パラメータファイルとして読み込む

あたりが対処方法かな〜。

*1:全体的には、汎用ビルドルールは \ 区切り、C# 専用のビルドルールは両方が扱え、Url とディレクトリのどっちでも受け取れるプロパティは / で区切らないといけない