WSH JScript用ライブラリとVSCodeでWindows用バッチファイルを作ろう

ライブラリ制作
スポンサーリンク

はじめに

みなさん、こんにちは~!

なたでと申します!

本日は、WSHを利用したJScriptのライブラリ「SenkoWSH」を作成しましたので、その紹介と実際に使うところまでをお話ししたいと思います。

SenkoWSHとは

解説

このライブラリは、WindowsでJavaScriptを利用して作成するバッチファイル向けのライブラリとなっています。ライブラリの目的は以下のようになっています。

  1. JScriptのES3程度の機能をいくつか拡張する
  2. Visual Studio Codeを利用してバッチファイルを開発できる
  3. ファイルをオブジェクトとして扱えるようにする
  4. よく利用する機能を拡張する

使用方法はヘルプファイルとしてWebで閲覧することが可能です。

1番目は、JScriptの機能を改善させます(参考:JScriptの仕様)。といっても全て実装したわけではなく、JSONStringを中心によく利用すると思われる機能のみを追加しています。

2番目は、型定義ファイル(*.d.ts)を用意しており、バッチの作成が容易にできるようになります。

3番目は、ファイル用のSFile クラスを用意しており、CSV クラスと組み合わせることで、ファイルの読み書きを簡単に行えるようになっています。

4番目は、sprintfと同じような記載ができるFormat クラス。ダイアログを表示するDialog クラス。ウィンドウの自動操作や入力が行えるRobot クラスなどを用意しています。

手っ取り早い実行方法

実際には、Visual Studio Codeを利用して開発することを目的としていますが、まずは何もなしで手っ取り早く使う方法をご紹介します。

  1. SenkoWSH-master.zip」をダウンロードして展開します。
  2. \SenkoWSH\build\SenkoWSH.js」「\SenkoWSH\examples\Example.wsf」「\SenkoWSH\examples\Example.js」をどこかのディレクトリに設置します。
  3. Example.wsf」の中身を以下のように、編集します。
<job>
	<script language="JavaScript" src="./SenkoWSH.js"></script>
	<script language="JavaScript" src="./Example.js"></script>
</job>
  1. Example.js」の中身を以下のように、編集します。
System.executeOnCScript();
System.initializeCurrentDirectory();
console.log("Hello World");
System.stop();

これで、「Example.wsf」をダブルクリックで実行すれば、CUIウィンドウに「Hello World」と表示されます。

少し解説を行うと以下のようなことをしています。

Example.wsf」では、

  1. ライブラリファイル(SenkoWSH)をロード
  2. 実際の動作書いてあるスクリプトをロード

Example.js」では、

  1. CUIモードで起動を宣言。
  2. カレントディレクトリをwsfファイルがある場所に設定
  3. コンソールに「Hello World」と表示
  4. 動作をストップする

Visual Studio Codeとの連携

今度は、Visual Studio Codeとの連携方法を説明します。

まずは、以下の記事を参考にVisual Studio Codeをインストールしてください。

JavaScript開発環境をNetBeansからVisual Studio Codeに切り替えた話 – なたで日記

記事内の以下の章だけを参考にしてください。

  • ESLint の準備
    • 1. npm (Node.js)インストール
  • VSCodeの準備
    • 1. VSCodeのインストール
    • 2. 日本語化する
    • 「3. 開発に必要な拡張機能のインストール」の「ESLint」をインストール

ここまで出来たら、以下のような状態になっていると思います。

  • Visual Studio Code インストール完了
  • Visual Studio Code に拡張機能として「Japanese Language Pack for Visual Studio Code」と「ESLint」をインストール完了
  • npm (Node.js) インストール完了

次から、SenkoWSHと連携していきます。

  1. SenkoWSH-master.zip」をダウンロードして展開します。
  2. 「ファイル」→「フォルダーを開く」で、「package.json」がおいてあるフォルダを選択し、開いたら一度「名前を付けて保存」でプロジェクトファイル(*.code-workspace)を保存してください。
  3. 右下のターミナルに、npm installと入力してください。

これで完了です。

サンプルを実行する場合は、エクスプローラ上から「\examples\Example.wsf」をダブルクリックするか、ターミナルに、npm run startと入力してください。

バッチファイルを作成する場合は、「\examples\Example.js」を直接編集していってください。

