WSH JScript Chakra エンジンの共存による機能制限の回避(WScript Quit 代替)
はじめに
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済みですが、
大体下記のように処理しています。
追記(おそらく最適解):例外を握りつぶすと戻り値が使えるらしい
2022/05/28:
例外をキャッチすれば正しく戻り値が扱えるという話を見つけました。
実際試してみると、普通に扱えているようなので、これが最適解でしょう。
WSHでES2015を利用するベストプラクティス - Qiita
戻り値を確認するため、exit /b
で戻り値を設定するバッチに、スクリプトを埋め込みます。
0</* :: @%windir%\System32\cscript.exe /nologo /E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} "%~f0" %* @echo 戻り値は、%errorlevel%です。&pause @exit /b %errorlevel%&*/0; // エントリ let global_result=(()=>{ // ポップアップを表示 // ActiveXObject は未定義なので、CreateObject を使う const shell = WSH.CreateObject("WScript.Shell"); shell.popup(`${WSH} for Chakra`); return 123; })(); try {WScript.Quit(global_result); }catch(e){// JavaScript runtime error: Object doesn't support this action }
おわりに
一般的に、外部から呼び出されることを想定するプログラムは、
戻り値から、エラー原因を特定できるようにしています。
Chakra を指定したスクリプトでは、スクリプトが失敗しても
戻り値から判断できない状況でしたが、これで一件落着?でしょうか。
元々 Chakra を指定する事自体が、仕様の穴をつくような方法なので、
回りくどい対応を取らざる得なくなる事もありますが、
それを差し引いても ES6 が利用できる点は大きいかと思います。