UIオートメーション〜コピペで体験Windowsアプリの自動操作

テスト自動化のお仕事をしているきむらです。
Windowsアプリの自動操作に興味があるけど、自動化ツールが色々あってよくわからない。
自動化ツールのセットアップとか難しそう。。
そんな、自動化の世界に飛び込めずにいる方のために、自動化ツールをインストールしないで、Windowsに標準搭載されている「UI オートメーション」機能を使って、アプリを自動操作する方法を紹介したいと思います。

用意したPowerShellのスクリプトをコピペして実行するだけで体験できますので、気楽な感じでお付き合いいただければと思います。

環境

この記事のPowerShellスクリプトは下記の環境で動作確認しています。

  • Microsoft Windows 10 Pro 22H2
    • PowerShell 5.1.19041.2673
  • Microsoft Windows 11 Pro 22H2
    • PowerShell 5.1.22621.963
  • Microsoft Edge 112.0.1722.58

UIオートメーションとは

「UIオートメーション」は、ざっくり言うと「ユーザーインターフェイスをプログラムから操作したりすることができる」機能で、Windowsに標準搭載されています。
もっと詳しく知りたい方は「UI オートメーションの概要」をご参照ください。

Windows PowerShell ISEを起動する

「Windows PowerShell ISE」はWindowsに標準搭載されているPowerShellの統合開発環境で、PowerShellスクリプトを効率的に作成することができるアプリです。
タスクバーの検索ボックスに「powershell」を入力して、検索結果から「Windows PowerShell ISE」を選択して起動します。起動したら、ツールバーの「スクリプトを新規作成します」ボタンを選択します。(またはメニューバーから [ファイル] > [新規作成] を選択します。)

上半分の背景が白色のウィンドウが「スクリプトウィンドウ」で、こちらにスクリプトをコピペしていきます。

下半分の背景が紺色のウィンドウは「コンソールウィンドウ」で、コマンドを入力・実行したりするウィンドウです。

UIを自動操作するための土台となるスクリプトコピペする

まずは、UIを自動操作するための土台となるスクリプトをコピペします。
このスクリプトには、要素をクリックしたり、キーボード操作をする関数(部品のようなもの)が書かれていて、この関数を使って自動操作をおこないます。
下記のスクリプトをコピーします。

# Windows PowerShellでアプリを自動操作するスクリプト

# UI オートメーションを使うための準備
Add-Type -AssemblyName "UIAutomationClient"
Add-Type -AssemblyName "UIAutomationTypes"
$AutomationElement = [System.Windows.Automation.AutomationElement]
$TreeScope = [System.Windows.Automation.TreeScope]
$Condition = [System.Windows.Automation.Condition]
$InvokePattern = [System.Windows.Automation.InvokePattern]
$SendKeys = [System.Windows.Forms.SendKeys]
$Cursor = [System.Windows.Forms.Cursor]

# マウスの左クリック操作をおこなうための準備
$SendInputSource =@"
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public class MouseClick {
    [StructLayout(LayoutKind.Sequential)]
    struct MOUSEINPUT {
        public int dx;
        public int dy;
        public int mouseData;
        public int dwFlags;
        public int time;
        public IntPtr dwExtraInfo;
    }
    
    [StructLayout(LayoutKind.Sequential)]
    struct INPUT
    {
        public int type;
        public MOUSEINPUT mi;
    }

    [System.Runtime.InteropServices.DllImport("user32.dll")]
    extern static uint SendInput(uint cInputs, INPUT[] pInputs, int cbSize);

    public static void Click() {
        INPUT[] input = new INPUT[2];
        input[0].mi.dwFlags = 0x0002;
        input[1].mi.dwFlags = 0x0004;
        SendInput(2, input, Marshal.SizeOf(input[0]));
    }
}
"@
Add-Type -TypeDefinition $SendInputSource -ReferencedAssemblies System.Windows.Forms, System.Drawing
$MouseClick = [MouseClick]

# 要素を取得する関数
function GetElements {
    Param($RootWindowName = $null)
    if ($RootWindowName -eq $null) {
        try {
            return $AutomationElement::RootElement.FindAll($TreeScope::Subtree, $Condition::TrueCondition)
        }
        catch {
            return $null
        }
    }
    else {
        $childrenElements = $AutomationElement::RootElement.FindAll($TreeScope::Children, $Condition::TrueCondition)
        foreach ($element in $childrenElements) {
            if ($element.GetCurrentPropertyValue($AutomationElement::NameProperty) -eq $RootWindowName) {
                return $element.FindAll($TreeScope::Subtree, $Condition::TrueCondition)
            }
        }
            Write-Host "指定された名前 '${RootWindowName}' のウィンドウが見つかりません。"
    }
    return $null
}

