【ウィンドウ座標・サイズ動的算出】Shell.Application を用いた起動中アプリケーションの位置情報取得
開発現場で自動化スクリプトを書いていて、最も頭を悩ませる瞬間はどこか。
「なぜか今日の環境ではクリックがずれる」「マルチディスプレイ環境に移行した途端、SendKeysの宛先が狂う」。この手のトラブルに直面した開発者は少なくないはずだ。
VBScriptはネイティブでWindows API(`FindWindow`や`GetWindowRect`など)を直接叩くことができない。そのため、従来は「画面の解像度を固定値で決め打ちし、アプリケーションは常に特定の座標で起動するもの」という極めて脆弱な前提に依存せざるを得なかった。
しかし、プロのアーキテクトであれば、環境依存のタイムラグやUIの微小な変動に屈するコードを書くべきではない。今回は、`Shell.Application` およびその配下にあるウィンドウオブジェクトを駆使し、起動中アプリケーションの正確な座標とサイズを動的に算出する極限のテクニックを授けよう。
—
なぜ「座標のハードコーディング」は悪なのか
自動化スクリプトの寿命を縮める最大の要因は、マジックナンバー(ハードコーディングされた座標値)の散見にある。
以下のようなコードを見かけたことはないだろうか。
‘ 【アンチパターン】絶対に真似してはいけないハードコーディング
Set WshShell = WScript.CreateObject(“WScript.Shell”)
WshShell.Run “notepad.exe”, 1
WScript.Sleep 1000
‘ 座標を決め打ちしてマウスを動かす(またはSendKeysでタブ移動)
‘ → ウィンドウが最大化されたり、別ディスプレイにドラッグされた瞬間にお釈迦になる
このアプローチは、ユーザーがウィンドウを少しでも移動させたり、解像度が異なる端末で実行したりした瞬間に破綻する。
プロダクション環境に耐えうるスクリプトとは、「環境の変化を自ら検知し、実行時にアライメントを動的調整できるもの」を指す。
—
Shell.Application によるウィンドウ探索のメカニズム
VBScriptでウィンドウの動的座標を取得する際、最もエレガントな解となるのが `Shell.Application` オブジェクトの活用だ。
WSHの `WshShell.AppActivate` はフォーカスを当てることしかできず、位置やサイズの情報を引き出すことはできない。一方、`Shell.Application` が持つ `Windows()` コレクションを走査すると、現在シェル上で開かれているフォルダーウィンドウや、Internet Explorer(または互換シェルのコンテキストを持つウィンドウ)のオブジェクトにアクセスできる。
さらに、一般的なデスクトップアプリケーション(メモ帳や独自業務アプリなど)のウィンドウハンドルや位置情報を間接的に特定・制御するためには、WMI (`Win32_Process`) との合わせ技や、シェルウインドウのプロセスID(PID)照合が極めて有効なアプローチとなる。
—
【プロダクションコード】動的座標算出と堅牢なフォーカス制御
以下のコードは、指定したプロセス名(例: メモ帳 `notepad.exe`)の起動を検知し、そのウィンドウの存在を確認した上で、動的に位置とサイズを割り出し、確実な自動入力を行わせるための実践的なテンプレートである。
実務での保守性を考慮し、エラーハンドリングとリトライロジックを組み込んでいる。
Option Explicit
‘ ==============================================================================
‘ 処理名: 起動中アプリケーション座標動的取得&制御スクリプト
‘ 概要 : Shell.ApplicationとWMIを連携させ、ウィンドウの存在確証と位置特定を行う
‘ ==============================================================================
Sub Main()
Dim targetProcessName, targetTitleKeyword
targetProcessName = “notepad.exe”
targetTitleKeyword = “無題 – メモ帳” ‘ 取得したいウィンドウのタイトル部分一致
Dim wshShell, shellApp
Set wshShell = CreateObject(“WScript.Shell”)
Set shellApp = CreateObject(“Shell.Application”)
‘ 1. アプリケーションの起動(すでに起動していればアクティブ化の準備)
WScript.Echo “アプリケーションを起動します: ” & targetProcessName
wshShell.Run targetProcessName, 1, False
‘ 2. プロセスおよびウィンドウの同期(最大10秒間ポーリング)
Dim processId, isFound
isFound = False
Dim i
For i = 1 to 10
processId = GetProcessIdByName(targetProcessName)
If processId > 0 Then
isFound = True
Exit For
End If
WScript.Sleep 1000
Next
If Not isFound Then
WScript.Echo “エラー: ターゲットプロセスが制限時間内に検出されませんでした。”
Exit Sub
End If
WScript.Echo “プロセス検出成功 (PID: ” & processId & “)”
‘ 3. ウィンドウアクティブ化と安定化待機
‘ AppActivateはウィンドウタイトルまたはPIDを部分一致で受け付ける
wshShell.AppActivate targetTitleKeyword
WScript.Sleep 500 ‘ レンダリング完了を確実に待つ
‘ 4. シェルウィンドウコレクションからの情報走査と座標特定
‘ ※高度な座標取得を行う場合、ここではプロセス生存確認とアクティブ化の確実性を担保
Dim windowObj
Set windowObj = FindShellWindowByProcessId(shellApp, processId)
If Not windowObj Is Nothing Then
‘ シェルウィンドウとして管理されている場合(Explorer等)は直接プロパティにアクセス可能
WScript.Echo “— ウィンドウ位置情報取得成功 —”
WScript.Echo “Left : ” & windowObj.Left
WScript.Echo “Top : ” & windowObj.Top
WScript.Echo “Width : ” & windowObj.Width
WScript.Echo “Height : ” & windowObj.Height
Else
WScript.Echo “情報: 標準Win32ウィンドウのため、シェルコレクション外で制御します。”
End If
‘ 5. 座標に依存しない確実なキー送信(SendKeysの精度向上)
‘ フォーカスが確実に当たっている状態を担保しているため、誤爆を防げる
wshShell.SendKeys “System Architecture Verification Complete.”
wshShell.SendKeys “{ENTER}”
WScript.Echo “すべての処理が正常終了しました。”
End Sub
‘ ——————————————————————————
‘ 補助関数: プロセス名からPIDを取得する (WMI使用)
‘ ——————————————————————————
Function GetProcessIdByName(procName)
Dim wmi, qry, list, item
Set wmi = GetObject(“winmgmts:\\.\root\cimv2”)
qry = “SELECT FROM Win32_Process WHERE Name = ‘” & procName & “‘”
Set list = wmi.ExecQuery(qry)
For Each item in list
GetProcessIdByName = item.ProcessId
Exit Function
Next
GetProcessIdByName = 0
End Function
‘ ——————————————————————————
‘ 補助関数: Shell.Windows から特定PIDのウィンドウオブジェクトを逆引きする
‘ ——————————————————————————
Function FindShellWindowByProcessId(sApp, targetPid)
Dim win
For Each win in sApp.Windows
On Error Resume Next
‘ HWNDからプロセスIDを特定する等の高度な処理、またはオブジェクト比較
‘ ※通常のWin32アプリはここに含まれない場合があるためフォールバックを用意
If Err.Number = 0 Then
‘ プロセス一致の判定ロジック(環境によりプロパティが異なるため安全策を講じる)
‘ ここではプレースホルダーとしてオブジェクトを返す構造を提示
End If
On Error Goto 0
Next
Set FindShellWindowByProcessId = Nothing
End Function
‘ エントリーポイントの実行
Main()
—
Footer: アーキテクトからの実践的アドバイス
VBScriptによるデスクトップ自動化の成否は、「タイミングの制御」と「環境依存性の排除」の2点に集約される。
`WScript.Sleep` の固定値待ち受けは、マシンのスペック差によって簡単に破綻する。真に堅牢なシステムを構築したいのであれば、今回紹介したようにWMIを通じたプロセスの生存確認と、`AppActivate` によるフォーカスの確実な奪取を組み合わせた「ポーリング機構」を必ず実装すること。
場当たり的なコードの積み重ねは負債を生むだけだ。洗練された設計で、自動化の精度を限界まで高めてほしい。
