イネマルのプログラミング備忘録

知りたいことだけ端的にまとめたい

JScript Chakra wscriptで、ECMAScript 2015 (ES6) を利用する

はじめに

wscript を利用する際に、Chakra エンジンを指定する方法です。

cscript で JScript を利用する場合、Chakra エンジンを指定するために
バッチファイルと組み合わせる方法がありますが、
wscript ではコマンドプロンプトの画面が不要なので、非表示で起動する方法です。

方法

必要なコードを先に載せます。
.js もしくは .jse 形式で保存して、そのまま実行可能です。

//@cc_on for(s='"'+WSH.ScriptFullName+'"',i=0;i<WSH.Arguments.length;i++)s+=' "'+WSH.Arguments(i)+'"';WSH.CreateObject("WScript.Shell").Run("wscript /E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} "+s)@if(0)

// 引数を表示
let args=[];
for(let a=WSH.Arguments,c=0;c<a.length;c++)args.push(a(c));
WSH.Echo(`実行引数の数:${WSH.Arguments.length}\n${args.join("\n")}`);

//@end

もしくは、コマンドライン引数を利用しない場合、もう少し短くなります。

//@cc_on WSH.CreateObject("WScript.Shell").Run('wscript /E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} "'+WSH.ScriptFullName)@if(0)

// 適当にコードを書く
const echo = ()=>WSH.Echo("Hello WScript Chakra");
echo();

//@end

JScript エンジンで実行された後、Chakraエンジンで自身を起動し直す仕組みで、
実行すると、メッセージボックスが表示されます。

JScriptChakra のコードを埋め込む

JScript には、IEスクリプトエンジンとして利用されている経緯から
Chakra には無い、ブラウザの判別用の「条件付きコンパイル」機能が搭載されています。

これは、コメント行にコードを利用するかどうかの判定を埋め込む代物なので、
JScriptコンパイラが読み飛ばす部分(@if(0)~@end)は、処理されません。
つまり、ES6の機能を利用しても、コンパイルエラーしないという事です。

/*@cc_on

WSH.Echo("JScript で実行される範囲");

@if(0) @*/

WSH.Echo(`Chakra で実行される範囲`);

//@end

結果、ES6ベースなコードの埋め込みが実現し、
JScript で実行される範囲」から、Chakraエンジンを指定して自身を起動し直すことで、
Chakraエンジンからは、コメントアウトされていない部分だけが実行されます。

無事コマンドプロンプトを表示せずに、Chakraエンジンでスクリプトが実行されました。

おわりに

JScriptChakra のコードを埋め込む方法は、
こちらを参考にしました。
qiita.com

Chakraで再起動する処理は、コーディングに直接関係ないので
Minifierを使って1行に収めてしまいましたが、
コマンドライン引数を利用する為の処理が、そこそこ長くなっています。

WSH.Arguments は、配列型ではないので無難にforを使いましたが、
もしかすると、より短く書けるかもしれません。

おわり。

WSH WSF JScriptで、YAML をパースする方法(js-yaml)

JScriptyaml ファイルをパースしたいとき、
wsf形式と外部パッケージのjs-yamlで、簡単に実現できます。
github.com

実装