# 要素を検索する関数
function FindElement {
    Param($RootWindowName = $null, $PropertyType, $Identifier, $Timeout)
    $startTime = (Get-Date).Ticks
    do {
        foreach ($element in GetElements -RootWindowName $RootWindowName) {
            try {
                if ($element.GetCurrentPropertyValue($AutomationElement::$PropertyType) -eq $Identifier) {
                    return $element
                }
            }
            catch {
                continue
            }
        }
    }
    while (((Get-Date).Ticks - $startTime) -le ($Timeout * 10000))
    throw "指定された要素 '${Identifier}' が見つかりません。"
}

# クリック操作をおこなう関数
function ClickElement {
    Param($RootWindowName = $null, $PropertyType, $Identifier, $Timeout = 5000)
    $startTime = (Get-Date).Ticks
    do {
        $element = FindElement -RootWindowName $RootWindowName -PropertyType $PropertyType -Identifier $Identifier -Timeout $Timeout
        $isEnabled = $element.GetCurrentPropertyValue($AutomationElement::IsEnabledProperty)
        if ($isEnabled -eq "True") { break }
    }
    while (((Get-Date).Ticks - $startTime) -le ($Timeout * 10000))
    if ($isEnabled -ne "True") {
        throw "指定された要素 '${Identifier}' が有効状態になりません。"
    }

    if ($element.GetCurrentPropertyValue($AutomationElement::IsInvokePatternAvailableProperty) -eq "True") {
        $element.GetCurrentPattern($InvokePattern::Pattern).Invoke()
    }
    else {
        # IsInvokePatternAvailablePropertyがFalseの時はマウスカーソルを要素に移動して左クリックする
        $clickablePoint = $element.GetClickablePoint()
        $Cursor::Position = New-Object System.Drawing.Point($clickablePoint.X, $clickablePoint.Y)
        $MouseClick::Click()
    }
}

# キーボード操作をおこなう関数
function SendKeys {
    Param($RootWindowName = $null, $PropertyType, $Idendifier = $null, $Keys, $Timeout = 5000)
    if ($Idendifier -ne $null) {
        $element = FindElement -RootWindowName $RootWindowName -PropertyType $PropertyType -Identifier $Idendifier -Timeout $Timeout
        $element.SetFocus()
    }
    $SendKeys::SendWait($Keys)
}

# Microsoft EdgeでWebキャプチャの保存操作をおこなう関数
function SaveWebCaptureByMicrosoftEdge {
    SendKeys -Keys "^(+S)"
    ClickElement -PropertyType "AutomationIdProperty" -Identifier "view_52561"
    ClickElement -PropertyType "AutomationIdProperty" -Identifier "save_button_id"
    Start-Sleep -Seconds 3
    SendKeys -Keys "{ESCAPE}"
}

# ↓↓↓↓↓ この行以降にアプリを自動操作するスクリプトを書く ↓↓↓↓↓

次に「Windows PowerShell ISE」のスクリプトウィンドウに貼り付けます。

「ここで一度ファイルに保存しておこうかな」となるかもしれませんが、ここでは保存しないでください。ファイルに保存すると、PowerShellの実行ポリシーの設定によってはスクリプトが実行できなくなるためです。
PowerShellの実行ポリシーについてはあとで説明します。

電卓を自動操作するスクリプトをコピペして実行する

では、Windowsアプリの自動操作の練習台として定番(?)の電卓を自動操作してみましょう。
「電卓を起動して、1から10までを足し算する操作」を自動操作してみます。
下記のスクリプトをコピーします。

################################################################################
# 電卓を自動操作する
################################################################################

# 足し算する値の範囲を設定する
$start = 1
$end = 10

# ボタンをクリックした後の待機ミリ秒を設定する
$waitMilliseconds = 300

# 電卓アプリを開始する
Start-Process calc -Wait

# 電卓アプリを操作する
foreach ($count in $start..$end) {
    # 数値を1桁ずつに分割する
    $array = $count.ToString().ToCharArray()

    # 電卓アプリの数値ボタンをクリックする
    foreach ($number in $array) {
        ClickElement -RootWindowName "電卓" -PropertyType "AutomationIdProperty" -Identifier "num${number}Button"
        Start-Sleep -Milliseconds $waitMilliseconds
    }

    # 現在のカウントで処理を分岐する
    if ($count -ne $end) {
        # 範囲の終わり以外の時は[+]ボタンをクリックする
        ClickElement -RootWindowName "電卓" -PropertyType "AutomationIdProperty" -Identifier "plusButton"
        Start-Sleep -Milliseconds $waitMilliseconds
    }
    else {
        # 範囲の終わりの時は[=]ボタンをクリックする
        ClickElement -RootWindowName "電卓" -PropertyType "AutomationIdProperty" -Identifier "equalButton"
        Start-Sleep -Milliseconds $waitMilliseconds
    }
}