ファイル名や構成を変更したい場合は以下のファイル内を見て、何をしているかを確認してください。

  • ./package.json の"scripts""start"の部分
  • ./scripts/execExample.js
  • ./examples/Example.wsf

Node.jsとは

上記はNode.jsを使用しています。Node.jsは、JScriptと同じようにサーバーサイド(デスクトップ環境など)で動くJavaScriptの実行環境です。Node.jsは最新のECMAScriptを高速で動作させることが可能ですが、JScriptは、2009年のECMAScript 5程度に留まっており、速度もずっと遅いです……。ここまできくと、Node.jsが良いように見えますが、配布サイズが大きくなったりすることで、会社内で手軽にバッチ目的で利用できるものではありません。

色々な利用方法

クラスの紹介

利用できるクラスを紹介します。(docファイル)

クラス名 主な機能 利用例
System
  • モード切替
  • コンソールへの入出力
  • 引数の取得
  • コマンド/WindowsAPI/PowerShellの実行
// 文字の表示
System.println("ABC");
// 10秒待つ
System.sleep(10.0);
// ビープ音を出す
System.beep(440, 0.5);
// クリップボードの取得
var cb = System.getClipBoardText();
SFile
  • テキストファイルの入出力
  • バイナリファイルの入出力
  • ウェブサーバーへGETリクエスト
  • ファイルの更新日情報などの書き換え
  • ファイルとフォルダのコピー/移動/削除
// テキストを扱う
var text_file = new SFile("test.txt");
var text = text_file.readString("utf-8");
// バイナリを扱う
var bin_file = new SFile("test.bin");
bin_file.writeBinary([0x01, 0x02, 0x03]);
CSV
  • CSVテキスト⇔CSV二次元配列への変換
  • CSV二次元配列⇔JSONへの変換
var file = new SFile("test.txt");
var array = CSV.parse(file)
Format
  • sprintf
  • 日付用のフォーマット
var text = Format.textf("%02X", 1234));
var date = new Date("2000/1/2 2:03:04");
var date_text = Format.datef("YYYY/MM/DD hh:mm:ss", date));
Dialog
  • 情報ダイアログ
  • ファイルを開くダイアログ
  • 名前を付けて保存ダイアログ
console.log(Dialog.popupOpenFile({
	title : "ファイルを選択してください"
}));
console.log(Dialog.popupSaveAs({
	title : "ファイル名を設定して下さい。"
}));
Robot
  • キーボード/マウス操作
  • ウィンドウ操作
// エンターを押す
Robot.setKeyEvent(Robot.VK.VK_RETURN);
// メモ帳のハンドルを取得する
var handle = Robot.getHandleOfClassName("Notepad");
// アクティブにする
Robot.setActiveWindow(handle);
// ウィンドウを動かす
var rect = Robot.getWindowRect(handle);
rect.width += 10;
rect.height += 10;
Robot.setWindowRect(handle, rect);
String
Comparator
  • 自然順ソート
var v = ["123","20","3"];
console.log(v.sort(StringComparator.DEFAULT));
console.log(v.sort(StringComparator.NATURAL));
Japanese
  • 日本語に関する文字変換
// 横幅を半角/全角でカウント
var len = Japanese.getWidth("abcあいう");
// 英数記号を半角に変換
var text = Japanese.toHalfWidthAsciiCode("123");
Random
  • 再現性がある乱数
  • 精度を指定した乱数
var seed = 100;
var random = new Random(seed);
console.log(random.nextDouble());
console.log(random.nextGaussian());

ファイル名を一括変換するバッチ

GitHubのプロジェクトに置いてあるサンプルスクリプトをここに載せます。

利用方法としては、スクリプトファイル内にフォルダをドラッグ&ドロップをすることで、内部のファイル名の一括変更することができます。

/// <reference path="../build/SenkoWSH.d.ts" />
System.executeOnCScript();
System.initializeCurrentDirectory();

System.println("ファイル名の一括変更バッチ");

/**
 * @param {string[]} files
 * @returns {SFile[]}
 */