yamlファイル。(sample.yaml
文字コードは、SJISを使用。

# 設定ファイル
config:
    title: "AppName"
    width: 1280
    height: 720

適当なバッチに埋め込んで使います。

<!-- :
@setlocal&pushd %~dp0
@cscript //nologo "%~f0?.wsf" %* || pause
@popd&endlocal&exit /b
-->
<job>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.14/es5-shim.min.js" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.14/es5-sham.min.js" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/3.13.1/js-yaml.js" />
<object id="shell" progid="WScript.Shell" />
<object id="fso" progid="Scripting.FileSystemObject" />
<script language="JScript">
WSH.Quit(function()
{
    try{
        // 読み込み
        var yamlFile = fso.OpenTextFile("sample.yaml", 1, true);
        var yamlText = yamlFile.ReadAll();
        yamlFile.Close();
        var doc = jsyaml.load(yamlText);
        
        // 表示
        WSH.Echo(doc.config.title);
        WSH.Echo(doc.config.width);
        WSH.Echo(doc.config.height);
        
        // Enter入力待ち
        WSH.StdIn.ReadLine();
    }
    catch(e){
        WSH.Echo(e.name + ":" + e.message);
        return -1;
    }
    return 0;
}());
</script>
</job>

仕組み

js-yamlでは、ES5に対応した環境が求められますが、
wsf 形式は、ES3程度の機能しか使えないのでes5-shimライブラリを使って
擬似的にES5環境を扱えるようにします。
読み込んだ後は、jsyamlオブジェクトを扱うだけです。

github.com

その他

オフライン環境で使いたい場合など、
ライブラリの参照にCDNを使いたくなければ、ローカルに保存して使いましょう。

<script src="./es5-shim.min.js" />
<script src="./es5-sham.min.js" />
<script src="./js-yaml.js" />

以上、おわり。

WSH JScript で、高精度に処理時間を計測する(Performance API)

はじめに

JScriptで、スクリプトの処理時間を計測するとき、
Date オブジェクトを使って計測することがありますが、これはミリ秒単位の精度です。

厳密な速度計測が不要であれば、ミリ秒もあれば事足りますが、
より精度の高いマイクロ秒単位が必要であれば、
Web API (Performance API) の Performance インターフェースを使います。

実装

計測対象の関数は、ここから拝借。
フィボナッチ数を出力する関数を作る - Qiita

function do_something()
{
    var fib = function (n) { return n > 2 ? fib(n - 1) + fib(n - 2) : 1; };
    fib(33);
}

Performance インターフェースによる計測

JScript環境の、Performance インターフェースは、
JSON オブジェクト等と同様で、htmlfile オブジェクトから取得できます。

WSH.Quit(function()
{
    try{
        // Performance を取得
        var htmlfile = WSH.CreateObject("htmlfile");
        htmlfile.write('<meta http-equiv="x-ua-compatible" content="IE=Edge"/>');
        var performance = htmlfile.parentWindow.performance;
        htmlfile.close();
        
        // 計測
        var startTime = performance.now();
        do_something();
        var endTime = performance.now();
        
        // 結果
        // 浮動小数が得られる。小数点以下が、マイクロ秒。
        // (小数第1位までしか得られない?)
        WSH.Echo((endTime - startTime) + " ms");
    }
    catch(e){
        WSH.Echo(e.name + ":" + e.message);
        return -1;
    }
    return 0;
}());

過去の手法

Date オブジェクトによる計測

WSH.Quit(function()
{
    try{
        // Performance を取得
        var htmlfile = WSH.CreateObject("htmlfile");
        htmlfile.write('<meta http-equiv="x-ua-compatible" content="IE=Edge"/>');
        var performance = htmlfile.parentWindow.performance;
        htmlfile.close();
        
        // 計測
        var startTime = new Date();
        do_something();
        var endTime = new Date();
        
        // 結果(※ これでは、ミリ秒に丸められた結果しか得られない)
        WSH.Echo((endTime.getTime() - startTime.getTime()) + " ms");
    }
    catch(e){
        WSH.Echo(e.name + ":" + e.message);
        return -1;
    }
    return 0;
}());

その他メモ

バッチコマンドの計測であれば、powershell の Measure-Command が手軽です。

powershell -c (Measure-Command { (ここに計測するコマンド) })

おわり。 developer.mozilla.org

Chakra版 JScriptで、JSONファイルをパースする

以前、既定のJScript ではJSONオブジェクトが無いことの対処法を記事にしましたが、
今回は、Chakra エンジンを使うケースの話。

結論から言うと、
Chakra エンジンを使うのであれば、そのままでJSONオブジェクトが使えます。

Chakra エンジンを使う方法については、こちら

実装

エンジンを指定するため、適当な .bat に埋め込んで使います。

0</*! ::
@%windir%\System32\cscript.exe //nologo //E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} "%~f0" %*||pause
@pause rem 入力待機
@exit /b */0;

(()=>{
    try
    {    // JSONをパース
        const jsonTxt =
        `{"Application": {"title": "SampleApp", "width": 1280, "height": 720}}`;
        let rootObj = JSON.parse(jsonTxt);
        
        // 結果を表示
        let app = rootObj.Application;
        WSH.Echo(`${app.title}\n${app.width}\n${app.height}`);
    }
    catch(e){
        WSH.Echo(e.name + " : " + e.message);
    }
    finally{}
})();

結果

SampleApp
1280
720

以上、おわり。

developer.mozilla.org

WSH JScript Chakra を使用した ES2015(ES6) 対応 ( スクリプトエンジン まとめ )

はじめに

WSH JScript (wscript cscript) で使用できる スクリプトエンジン まとめです。

JavaScript限定、プリインストールされている物だけなので注意。
ツールとして独立しているChakraCoreや、その他サードパーティーは含みません。

JavaScript エンジン について

JScript (JavaScript) だけでも、4種類ほどスクリプトエンジンがあるようです。
以下、それぞれバッチファイルから cscript で実行する際のサンプルコマンドです。

JScript

JScript を使用する際、一般的に指定されるスクリプトエンジンです。
バージョンは、環境によって異なりますが、
現時点のOSサポートを考慮すれば、5.7 か 5.8 でしょう。

rem jscript.dll
%windir%\System32\cscript.exe /nologo /E:JScript "%~f0" %*

JScript.Compact

JScript Compact Profile (ECMA 327) で実行する、スクリプトエンジンです。
MSの情報が少ないので、詳細は未確認ですが
簡単に説明すれば、「いくつかの機能を制限して、パフォーマンス向上を図る設定を使用する」と言った感じでしょうか。
機能の制限について、少なくとも with ステートは、サポートされていないようです。

rem JScript Compact Profile (ECMA 327)
%windir%\System32\cscript.exe /nologo /E:JScript.Compact "%~f0" %*

JScript9

IE9 から使用されている、JScript9.dll をスクリプトエンジンとして指定できます。
ProgID が公開されていないので、CLSID を指定する必要があります。

ちなみに、JScript9 の 9 は、エンジンバージョンと言うわけでは無い様で、
Windows10 では、バージョン 11.0 として扱われていました。(中身は、Chakra っぽい?)

JScript と同程度の機能しか使えませんが、JScript9.dll の方が高速に動作します。
ただし、JScript 互換は完全ではなく WScript.Quit や GetObject 等の一部機能が使用できません。

rem jscript9.dll
%windir%\System32\cscript.exe /nologo /E:{16d51579-a30b-4c8b-a276-0ff4dc41e755} "%~f0" %*

Chakra

Microsoft EdgeJavaScriptエンジン です。
こちらも、ProgID が公開されていないので、CLSID を指定する必要があります。

Chakra を使用する場合は、ES2015(ES6) に対応している為、クラスなどが使用できます。
ただし、JScript9 と同様に、WScript.Quit 等は、使用できません。

rem chakra.dll
%windir%\System32\cscript.exe /nologo /E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} "%~f0" %*