次に、スクリプトウィンドウの「141行目」に貼り付けます。

これで準備ができました。
では、ツールバーの緑色の「▶︎」(スクリプトを実行)ボタンを選択して、スクリプトを実行してみましょう。

電卓が起動して、1から10までを足し算する操作がおこなわれて、計算結果に55が表示されます。

操作間隔を空けずに自動操作する

実行したスクリプトは、自動操作の様子を確認しやすいように操作の間隔を空けています。
自動操作の間隔を空けずに実行するとどうなるか見てみましょう。
「1から10までの足し算」はあっと言う間に終わってしまいますので、ついでに足し算する範囲を「1から100まで」に変更してみましょう。
まず、スクリプトの147行目の「$end = 10 」を「$end = 100」に編集します。

145 # 足し算する値の範囲を設定する
146 $start = 1
147 $end = 100

次に、スクリプトの150行目の「$waitMilliseconds = 300」を「$waitMilliseconds = 0」に編集します。

149 # ボタンをクリックした後の待機ミリ秒を設定する
150 $waitMilliseconds = 0

ツールバーの緑色の「▶︎」(スクリプトを実行)ボタンを選択して、スクリプトを実行してみましょう。なお、スクリプトの実行を途中で停止したい時は、ツールバーの赤色の「■」(操作の停止)ボタンを選択します。電卓が起動して、1から100までを足し算する操作が爆速でおこなわれて、計算結果に5050が表示されます。筆者の環境では、自動操作の間隔を空けた時の実行所要時間は「およそ1分50秒」で、間隔を開けない時の実行所要時間は「およそ15秒」でした。

Microsoft Edgeを自動操作するスクリプトをコピペして実行する

続いて、ブラウザを自動操作するスクリプトを紹介します。
ブラウザの自動操作は「Selenium」や「Autify」、「mabl」などの自動化ツールを使う方が多いかもしれませんが、ちょっとした操作であれば「UI オートメーション」でも自動操作することができます。

今回は「Microsoft Edgeを起動して、Bingのトップページを開いたあとに、検索とWebキャプチャを保存する操作をおこない、最後にダウンロードフォルダーを開く」操作を自動操作してみます。

「Microsoft Edge」を初めて使う場合は、初回実行時の設定が必要になりますので、一度「Microsoft Edge」を起動して初回実行時の設定を完了させておいてください。

また、アドレスバーに「edge://settings/downloads」を入力しEnterキーを押下して表示されるダウンロードの設定項目「ダウンロード時の動作を毎回確認する」が既定値の「オフ」になっていることを前提として自動操作しますので、設定を「オン」にしている場合は「オフ」に設定してください。
まず、電卓を自動操作するスクリプト(141行目から最後の行まで)を削除します。
次に、下記のスクリプトをコピーします。

################################################################################
# Microsoft Edgeを自動操作する
################################################################################

# 検索する値を設定する
$searchWords = @(
    'フェンダー'
    'ギブソン'
    'ポール・リード・スミス'
)

# ページ読み込み完了待ち秒数を設定する
$pageLoadWaitSeconds = 3

# Microsoft Edgeを開始する
Start-Process msedge -Wait

# Microsoft Bingのトップページを開く
SendKeys -PropertyType "AutomationIdProperty" -Idendifier "view_1020" -Keys "https://www.bing.com/{ENTER}"
Start-Sleep -Seconds $pageLoadWaitSeconds

# 「$searchWords」に記載されている値を検索してWebキャプチャを保存する
$index = 0
foreach ($word in $searchWords) {
    # 2回目以降の検索時は検索ボックスの内容をクリアする
    if ($index -gt 0) {
        ClickElement -PropertyType "AutomationIdProperty" -Identifier "sb_form_q"
        ClickElement -PropertyType "AutomationIdProperty" -Identifier "sw_clx"
        Start-Sleep -Seconds 1
    }

    # 検索ボックスに値を入力して検索する
    SendKeys -PropertyType "AutomationIdProperty" -Identifier "sb_form_q" -Keys "${word}{ENTER}"
    Start-Sleep -Seconds $pageLoadWaitSeconds

     # 1回目の検索時は画像リンクをクリックする
    if ($index -eq 0) {
        ClickElement -PropertyType "NameProperty" -Identifier "さらに表示"
        ClickElement -PropertyType "NameProperty" -Identifier "画像"
        Start-Sleep -Seconds $pageLoadWaitSeconds
    }

    # Webキャプチャを保存する
    SaveWebCaptureByMicrosoftEdge
    $index += 1
}

