2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

VBAをエクスポート・インポートするWSH+JavaScriptをささっと作る

2
Last updated at Posted at 2026-03-22

はじめに

下記の記事で VBA マクロのデバッグ時,全ての VBA マクロモジュールを解放したり,エクスポート,あるいはインポートする作業を頻繁に行いました。

この種の作業を自動化する VBA マクロ自体よく知られていますが,わざわざ VBA マクロを解放するためだけに専用の VBA マクロを組むというのも馬鹿らしい気がします。もちろん VBA マクロモジュールを外部ファイルとして用意しておき,そのファイルをインポートして実行するだけで良いのですが,その手間すら煩わしいと思っています。

この手の作業を VBA マクロを組む,あるいはインポートするのではなく,外部スクリプトとして(文字通り?)アウトソーシングしたいと思いました。

仕様案(お品書き)

  • WSH + JavaScript で作ります。これまで VBA によく似た VBScript を愛用していましたが,もう使用期限が迫っているので JavaScript に移行します。
  • 下記の機能を実装したいと思います。
    • マクロモジュールの一覧表示
    • マクロモジュールの解放
    • マクロモジュールのエクスポート
      出力先フォルダを指定できるようにしたい
    • マクロモジュールのインポート
      複数ファイル名を指定かつワイルドカードも使いたい
  • せっかくなので対象は Excel に限らず,WordPowerPoint も含めることにします。

実装コード