Chakraを使用したスクリプト を バッチに埋め込む場合

JScript9までは、@if を使用してバッチファイルにJScriptを埋め込むこと(shebangモドキ)が出来ましたが、
Chakra を使用する場合は、構文エラーとなってしまうため、少し工夫が必要です。

JScript共通の埋め込み

@if を使用しない JScriptshebangモドキがあるので、そちらを使います。
ildar-shaimordanov/hello.jscript.bat

0</* ::
@%windir%\System32\cscript.exe /nologo /E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} "%~f0" %* || pause
@exit /b %errorlevel%
*/0;

Chakra限定の埋め込み

Chakra が「<!--」をコメント扱いすることを利用した方法があります。
https://qiita.com/snipsnipsnip/items/50e4ca88e3ce3f8cffda

<!-- : ^
/*
@%windir%\System32\cscript.exe /nologo /E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} "%~f0" %* || pause
@exit /b %errorlevel%
*/

JScript9 や Chakra で、戻り値を返したい場合

WScript.Quit が使えないのは、少々痛手なので、代替案です。
Chakra用に定義した関数の戻り値を、JScript側の WScript.Quit で受け取る形です。

.bat にコピペで、動作確認できます。

0</* ::
@cscript /nologo /E:JScript "%~f0" %*
@echo.戻り値:%errorlevel%&pause
@exit /b %errorlevel%
*/0;//@cc_on @if(0)

// 疑似エントリーポイント
function main()
{
    WSH.Echo(`${WSH} for Chakra`);
    return 123;// WScript.Quit(<戻り値>) の代替
}

var quit=!1;let w=WSH.CreateObject("Shell.Application").Windows(),i=0,item,ie;
for(;i<w.Count;i++){item=w(i);if(item&&item.hWnd==WSH.Arguments(0)){ie=item;break}}
for(ie.PutProperty("@",this);!quit;)WSH.Sleep(1e3);/*@end @cc_on
var ckr,ie=WSH.CreateObject("InternetExplorer.Application"),sh=WSH.CreateObject("WScript.Shell");
for(sh.Exec('cscript /E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} "'+WSH.ScriptFullName+'" '+ie.hWnd);!(ckr=ie.GetProperty("@"));)
WSH.Sleep(1e3);this.CKR=ckr,ckr.JS=this,ckr.WSH=WSH,ie.Quit();r=CKR.main();ckr.quit=!0,WSH.Quit(r)@*/

補足

念のため触れておくと ChakraCore を利用する場合、
WScript.Quit は定義済みで普通に呼び出せるので、このような悩みとは無縁です。
https://github.com/microsoft/ChakraCore/blob/bd91335361ddd215622d810cc7341a180abd2db4/bin/ch/WScriptJsrt.cpp#L1090

おわりに

WSH ってだけで、今更感満載のレガシーなのですが、
初期状態のWindowsで使用できる手軽さで、根強く?使われている様子。

今回のまとめは、WSH JScript で、クライアントの環境に依存せずに
ES2015 ベースのコードを使えないか調査したときの副産物です。

結果的に、ES2015 対応は行っていませんが、
TypeScript 経由で、対応できそうかなと言ったところでオチ。

参考リンク