レシピ
OS:Windows XP
使用言語:WSH(VBScript)
PHP(バージョン4または5)

 数年前からセキュリティが厳しすぎてファイル添付のメールが送れない企業が出てきています。バイナリを添付できないというのは仕事上とても厄介です。ではバイナリ・ファイルをテキスト化してメール本文として送ってしまえば…というのが今回のアイデアのスタートです。


リスト1●base64エンコードするVBScriptのプログラム(enc.vbs)
[画像のクリックで拡大表示]

リスト2●リスト1が呼び出すPHPスクリプト(enc.php)
[画像のクリックで拡大表示]

リスト3●デコードするVBScriptのプログラム(dec.vbs)
[画像のクリックで拡大表示]

リスト4●リスト3が呼び出すPHPスクリプト(dec.php)
[画像のクリックで拡大表示]

図1●リスト1,2でエンコードしたバイナリ
[画像のクリックで拡大表示]

 インターネットが普及する以前,パソコン通信の時代に,バイナリをテキスト化するishという有名なフリーソフトがありました。今回は,2006年なりのishを作ってみようというものです。バイナリ・ファイルをやり取りする場合,誤動作を避けるためにテキスト化することは多々あります。例えば,メールにファイルを添付する際にはbase64というエンコード形式を使用します。今回は,このbase64エンコード/デコードを使用して,バイナリ・ファイルのテキスト化と復元を行います。といっても,base64そのものを作りこむわけではありません。base64変換の機能は,PHPに用意された関数を使います。すべてをPHPで作り上げることもできますがそれでは芸がないので,VBScriptからPHPスクリプトを呼び出して実行させ,またVBScriptに制御を戻すという小技を勉強してみることにします。この小技を知っていれば,PHP,Python,Perl,Rubyなどのスクリプト言語を使いつつ,メッセージボックスを表示するなど,インタフェースとしてはWindows標準ぽく見せてしまえます。スクリプト/バッチ系プログラムの妙味ですね。なお,PHPは標準状態(C:\php)にインストールしてあるものとします。

PHPの処理終了を待ってVBSに復帰する

 まず,C:\fooというフォルダを作成してください。そこにプログラム・ファイルを配置します。ファイルは全部で四つ。エンコード用にenc.vbs(リスト1)とenc.php(リスト2),デコード用にdec.vbs(リスト3)とdec.php(リスト4)です。

 まずbase64エンコードを実行してみましょう。適当なバイナリ・ファイルをドラッグして,enc.vbsにドロップすると処理が始まります。enc.vbsはドロップされたファイルの名前をenc.phpに渡します。enc.phpはそのファイルを開いて中身を読み込み,base64エンコードしてC:\fooにenc.txtという名前で保存します。ここで処理はVBScriptに戻り,メモ帳が起動してenc.txtの内容を表示します(図1)。

 リスト1を見てください。PHPスクリプトの起動にはWScript.ShellのExec関数を使用しています。ただし,これだと外部プログラムの実行状態に関係なくVBScriptの次の行の処理が続行してしまうので,While EX.Status = 0というループで外部プログラム(PHP)の処理終了を待っています。バッチ処理では複数の外部プログラムを連続的に動作させる場合,それぞれが終了した段階で次の行を動かしたいということがままあります。Statusプロパティを使えばWSHでもこのように連続動作が可能です。

 デコード側(リスト3と4)もほぼ同じ動作です。ドロップされたファイルをデコードして元の形式に戻そうとします。復元できるのはenc.vbsを使ってエンコードしたファイルのみです。復元に成功するとオリジナルのファイル名の頭に dec_ というプレフィクスが付いたファイルが作成されます。

 なおPHP側で処理に失敗しても,失敗内容はVBScriptには送られません。したがってあたかも処理が完了したかのようなMsgBoxが出てきます。複数の言語を使っている変則プログラムでは避けがたい問題ですが,「自分専用ツール」であればこの程度のバグは気にならないものです。細かいことよりも,やりたいことを得意とする言語の組み合わせで実現できしまうことのメリットのほうが大きいですからね。うまくHTA(HTML Application)などと組み合わせられれば,PHPやPerlでは実現が難しいWindowsのUI付きプログラムを簡単に作れてしまうというわけです。いつものように最小限のコードなので,エラー・トラップはゆるいです。皆さんの創意工夫で完成度を高めてください。


リスト1●base64エンコードするVBScriptのプログラム(enc.vbs)
Set objArgs = WScript.Arguments
If objArgs.Count = 1 Then 'ファイルがドロップされたら
 MsgBox objArgs(i) & "をエンコードします"
 Set WS = CreateObject("WScript.Shell")
 'enc.phpの後のスペースに注意
 Set EX = WS.Exec("c:\php\php c:\foo\enc.php " & objArgs(0))
 While EX.Status = 0 'Execの処理終了を待つ
  WScript.Sleep (100)
 Wend
 MsgBox "エンコード処理終了"
 Set EX = WS.Exec("notepad.exe c:\foo\enc.txt") 'メモ帳起動
Else
 MsgBox "ファイルをひとつドロップしてください"
End If

リスト2●リスト1が呼び出すPHPスクリプト(enc.php)
$fname = $_SERVER[argv][1];
$fname = str_replace("\\","/",$fname);
$a_f = split("/",$fname);
$max = count($a_f) - 1;
$r_fname = $a_f[$max];
if(file_exists($fname)){
 $fhr = fopen($fname,"r");
 $buff = fread($fhr, filesize($fname));
 fclose($fhr);
}else{
 return -1;
 exit;
}
$buff_h = $r_fname."--split_point--\r\n";
$buff = chunk_split(base64_encode ($buff));
$buff = $buff_h.$buff;
$fh = fopen("c:/foo/enc.txt","w");
fwrite($fh, $buff);
fclose($fh);
?>

リスト3●デコードするVBScriptのプログラム(dec.vbs)
Set objArgs = WScript.Arguments
If objArgs.Count = 1 Then
 MsgBox objArgs(i) & "をデコードします"
 Set WS = CreateObject("WScript.Shell")
 Set EX = WS.Exec("c:\php\php c:\foo\dec.php " & objArgs(0))
 While EX.Status = 0
  WScript.Sleep (100)
 Wend
 MsgBox "デコード処理終了"
Else
 MsgBox "デコードするファイルをひとつドロップしてください"
End If

リスト4●リスト3が呼び出すPHPスクリプト(dec.php)
$fname = $_SERVER[argv][1];
$fname = str_replace("\\","/",$fname);
if(file_exists($fname)){
 $fhr = fopen($fname,"r");
 $buff = fread($fhr, filesize($fname));
 fclose($fhr);
 list($title,$body) = split("--split_point--\r\n",$buff);
}else{
 return -1;
 exit;
}
$body = base64_decode ($body);
$fh = fopen("dec_".$title,"w");
fwrite($fh, $body);
fclose($fh);
?>