var getFileList = function(files) {
	/**
	 * @type {SFile[]}
	 */
	var file_data = [];
	for(var i = 0; i < files.length; i++) {
		var file = new SFile(files[i]);
		if(!file.exists()) {
			continue;
		}
		if(file.isDirectory()) {
			var allfilse = file.getAllFiles();
			for(var j = 0; j < allfilse.length; j++) {
				var target = new SFile(allfilse[j]);
				if(target.isFile()) {
					file_data.push(target);
				}
			}
		}
		else {
			file_data.push(file);
		}
	}
	return file_data;
};

/**
 * @type {SFile[]}
 */
var input_files = getFileList(System.getArguments());

/**
 * @type {SFile[]}
 */
var output_files = [];
var format_text = "{id%03d}-{name%s}.{ext%s}";

var createOutputFile = function() {
	output_files = [];
	for(var i = 0; i < input_files.length; i++) {
		var parent = input_files[i].getParent();
		var ext = input_files[i].getExtensionName();
		var filename = input_files[i].getName();
		filename = filename.substr(0, filename.length - ext.length - 1);

		var new_filename = format_text.replace(/\{[^}]+\}/g, function(text) {
			var nakami = text.substr(1, text.length - 2);
			var type = nakami.split("%")[0].toLocaleLowerCase();
			var format = nakami.substr(type.length);
			if(type === "id") {
				return Format.textf(format, i + 1);
			}
			else if(type === "name") {
				return filename;
			}
			else if(type === "ext") {
				return ext;
			}
			else {
				return "";
			}
		});

		var new_file = new SFile(parent + "\\" + new_filename);
		output_files.push(new_file);
	}
};

createOutputFile();

var showFileList = function() {
	for(var i = 0; i < input_files.length; i++) {
		var date_text = Format.datef("YYYY-MM-DD hh:mm:ss", input_files[i].lastModified());
		System.printf("%-24s\t->\t%-24s\t%1.0fKB\t%s", input_files[i].getName(), output_files[i].getName(), input_files[i].length() / 1024, date_text);
	}
};

var renameFileList = function() {
	for(var i = 0; i < input_files.length; i++) {
		input_files[i].renameTo(output_files[i]);
	}
};

var sortFileList = function(sort_type) {

	/**
	 * @type {function(SFile, SFile): number}
	 */
	var sort_function = null;

	// ファイル名の昇順
	if(sort_type === 1) {
		sort_function = function(a, b) {
			return StringComparator.DEFAULT(a.getName(), b.getName());
		};
	}
	else if(sort_type === 2) {
		sort_function = function(a, b) {
			return StringComparator.NATURAL(a.getName(), b.getName());
		};
	}
	else if(sort_type === 3) {
		sort_function = function(a, b) {
			return StringComparator.DEFAULT(a.lastModified().getTime(), b.lastModified().getTime());
		};
	}

	input_files.sort(sort_function);
	
	createOutputFile();
};

while(true) {

	showFileList();

	System.println("コマンドを選んでください。");
	System.println("[1] 名前を変更する。");
	System.println("[2] ソートする。");
	
	var read = System.readLine() | 0;
	System.println(read);
	if(read === 1) {
		renameFileList();
	}
	else if(read === 2) {
		System.println("[1] タイトルの文字列ソート");
		System.println("[2] タイトルの自然順ソート");
		System.println("[3] ファイルの更新日時ソート");
		read = System.readLine() | 0;
		sortFileList(read);
	}
}

このファイルは「\SenkoWSH\examplesChangeFileName.wsf」にあるので、使ってみてください。

JScriptの文法について

利用できる文法が限られています。以下の記事を参照してください。

連携ライブラリについて

以下のライブラリは、JScript及び、SenkoWSHを用いて動作することを確認しております。作りたいバッチに合わせて、機能を拡張しましょう。

MojiJS

githubからダウンロードできます。本ライブラリは、Shift_JISなど日本語用の文字のエンコードが行えます。また、漢字水準の確認なども行えます。JScriptから使用する場合は、/build/mojijs.wsh.jsを利用してください。

npmでも公開しており、node.jsなどからも利用が可能です。

konpeito-ES3

githubからダウンロードできます。本ライブラリは、高機能数値計算ライブラリ konpeito から、行列計算、統計処理を抽出してJScriptから利用できるようにしたライブラリです。

元となったライブラリは、npmでも公開しており、node.jsなどからも利用が可能です。

技術情報

ここでは、このJScript用ライブラリの設計に関して技術的な内容を少しご紹介します。