実装コードはコチラ ※300行弱あります
VBAutil.js
//------------------------------------------------------------------------------
// モジュール属性テーブル
//------------------------------------------------------------------------------
var	table = {
	  1: { ext: ".bas",	name: "標準モジュール"   },	// vbext_ct_StdModule
	  2: { ext: ".cls",	name: "クラスモジュール" },	// vbext_ct_ClassModule
	  3: { ext: ".frm",	name: "MSフォーム"     },	// vbext_ct_MSForm
	 11: { ext: null,	name: "ActiveXデザイナ"  },	// vbext_ct_ActiveXDesigner
	100: { ext: null,	name: null               }	// vbext_ct_Document
};
//------------------------------------------------------------------------------
// メイン関数の呼び出し
//------------------------------------------------------------------------------
var	args = WScript.Arguments.Unnamed;
var	ret = main(args);
try {
	WScript.Quit(ret);
} catch(e) {
	/* 何もしない */
}
//------------------------------------------------------------------------------
// メイン関数
//------------------------------------------------------------------------------
function main(args) {
	//--------------------------------------------------------------------------
	// ヘルプメッセージ
	//--------------------------------------------------------------------------
	if(args.Count < 2) {
		var	msg = [
			"Microsoft Office ファイルの VBA マクロユーティリティ",
			"",
			"VBAUTIL(.JS) [ファイル名] [コマンド] ...",
			"",
			"<ファイル名>",
			"Word, Excel, PowerPoint ファイルを指定可能です。",
			"      Word:拡張子 *.docx, *.docm",
			"     Excel:拡張子 *.xlsx, *.xlsm, *.xlsb",
			"PowerPoint:拡張子 *.pptx, *.pptm",
			"",
			"<コマンド>",
			"   LIST マクロ一覧を表示します。",
			"RELEASE マクロを解放します。",
			" IMPORT [ファイル名]",
			"        指定したファイルをインポートします。",
			"        ファイル名は複数指定可能です。",
			"        ワイルドカードを使用できます。",
			" EXPORT (フォルダ名)",
			"    マクロをエクスポートします。",
			"    フォルダ名を省略するとカレントディレクトリに出力します。",
			"     標準モジュール:拡張子 *.bas",
			"    クラスモジュール:拡張子 *.cls",
			"      MSフォーム:拡張子 *.frm"
		];
		WScript.Echo(msg.join("\n"));
		return -1;
	}
	//--------------------------------------------------------------------------
	// コマンドライン解析
	//--------------------------------------------------------------------------
	var	filename = args(0);
	var	 command = args(1);
	var	  params = [];
	for(var i = 2; i < args.Count; i++)
		params.push(args(i))
	//--------------------------------------------------------------------------
	// コマンド分岐
	//--------------------------------------------------------------------------
	switch(command.toUpperCase()) {
	  case "LIST":		return command_list   (filename);
	  case "RELEASE":	return command_release(filename);
	  case "IMPORT":	return command_import (filename, params);
	  case "EXPORT":	return command_export (filename, params);
	  default:
		WScript.Echo("コマンド " + command + " には対応していません!!");
	}
}
//------------------------------------------------------------------------------
// Microsoft Office ファイルの VBAComponents オブジェクトを取得する。
//------------------------------------------------------------------------------
function get_components(filename) {
	//--------------------------------------------------------------------------
	// ファイルの存在チェック
	//--------------------------------------------------------------------------
	var	fso = WScript.CreateObject("Scripting.FileSystemObject");
	if(!fso.FileExists(filename)) {
		WScript.Echo("ファイル " + filename + " が存在しません!!");
		return null;
	}
	//--------------------------------------------------------------------------
	// 拡張子に応じたアプリケーションの起動
	//--------------------------------------------------------------------------
	var	ext = fso.GetExtensionName(filename);
	var	fullpath = fso.GetAbsolutePathName(filename);
	var	app = null;
	var	collect;
	switch(ext.toLowerCase()) {
	  case "docx": case "docm":
		try {
			app = GetObject("", "Word.Application");
		} catch(e) {
			app = WScript.CreateObject("Word.Application");
		}
		if(app == null) {
			WScript.Echo("Word の起動に失敗しました!!");
			return null;
		}
		collect = app.Documents;
		break;
	  case "pptx": case "pptm":
		try {
			app = GetObject("", "PowerPoint.Application");
		} catch(e) {
			app = WScript.CreateObject("PowerPoint.Application");
		}
		if(app == null) {
			WScript.Echo("PowerPoint の起動に失敗しました!!");
			return null;
		}
		collect = app.Presentations;
		break;
	  case "xlsx": case "xlsm": case "xlsb":
		try {
			app = GetObject("", "Excel.Application");
		} catch(e) {
			app = WScript.CreateObject("Excel.Application");
		}
		if(app == null) {
			WScript.Echo("Excel の起動に失敗しました!!");
			return null;
		}
		collect = app.Workbooks;
		break;
	  default:
		WScript.Echo("拡張子 " + ext + " には対応していません!!");
		return null;
	}
	app.Visible = true;
	//--------------------------------------------------------------------------
	// ドキュメント/プレゼンテーション/ワークブックの取得
	//--------------------------------------------------------------------------
	var	obj = null;
	for(var i = 1; i <= collect.Count; i++) {
		if(collect(i).FullName.toLowerCase() == fullpath.toLowerCase()) {
			obj = collect(i);
			break;
		}
	}
	//--------------------------------------------------------------------------
	// ドキュメント/プレゼンテーション/ワークブックの新規オープン
	//--------------------------------------------------------------------------
	if(obj == null) {
		obj = collect.Open(fullpath);
		if(obj == null) {
			WScript.Echo("ファイル " + filename + " のオープンに失敗しました!!");
			return null;
		}
	}
	//--------------------------------------------------------------------------
	// VBAcomponents の取得
	//--------------------------------------------------------------------------
	try {
		return obj.VBProject.VBComponents;
	} catch(e) {
		var	msg = [
			"Visual Basic プロジェクトのアクセスが許可されていません!!",
			"",
			"アプリケーションを立ち上げて以下の設定を行って下さい。",
			"",
			"[開発] タブの [マクロのセキュリティ] を選択するか,もしくは [ファイル] タブの",
			"[オプション][セキュリティセンター][セキュリティセンターの設定][マクロの設定]",
			"を選んで,[VBA プロジェクトオブジェクトモデルへのアクセスを信頼する] に",
			"チェックして下さい。"
		];
		WScript.Echo(msg.join("\n"));
		return null;
	}
}
//------------------------------------------------------------------------------
// コマンド LIST
//------------------------------------------------------------------------------
function command_list(filename) {
	var	vba = get_components(filename);
	if(vba == null) return -1;
	for(var i = 1; i <= vba.Count; i++) {
		var	module = vba(i);
		var	name = table[module.Type].name;
		if(name) WScript.Echo(name + ": " + module.Name);
	}
	return 0;
}
//------------------------------------------------------------------------------
// コマンド RELEASE
//------------------------------------------------------------------------------
function command_release(filename) {
	//--------------------------------------------------------------------------
	// 解放対象をリストアップ
	//--------------------------------------------------------------------------
	var	vba = get_components(filename);
	if(vba == null) return -1;
	var	list = [];
	for(var i = 1; i <= vba.Count; i++) {
		var	module = vba(i);
		var	name = table[module.Type].name;
		if(name) list.push(module);
	}
	if(list.length == 0) return 0;
	//--------------------------------------------------------------------------
	// 解放の実行
	//--------------------------------------------------------------------------
	WScript.Echo("マクロを解放します。");
	for(var i = 0; i < list.length; i++) {
		var	module = list[i];
		var	name = table[module.Type].name;
		WScript.Echo(name + ": " + module.Name);
		vba.Remove(module);
	}
	return 0;
}
//------------------------------------------------------------------------------
// コマンド EXPORT
//------------------------------------------------------------------------------
function command_export(filename, params) {
	//--------------------------------------------------------------------------
	// 出力先フォルダの存在チェック
	//--------------------------------------------------------------------------
	var	fso = WScript.CreateObject("Scripting.FileSystemObject");
	var	path = (params.length > 0) ? params[0] : ".";
	if(!fso.FolderExists(path)) {
		WScript.Echo("フォルダ " + path + " が存在しません!!");
		return -1;
	}
	var	abspath = fso.GetAbsolutePathName(path);
	//--------------------------------------------------------------------------
	// エクスポートの実行
	//--------------------------------------------------------------------------
	var	vba = get_components(filename);
	if(vba == null) return -1;
	WScript.Echo("マクロをエクスポートします。");
	for(var i = 1; i <= vba.Count; i++) {
		var	module = vba(i);
		var	ext = table[module.Type].ext;
		if(ext == null) continue;
		var	fullpath = fso.BuildPath(abspath, module.Name + ext);
		WScript.Echo(fullpath);
		module.Export(fullpath);
	}
	return 0;
}
//------------------------------------------------------------------------------
// コマンド IMPORT
//------------------------------------------------------------------------------
function command_import(filename, params) {
	//--------------------------------------------------------------------------
	// インポートリストの作成
	//--------------------------------------------------------------------------
	if(params.length == 0) {
		WScript.Echo("インポートファイルを指定して下さい!!");
		return -1;
	}
	var	fso = WScript.CreateObject("Scripting.FileSystemObject");
	var	shell = WScript.CreateObject("WScript.Shell");
	var	list = [];
	for(var i = 0; i < params.length; i++) {
		var	proc = shell.Exec("CMD /Q /C for %I in (" + params[i] + ") do echo %~fI");
		while(!proc.StdOut.AtEndOfStream) {
			var	fullpath = proc.StdOut.ReadLine();
			if(fso.FileExists(fullpath)) list.push(fullpath);
		}
	}
	if(list.length == 0) {
		WScript.Echo("インポートファイルがありません!!");
		return -1;
	}
	//--------------------------------------------------------------------------
	// インポートの実行
	//--------------------------------------------------------------------------
	var	vba = get_components(filename);
	if(vba == null) return -1;
	WScript.Echo("マクロをインポートします。");
	for(var i = 0; i < list.length; i++) {
		var	fullpath = list[i];
		WScript.Echo(fullpath);
		vba.Import(fullpath);
	}
	return 0;
}

