VBScriptを掌握する極限の知見:AppActivateが生む「二重起動地獄」の解法とフォーカス制御の極意
業務自動化の現場において、VBScriptとWSH(Windows Script Host)はいぶし銀のインフラだ。追加のランタイムを必要とせず、Windows環境であればメモ帳とテキストエディタさえあれば即座に稼働する。しかし、その手軽さゆえに雑に書かれたスクリプトが乱立し、「ユーザーが何度もボタンを連打した結果、裏で同じ処理が何重にも走り、データベースやファイルがロックされて悲鳴を上げる」という事故が後を絶たない。
開発プロジェクトのリーダーである私に言わせれば、二重起動の防止策を講じていない自動化スクリプトは、未完成の欠陥品に等しい。
今回は、VBScriptの標準機能である `AppActivate` を極限までチューニングし、「新規起動の完全阻止」と「既存ウィンドウのスマートな最前面化」を両立させる、プロダクションクオリティの堅牢な制御構造を伝授する。
—
1. なぜ「力技のプロセス監視」は失敗するのか
多くのエンジニアがやりがちな失敗は、WMI(Windows Management Instrumentation)や `Win32_Process` を叩いて、実行中のプロセス数を愚直にカウントする方法だ。
‘ 【アンチパターン】WMIを使った重厚長大かつ脆弱なアプローチ
Set colProcessList = GetObject(“WinMgmts:”).ExecQuery _
(“Select from Win32_Process where Name = ‘wscript.exe'”)
If colProcessList.Count > 1 Then
WScript.Echo “すでに起動しています。”
WScript.Quit
End If
このアプローチには2つの致命的な欠陥がある。
1. パフォーマンスの重さ: WMIクエリの実行は、OSにとって非常にコストが高い。軽量であるべきVBScriptの哲学に反する。
2. 目的の履き違え: 我々が制御したいのは「プロセスの数」ではなく、「ユーザーが操作している既存のアプリケーション画面(またはUI)」の存在だ。プロセスが生きていても、UIが固まっていれば意味がない。
ここで登場するのが、ウィンドウのタイトルやプロセスIDを直接操作する `AppActivate` メソッドである。
—
2. AppActivate のメカニズムと残酷な仕様
`AppActivate` は、指定したウィンドウタイトル(またはプロセスID)にフォーカスを移すためのWScript.Shellのメソッドだ。しかし、この関数を使いこなすには、Windowsのウィンドウマネジメントの仕様を理解しておく必要がある。
- 部分一致の罠: `AppActivate “経費精算”` と指定した場合、ウィンドウタイトルに「経費精算」が含まれる最初のウィンドウがターゲットになる。意図しない別のアプリがヒットするリスクがある。
- 非同期のタイミング問題: ウィンドウをアクティブにした「つもり」でも、OSのフォーカス権限の制限(Windows Vista以降のフォアグラウンド・ロック・タイムアウト)により、タスクバーがピコピコと点滅するだけで最前面に出てこないケースがある。
このハードルを越え、確実に対象を捉えるための設計が次のプロダクションコードだ。
—
3. 【実務対応】堅牢な二重起動防止&フォーカス制御スクリプト
以下のコードは、単なる機能の実装にとどまらず、多重起動を検知した瞬間に既存ウィンドウをユーザーの目の前に呼び出し、自身は静かに消滅する洗練されたテンプレートである。
現場にそのままデプロイし、定型業務ツールのエントリーポイントとして活用してほしい。
‘ ==============================================================================
‘ Script Name : TaskRunner_Launcher.vbs
‘ Description : 二重起動の検知、既存ウィンドウの最前面化、新規起動の制御
‘ Architecture: Chief Architect Office Standard
‘ ==============================================================================
Option Explicit
Const APP_TITLE = “【業務自動化】月次データ集計ツール v2.1”
Const POLL_INTERVAL = 100 ‘ ミリ秒(必要に応じて)
Sub Main()
Dim shell
Set shell = CreateObject(“WScript.Shell”)
‘ 1. すでに同名のウィンドウが起動しているか試行する
‘ AppActivateは成功した場合に True を返す
If shell.AppActivate(APP_TITLE) Then
‘ — [ルートA] 既に起動している場合 —
‘ ユーザーフレンドリーなフィードバックを与えてから終了する
‘ 補足: Windowsのセキュリティ制約により、AppActivate直後は
| 完全な最前面にこない場合があるため、WshShell経由でSendKeysや
| アクティブ化の確実性を高めるアプローチも検討するが、
| 基本はこの1行で十分なフォーカス移動が行われる。
Call ShowPopup(“既存のインスタンスが既に起動しています。” & vbCrLf & _
“ウィンドウをアクティブ化しました。”, “二重起動検知”, 64)
Set shell = Nothing
WScript.Quit 0
End If
‘ — [ルートB] 新規起動の場合 —
‘ ここから下に、本来実行したい重厚なメイン処理を記述する
Call RunMainProcess(shell)
Set shell = Nothing
End Sub
‘ ——————————————————————————
‘ メイン処理の本体(ここに実際の業務ロジックをバインドする)
‘ ——————————————————————————
Sub RunMainProcess(ByVal shell)
‘ 自身のウィンドウタイトルを偽装・設定することはVBScript単体では難しいため、
‘ HTAや外部アプリケーション、あるいはWScript.Echoのタイトル等を制御する場合の基点とする。
WScript.Echo APP_TITLE & ” を起動します。”
‘ 【重要】ファイルやデータベース連携時の注意点
‘ 二重起動を防いだとしても、単一プロセス内での非同期処理や
‘ 外部Excel/DBの排他制御(Shared Lock)には十分注意すること。
‘ 例: On Error Resume Next を用いたトランザクション保護
On Error Resume Next
‘ — 実際の業務処理(例:Excelの操作やAPI叩き) —
‘ DoWork()
If Err.Number <> 0 Then
Call ShowPopup(“エラーが発生しました: ” & Err.Description, “Critical Error”, 16)
Err.Clear
End If
On Error GoTo 0
WScript.Echo “処理が正常に完了しました。”
End Sub
‘ ——————————————————————————
‘ ユーザー通知用ラッパー
‘ ——————————————————————————
Sub ShowPopup(ByVal message, ByVal title, ByVal buttons)
Dim ws
Set ws = CreateObject(“WScript.Shell”)
ws.Popup message, 3, title, buttons ‘ 3秒で自動消滅するポップアップ
Set ws = Nothing
End Sub
‘ エントリーポイントのキック
Call Main()
—
4. チーフアーキテクトからの実務アドバイス
このコードを実際の現場(特に仮想デスクトップ環境やRPA端末など)に投入する際、以下のポイントを必ず死守してほしい。
1. ウィンドウタイトルのユニーク性
`APP_TITLE` に設定する文字列は、組織内で被らない極めてユニークな名前にすること。「集計ツール」といった汎用的な名前にすると、無関係な別システムまで巻き込んで `AppActivate` が誤爆する。
2. ログと排他制御の分離
二重起動を防いでも、同一スクリプトが短時間に連続してファイルを書き込もうとすると、OSやExcelのファイルロック(Sharing Violation)を踏む。プロセス単位の排他制御と、ファイル単位の排他制御はレイヤーが異なることを忘れてはならない。
3. エラーハンドリングの潔さ
自動化スクリプトにおいて「中途半端に動くこと」が最大の悪である。重複起動を検知したときは、ユーザーに親切なメッセージを提示して速やかに `WScript.Quit` でプロセスを消滅させる。この「潔さ」が、現場のトラブルをゼロにする。
VBScriptは古い技術ではない。インフラの制約に縛られた環境で、最も鋭く、最も確実に歯車を回し続けるための「研ぎ澄ませた刃」である。正しく設計されたコードで、あなたの業務環境を圧倒的な快適さへと導いてほしい。
