UIA(UI Automation)で他アプリ・Webフォームへ自動入力 ― Selenium不要のRPA代替
VBA禁止・マクロ禁止の完全閉域環境での代替手法は → VBAなしでWebシステム入力を自動化するQRリーダー方式 およびQRシリーズ全編を参照してください。VBAが使えるなら、QRのような物理的なエミュレーションではなく、Windows標準のAPIで他アプリやWebフォームを直接制御できます。
1. なぜ「UI Automation」なのか?
Internet Explorerの廃止に伴い、VBAから直接DOMを操作する「IE制御」は過去のものとなりました。現在、Webスクレイピングや自動操作といえば「SeleniumBasic」やWebDriver系のツールが有名ですが、これには「ブラウザドライバのバージョン管理」「環境構築」「社内PCへの導入許可」といった実務上のハードルがあります。
そこで選択肢になるのが、Windowsに標準搭載されているアクセシビリティAPI「UI Automation(UIA)」です。UIAは、画面上のボタン、入力欄、一覧、チェックボックスなどを「UI要素」として取得し、VBAから入力・クリックなどの操作を行う仕組みです。
UIAは、EdgeやChromeなどのモダンブラウザだけでなく、ERP、会計ソフト、受注システム、社内向けのWindowsアプリなどにも応用できます。
2. このサンプルで体感できること
この記事のサンプルコードを実行すると、以下の処理がすべて自動で行われます。
- VBAが一時フォルダにテスト用HTMLフォームを自動生成する
- EdgeでそのHTMLフォームを開く
- UI Automationでブラウザ画面を探す
- 社員番号、氏名、金額、備考の入力欄を探す
- 3名分のデータを1項目ずつ、ゆっくり自動入力する
- 1名分ごとに登録ボタンをクリックする
- 画面下部に登録履歴を追記表示する
つまり、読者はHTMLファイルを別途作る必要がありません。Excel VBAの標準モジュールにコードを貼り付け、マクロを実行するだけで、UIAによる自動入力を体感できます。
UIAutomationClientの参照設定を使います。CreateObject("UIAutomationClient.CUIAutomation")は使いません。ADODB.Streamも使いません。- HTMLファイルはVBA側で自動生成します。
- 入力が速すぎて見えない問題を避けるため、入力ごとに短い待機を入れています。
3. 事前準備
このサンプルでは、VBAからUI Automationを使うために、最初に1回だけ参照設定を行います。
- Excelを開く
Alt + F11でVBE(Visual Basic Editor)を開くツール > 参照設定を開く- UIAutomationClient にチェックを入れる
挿入 > 標準モジュールを選択する- この記事のコードをそのまま貼り付ける
UIA_体感デモ_3名分_ゆっくり入力を実行する
Set uia = New CUIAutomation で作成します。環境によっては CreateObject("UIAutomationClient.CUIAutomation") が429エラーになるため、この記事では参照設定を前提にした安定寄りのコードにしています。
4. 貼り付けるだけで動く全部入りコード
以下のコードを、Excel VBAの標準モジュールにそのまま貼り付けてください。貼り付け後、UIA_体感デモ_3名分_ゆっくり入力 を実行します。
Option Explicit
' ============================================================
' UI Automation 体感デモ 3名分 ゆっくり入力版
'
' 前提:
' VBEの「ツール」→「参照設定」で
' 「UIAutomationClient」にチェックを入れてください。
'
' このコードの目的:
' ・VBAがHTMLフォームを自動生成する
' ・EdgeでHTMLを開く
' ・UI Automationで入力欄を探す
' ・3名分のデータをゆっくり入力する
' ・登録ボタンを押して履歴を表示する
'
' 注意:
' ・CreateObject("UIAutomationClient.CUIAutomation") は使いません。
' ・ADODB.Stream も使いません。
' ============================================================
Private Const SCOPE_CHILDREN As Long = 2
Private Const SCOPE_SUBTREE As Long = 7
Private Const UIA_NAME_PROPERTY_ID As Long = 30005
Private Const UIA_CONTROL_TYPE_PROPERTY_ID As Long = 30003
Private Const UIA_INVOKE_PATTERN_ID As Long = 10000
Private Const UIA_VALUE_PATTERN_ID As Long = 10002
Private Const UIA_BUTTON_CONTROL_TYPE_ID As Long = 50000
Private Const UIA_EDIT_CONTROL_TYPE_ID As Long = 50004
Public Sub UIA_体感デモ_3名分_ゆっくり入力()
Dim uia As CUIAutomation
Dim root As IUIAutomationElement
Dim browserWindow As IUIAutomationElement
Dim htmlPath As String
Dim url As String
Dim data(1 To 3, 1 To 4) As String
Dim i As Long
On Error GoTo ErrHandler
' 1. デモ用データ 3名分
data(1, 1) = "A001"
data(1, 2) = "山田太郎"
data(1, 3) = "12500"
data(1, 4) = "1人目:UIAで自動入力しています。"
data(2, 1) = "A002"
data(2, 2) = "佐藤花子"
data(2, 3) = "23800"
data(2, 4) = "2人目:同じフォームに連続入力しています。"
data(3, 1) = "A003"
data(3, 2) = "鈴木一郎"
data(3, 3) = "9800"
data(3, 4) = "3人目:登録ボタンまで自動で押します。"
' 2. UI Automation オブジェクトを作成
Set uia = New CUIAutomation
' 3. 一時フォルダにHTMLファイルを自動作成
htmlPath = Environ$("TEMP") & "\uia_test_form_from_vba.html"
CreateSampleHtmlFile htmlPath
' 4. file URLに変換
url = "file:///" & Replace(htmlPath, "\", "/")
' 5. Edgeで開く
OpenUrlWithEdge url
' 6. ブラウザ起動待ち
Application.Wait Now + TimeValue("00:00:03")
' 7. デスクトップ全体を取得
Set root = uia.GetRootElement
' 8. ブラウザウィンドウを探す
Set browserWindow = WaitWindowByName(uia, root, "UIA入力練習フォーム", 15)
If browserWindow Is Nothing Then
MsgBox "ブラウザ画面が見つかりませんでした。" & vbCrLf & _
"HTMLは作成されています。" & vbCrLf & vbCrLf & _
htmlPath, vbExclamation
Exit Sub
End If
' 9. 3名分をゆっくり入力して登録
For i = 1 To 3
Application.StatusBar = i & "人目を入力中..."
' 前回入力値を消す
SetValueByName uia, browserWindow, "社員番号", ""
SetValueByName uia, browserWindow, "氏名", ""
SetValueByName uia, browserWindow, "金額", ""
SetValueByName uia, browserWindow, "備考", ""
DemoPause 0.5
' 社員番号
SetValueByName uia, browserWindow, "社員番号", data(i, 1)
DemoPause 0.8
' 氏名
SetValueByName uia, browserWindow, "氏名", data(i, 2)
DemoPause 0.8
' 金額
SetValueByName uia, browserWindow, "金額", data(i, 3)
DemoPause 0.8
' 備考
SetValueByName uia, browserWindow, "備考", data(i, 4)
DemoPause 0.8
' 登録ボタン
InvokeButtonByName uia, browserWindow, "登録"
DemoPause 1.5
Next i
Application.StatusBar = False
MsgBox "3名分のデモ入力が完了しました。" & vbCrLf & vbCrLf & _
"VBAでHTMLを作成し、Edgeを開き、UIAで自動入力しました。" & vbCrLf & vbCrLf & _
"作成されたHTML:" & vbCrLf & htmlPath, vbInformation
Exit Sub
ErrHandler:
Application.StatusBar = False
MsgBox "エラーが発生しました。" & vbCrLf & _
"番号: " & Err.Number & vbCrLf & _
"内容: " & Err.Description, vbCritical
End Sub
' ============================================================
' テスト用HTMLを作成
' ADODB.Streamは使わず、通常のファイル出力で保存
' ============================================================
Private Sub CreateSampleHtmlFile(ByVal filePath As String)
Dim html As String
html = ""
html = html & "<!DOCTYPE html>" & vbCrLf
html = html & "<html lang=""ja"">" & vbCrLf
html = html & "<head>" & vbCrLf
html = html & "<meta charset=""shift_jis"">" & vbCrLf
html = html & "<title>UIA入力練習フォーム</title>" & vbCrLf
html = html & "<style>" & vbCrLf
html = html & "body {" & vbCrLf
html = html & " font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;" & vbCrLf
html = html & " max-width: 760px;" & vbCrLf
html = html & " margin: 40px auto;" & vbCrLf
html = html & " line-height: 1.8;" & vbCrLf
html = html & " background: #f6f8fb;" & vbCrLf
html = html & " color: #222;" & vbCrLf
html = html & "}" & vbCrLf
html = html & ".card {" & vbCrLf
html = html & " background: #fff;" & vbCrLf
html = html & " border-radius: 14px;" & vbCrLf
html = html & " padding: 28px;" & vbCrLf
html = html & " box-shadow: 0 8px 24px rgba(0,0,0,0.08);" & vbCrLf
html = html & "}" & vbCrLf
html = html & "h1 {" & vbCrLf
html = html & " margin-top: 0;" & vbCrLf
html = html & " font-size: 26px;" & vbCrLf
html = html & "}" & vbCrLf
html = html & ".desc {" & vbCrLf
html = html & " background: #eef5ff;" & vbCrLf
html = html & " border-left: 5px solid #4285f4;" & vbCrLf
html = html & " padding: 12px 16px;" & vbCrLf
html = html & " margin-bottom: 22px;" & vbCrLf
html = html & "}" & vbCrLf
html = html & "label {" & vbCrLf
html = html & " display: block;" & vbCrLf
html = html & " margin-top: 16px;" & vbCrLf
html = html & " font-weight: bold;" & vbCrLf
html = html & "}" & vbCrLf
html = html & "input, textarea, button {" & vbCrLf
html = html & " width: 100%;" & vbCrLf
html = html & " padding: 11px 12px;" & vbCrLf
html = html & " font-size: 16px;" & vbCrLf
html = html & " box-sizing: border-box;" & vbCrLf
html = html & " border-radius: 8px;" & vbCrLf
html = html & " border: 1px solid #ccd3dd;" & vbCrLf
html = html & "}" & vbCrLf
html = html & "textarea {" & vbCrLf
html = html & " min-height: 88px;" & vbCrLf
html = html & "}" & vbCrLf
html = html & "button {" & vbCrLf
html = html & " margin-top: 24px;" & vbCrLf
html = html & " cursor: pointer;" & vbCrLf
html = html & " background: #2563eb;" & vbCrLf
html = html & " color: #fff;" & vbCrLf
html = html & " border: none;" & vbCrLf
html = html & " font-weight: bold;" & vbCrLf
html = html & "}" & vbCrLf
html = html & "button:hover {" & vbCrLf
html = html & " background: #1d4ed8;" & vbCrLf
html = html & "}" & vbCrLf
html = html & "#result {" & vbCrLf
html = html & " margin-top: 24px;" & vbCrLf
html = html & " padding: 14px;" & vbCrLf
html = html & " background: #f3f6fa;" & vbCrLf
html = html & " border-left: 5px solid #999;" & vbCrLf
html = html & " white-space: pre-wrap;" & vbCrLf
html = html & "}" & vbCrLf
html = html & "#result.done {" & vbCrLf
html = html & " background: #ecfdf3;" & vbCrLf
html = html & " border-left-color: #22c55e;" & vbCrLf
html = html & "}" & vbCrLf
html = html & "</style>" & vbCrLf
html = html & "</head>" & vbCrLf
html = html & "<body>" & vbCrLf
html = html & "<div class=""card"">" & vbCrLf
html = html & "<h1>UIA入力練習フォーム</h1>" & vbCrLf
html = html & "<div class=""desc"">" & vbCrLf
html = html & "この画面はVBAが自動生成したHTMLです。<br>" & vbCrLf
html = html & "Excel VBAからUI Automationで、3名分のデータがゆっくり入力されます。" & vbCrLf
html = html & "</div>" & vbCrLf
html = html & "<form>" & vbCrLf
html = html & " <label for=""empCode"">社員番号</label>" & vbCrLf
html = html & " <input id=""empCode"" type=""text"" aria-label=""社員番号"" placeholder=""ここに社員番号が入ります"">" & vbCrLf
html = html & " <label for=""empName"">氏名</label>" & vbCrLf
html = html & " <input id=""empName"" type=""text"" aria-label=""氏名"" placeholder=""ここに氏名が入ります"">" & vbCrLf
html = html & " <label for=""amount"">金額</label>" & vbCrLf
html = html & " <input id=""amount"" type=""text"" aria-label=""金額"" placeholder=""ここに金額が入ります"">" & vbCrLf
html = html & " <label for=""memo"">備考</label>" & vbCrLf
html = html & " <textarea id=""memo"" aria-label=""備考"" placeholder=""ここに備考が入ります""></textarea>" & vbCrLf
html = html & " <button id=""registerButton"" type=""button"" aria-label=""登録"" onclick=""registerData();"">登録</button>" & vbCrLf
html = html & "</form>" & vbCrLf
html = html & "<div id=""result"">未登録</div>" & vbCrLf
html = html & "</div>" & vbCrLf
html = html & "<script>" & vbCrLf
html = html & "var registerCount = 0;" & vbCrLf
html = html & "" & vbCrLf
html = html & "function registerData() {" & vbCrLf
html = html & " registerCount++;" & vbCrLf
html = html & " var empCode = document.getElementById('empCode').value;" & vbCrLf
html = html & " var empName = document.getElementById('empName').value;" & vbCrLf
html = html & " var amount = document.getElementById('amount').value;" & vbCrLf
html = html & " var memo = document.getElementById('memo').value;" & vbCrLf
html = html & " var result = document.getElementById('result');" & vbCrLf
html = html & " result.className = 'done';" & vbCrLf
html = html & "" & vbCrLf
html = html & " var line =" & vbCrLf
html = html & " '【' + registerCount + '人目 登録完了】' + '\n' +" & vbCrLf
html = html & " '社員番号: ' + empCode + '\n' +" & vbCrLf
html = html & " '氏名: ' + empName + '\n' +" & vbCrLf
html = html & " '金額: ' + amount + '\n' +" & vbCrLf
html = html & " '備考: ' + memo + '\n' +" & vbCrLf
html = html & " '-----------------------------\n';" & vbCrLf
html = html & "" & vbCrLf
html = html & " if (result.textContent === '未登録') {" & vbCrLf
html = html & " result.textContent = line;" & vbCrLf
html = html & " } else {" & vbCrLf
html = html & " result.textContent = result.textContent + '\n' + line;" & vbCrLf
html = html & " }" & vbCrLf
html = html & "}" & vbCrLf
html = html & "</script>" & vbCrLf
html = html & "</body>" & vbCrLf
html = html & "</html>" & vbCrLf
WriteTextFile filePath, html
End Sub
' ============================================================
' テキストファイルを書き出す
' ============================================================
Private Sub WriteTextFile(ByVal filePath As String, ByVal text As String)
Dim ff As Integer
ff = FreeFile
Open filePath For Output As #ff
Print #ff, text
Close #ff
End Sub
' ============================================================
' EdgeでURLを開く
' ============================================================
Private Sub OpenUrlWithEdge(ByVal url As String)
On Error GoTo Fallback
Shell "msedge.exe --new-window """ & url & """", vbNormalFocus
Exit Sub
Fallback:
Err.Clear
' Edgeが見つからない場合は既定ブラウザで開く
Shell "cmd /c start """" """ & url & """", vbNormalFocus
End Sub
' ============================================================
' タイトルに指定文字を含むウィンドウを待って取得
' ============================================================
Private Function WaitWindowByName( _
ByVal uia As CUIAutomation, _
ByVal root As IUIAutomationElement, _
ByVal keyword As String, _
ByVal timeoutSeconds As Long) As IUIAutomationElement
Dim startTime As Single
Dim windows As IUIAutomationElementArray
Dim cond As IUIAutomationCondition
Dim elm As IUIAutomationElement
Dim i As Long
Dim nameValue As String
startTime = Timer
Do While Timer - startTime < timeoutSeconds
Set cond = uia.CreateTrueCondition
Set windows = root.FindAll(SCOPE_CHILDREN, cond)
For i = 0 To windows.Length - 1
Set elm = windows.GetElement(i)
On Error Resume Next
nameValue = elm.CurrentName
On Error GoTo 0
If InStr(1, nameValue, keyword, vbTextCompare) > 0 Then
Set WaitWindowByName = elm
Exit Function
End If
Next i
DoEvents
Application.Wait Now + TimeValue("00:00:01")
Loop
End Function
' ============================================================
' Name + ControlType で要素を探す
' ============================================================
Private Function FindElementByNameAndControlType( _
ByVal uia As CUIAutomation, _
ByVal parent As IUIAutomationElement, _
ByVal elementName As String, _
ByVal controlTypeId As Long) As IUIAutomationElement
Dim condName As IUIAutomationCondition
Dim condType As IUIAutomationCondition
Dim condAnd As IUIAutomationCondition
Set condName = uia.CreatePropertyCondition(UIA_NAME_PROPERTY_ID, elementName)
Set condType = uia.CreatePropertyCondition(UIA_CONTROL_TYPE_PROPERTY_ID, controlTypeId)
Set condAnd = uia.CreateAndCondition(condName, condType)
Set FindElementByNameAndControlType = parent.FindFirst(SCOPE_SUBTREE, condAnd)
End Function
' ============================================================
' 入力欄へ値を入れる
' ============================================================
Private Sub SetValueByName( _
ByVal uia As CUIAutomation, _
ByVal parent As IUIAutomationElement, _
ByVal elementName As String, _
ByVal inputValue As String)
Dim elm As IUIAutomationElement
Dim valuePattern As IUIAutomationValuePattern
Set elm = FindElementByNameAndControlType( _
uia, _
parent, _
elementName, _
UIA_EDIT_CONTROL_TYPE_ID)
If elm Is Nothing Then
MsgBox "入力欄が見つかりません: " & elementName, vbExclamation
Exit Sub
End If
On Error Resume Next
Set valuePattern = elm.GetCurrentPattern(UIA_VALUE_PATTERN_ID)
If Not valuePattern Is Nothing Then
valuePattern.SetValue inputValue
End If
If Err.Number <> 0 Or valuePattern Is Nothing Then
Err.Clear
' ValuePatternが使えない場合の予備動作
elm.SetFocus
Application.Wait Now + TimeValue("00:00:01")
SendKeys "^a", True
SendKeys inputValue, True
End If
On Error GoTo 0
End Sub
' ============================================================
' ボタンをクリックする
' ============================================================
Private Sub InvokeButtonByName( _
ByVal uia As CUIAutomation, _
ByVal parent As IUIAutomationElement, _
ByVal elementName As String)
Dim elm As IUIAutomationElement
Dim invokePattern As IUIAutomationInvokePattern
Set elm = FindElementByNameAndControlType( _
uia, _
parent, _
elementName, _
UIA_BUTTON_CONTROL_TYPE_ID)
If elm Is Nothing Then
MsgBox "ボタンが見つかりません: " & elementName, vbExclamation
Exit Sub
End If
On Error Resume Next
Set invokePattern = elm.GetCurrentPattern(UIA_INVOKE_PATTERN_ID)
If Not invokePattern Is Nothing Then
invokePattern.Invoke
End If
If Err.Number <> 0 Or invokePattern Is Nothing Then
Err.Clear
' InvokePatternが使えない場合の予備動作
elm.SetFocus
Application.Wait Now + TimeValue("00:00:01")
SendKeys "{ENTER}", True
End If
On Error GoTo 0
End Sub
' ============================================================
' デモ用の短い待機
'
' Application.Waitは1秒単位になりやすいため、
' Timer + DoEvents で小数秒の待機にする
' ============================================================
Private Sub DemoPause(Optional ByVal seconds As Double = 0.7)
Dim startTime As Double
startTime = Timer
Do While Timer - startTime < seconds
DoEvents
Loop
End Sub
5. 実行すると何が起きるのか?
このマクロを実行すると、VBAが一時フォルダにHTMLフォームを作成し、Edgeで開きます。その後、UI Automationを使って画面上の入力欄を探し、3名分のデータを順番に入力します。
今回のデモでは、あえて入力ごとに短い待機を入れています。通常のUIA操作は非常に高速なため、待機を入れないと一瞬で完了してしまい、読者が何が起きたのか分かりにくくなるためです。
中心となる処理は以下です。
For i = 1 To 3
SetValueByName uia, browserWindow, "社員番号", data(i, 1)
DemoPause 0.8
SetValueByName uia, browserWindow, "氏名", data(i, 2)
DemoPause 0.8
SetValueByName uia, browserWindow, "金額", data(i, 3)
DemoPause 0.8
SetValueByName uia, browserWindow, "備考", data(i, 4)
DemoPause 0.8
InvokeButtonByName uia, browserWindow, "登録"
DemoPause 1.5
Next i
この部分を見ると、VBA側のデータをWebフォームの各項目に入れ、登録ボタンを押す流れが分かります。
| VBAコード内の名前 | 画面上の項目 | 実行内容 |
|---|---|---|
"社員番号" |
社員番号入力欄 | A001、A002、A003 を順番に入力 |
"氏名" |
氏名入力欄 | 山田太郎、佐藤花子、鈴木一郎 を入力 |
"金額" |
金額入力欄 | 12500、23800、9800 を入力 |
"備考" |
備考欄 | 各人ごとの説明文を入力 |
"登録" |
登録ボタン | 1名分ごとにクリック |
電卓の num1Button や plusButton のような内部IDではなく、画面に見える項目名とVBAコードが対応しているため、UIAの動きが理解しやすくなります。
6. HTML側とVBA側の対応関係
このサンプルでは、VBAが生成するHTMLの入力欄に aria-label を付けています。
例えば、社員番号欄は内部的に次のようなHTMLとして作成されています。
<input id="empCode" type="text" aria-label="社員番号">
この aria-label="社員番号" が、EdgeやChromeのUIAツリー上では Name として取得されます。そのため、VBA側では次の条件で探せます。
Set condName = uia.CreatePropertyCondition(UIA_NAME_PROPERTY_ID, "社員番号")
ただし、同じ名前のラベルや要素が画面上に存在する可能性があるため、このコードでは Name だけでなく ControlType も組み合わせています。
Set condName = uia.CreatePropertyCondition(UIA_NAME_PROPERTY_ID, elementName)
Set condType = uia.CreatePropertyCondition(UIA_CONTROL_TYPE_PROPERTY_ID, controlTypeId)
Set condAnd = uia.CreateAndCondition(condName, condType)
これにより、例えば「社員番号という名前を持つ入力欄」だけを探すことができます。
7. UIAで使っている2つの操作パターン
UI Automationでは、画面上の要素を操作するために「パターン」と呼ばれる仕組みを使います。このサンプルで使っている主なパターンは2つです。
7-1. ValuePattern
ValuePattern は、テキストボックスなど「値を持つ要素」に対して使います。
Set valuePattern = elm.GetCurrentPattern(UIA_VALUE_PATTERN_ID)
valuePattern.SetValue inputValue
この処理によって、社員番号欄や氏名欄へ文字列を直接セットしています。
7-2. InvokePattern
InvokePattern は、ボタンなど「実行できる要素」に対して使います。
Set invokePattern = elm.GetCurrentPattern(UIA_INVOKE_PATTERN_ID)
invokePattern.Invoke
この処理によって、登録ボタンをクリックしています。
ValuePattern や InvokePattern が期待どおり取得できない場合があります。そのため、このサンプルでは予備動作として SetFocus と SendKeys も入れています。実務では対象アプリごとに最も安定する方法を選びます。
8. 待機時間を調整する
デモの速度は、コード内の DemoPause の秒数で調整できます。
DemoPause 0.8
もっとゆっくり見せたい場合は、例えば以下のようにします。
DemoPause 1.2
逆に、実務処理として高速に動かしたい場合は、DemoPause の行を削除するか、待機時間を短くします。
9. 実務Webシステムへの応用
このサンプルはローカルHTMLを自動生成して試していますが、考え方は社内Webシステムでも同じです。
- 対象画面をEdgeまたはChromeで開く
- Inspect.exe や Accessibility Insights で入力欄やボタンの情報を調べる
Name、AutomationId、ControlTypeなどで要素を探す- 入力欄には
ValuePattern.SetValueを使う - ボタンには
InvokePattern.Invokeを使う - 複数件のデータをループで入力する
- 画面遷移後は、要素が出るまで再試行する
実務Webシステムでは、HTML側に分かりやすい aria-label や id が付いていない場合もあります。その場合は、Name、AutomationId、ControlType、ClassName などを組み合わせて対象を絞り込みます。
9-1. AutomationIdで探す場合
対象要素に安定した AutomationId がある場合は、Name ではなく AutomationId で探した方が安定することがあります。
Set cond = uia.CreatePropertyCondition(UIA_AutomationIdPropertyId, "empCode")
ただし、Webアプリでは AutomationId が取得できない場合や、画面更新のたびに変わる場合もあります。その場合は Name や ControlType と組み合わせます。
9-2. 複数条件で探す場合
同じ名前の要素が複数ある場合は、条件を組み合わせます。この記事のサンプルでも、以下のように Name と ControlType を組み合わせています。
Set condName = uia.CreatePropertyCondition(UIA_NAME_PROPERTY_ID, "社員番号")
Set condType = uia.CreatePropertyCondition(UIA_CONTROL_TYPE_PROPERTY_ID, UIA_EDIT_CONTROL_TYPE_ID)
Set condAnd = uia.CreateAndCondition(condName, condType)
Set elm = parent.FindFirst(SCOPE_SUBTREE, condAnd)
10. UIAの実務での落とし穴と対策
UIAは強力ですが、実務では以下の点に注意が必要です。
- タイミング問題:画面遷移後にすぐ要素を取得しようとすると
Nothingが返る場合があります。Application.Waitだけに頼らず、要素が見つかるまで再試行する処理を入れると安定します。 - 要素の特定が困難:Webアプリの場合、
AutomationIdがなく、Nameも動的に変わるケースがあります。その場合はCreateAndConditionで複数条件を組み合わせます。 - 同じNameの要素が複数ある:例えば「検索」というボタンが複数ある画面では、親要素を絞ってからボタンを探す、または
ControlTypeを併用します。 - スクロール外の要素:UIAは画面に描画されている要素を取得するAPIです。スクロール外や仮想化された一覧の中にある要素は、取得できない場合があります。
- 管理者権限の壁:管理者権限で起動しているアプリケーションの要素は、通常権限のExcel VBAからアクセスできない場合があります。Excelと操作対象アプリを同じ権限レベルで実行する必要があります。
- 仮想デスクトップ:CitrixやVDI環境では、UIAが期待通りに動作しない場合があります。その場合はQR Automation Labのような、USB-HID型QRリーダーを使った物理入力方式が代替手段になります。
- セキュリティ:パスワードや個人情報を自動入力する場合は、社内規程、ログ、誤送信対策、承認フローを必ず確認してください。
11. QR Automation Lab vs UIA ― 環境で選ぶ自動入力手法
| 比較項目 | QR Automation Lab(本編) | UIA VBA(この記事) |
|---|---|---|
| VBA | 完全不要 ✅ | 必須 |
| ソフトウェア制約 | 全てのソフトが禁止でもOK | VBA実行が許可されている必要あり |
| 対象 | 入力欄にカーソルを置ける画面全般 | UIAで要素取得できるアプリ・Web画面 |
| 速度 | QR読取1件=1〜3秒程度 | プログラム実行のため高速 |
| 安定性 | 画面構造に依存しにくい | Name / AutomationId / 画面構造に依存 |
| 仮想デスクトップ | ハードウェア入力なので比較的強い ✅ | Citrix等で制限される場合あり |
| 推奨環境 | 完全閉域・ソフト全面禁止 | VBA許可・ローカルPC・UIA取得可能な画面 |
両方の手法を理解しておけば、環境に応じて最適な自動入力手法を選べます。
12. 公式リファレンス
UIAを実務で利用する場合は、以下のMicrosoft公式情報も確認しておくと理解が深まります。