技術解説

アプリケーション起動

コマンドラインから Microsoft Office ファイル名 filename が与えらえているものとします。ファイル名は相対パスとします。

  • ファイルの拡張子に応じて起動するアプリケーションを切り替えます。
  • すでにアプリケーションが起動していれば,そのアプリケーションオブジェクトを取得し,そうでなければアプリケーションを起動します。ここで例外処理が必要なのがちょっと気持ち悪いところですが,この手の処理のテンプレートなので仕方ないところです。
  • アプリケーションに応じてコレクション collect を取得します。Word であれば Document コレクション,PowerPoint であれば Presentation コレクション,Excel であれば Workbook コレクションを取得します。
  • 最後にアプリケーションを表示させる Visilbe = true を忘れてはいけません。筆者はこれを忘れてゾンビプロセスを多数発生させたことがあります。
アプリケーション起動
var	fso = WScript.CreateObject("Scripting.FileSystemObject");
var	ext = fso.GetExtensionName(filename);
var	fullpath = fso.GetAbsolutePathName(filename);
var	app = null;
var	collect;
switch(ext.toLowerCase()) {
  case "docx": case "docm":
	try {
		app = GetObject("", "Word.Application");
	} catch(e) {
		app = WScript.CreateObject("Word.Application");
	}
	if(app == null) {
		WScript.Echo("Word の起動に失敗しました!!");
		return null;
	}
	collect = app.Documents;
	break;
  case "pptx": case "pptm":
	try {
		app = GetObject("", "PowerPoint.Application");
	} catch(e) {
	app = WScript.CreateObject("PowerPoint.Application");
	}
	if(app == null) {
		WScript.Echo("PowerPoint の起動に失敗しました!!");
		return null;
	}
	collect = app.Presentations;
	break;
  case "xlsx": case "xlsm": case "xlsb":
	try {
		app = GetObject("", "Excel.Application");
	} catch(e) {
		app = WScript.CreateObject("Excel.Application");
	}
	if(app == null) {
		WScript.Echo("Excel の起動に失敗しました!!");
		return null;
	}
	collect = app.Workbooks;
	break;
  default:
	WScript.Echo("拡張子 " + ext + " には対応していません!!");
	return null;
}
app.Visible = true;

