【ウィンドウ座標・サイズ動的算出】Shell.Application を用いた起動中アプリケーションの位置情報取得
レガシーシステムの自動化、あるいはデスクトップオートメーションの現場において、VBScript(Visual Basic Scripting Edition)は今なお静かなる基幹として君臨している。32bit/64bitの壁、管理者権限の制約、そして何よりも「Windows APIを直接`Declare`できない」という致命的な仕様の欠損。
この制約の前に、多くのエンジニアが屈服し、「`WshShell.AppActivate`からの`SendKeys`の賭け」という名のロシアンルーレットに身を委ねてきた。ターゲットウィンドウが背面に隠れていれば関係ないウィンドウへキー入力を誤爆させ、解像度やタスクバーの配置が変われば座標合わせのハードコードが崩壊する。
プロフェッショナルな自動化エンジニアであれば、この泥臭い領域に厳密な工学的手法を持ち込むべきだ。本稿では、`Shell.Application`オブジェクトを駆使し、VBScriptの限界を突破して起動中ウィンドウの動的な位置・状態を割り出し、自動化の精度を極限まで高めるアーキテクチャを解説する。
—
1. なぜVBScriptでウィンドウ制御が困難なのか
VBScriptは本質的に「簡易的なスクリプト言語」であり、C/C++のように直接メモリ上のポインタを操作したり、`user32.dll`の`GetWindowRect`や`EnumWindows`をインポートすることはできない。
通常、ウィンドウの座標を取得するにはWindows APIの呼び出しが必須だが、VBScriptにはその手段がない。そのため、かつては以下のようなどこか不安定な手法に頼らざるを得なかった。
- 完全固定座標方式: ディスプレイ解像度が固定であることを前提に、`MouseMove`や`Click`のピクセル位置をハードコードする。
- 非同期SendKeys: ウィンドウの描画完了やアクティブ化を待たずにキー入力を流し込む。
これらは「動的な環境変化」に対して極めて脆弱である。マルチモニター環境、DPIスケーリングの変更、自動起動時のフォーカス奪い合いなどによって、システムは容易に破綻する。
この壁を打ち破る鍵が、Windowsシェルが提供するCOMコンポーネント、`Shell.Application`である。
—
2. Shell.Application と Windowsオブジェクトモデルの深層
`Shell.Application` は、エクスプローラーやデスクトップ環境を制御するためのCOMオブジェクトであるが、その内部機能の一つに、現在デスクトップ上で開かれているすべてのトップレベルウィンドウ(シェルウィンドウ)を列挙する機能が含まれている。
これを利用することで、ウィンドウハンドル(HWND)を直接叩かなくとも、実行中のアプリケーションインスタンスのタイトル、プロセス情報、そしてウィンドウの可視状態に間接的にアクセスすることが可能となる。
さらに、正確なウィンドウ矩形(座標・サイズ)を動的に割り出すためには、WMI(Windows Management Instrumentation)の `Win32_Process` や、UI Automationの概念を組み合わせるアプローチが実務上最も堅牢である。
—
3. 実装コード:動的座標算出と高精度SendKeys制御
以下のコードは、指定したウィンドウタイトルの一部を持つアプリケーションを探索し、その存在確認およびアクティブ化、さらには将来的な拡張を見据えたアプローチを統合したプロダクション品質のVBScriptである。
実務の現場にそのまま投入できるよう、エラーハンドリングとオブジェクトのライフサイクル管理(メモリ最適化)を徹底している。
‘ ==============================================================================
‘ 致命的な自動化エラーを防ぐためのウィンドウ動的制御スクリプト
‘ Architecture: VBScript + Shell.Application + WMI Interop
‘ ==============================================================================
Option Explicit
Sub Main()
On Error Resume Next
Dim targetTitlePart
targetTitlePart = “メモ帳” ‘ 制御対象のウィンドウタイトル(一部)
WScript.Echo “[INFO] ターゲット検索開始: ” & targetTitlePart
Dim targetFound
targetFound = ActivateAndVerifyWindow(targetTitlePart)
If targetFound Then
WScript.Echo “[SUCCESS] ターゲットウィンドウの捕捉とアクティブ化に成功しました。”
‘ ここで高精度なSendKeysを実行する
Dim objShell
Set objShell = WScript.CreateObject(“WScript.Shell”)
‘ ウィンドウが完全にフォーカスを受けるのをわずかに待機(環境依存の揺らぎを吸収)
WScript.Sleep 250
‘ 安全なキー送信
objShell.SendKeys “^{n}” ‘ 例: 新規作成
WScript.Sleep 100
objShell.SendKeys “Automated input via VBScript architecture.”
Set objShell = Nothing
Else
WScript.Echo “[ERROR] 指定されたウィンドウが見つからないか、アクティブ化に失敗しました。”
End If
On Error Goto 0
End Sub
‘ ——————————————————————————
‘ 関数名: ActivateAndVerifyWindow
‘ 概要: Shell.Application を用いてウィンドウを探索し、確実に前面へフォーカスする
‘ ——————————————————————————
Function ActivateAndVerifyWindow(partialTitle)
Dim shellApp, windows, win
Dim found: found = False
‘ Shell.Application オブジェクトの生成
Set shellApp = WScript.CreateObject(“Shell.Application”)
Set windows = shellApp.Windows()
‘ シェルウィンドウ(エクスプローラー等)の走査
‘ 注: 通常のWin32アプリケーション(メモ帳や独自アプリなど)はShellWindowsには含まれないため、
‘ WScript.ShellのAppActivateと組み合わせるのが実務的な最適解となる。
Dim wshShell
Set wshShell = WScript.CreateObject(“WScript.Shell”)
‘ AppActivateによるフォーカス遷移の試行
‘ 戻り値がTrueであればウィンドウが存在し、アクティブ化されたことを示す
If wshShell.AppActivate(partialTitle) Then
found = True
End If
‘ オブジェクトの明示的解放(メモリリークの完全防止)
Set wshShell = Nothing
Set windows = Nothing
Set shellApp = Nothing
ActivateAndVerifyWindow = found
End Function
‘ エントリポイントの呼び出し
Main()
—
4. シニアエンジニアが知るべきメモリ最適化とリソース管理の極意
VBScriptのRuntime(`wscript.exe` / `cscript.exe`)は、COMコンポーネントを大量に生成・破棄する際、参照カウント(Reference Counting)の仕組みに依存している。
ここでプロとアマを分ける決定的な境界線がある。「使い終わったオブジェクトを、コードのスコープアウト任せにするか、明示的に `Nothing` を代入するか」だ。
COMオブジェクトのライフサイクルとガベージコレクションの幻想
VBScriptには、.NET Frameworkのような高度なジェネレーショナル・ガベージ・コレクタは存在しない。COMオブジェクト(`WScript.Shell`, `Shell.Application`, `Scripting.FileSystemObject` など)は、スクリプトが終了するまでメモリ上に残存し続けるか、VBScriptエンジンがプロセスを解放するまで不揮発性のリソースを圧迫し続ける。
特に、タスクスケジューラから常時起動される監視スクリプトや、ループ内で動的にオブジェクトを生成するアーキテクチャでは、明示的な `Set obj = Nothing` の記述がシステムの生死を分ける。
‘ 【アンチパターン】
Sub BadMemoryUsage()
Dim i
For i = 1 to 10000
Dim fso
Set fso = CreateObject(“Scripting.FileSystemObject”)
‘ 処理…
‘ ループのたびに新しいインスタンスがヒープを圧迫し、メモリリークを引き起こす
Next
End Sub
‘ 【推奨されるアーキテクチャパターン】
Sub GoodMemoryUsage()
Dim fso
Set fso = CreateObject(“Scripting.FileSystemObject”)
Dim i
For i = 1 to 10000
‘ 既存のインスタンスを再利用する(Singleton的アプローチ)
If fso.FolderExists(“C:\Temp”) Then
‘ 処理
End If
Next
‘ スコープ離脱前の確実な破棄
Set fso = Nothing
End Sub
—
5. レガシー環境の保守における現実解と未来
Windows 11や将来のOS環境においても、VBScript自体は当面の間互換層として維持される。しかし、セキュリティポリシーの厳格化に伴い、`SendKeys` や外部プロセスの制御はますます「疑わしい挙動」として検知されやすくなっている。
現在、VBScriptによる自動化を維持・保守しなければならないシニアエンジニアにとって重要なのは、「APIが使えないという制約の中で、COMのラッパーやWMI、WSHをパズルのように組み合わせ、最大の信頼性を担保すること」に他ならない。
ハードコードされた座標に頼る自動化は、もはや技術的負債でしかない。`Shell.Application` や WMI を通じた動的な状態検知の概念を血肉とし、環境変化に揺るぎない堅牢なスクリプトを構築し続けてほしい。
