| name | windows-app-automation |
| description | Windows デスクトップアプリ(Win32・WPF・WinForms・UWP・メモ帳・タスクマネージャーなど)の GUI 自動化・UIテストスキル。「Windows アプリを自動化して」「デスクトップアプリをテストして」「ファイルダイアログを操作して」「Excelを自動操作して」などで発動。pywinauto/winauto 使用。Copilot・Kiro・WSL 対応。 |
| metadata | {"version":"1.2.0","tier":"experimental","category":"implementation","tags":["windows","native-app","pywinauto","ui-automation","win32","wpf","winforms","desktop-automation","e2e-testing","wsl","copilot","kiro","cross-agent"]} |
windows-app-automation
Windows ネイティブアプリを自動化するときは、Python + pywinauto で自動化スクリプトを作成する。
対応エージェント環境
| 環境 | 実行場所 | winauto コマンド |
|---|
| Claude Code (Windows) | PowerShell / CMD ターミナル | winauto または python winauto.py |
| GitHub Copilot (VS Code) | VS Code 統合ターミナル(PowerShell) | winauto または python winauto.py |
| Kiro (AWS IDE) | Kiro 統合ターミナル(PowerShell) | winauto または python winauto.py |
| WSL (WSL2 端末) | bash/zsh from VS Code / Kiro / Windows Terminal | winauto(ラッパー経由で Windows Python を呼ぶ) |
注意: pywinauto は Windows 専用。WSL から呼ぶ場合は Windows 側 Python で実行される。
セットアップ(初回のみ)
Windows ネイティブ(Claude Code / Copilot / Kiro 共通)
# 依存ライブラリと winauto コマンドのインストール
python tools/winauto/install.py
# インストール確認(doctor が pywinauto・デスクトップ到達性・ロック状態を確認する)
winauto doctor
winauto apps
WSL 端末から使う場合
python tools/winauto/install.py
winauto doctor
winauto apps
winauto doctor は WSL 側でも Windows 側でも動く唯一のコマンドで、WSL から実行すると
ラッパー越しに Windows 側の doctor を呼び出して所見を合流させる。--output json で機械可読。
問題があれば終了コード 1 を返すので、セットアップスクリプトからも判定に使える。
インストーラーが Windows 側 Python を見つけられない場合:
cmd.exe /c where python
cmd.exe /c python -m pip install pywinauto Pillow pywin32 comtypes
依存ライブラリのみインストール(既存 Python 環境に追記)
pip install pywinauto>=0.6.9 Pillow>=9.0.0 pywin32>=306 comtypes>=1.4.0
環境別の実行方法
Claude Code / GitHub Copilot / Kiro(Windows ターミナル)
これらはすべて Windows 上で動作するため、同じコマンドが使える。
# winauto CLI(インストール済みなら直接呼べる)
winauto apps
winauto tree --app notepad
winauto click "name:=OK" --app notepad
# またはリポジトリから直接
python tools/winauto/winauto.py apps
python tools/winauto/winauto.py tree --app notepad
# ヘルパースクリプトも同様
python .github/skills/windows-app-automation/scripts/element_inspector.py --list
Copilot(VS Code)固有の注意点:
- VS Code のターミナルが PowerShell の場合、
python コマンドが正しく通るか確認する
python --version で Python 3.9 以上が表示されることを確認する
Kiro IDE 固有の注意点:
- Kiro の統合ターミナルから実行する(PowerShell)
- Kiro エージェントは直接
!winauto tree --app notepad のようにシェルコマンドを呼べる
- スキルの自動化スクリプトを Kiro に書かせた後、Kiro のターミナルで実行する
WSL 端末(VS Code Remote / Kiro WSL / Windows Terminal)
winauto apps
winauto tree --app notepad
winauto screenshot --app notepad --output /tmp/screenshot.png
winauto run my_automation.py
cmd.exe /c python .github/skills/windows-app-automation/scripts/element_inspector.py --list
WSL 固有の注意点:
- ラッパーは Windows の
python.exe を直接 exec する(cmd.exe を挟まない)。
cmd.exe 経由だと WSL の cwd に対して「CMD does not support UNC paths as current
directories」を吐き、出力を読むエージェントを惑わせるため
- 引数のパス変換はラッパーが行うが、変換対象は限定されている。変換されるのは
screenshot / codegen の --output の値、run のスクリプト位置引数、
および / で始まる絶対パスだけ。セレクタ(name:=OK)や type に渡す入力テキストは
変換しない(同名のファイルが cwd にあるだけで壊れるのを避けるため)
winauto run に渡すスクリプトの中身のパスは Windows パス(C:/...)で書く
——変換されるのはコマンドライン引数だけで、スクリプト本文は解釈しない
WINAUTO_NO_PATH_CONV=1 で変換を完全に無効化できる
- WSL ターミナルに出力は返ってくるが、GUI 操作の対象は Windows デスクトップ上のウィンドウ
kiro-cli から呼び出す場合
kiro-cli は WSL 上で動作する AI エージェント CLI。--trust-all-tools を付けると winauto コマンドを
自律的に呼び出してスクリプトを生成・実行できる。詳細は references/kiro-cli-usage.md を参照。
基本パターン:
kiro-cli chat --no-interactive --trust-all-tools \
"Notepad を起動してテキストを入力してスクリーンショットを /tmp/sc.png に保存して。winauto CLI が使える。"
自動実行フロー:
kiro-cli --trust-all-tools
├─ winauto apps ← 起動中アプリを偵察
├─ winauto tree --app <name> ← UI 要素を偵察
├─ [Python スクリプト生成] ← pywinauto スクリプトをファイルに書く
├─ winauto run script.py ← 実行(Windows Python が動く)
└─ winauto screenshot ← 結果を画像で確認
kiro-cli 固有の注意点:
--trust-all-tools がないと winauto などの外部コマンド実行が承認待ちになる
- GUI 操作は Windows デスクトップ側で発生するため stdout に状況は出ない。
winauto screenshot で確認
- kiro-cli が生成したスクリプト内の Windows パスは
C:/... 形式にする
並列実行とデスクトップ排他
Windows デスクトップは 1 セッションに 1 つしかない共有排他資源。フォーカスとマウスカーソルは
1 組しかないため、複数プロセスが同時に set_focus() / click_input() を撃つと互いの操作を
奪い合って壊れる。agent-flow を --workers 3 のように並列で回すと即座にこれが起きる。
winauto は入力・フォーカスを奪うコマンドをファイルロックで直列化する(既定で有効)。
| コマンド |
|---|
| ロックを取る | launch click type keys screenshot run inspect codegen |
| ロックを取らない | apps tree get-text wait doctor |
読み取り専用をロック対象から外してあるのは、長い wait がロックを占有して他の発行を
止めてしまわないようにするため。
WSL からの呼び出しもラッパー経由で Windows Python に収束するので、ロック 1 本で
「WSL 発」と「Windows ネイティブ発」の双方が直列化される。分散実行(agent-flow --git)では
PC ごとにデスクトップが別なので、PC 単位でロックが閉じるこの構造が意味論的にも正しい。
winauto --lock-timeout 600 click "name:=OK" --app myapp
winauto --no-lock screenshot --app myapp
WINAUTO_LOCK_TIMEOUT=600 winauto click ...
ロック待ちの告知と待ちタイムアウトは stderr に出る(stdout はエージェントが読む結果なので汚さない)。
誰が握っているかは winauto doctor の lock 行で分かる。
agent-flow から使うときの注意:
- GUI ノードは Windows デスクトップのある PC でしか実行できない。分散時は GUI 対応 PC だけが
participate するバス/ブランチに分ける
agent_timeout(既定 600 秒)は GUI タスクには短いことがある。ロック待ち時間も含まれる点に注意
- 画面ロック中・RDP 切断中・未ログオンのセッションでは GUI 操作もスクリーンショットも失敗する。
winauto doctor の desktop 行が warn を出す
利用可能な補助スクリプト
scripts/element_inspector.py — UIツリーの探索・セレクタの特定(最初に必ず実行)
scripts/app_launcher.py — アプリ起動・待機・コマンド実行・終了を一括管理
tools/winauto/winauto.py — Playwright 風の統合 CLI(inspect/click/type/screenshot/codegen)
最初に --help を実行して利用方法を確認する。必要になるまでスクリプト本体は読まない。
進め方の判断フロー
依頼内容 → 対象アプリは何か?
├─ Win32 (MFC / VCL / Delphi / 古いC++) → backend=win32 を優先
├─ WPF / UWP / WinForms / Qt → backend=uia を使う
└─ 不明 → uia から試し、失敗なら win32
アプリは起動済みか?
├─ No → Application.start(app_path) で起動
└─ Yes → Application.connect(title_re / process) でアタッチ
要素が見つかるか? → 必ず先に element_inspector.py で探索する
├─ auto_id あり → child_window(auto_id="...") ← 最優先
├─ name + type → child_window(title="...", control_type="...")
└─ class あり → child_window(class_name="...") ← Win32 向け
ワークフロー: 調査 → 生成 → 実行
Step 1: 要素ツリーを探索する
python scripts/element_inspector.py --list
python scripts/element_inspector.py --app notepad --depth 4
python scripts/element_inspector.py --app notepad --selector "control:=Document"
python scripts/element_inspector.py --app notepad --json > tree.json
winauto CLI でも同等の操作が可能:
python tools/winauto/winauto.py apps
python tools/winauto/winauto.py tree --app notepad --depth 4
python tools/winauto/winauto.py inspect --app notepad
Step 2: スクリプトを生成する
python tools/winauto/winauto.py codegen notepad.exe --output test_notepad.py
または、手動で直接スクリプトを書く(Step 1 で取得したセレクタを使う)。
Step 3: 実行する
python my_automation.py
python scripts/app_launcher.py --app notepad.exe -- python my_automation.py
python tools/winauto/winauto.py click "name:=OK" --app notepad
pywinauto 基本パターン
アプリ起動・接続
from pywinauto import Application
BACKEND = "uia"
app = Application(backend=BACKEND).start("notepad.exe")
app = Application(backend=BACKEND).start(r"C:\MyApp\app.exe --arg1 value")
app = Application(backend=BACKEND).connect(title_re=".*Notepad.*")
app = Application(backend=BACKEND).connect(process=12345)
app = Application(backend=BACKEND).connect(path="notepad.exe")
win = app.top_window()
win = app.window(title_re=".*Notepad.*")
win.wait("ready", timeout=10)
要素の検索
btn = win.child_window(auto_id="btnSubmit")
btn = win.child_window(title="OK", control_type="Button")
edit = win.child_window(class_name="Edit")
first_edit = win.child_window(control_type="Edit", found_index=0)
ok_btn = win.child_window(auto_id="mainPanel") \
.child_window(control_type="Button", title="OK")
if btn.exists(timeout=3):
btn.click_input()
btn.wait("enabled", timeout=10)
btn.wait("exists,visible", timeout=10)
操作
from pywinauto.keyboard import send_keys
btn.click_input()
edit.set_text("Hello World")
edit.type_keys("Hello World", with_spaces=True)
edit.type_keys("{CTRL}a{DEL}")
send_keys("^s")
send_keys("%{F4}")
send_keys("{ENTER}")
text = edit.window_text()
all_texts = [c.window_text() for c in win.children()]
win.menu_select("File->Save As")
win.menu_select("Edit->Find->Find Next")
list_box.scroll("down", "page")
list_box.scroll("up", "line", count=3)
src.drag_mouse_input(dst)
スクリーンショット
win.set_focus()
img = win.capture_as_image()
img.save("/tmp/screenshot.png")
elem = win.child_window(auto_id="mainPanel")
img = elem.capture_as_image()
img.save("/tmp/element.png")
ダイアログ処理
dlg = app.window(title_re=".*Save As.*")
dlg.wait("ready", timeout=10)
filename_field = dlg.child_window(auto_id="1001")
if not filename_field.exists(timeout=2):
filename_field = dlg.child_window(class_name="Edit")
filename_field.set_text(r"C:\output\result.txt")
dlg.child_window(title="Save", control_type="Button").click_input()
confirm = app.window(title_re=".*Confirm.*|.*Replace.*")
if confirm.exists(timeout=2):
confirm.child_window(title="Yes", control_type="Button").click_input()
コントロールタイプ別 Tips
リストボックス / コンボボックス
lb = win.child_window(control_type="ListBox")
lb.select("Item Name")
lb.get_item(0).click_input()
cb = win.child_window(control_type="ComboBox")
cb.select("Option 1")
items = cb.item_texts()
items = [item.window_text() for item in lb.children()]
ツリービュー
tv = win.child_window(control_type="Tree")
roots = tv.children(control_type="TreeItem")
root = roots[0]
root.expand()
children = root.children(control_type="TreeItem")
tv.get_item(r"\Root\Child\Grandchild").select()
チェックボックス / ラジオボタン
cb = win.child_window(title="Enable feature", control_type="CheckBox")
cb.check()
cb.uncheck()
is_checked = cb.get_toggle_state() == 1
rb = win.child_window(title="Option A", control_type="RadioButton")
rb.select()
タブコントロール
tab = win.child_window(control_type="Tab")
tab.select("Settings")
tab.select(1)
よくある落とし穴と対処法
| 症状 | 原因 | 対処 |
|---|
ElementNotFoundError | セレクタが間違い / UIが未描画 | element_inspector.py で再確認; wait() を追加 |
| 操作が失敗・無応答 | 要素が disabled / focus なし | wait("enabled") → set_focus() → click_input() |
| テキスト入力が文字化け | IME / Unicode 問題 | type_keys() の代わりに set_text() を使う |
click() が効かない | 座標クリックが必要 | click_input() を使う(より低レベル) |
| ダイアログが検出できない | タイトルの一致パターンが違う | title_re=".*キーワード.*" で部分一致 |
backend=uia で要素が見えない | アプリが UIA 非対応 | backend=win32 に切り替える |
| 高速実行で要素見つからない | UI描画遅延 | win.wait("ready") / elem.wait("exists") を使う |
| 管理者権限アプリを操作できない | UAC 分離 | スクリプト自体を管理者権限で実行 |
WSL: pywinauto が import できない | Linux Python に入っている | Windows Python で実行する: cmd.exe /c python script.py |
WSL: winauto コマンドが見つからない | インストール未完 / PATH 未設定 | python tools/winauto/install.py を再実行; source ~/.bashrc |
WSL: winauto apps が空 | WSL から Windows デスクトップが見えない | winauto doctor で interop / ラッパー / デスクトップ到達性を切り分ける |
| 操作が数分止まったまま | 別プロセスが GUI ロックを保持している | winauto doctor の lock 行で保持者を確認。並列度を下げるか --lock-timeout を延ばす |
GUI ロックを … 秒以内に取得できませんでした | 先行の GUI 操作が長い/異常終了 | --lock-timeout 0(無限待ち)か、保持者プロセスを終了させる |
| スクリーンショットが真っ黒/ウィンドウが 0 個 | 画面ロック中・RDP 切断中・未ログオン | セッションをアンロックした状態を保つ。winauto doctor の 行で検知できる |
ベストプラクティス
- 必ず先に explorer で探索する:
element_inspector.py --app <name> でセレクタを確認してからスクリプトを書く
auto_id を最優先にする: 開発者が設定した AutomationID は最も安定したセレクタ
wait() を省略しない: click_input() の前に必ず wait("enabled") か wait("exists") を入れる
click_input() を使う: click() より確実。マウスイベントを直接送信する
set_text() を使う: type_keys() は IME 問題が起きやすい。値を確定させるだけなら set_text() が確実
app_launcher.py でライフサイクル管理: アプリの起動・終了をスクリプト内に書かず、app_launcher.py に任せる
- スクリーンショットで状態確認: エラー時は
capture_as_image().save() で状態を記録する
参照
references/selector-syntax.md — セレクタ構文の詳細リファレンス
examples/notepad_automation.py — Notepad 自動化の完全サンプル
examples/file_dialog_handling.py — ファイルダイアログ操作パターン集
tools/winauto/winauto.py — winauto CLI(--help で各コマンド確認)
- pywinauto Documentation
- Windows UI Automation