バッチファイル内でpowershell経由でC#コードを実行する記事は既にいくつか存在しますが、1ファイル内でバッチファイル部とC#コード部が綺麗に分かれているものが見つからなかったので、より可読性とメンテナンス性向上を目指しつつ作成してみました。
コード
RunCSCode.bat
@echo off
setlocal
set BAT_PATH=%~f0
powershell -NoProfile -Command "& {$cscode=[regex]::Split([IO.File]::ReadAllText($env:BAT_PATH,[Text.Encoding]::UTF8),':EndBatch')[2]; Add-Type -TypeDefinition $cscode -Language CSharpVersion3; [CSBatch.Program]::Main($Args);}" %*
endlocal
exit /b
:EndBatch
//CSCode
using System;
using System.Runtime.InteropServices;
namespace CSBatch
{
public class Program
{
[DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
private static extern int MessageBox(IntPtr hWnd, string text, string caption, int options);
public static int Main(string[] args)
{
string msg = "";
for( int i=0; i<args.Length; i++)
{
msg += String.Format("args[{0}]={1}\r\n", i, args[i]);
}
if( msg=="" )
{
msg = "no args.";
}
int MB_OK = 0;
MessageBox(IntPtr.Zero, msg, "CSTestCode", MB_OK);
return args.Length;
}
}
}
実行結果
>RunCSCode.bat あああ いいい ううう
3
動作原理
1.通常のバッチファイルとして起動
2.環境変数にバッチファイル自身のパスを格納(直接powershellコマンドに埋め込んでもいいかも)
3.(powershellコマンド) バッチファイル自身をFile.ReadAllTextでutf-8 stringデータとして読み込む
4.(powershellコマンド) Regex.Splitを使用して上記stringデータを:EndBatchラベルで分割し、C#コード部を分離
5.(powershellコマンド) 分離したC#コード部をAdd-Typeで型追加し、C#のメソッドを実行
6. powershellでのC#メソッド実行終了後、exit /b でバッチファイル終了。
(これにより、以降のC#コード部の影響を受けなくなる)
注意)バッチファイル読み込み時にutf-8エンコーディングを指定していますが、BOM無しutf-8でバッチファイルを保存しないと、実行時にゴミが付きます。
MessageBox.Show じゃなくて WinAPI の MessageBox 使っているのは、WinAPIも呼び出せますよっていうのを見せたかっただけです。まあC#なので出来て当たり前ですが…
なんか色々面白い事(危ない事ともいう)出来そうですね。
参考記事
バッチファイルにC#コードを書く
バッチファイルからps1ファイルに記述したC#コードを呼び出して満足した後にこの記事を見て、目から鱗が落ちました。