PowerShellでEdgeの自動操縦について

この記事は、[ZOZO #11 Advent Calendar 2024](https://qiita.com/advent-calendar/2024/zozo) 6日目の記事になります。
昨日は@ssssotaさんの記事でした。

## はじめに

コーポレートエンジニアでは省力化のため自動化をおこなっています。
ランサムウェアの被害が多発している昨今、セキュリティ対策がますます重要になっています。
Windows共有禁止や、リモートワーク環境で認証レスでのファイル置き場に制限など。

運用スクリプトでファイルを取得するにもセキュリティ意識が必要な時代です。
そこで今回はPowerShellでEdgeの自動操縦について紹介します。

## おさらい

- 昔はInvoke-RestMethodでファイルをダウンロードすることが出来ました。

```powershell
# ユーザー名とパスワードを設定
$username = "yourUsername"
$password = "yourPassword"

# 認証情報を作成
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($username, $securePassword)

# Restメソッドでファイルをダウンロード
Invoke-RestMethod -Uri $downloadUrl -Method Get -Credential $credential -OutFile $downloadPath
```

しかし最近の動的なWebサイトでは、この方法ではダウンロードが出来ないことが増えてきました。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>動的なWebページ</title>
    <script>
        document.addEventListener("DOMContentLoaded", function() {
            // 動的に生成する内容
            var content = `
                <h1>ようこそ</h1>
                <p>これは動的に生成されたコンテンツです。</p>
                <button id="downloadButton">ファイルをダウンロード</button>
            `;

            // bodyにコンテンツを追加
            document.body.innerHTML = content;

            // ダウンロードボタンにイベントリスナーを追加
            document.getElementById("downloadButton").addEventListener("click", function() {
                // ダウンロード処理
                window.location.href = "path/to/your/file.zip";
            });
        });
    </script>
</head>
<body>
    <!-- 初期のbodyは空 -->
</body>
</html>
```

動的にページを生成するアプリでファイルのURLの都度変更やUIのHTMLの動的生成が行われている場合もあり固定のダウンロードURLではファイルが取得できません。

そこで、Edgeの自動操縦となります。

## Edgeの自動操縦

### 前提

- 今回のスクリプトはWindowsServerに入っている 5.1系のPowerShellを想定
- Edgeのバージョンも比較的最新のものを想定

### WebDriverの紹介

WebDriverは、ブラウザを自動操作するためのプロトコルです。
過去のTrident エンジンベースのEdge Legacyであれば設定>オプション機能から有効にすることが出来ましたが、現在のBlinkエンジンのEdgeでは準備が必要となります。

#### Edgeのバージョン確認

構築スクリプトを作りこみたい場合もあるかと思いますのでスクリプトでのEdgeのバージョンの確認について紹介します。

```powershell
$edgeVersion = (Get-Item (${env:ProgramFiles(x86)} + "\Microsoft\Edge\Application\msedge.exe")).VersionInfo.FileVersion
# 131.0.2903.63
```

#### WebDriverのダウンロード

Microsoft Edge Developer で調べたEdgeバージョンとアーキテクチャが一致しているWebDriverを選んでダウンロードします。
完全一致のものが見つからない場合はEdgeバージョンのドットの右側から削って一致するものを選びます。

- [Microsoft Edge WebDriver](https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/)

### Selenium モジュールダウンロード

NuGetからSeleniumモジュールをダウンロードします。

- [NuGet Gallary|Selenium.WebDriver 4.26.1](https://www.nuget.org/packages/Selenium.WebDriver)
  - Download package  (12.55 MB) のリンクをダウンロードして拡張子をzipに変更して展開して利用します。

### ファイルの展開

- Selenium.WebDriver をzip展開
- 同じフォルダに msedgedriver.exe を配置

```plaintext
プロジェクトルート/
├── webdriver/
│   ├── _rels/
│   ├── build/
│   ├── Driver_Notes/
│   ├── images/
│   ├── manager/
│   ├── package/
│   ├── .signature.p7s
│   ├── [Content_Types].xml
│   ├── msedgedriver.exe
│   ├── Selenium.WebDriver.nuspec
├── src/
│   ├── Get-EdgeWebDriver.ps1
│   └── Script.ps1
└── run.bat
```

### スクリプト

#### run.bat

- ファイルの位置をカレントディレクトリに設定してからスクリプトを実行
  - 複数のファイルなのでカレントディレクトリを設定してから実行することでファイルの位置を意識しなくても実行できる

``` batch
@echo off
pushd %~dp0
powershell -File .\src\Script.ps1
```

#### Get-EdgeWebDriver.ps1

- EdgeWebDriver の環境構築
  - オブジェクト生成は.NETのクラスを利用して行う

```powershell
using namespace OpenQA.Selenium

# 設定ファイル読み込み
Function Get-EdgeDriver{
    <#
    .SYNOPSIS
    EdgeWebDriver の環境構築
    .DESCRIPTION
    msedgedriver.exe と net48 の WebDriver.dll を利用して EdgeDriverを作成する
    #>
    # スクリプトの位置をカレントディレクトリに設定

    $basePath = Get-Item -Path (Join-Path (Get-Location) ./webdriver).FullName
    $ret = [Reflection.Assembly]::LoadFile((Join-Path $basePath "lib\net48\WebDriver.dll"))
    
    $opt = New-Object Edge.EdgeOptions
    # Edge へのフルパス
    $opt.BinaryLocation = "${env:ProgramFiles(x86)}\Microsoft\Edge\Application\msedge.exe"

    # msedgedriver.exe の絶対パスを登録
    $svc = [Edge.EdgeDriverService]::CreateDefaultService($basePath)

    $driver = New-Object Edge.EdgeDriver($svc, $opt)
    $driver.Manage().Timeouts().ImplicitWait = [System.TimeSpan]::FromSeconds(4) 
    # 暗黙的な待機4秒

    return $driver
}
```

### Script.ps1

- メインスクリプト
  - EdgeDriverを利用してWebページを操作するスクリプト

```powershell
using namespace OpenQA.Selenium
. ./Get-EdgeWebDriver.ps1

# EdgeDriver起動
$driver = Get-EdgeDriver
# URLへ移動
$driver.Navigate().GoToUrl("https://path/to/your/url")
# ログインページ
$username = $driver.FindElement([By]::XPath('//input[@id="id"]'))
$username.SendKeys("yourUsername")
$password = $driver.FindElement([By]::XPath('//input[@id="password"]'))
$password.SendKeys("yourPassword")
$login = $driver.FindElement([By]::XPath('//input[@id="loginButton"]'))
$login.Click()
# メニューページ
$menu = $driver.FindElement([By]::XPath('//div[@class="menu_elements"]/a'))
$menu.Click()
# ダウンロードページ
$download = $driver.FindElement([By]::XPath('//input[@id="download"]'))
$download.Click()
# ダウンロード待ち
Start-Sleep -Seconds 10
# EdgeDrive終了
$driver.Quit()

# Downloadフォルダから最新CSVファイルをスクリプトのローカルに移動
$downloadFile = ( Get-ChildItem -Path "$env:USERPROFILE\Downloads" -Filter "*.csv" | Sort-Object CreationTime -Descending)[0]
Move-Item -Path $downloadFile.FullName -Destination (Join-Path (Get-Location) "GetCSVFile.csv") -Force
```

1. "https://path/to/your/url" へ接続
2. ログインページが完成するのを暗幕の待機で待つ
3. ユーザー名入力欄をDOMのXPathから取得
4. ユーザ名をSendKeysで入力
5. パスワード入力欄をDOMのXPathから取得
6. パスワードをSendKeysで入力
7. ログインボタンをDOMのXPathから取得
8. ログインボタンをクリック
9. メニューページが完成するのを暗幕の待機で待つ
10. メニューページのリンクをDOMのXPathから取得
11. メニューページのリンクをクリック
12. ダウンロードページが完成するのを暗幕の待機で待つ
13. ダウンロードリンクをDOMのXPathから取得
14. ダウンロードリンクをクリック
15. ダウンロード完了まで10秒待つ
16. EdgeDriverを終了
17. ダウンロードフォルダから最新のCSVファイルをスクリプトのローカルに移動

- Xpathの調査や評価については目的のサイトをEdgeで開いてF12で開発者ツールを開いて該当の要素を選択して右クリックからCopy>Xpathで取得できます

## まとめ

- Edgeの自動操縦について紹介しました
- PowerShell 5.1系で実行出来るためWindowsServerにインストールは最小限で済みます
  - PowerShellスクリプトを自己証明書で署名する運用にするとセキュリティも向上します

明日も私(@zz_takumi_ito)で「Google スプレットシートで簡易検索について」になります。
お楽しみに。