ドキュメント/プレゼンテーション/ワークブックの取得

指定したファイル名と同じファイルを既にオープンしていれば,そのファイルのオブジェクトを取得し,オープンしていなければ新規オープンします。

Word, PowerPoint, Excel のいずれもメソッド名が同じなので共通処理となっています。

var	obj = null;
for(var i = 1; i <= collect.Count; i++) {
	if(collect(i).FullName.toLowerCase() == fullpath.toLowerCase()) {
		obj = collect(i);
		break;
	}
}
if(obj == null) {
	obj = collect.Open(fullpath);
	if(obj == null) {
		WScript.Echo("ファイル " + filename + " のオープンに失敗しました!!");
		return null;
	}
}

VBAコンポーネントの取得

Word, PowerPoint, Excel のいずれもメソッド名が同じなので共通処理となっています。

なお,セキュリティ設定によっては例外を起こす場合があるので,ここはエラーメッセージをいつもより親切に書いています。

VBAコンポーネントの取得
try {
	return obj.VBProject.VBComponents;
} catch(e) {
	var	msg = [
		"Visual Basic プロジェクトのアクセスが許可されていません!!",
		"",
		"アプリケーションを立ち上げて以下の設定を行って下さい。",
		"",
		"[開発] タブの [マクロのセキュリティ] を選択するか,もしくは [ファイル] タブの",
		"[オプション][セキュリティセンター][セキュリティセンターの設定][マクロの設定]",
		"を選んで,[VBA プロジェクトオブジェクトモデルへのアクセスを信頼する] に",
		"チェックして下さい。"
	];
	WScript.Echo(msg.join("\n"));
	return null;
}

LIST コマンド

上記で取得した VBA コンポーネントオブジェクト vba とします。

指定した Microsoft Office ファイルに含まれる VBA モジュールの種類とモジュール名を表示します。ドキュメントモジュールは表示しません。

LIST コマンド
var	table = {
	  1: { ext: ".bas",	name: "標準モジュール"	},	// vbext_ct_StdModule
	  2: { ext: ".cls",	name: "クラスモジュール"	},	// vbext_ct_ClassModule
	  3: { ext: ".frm",	name: "MSフォーム"		},	// vbext_ct_MSForm
	 11: { ext: null,	name: "ActiveXデザイナ"	},	// vbext_ct_ActiveXDesigner
	100: { ext: null,	name: null				}	// vbext_ct_Document
};
for(var i = 1; i <= vba.Count; i++) {
	var	module = vba(i);
	var	name = table[module.Type].name;
	if(name) WScript.Echo(name + ": " + module.Name);
}

RELEASE コマンド

指定した Microsoft Office ファイルに含まれる VBA モジュールを解放(削除)します。ドキュメントモジュールは解放しません。

ループを二つに分け,まず1回目のループで削除対象のモジュールを列挙します。

削除対象のリスト作成
var	list = [];
for(var i = 1; i <= vba.Count; i++) {
	var	module = vba(i);
	var	name = table[module.Type].name;
	if(name) list.push(module);
}
if(list.length == 0) return 0;

2回目のループで削除します。

マクロの解放
WScript.Echo("マクロを解放します。");
for(var i = 0; i < list.length; i++) {
	var	module = list[i];
	var	name = table[module.Type].name;
	WScript.Echo(name + ": " + module.Name);
	vba.Remove(module);
}

EXPORT コマンド

配列の先頭要素 params[0] に出力フォルダ名が相対パスで格納されているものとします。ただし,出力フォルダ名は省略可能とし,省略した場合はカレントディレクトリとします。

出力フォルダ名の生成
var	fso = WScript.CreateObject("Scripting.FileSystemObject");
var	path = (params.length > 0) ? params[0] : ".";
if(!fso.FolderExists(path)) {
	WScript.Echo("フォルダ " + path + " が存在しません!!");
	return -1;
}
var	abspath = fso.GetAbsolutePathName(path);

