Windows Script Host 用フレームワーク wes の紹介

[追記]  
フレームワークの内容が一部変更されている為、最新は [*wes*](https://github.com/wachaon/wes) そのものを参照してください。

1998 年に *WSH (Windows Script Host)* が登場してから 20 年以上が経ちます。
標準で搭載されているスクリプト実行環境でありながら、仕様の古さから記述が冗長になりがちです。

スクリプトエンジンを Chakra に変更できことが知られて多少注目されたことがありましたが、実行環境や開発環境としては根本的に変わっておらず、徐々に忘れ去られていく技術になっています。

[*wes (Windows EcmaScript host)*](https://github.com/wachaon/wes) は *WSH* の *JScript* を効率的に開発するためのフレームワークです。

## *wes* の特徴

-  スクリプトエンジンを *Chakra* に変更して *ES2015+* の仕様で記述できます
-  モジュールシステムが従来より開発効率を高めます
-  ビルトインモジュールがファイルの入出力やコンソールへ色付き文字を出力などの基本的な処理をサポートします
-  ファイルの読み込みにエンコードを自動推測させることができるので、エンコードなどを気にせず開発が可能です
-  パッケージ (モジュール) の外部公開や取得もサポートします

## *wes* のインストール

[*wes*](https://github.com/wachaon/wes) へアクセスして *wes.js* をコピーするかコマンドプロンプトで下記コマンドを入力してダウンロードしてください。

```bat
bitsadmin /TRANSFER GetWES https://raw.githubusercontent.com/wachaon/wes/master/wes.js %CD%\\wes.js
```

## 使い方

```bat
wes index
```

*wes* に続けて起点となるファイルパスを入れてください。(`.js` の拡張子は省略可能)

*wes* は実行したコマンドプロンプトのウィンドウに `SendKeys()` で再度をコマンド送信します。
`SendKeys()` の仕様上 *wes* の絶対パスに *ascii* 以外の文字があると、正しくコマンドを送信できません。
同様の理由で送信時に *IME* の入力モードが *ascii* 以外の場合も正しくコマンドが送信でません。

また、コマンドプロンプトで `wes` のみで実行すると *REP (Read-Eval-Print)* が起動します。
スクリプトを入力後、Enter を3連続入力するとスクリプトを実行します。
*REP* を使用してサンプルを試してください。

### Chakra

*Chakra* は *JScript* のランタイムを *ECMA-262 3rd edition* 相当から *ECMAScript 2015+* が実行できるように変更します。
*wes* が面倒なエンジンの変更オプションも自動で入力してくれます。

### モジュールシステム

*wes* では `require(...)` や `import ... from ...` でのモジュールの読み込みが可能です。
*WSH* 自体が非同期実行をサポートしていないので、*dynamic import* はサポート外になります。
また、`require()` での *ActiveX* 読み込みもサポートします。

ファイルの読み書きやパスの合成など基本的な処理はビルトインモジュールがサポートします。

*JScript* にはない *VBScript* の機能を使用して *ActiveX* の型を調べるサンプル。

```javascript
const { TypeName } = require('VBScript')
const FSO = require('Scripting.FileSystemObject')
console.log('%O', TypeName(FSO))
```

ビルトインモジュールを使用してファイルを読み込むサンプル。
`readTextFileSync()` はファイルエンコードを推測して読み込みます。

```javascript
const fs = require('filesystem')
const path = require('pathname')
const readme = path.resolve(__dirname, 'README.md')
const contents = fs.readTextFileSync(readme)
console.log(contents)
```

エンジンを *Chakra* にした場合には使用できなかった `new ActiveXObject` を使用して *JScript* らしく記述したサンプル。
*wes* での `new Enumerator` は *Enumerator object* ではなく *Array* を返します。

```javascript
const { Enumerator, ActiveXObject } = require('JScript')
const FSO = new ActiveXObject('Scripting.FileSystemObject')
const dir = FSO.getFolder(__dirname).Files
const files = new Enumerator(dir)
files.forEach(file => console.log(file.Name))
```

*WMI (Windows Management Instrumentation)* を使用し、サービス一覧を取得するサンプル。

```javascript
const { GetObject, Enumerator } = require('JScript')

const ServiceSet = GetObject("winmgmts:{impersonationLevel=impersonate}").InstancesOf("Win32_Service")
new Enumerator(ServiceSet).forEach(service => console.log(
    'Name: %O\nDescription: %O\n',
    service.Name,
    service.Description
))
```

## パッケージ (モジュール) 公開と取得

モジュールを複数集めてひとまとまりにしたものを *wes* ではパッケージと呼ぶことにします。
*GitHub* リポジトリを利用してパッケージの公開と取得が可能です。
パッケージの公開にはいくつかの制約があります。

-  1 リポジトリに 1 パッケージのみ公開できます
-  リポジトリ名がパッケージ名になります
-  リポジトリ名とローカルリポジトリのディレクトリ名は同名にします
-  `require()` や `import ... from ...` の記述はモジュールのトップレベルに記述します

### パッケージの公開方法

公開するにはコマンドプロンプトで下記のコマンドを実行します。(`index.js` がパッケージの起点ファイルの場合)

```bat
wes bundle index
```

コマンドを実行すると *wes* は必要なファイルを (動的に) 集めて 1 つの `.json` ファイルにまとめます。
このファイルをプッシュしてください。

### パッケージの取得方法

取得するにはコマンドプロンプトで下記のコマンドを実行します。

```bat
wes install @wachaon/calc
```

`wes install` に続けて `@author/package_name` 形式で記述します。

製作者ごとにフォルダを分けたくないのであれば下記のコマンドを実行します。

```bat
wes install @wachaon/calc --unsafe --bare
```

グローバルでインストールしたい場合は下記のコマンドを実行します。

```bat
wes install @wachaon/calc --global
```

## パッケージの紹介

### *@wachaon/edge*

[*@wachaon/edge*](https://github.com/wachaon/edge) は *Microsoft Edge based on Chromium* (chromium版 edge) を自動操縦をサポートするパッケージです。

#### インストール

```bat
wes install @wachaon/edge
```

パッケージ以外に *Microsoft Edge based on Chromium* と *web driver* が必要になります。
*web driver* は *@wachaon/edge* をインストールしたあとに下記のコマンドを実行してください。

```bat
wes @wachaon/edge --download
```

インストールされている *Microsoft Edge based on Chromium* に適した *web driver* をダウンロードします。
~~zip を解凍してカレントディレクトリもしくは環境変数に登録されているディレクトリに *msedgedriver.exe* を配置してください。~~
(ダウンロードされたzipファイルは自動で解凍されます。)

#### 使い方

手続き型形式のように処理を１つずつ記述もできますが、*url* をイベントに登録して、ブラウザがその *url* へ訪れた際に処理を実行するイベントドリブン形式でも記述できます。

イベントドリブン形式だと自動操縦での操作が難しい局面もあえてリスナーを登録しないことで、手動操作に切り替え可能です。

リスナーの *url* は文字列だけはなく、正規表現での登録も可能です。

プログラムを終了させるにはブラウザを閉じるか `navi.emit('terminate', res)` を実行させてください。

`window` のくわしい操作は [*@wachaon/webdriver*](https://github.com/wachaon/webdriver) を参照してください。

下記サンプルはブラウザを閉じるか `https://www.yahoo` から始まる *url* に訪れるまでコマンドプロンプトに *url* を出力します。また、終了時には訪問した *url* の一覧をログファイルとして出力します。

```javascript
const edge = require('@wachaon/edge')

edge((window, navi, res) => {
    window.rect({
        x: 1,
        y: 1,
        width: 1200,
        height: 500
    })
    res.exports = []

    navi.on(/https?:\/\/./, (url) => {
        console.log('URL: %O', url)
        res.exports.push(url)
    })

    navi.on(/^https?:\/\/www\.yahoo\b/, (url) => {
        console.log('finished!')
        navi.emit('terminate', res, window)
    })

    window.getStatus()
    window.navigate('http://www.google.com')
})
```

[*online Visual Studio Code*](https://insiders.vscode.dev/) と *wes* を利用するば、レジストリを変更するようなインストールが規制されている環境でも、効率的に *WSH* の開発ができるようになります。