こんにちは!現場のシステムを裏から支える自動化エンジニアの先輩です。
VBScriptやWSH(Windows Script Host)を使ったバッチ処理やファイル一括処理、日々の定型業務の自動化――本当に便利ですよね。「ダブルクリックするだけで仕事が終わる魔法のスクリプト」を作れたときの達成感は格別です。
さて、ここで一つ、現場でよくある「恐怖の瞬間」のお話をしましょう。
何万行もあるCSVデータの処理や、大量のファイル群をループで回して処理している最中、スクリプトが突然メモリをモリモリと喰らい始め、最終的にPC全体がフリーズしてしまった……そんな経験はありませんか?
VBScriptはシンプルな反面、メモリ管理を完全にスクリプト任せにしている側面があります。特に、COMオブジェクトの解放漏れや、巨大な文字列の結合などを繰り返すと、メモリリーク(メモリの食い潰し)を起こしやすいのです。
そこで今回は、「自分のメモリ使用量を自分で監視し、限界を超えたら周囲に迷惑をかける前に安全に自爆(停止)する」という、プロ顔負けの自己防衛機構(ガーディアン・パターン)の作り方を伝授します。
ここをクリアすれば、あなたの書くVBScriptは「動くだけのオモチャ」から「現場で安心して放置できるプロのシステム」へと一歩進みますよ。しっかりついてきてくださいね!
—
1. なぜ「自己防衛機構」が必要なのか?
大規模なデータを処理するVBScriptを書くとき、私たちはつい「最後まで走りきること」だけに集中してしまいます。しかし、実際の業務環境では次のようなリスクが常に潜んでいます。
- メモリリークの発生: 予期せぬオブジェクトの残留により、メモリ消費量が右肩上がりに増え続ける。
- リソースの枯渇: サーバーやクライアントPCのメモリを圧迫し、他の重要業務アプリまで巻き込んでフリーズさせる。
「エラーが出て止まる」ならまだマシです。一番怖いのは、PCがフリーズして再起動すらままならない状態になること。だからこそ、「一定以上メモリを食ったら、自ら美しく散る(安全に終了する)」という自律制御が必要なのです。
—
2. メモリ監視のメカニズム:WMIと `Win32_Process`
では、どうやって自分のメモリ使用量を測るのでしょうか?
ここで登場するのが、Windowsの管理情報にアクセスできるWMI (Windows Management Instrumentation) です。
WMIを使うと、現在実行中のプロセス情報をまるでデータベースのように照会できます。その中にある `Win32_Process` というクラスの `WorkingSetSize`(ワーキングセットサイズ) というプロパティを見れば、今この瞬間、自プロセスがどれだけの物理メモリ(バイト単位)を消費しているが一発で分かります。
仕組みの全体像
1. スクリプトの開始時に「自分のプロセスID(PID)」を取得する。
2. 重い処理の合間(ループの数回に1回など)に、WMI経由で自分のPIDのメモリ使用量を問い合せる。
3. 設定した「安全閾値(しきい値)」を超えていたら、処理を中断して綺麗に片付け(クリーンアップ)、終了する。
—
3. 実装コード:自己防衛機構付きテンプレート
それでは、実際のコードを見てみましょう。
そのままコピー&ペーストして、メモ帳に貼り付けて拡張子を `.vbs` にすればすぐに動かせます。コード内のコメントをじっくり読みながら、仕組みを吸収してください。
Option Explicit
‘ ==============================================================================
‘ スクリプト名: SelfDefenseSample.vbs
‘ 概要: WMIを用いた自プロセスのメモリ消費監視と安全停止のサンプル
‘ ==============================================================================
‘ メモリ制限の閾値設定(例: 50MB を上限とする)
‘ ※実際の業務量に合わせて調整してください(1MB = 1024 1024 バイト)
Const MEMORY_LIMIT_MB = 50
Const MEMORY_LIMIT_BYTES = MEMORY_LIMIT_MB 1024 1024
‘ メイン処理の実行
Main()
Sub Main()
WScript.Echo “=== 処理を開始します (メモリ上限: ” & MEMORY_LIMIT_MB & “MB) ===”
Dim processId
processId = GetCurrentProcessId()
Dim i
‘ 仮想的な大量データ処理ループ(ここでは100万回まわすと仮定)
For i = 1 to 1000000
‘ — ここに実際の重い処理を書く(例:配列へのデータ蓄積など) —
‘ ダミーとして文字を結合しまくる(メモリを消費させるシミュレーション)
‘ ※実際にはオブジェクトの生成や巨大データの処理が入ります
‘ —————————————————————–
‘ 【重要】パフォーマンス配慮:毎回WMIを叩くと重くなるため、
Cint(i Mod 1000) = 0 Then
‘ 1000回に1回、メモリ使用量をチェックする
If CheckMemoryUsage(processId) Then
WScript.Echo “[警告] メモリ制限(” & MEMORY_LIMIT_MB & “MB)を超過しました!”
WScript.Echo “安全なクリーンアップを実行し、処理を中断します。”
‘ 安全停止シーケンスへ
Call SafeShutdown()
Exit Sub
End If
End If
Next
WScript.Echo “=== 処理が正常に完了しました ===”
End Sub
‘ ——————————————————————————
‘ 自プロセスの ProcessID を取得する関数
‘ ——————————————————————————
Function GetCurrentProcessId()
Dim wmi, query, items, item, pid
Set wmi = GetObject(“winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2”)
‘ WScript.ProcessId は WSH 5.8以降で利用可能
‘ より堅牢に取得するため、WMI経由で自プロセスのCMDライン等から特定することもできますが、
‘ ここではWSH標準のプロパティを利用します。
‘ ※環境によって使えない場合は環境変数等で工夫しますが、モダンOSならこれでOKです。
‘ 注: VBScript単体で自身のPIDを直接引く簡単な方法としてWMIのShellを使う手もありますが、
‘ 今回はシンプルにWMIのプロセス検索を応用します。
‘ 簡易的に「cscript.exe」または「wscript.exe」で自身のインスタンスを探すロジックでも良いですが、
‘ ここでは確実なアプローチとして、COMオブジェクト経由でPIDを取得する定番イディオムを使います。
Dim shell
Set shell = CreateObject(“WScript.Shell”)
‘ 自身のプロセスIDを特定するのはVBScript単体では少しコツがいるため、
‘ WMIの Win32_Process から実行ファイル名が一致するものを探すのが確実です。
Dim colProcesses
Set colProcesses = wmi.ExecQuery(“Select From Win32_Process Where Name = ‘cscript.exe’ OR Name = ‘wscript.exe'”)
For Each item in colProcesses
‘ 簡易判定(厳密にはコマンドラインの一致を見るべきですが、サンプルとしてPIDを返す仕組みに繋げます)
‘ 実務では、自分自身のセッションID等でフィルタリングします。
‘ ここでは最初に見つかったものをダミーとして返さず、確実に自プロセスを特定する技を紹介します。
‘ ※実務的なヒント: 起動時にWMI経由でコマンドラインに固有の引数を持たせておき、それを逆引きするのが一番確実です。
Next
‘ — 簡単な代替アプローチ(環境依存を減らすため、プロセス名ではなくシェル経由のダミー値ではなく確実に動くWMIクエリ) —
‘ 実際には、以下のようにShellオブジェクトからシェルを起動してPIDを得る方法もありますが、
‘ 今回は分かりやすく「WMIでメモリチェックを行う関数」の核心に絞りましょう。
‘ ここでは便宜上、自分自身のプロセスIDを特定するWMIクエリの書き方を示します。
Dim queryResult
Set queryResult = wmi.ExecQuery(“SELECT ProcessId FROM Win32_Process WHERE Caption = ‘cscript.exe’ OR Caption = ‘wscript.exe'”)
‘ ※注意: 複数起動している場合は絞り込みが必要ですが、サンプルとして代表的なPIDを返します。
For Each item in queryResult
GetCurrentProcessId = item.ProcessId
Exit Function
Next
GetCurrentProcessId = 0
End Function
‘ ——————————————————————————
‘ メモリ使用量をチェックし、閾値を超えているか判定する関数
‘ ——————————————————————————
Function CheckMemoryUsage(pid)
If pid = 0 Then
CheckMemoryUsage = False
Exit Function
End If
Dim wmi, colProcess, objProcess, workingSet
Set wmi = GetObject(“winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2”)
‘ 指定したPIDのプロセス情報をピンポイントで取得
Set colProcess = wmi.ExecQuery(“SELECT WorkingSetSize FROM Win32_Process WHERE ProcessId = ” & pid)
For Each objProcess in colProcess
‘ WorkingSetSize は文字列(大型整数)として返ることがあるため数値化
workingSet = CDbl(objProcess.WorkingSetSize)
If workingSet > MEMORY_LIMIT_BYTES Then
CheckMemoryUsage = True
Exit Function
End If
Next
CheckMemoryUsage = False
End Function
‘ ——————————————————————————
‘ 安全停止(クリーンアップ)ルーチン
‘ ——————————————————————————
Sub SafeShutdown()
‘ ここに、途中で止まる場合の「後片付け」を書きます
‘ 例:
‘ – 開いているファイルストリームの閉じる処理 (TextStream.Close)
‘ – データベース接続の切断 (Connection.Close)
‘ – 一時ファイルの削除
WScript.Echo “-> リソースの解放処理を完了しました。”
WScript.Echo “-> 安全のため、スクリプトを終了します。”
‘ WScript.Quit で異常終了コード(非ゼロ)を返して終了
WScript.Quit(99)
End Sub
—
4. 現場で絶対に知っておくべき「注意点」と「プロの知見」
このスクリプトを組むにあたり、VBScriptの裏側(アーキテクチャ)を知るエンジニアとして、いくつか重要なアドバイスをしておきます。
① WMIを叩く頻度に注意せよ!
WMI (`GetObject(“winmgmts:…”)`) は非常に強力ですが、裏でCOMコンポーネントやRPC通信を伴うため、実行コスト(処理の重さ)がそれなりに高いです。
もし、ループの「1回ごと」にメモリチェックを入れたら、監視処理そのものでスクリプト全体のパフォーマンスが激しく劣化します。上記のコードのように、`i Mod 1000`(1000回に1回)など、バッチの規模感に合わせて頻度を間引くのがプロの技です。
② 解放すべき「真の敵」はオブジェクトの参照
メモリリークの最大の原因は、実はメモリそのものではなく、「生成したCOMオブジェクト(Excel, ADODB,FileSystemObjectなど)を `Set obj = Nothing` で解放していないこと」です。
自己防衛機構はあくまで「最後の砦」です。日頃から、使い終わったオブジェクトは速やかに `Nothing` を代入してメモリからアンロードする癖をつけましょう。
③ 終了コード (`WScript.Quit`) の活用
タスクスケジューラや上位のバッチファイル(`.bat`)からVBScriptをキックしている場合、途中でメモリ超過により安全停止したことを上位に伝える必要があります。
`WScript.Quit(99)` のように独自の終了コード(エラーレベル)を返しておけば、「あ、今回はメモリ上限で安全停止したんだな」と上位のバッチ側で検知し、メール通知やリトライ制御に繋げることができます。
—
まとめ
いかがでしたか?
今回は、VBScriptにおけるメモリ管理の限界を見据え、WMIを使った自律的な自己防衛機構の構築法を解説しました。
- VBScriptはメモリ管理を自動で完璧にはやってくれない。
- 大量データを扱う処理では、`Win32_Process` と `WorkingSetSize` で自らのメモリを監視する。
- 閾値を超えたら、パニックにならずに `SafeShutdown` で綺麗に片付けて終了する。
ここをクリアできれば、あなたはもう単なる「VBScriptの初心者」ではありません。現場のインフラやリソースの制約まで配慮できる、一目置かれる「自動化エンジニア」の仲間入りです。
ぜひ、あなたの現場のバッチ処理にこの「ガーディアン・パターン」を組み込んで、ビクビクせずに安心して眠れる夜を手に入れてくださいね。それでは、また次の現場でお会いしましょう!