エクスポートするファイル名は VBA モジュール名とし,拡張子はモジュールのタイプに応じて決定します。

エクスポートの実行
WScript.Echo("マクロをエクスポートします。");
for(var i = 1; i <= vba.Count; i++) {
	var	module = vba(i);
	var	ext = table[module.Type].ext;
	if(ext == null) continue;
	var	fullpath = fso.BuildPath(abspath, module.Name + ext);
	WScript.Echo(fullpath);
	module.Export(fullpath);
}

IMPORT コマンド

インポートするファイル名は複数指定可能とし,配列 params[] に格納されているものとします。ワイルドカードも使用可能とします。このため子プロセスで for コマンドを用いてインポートファイルを列挙し,その出力を取り込むようにしました。

再帰的に検索するのであれば dir /s /b コマンドの出力を取り込むと検索結果を絶対パスで得られるので便利ですが,非再帰的に検索しようとして単に dir /b コマンドだと絶対パスを得られないので使えません。

本スクリプトで VBA マクロモジュールを再帰的に検索するような用途は考えにくいので,for コマンドでファイルを検索し,その出力を絶対パス %~fI で出力するようにしました。

dir コマンドと異なり,for コマンドは存在しないファイルを列挙できます。このためファイルの存在チェックを行うようにしました。

インポートファイルリストの作成
var	fso = WScript.CreateObject("Scripting.FileSystemObject");
var	shell = WScript.CreateObject("WScript.Shell");
var	list = [];
for(var i = 0; i < params.length; i++) {
	var	proc = shell.Exec("CMD /Q /C for %I in (" + params[i] + ") do echo %~fI");
	while(!proc.StdOut.AtEndOfStream) {
		var	fullpath = proc.StdOut.ReadLine();
		if(fso.FileExists(fullpath)) list.push(fullpath);
	}
}
if(list.length == 0) {
	WScript.Echo("インポートファイルがありません!!");
	return -1;
}
インポートの実行
WScript.Echo("マクロをインポートします。");
for(var i = 0; i < list.length; i++) {
	var	fullpath = list[i];
	WScript.Echo(fullpath);
	vba.Import(fullpath);
}

実行例

引数なしで実行するとヘルプメッセージを表示します。

引数なしで実行した場合
c:\Qiita>vbautil
Microsoft Office ファイルの VBA マクロユーティリティ

VBAUTIL(.JS) [ファイル名] [コマンド] ...

<ファイル名>
Word, Excel, PowerPoint ファイルを指定可能です。
      Word:拡張子 *.docx, *.docm
     Excel:拡張子 *.xlsx, *.xlsm, *.xlsb
PowerPoint:拡張子 *.pptx, *.pptm

<コマンド>
   LIST マクロ一覧を表示します。
RELEASE マクロを解放します。
 IMPORT [ファイル名]
        指定したファイルをインポートします。
        ファイル名は複数指定可能です。
        ワイルドカードを使用できます。
 EXPORT (フォルダ名)
    マクロをエクスポートします。
    フォルダ名を省略するとカレントディレクトリに出力します。
     標準モジュール:拡張子 *.bas
    クラスモジュール:拡張子 *.cls
      MSフォーム:拡張子 *.frm

LIST コマンドの実行例です。

LIST コマンドの実行例
c:\Qiita>vbautil sample.xlsb list
標準モジュール: Module1
標準モジュール: Module2
標準モジュール: Module3

EXPORT コマンドの実行例です。出力フォルダ名を省略するとカレントディレクトリに出力します。

EXPORT コマンドの実行例
c:\Qiita>vbautil sample.xlsb export
マクロをエクスポートします。
C:\Qiita\Module1.bas
C:\Qiita\Module2.bas
C:\Qiita\Module3.bas

RELEASE コマンドの実行例です。

RELEASE コマンドの実行例
c:\Qiita>vbautil sample.xlsb release
マクロを解放します。
標準モジュール: Module1
標準モジュール: Module2
標準モジュール: Module3

IMPORT コマンドの実行例です。カレントディレクトリにある全ての *.bas ファイルをインポートします。

IMPORT コマンドの実行例
c:\Qiita>vbautil sample.xlsb import *.bas
マクロをインポートします。
C:\Qiita\Module1.bas
C:\Qiita\Module2.bas
C:\Qiita\Module3.bas

参考文献

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?