強制的にCUI起動について

通常、jsをダブルクリックすると、CUIで起動しません。しかし、本ライブラリでは、System.executeOnCScript()をコードの最初に記載すると、CUIモードで動作するようになっています。

中で何をやっているのかというと、WSH.FullNameを見ることで、wscriptで実行しているのか、cscriptで実行しているのかを判別して、それに応じて引数含めて起動しなおしているわけです。

WSHのバッチは、起動時のスクリプト起動環境によってGUIで動作するか、CUIで動作するかが変わってきます。実行ファイルは以下のディレクトリに存在します。

  • C:\Windows\System32\wscript.exe
  • C:\Windows\System32\cscript.exe

classの拡張について

本ライブラリでは、JScriptでは対応していない機能、例えばString.prototype.trim()メソッドなど一部を追加しています。追加したいメソッドは予め、ExtendsStringなどのクラスに追加したうえで、以下のように標準クラスにメソッドを追加して拡張しています。

/**
 * @param {any} original 
 * @param {any} extension
 * @returns {any}
 * @private
 * @ignore
 */
const extendClass = function(original, extension) {
	for(const key in extension) {
		original.prototype[key] = function() {
			const x = [];
			x.push(this);
			for(let i = 0 ; i < arguments.length ; i++) {
				x.push(arguments[i]);
			}
			return extension[key].apply(this, x);
		};
	}
};
extendClass(String, typeExtendsString);

WindowsAPIの実行について

通常、JScriptからは、WindowsAPIを実行できません。そのため、ファイルの選択ダイアログの表示、保存ダイアログの表示や、マウスカーソルの操作、ウィンドウの操作などが一切行えません。

そこで、 エクセルオブジェクト(new ActiveXObject("Excel.Application");)を作成して、CALLを利用して呼び出すといった手法が一般的でした。しかし、PowerShellの登場と、Windows7からのPowerShell標準搭載によって、PowerShellを経由した呼出を行えるようになりました。PowerShellは、C#も実行できるため非常に強力です。

以下は、呼出しているスクリプトの一部を抜粋したものです。詳細は、Systemクラスを確認してください。

/**
 * PowerShell を実行する
 * @param {string} source
 * @returns {string}
 */
static execPowerShell(source) {
	const powershell_base = "powershell -sta -Command";
	const command = (powershell_base + " " + source).replace(/([\\"])/g, "\\$1");
	return System.exec(command).out.replace(/\r?\n$/, "");
}

/**
 * WindowsAPI を実行する
 * 
 * 例
 * - dll_name : user32.dll
 * - function_text : int MessageBox(IntPtr hWnd, string lpText, string lpCaption, UInt32 uType)
 * - exec_text : $api::MessageBox(0, "テキスト", "キャプション", 0);
 * @param {string} dll_name - 利用するdll
 * @param {string} function_text - 関数の定義データ($apiに代入されます。)
 * @param {string} exec_text - 実行コマンド
 * @returns {string}
 */
static WindowsAPI(dll_name, function_text, exec_text) {
	// 利用例
	// System.WindowsAPI(
	// 	"user32.dll",
	// 	"int MessageBox(IntPtr hWnd, string lpText, string lpCaption, UInt32 uType)",
	// 	"$api::MessageBox(0, \"テキスト\", \"キャプション\", 0);"
	// );
	// ダブルクォーテーションだとエスケープが面倒なので、ここはシングルクォーテーションを使用する
	// eslint-disable-next-line quotes
	const api_base = `$api = Add-Type -Name "api" -MemberDefinition "[DllImport(""` + dll_name + `"")] public extern static ` + function_text + `;" -PassThru;`;
	const command = api_base + " " + exec_text;
	return System.execPowerShell(command);
}

本ライブラリでは、上記のSystem.WindowsAPI()を用いて、様々な機能を呼び出ししています。呼び出ししている例としては、RobotクラスDialogクラスをご確認ください。

おわりに

どうでしょうか!ライブラリの紹介をこれで終わりたいと思います。

サンプルスクリプトはまたどこかのタイミングで増やしていければいいなーと思います!

皆様も良かったらお使いください。では!

コメント

  1. […] WSH JScript用ライブラリとVSCodeでWindows用バッチファイルを作ろう […]

タイトルとURLをコピーしました