# エクスプローラーでダウンロードフォルダーを開く
explorer.exe $env:USERPROFILE\Downloads

スクリプトウィンドウの「141行目」に貼り付けます。

なお、

145 # 検索する値を設定する
146 $searchWords = @(
147     'フェンダー'
148     'ギブソン'
149     'ポール・リード・スミス'
150 )

の「フェンダー」とかの文字列は、とりあえず適当に書いただけですので、他の文字列に編集してもよいです。
では、ツールバーの緑色の「▶︎」(スクリプトを実行)ボタンを選択して、スクリプトを実行しましょう。
Microsoft Edgeが起動してBingのトップページを開いたあとに、検索とWebキャプチャを保存する操作が繰り返しおこなわれ、最後にダウンロードフォルダーが開きます。

※ うまく動かない時は、ツールバーの赤色の「■」(操作の停止)ボタンを選択してスクリプトを停止してください。

PowerShellの実行ポリシー

UIを自動操作するための土台となるスクリプトコピペする」で、

ひとまずファイルに保存したくなるかもしれませんが、ここでは保存しないでください。

ファイルに保存すると、PowerShellの実行ポリシーの設定によってはスクリプトが実行できなくなるためです。

PowerShellの実行ポリシーについてはあとで説明します。

と書きましたが、ここでファイル保存した状態でスクリプトを実行してみましょう。

「Windows PowerShell ISE」ウィンドウのツールバーのフロッピーディスクアイコンのボタン(スクリプトを保存します)を選択します。(またはメニューバーから [ファイル] > [名前を付けて保存] を選択します。)
「名前を付けて保存」ダイアログが表示されたら、任意の場所・ファイル名で保存します。
では、ツールバーの緑色の「▶︎」(スクリプトを実行)ボタンを選択して、スクリプトを実行しましょう。
お使いの環境によりますが、PowerShellの実行ポリシーが「スクリプトの実行が許可されていない」ポリシーに設定されている場合は、悪意のあるスクリプトの実行を防止するための安全機能が働き「セキュリティ エラー」が発生して、スクリプトの実行が中断されます。

そこで今回は「今現在使用しているWindows PowerShell ISEのみスクリプトの実行を許可」してみます。
まず、コンソールウィンドウに下記を入力して実行します。

Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process

すると、異様に横長なダイアログが表示されるので [すべて続行] を選択します。

これでファイル保存したスクリプトが実行できるようになりましたので、試しにもう一度ツールバーの緑色の「▶︎」(スクリプトを実行)ボタンを選択して、スクリプトを実行してみましょう。 今度は「セキュリティ エラー」が発生せずにスクリプトが実行されます。

今回は「今現在使用しているWindows PowerShell ISEのみスクリプトの実行を許可」しましたが、他に「現在のユーザーにスクリプトの実行を許可(-Scope CurrentUser)」したりすることができます。

もっと詳しく知りたい方は「about_Execution_Policies」をご参照ください。

さいごに

今回は自動化ツールをインストールしないで、Windowsに標準搭載されている「UI オートメーション」機能を使ってアプリを自動操作する方法を紹介しました。

ブラウザの自動操作は「Microsoft Edgeを自動操作するスクリプトをコピペして実行する」で書いたように各種自動化ツールを使う方が多いかもしれませんが、簡単な内容であれば今回体験したように、自動化ツールをインストールしなくても自動操作することができます。

これを機に、自動化に取り組んでみてはいかがでしょうか。

また「自動化ツールをインストールすることができない環境で自動化したい」という状況の時にも対応することができるようになりますので、頭の片隅に残しておいていただけると、いつか役に立つ時がくるかもしれません。

それではまた機会があれば。

SHARE

  • facebook
  • twitter

SQRIPTER

AGEST Engineers

AGEST

記事一覧

AGESTのエンジニアが情報発信してます!
AGESTのサービスやソリューションのお問い合わせページはこちらです。

株式会社AGEST

Sqriptsはシステム開発における品質(Quality)を中心に、エンジニアが”理解しやすい”Scriptに変換して情報発信するメディアです

  • 新規登録/ログイン
  • 株式会社AGEST