はじめに
WSH JScript に Chakra を指定すると WScript Object の機能が制限される為、
WScript.Quit
関数が利用できず、戻り値を返す術が失われます。
今回は、この問題の対処例を挙げます。
前提
Windows 環境には、Windows Script Host(WSH)と呼ばれる
Node.js の様な JavaScript が動作するスクリプトの実行環境が標準搭載されています。
技術としては古いものなので、デフォルトでは ES3 程度のサポートですが、
スクリプトエンジンを Chakra に変更することでES6の機能が利用可能
です。
WSH JScript Chakra を使用した ES2015(ES6) 対応 ( スクリプトエンジン まとめ )
JScript と Chakra を共存させる
既定のJScript で、Chakra 用の処理をラップする形の対処法です。
WScript.Quit は、CScript でないと戻り値が機能しないので、バッチに埋め込みます。
この方法は、JScript と Chakra で各エンジンが共存するので、
機能制限の掛かる箇所は、JScript側で関数化する等の対処で回避可能になります。
※ 例えば、GetObject等のChakraでは未定義となる関数は、JScript 経由で利用できます。
0</* :: @cscript /nologo /E:JScript "%~f0" %* @rem 戻り値の確認用 @echo.ExitCode:%errorlevel%&pause @exit /b %errorlevel%&*/0;//@cc_on @if(0) // // Chakra // // エントリーポイント用の関数(プロセスの戻り値を返す) function main() { // コンソールに表示 const msg = `${WSH} for Chakra`; WSH.Echo(msg); // JScript側の関数を呼ぶ const r = JS.hoge(); // 強制終了するなら、例外を投げる //throw "quit"; // もしくは、早期リターン //return; // ポップアップを表示 // ActiveXObject は未定義なので、CreateObject を使う const shell = WSH.CreateObject("WScript.Shell"); shell.popup(msg); // 戻り値 return r; } function fuga() { WSH.Echo("fuga"); } var quit=!1;let item,ie,w=WSH.CreateObject("Shell.Application").Windows(),i=0; for(;i<w.Count;i++)if((item=w(i))&&item.hWnd==WSH.Arguments(0)){ie=item;break} for(ie.PutProperty("@",this);!quit;)WSH.Sleep(1e3);/*@end @cc_on for(var ckr,ie=WSH.CreateObject("InternetExplorer.Application"),sh=WSH.CreateObject("WScript.Shell"), p=sh.Exec('cscript /E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} "'+WSH.ScriptFullName+'" '+ie.hWnd);!(ckr=ie.GetProperty("@"));) {if(0!=p.Status){for(;!p.StdErr.AtEndOfStream;)WSH.StdErr.Write(p.StdErr.Read(256));ie.Quit(),WSH.Quit(p.ExitCode)}WSH.Sleep(1e3)} (this.CKR=ckr).JS=this,ckr.WScript=WSH,ckr.WSH=WSH,ie.Quit(),r=CKR.main(),ckr.quit=!0,WSH.Quit(r); // // JScript // function hoge() { // Chakra側の関数を呼ぶ CKR.fuga(); return 123; } @*/
仕組み
条件付きコンパイル
機能を使って、1つのスクリプト内に
エンジン毎(JScript, Chakra)で実行されるコードを共存させています。
また、プロセス間でオブジェクトを共有するために、
InternetExplorer
Object の PutProperty / GetProperty
を利用しています。
該当部分は、コーディングに直接関係無いのでMinify済みですが、
大体下記のように処理しています。
別解:一時ファイルを使用する方法
別解として泥臭い方法になりますが、
”戻り値だけ” であれば、一時ファイルで解決可能。
諸々の制限を回避できない分、仕組みはシンプルです。
exit /b
で戻り値を設定するため、バッチに埋め込みます。
ファイル名に日時と乱数を使用しているのは、多重起動に対応するため。
0</* :: @setlocal&set result_file=%tmp%\%~n0@%date:/=%%time::=%.%random%.txt @cscript /nologo /E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} "%~f0" %* @set /p code=<"%result_file%"&del /f "%result_file%" @exit /b %code%&endlocal */0; // エントリーポイント用の関数(プロセスの戻り値を返す) function main() { WSH.Echo(`${WSH} for Chakra`); return 123; } try { var shell=WSH.CreateObject("WScript.Shell"), fso=WSH.CreateObject("Scripting.FileSystemObject"),ExitCode=0; ExitCode=main(); } catch(e){WSH.Echo(`${e.name}:${e.message}`)} finally{ // 戻り値をテンポラリに書き出す。 const procEnv=shell.Environment("Process"), file=fso.OpenTextFile(procEnv("result_file"), 2, true, -2); file.Write(ExitCode),file.Close(); }
おわりに
一般的に、外部から呼び出されることを想定するプログラムは、
戻り値から、エラー原因を特定できるようにしています。
Chakra を指定したスクリプトでは、スクリプトが失敗しても
戻り値から判断できない状況でしたが、これで一件落着?でしょうか。
元々 Chakra を指定する事自体が、仕様の穴をつくような方法なので、
回りくどい対応を取らざる得なくなる事もありますが、
それを差し引いても ES6 が利用できる点は大きいかと思います。