次期HTA(HtmlApplication)としてのPowerShell+XAML+WebView2の利用

先日、本記事の前段となる下記記事を投稿したが、これは本記事の検証を無事に進められなかったために途中の結果を記録したものである。

* [次期HTAとしてのPowerShell+WebView2の利用 - Qiita](https://qiita.com/tkmry/items/dede9cadc0d2a2df6c1a)

本来やりたかった事について目処がたったので検証結果を本記事にまとめる。内容の差分としては XAML(WPF) を利用するか否かである。

# ■ 前書き
先日書いた[記事](https://qiita.com/tkmry/items/dede9cadc0d2a2df6c1a)で書きたい事は書いてしまったので省略する。
追記するならば、XAML を併用した PowerShell + WebView2 は HTA の代替候補として有用だと感じる。

なお、WebView2 用の DLL ファイルは NuGet より取得しているが、現在最新版として公開されてる `1.0.1210.30` では XAML や WPF との連携が上手くできないため、2022/05/08時点プリリリース版の利用が必要である点ご注意いただきたい。

# ■ WebView2 とは
まず WebView2 が何かについて Microsoft の HP より引用して紹介する。
> Microsoft Edge WebView2 を使用すると、Web テクノロジ (HTML、CSS、JavaScript) をユーザーのネイティブ アプリに埋め込みできます。 WebView2 コントロールは、Microsoft Edge をレンダリング エンジンとして使用して、ネイティブ アプリに Web コンテンツを表示します。
> https://docs.microsoft.com/ja-jp/microsoft-edge/webview2/

クライアントアプリの UI 内に MS Edge のレンダラーを埋め込む事ができ、クライアントアプリと WebView2 間でデータの交換や、イベントリスナの登録が可能になる。

# ■ 本記事で利用する技術
下記の技術を組みわせて検証・実装をする。

* PowerShell
  [PowerShell 使い方メモ - Qiita](https://qiita.com/opengl-8080/items/bb0f5e4f1c7ce045cc57)
* XAML
  [第2回　WPFとXAMLの関係とは？ XAMLの基礎を学ぶ：連載：WPF入門（1/3 ページ） - ＠IT](https://atmarkit.itmedia.co.jp/ait/articles/1006/22/news101.html)
* WebView2
  [Microsoft Edge WebView2 の概要 - Microsoft Edge Development | Microsoft Docs](https://docs.microsoft.com/ja-jp/microsoft-edge/webview2/)

# ■ HTA の利点との比較
HTA のメリットと本記事で利用する技術がカバーしている範囲について表にまとめた。

|# | 機能 | HTA | PowerShell | XAML | WebView2 |
|-:|:----|:---:|:---:|:---:|:---:|
| 1| デフォルトの環境で動作可能 | o | o | - | o<br />(DLL要同梱)|
| 2| UIをHTML/JS/CSSで作成可能 | o | - | o<br />(XML部分のみ) | o |
| 3| JSライブラリの利用で実装の手間を軽減可能 | o | - | - | o |
| 4| 実行ファイル自体がソースコード | o | o | o | - |
| 5| デスクトップアプリとして動作 | o | o | - | - |
| 6| 外部(アプリ)と連携、通信が可能 | o | o | - | ○<br />(PowerShellを通して可能) |

※ o=対応可能、-=対象(範囲)外

今回の組み合わせ PowerShell + XAML + WebView2 を利用することで、おおよそ HTA の良い点をカバーできていると感じる。
　※ 表に関する追加の記載項目の助言やご指摘はコメント欄にてお知らせください。

# ■ 本記事の以降の流れ
下記の流れで検証・実装を進める。

1. XAML で WebView2 を埋め込んだウィンドウを作成
1. PowerShell から WebVie2 に対して (A)スクリプトの実行、(B)データの送受信 を行う

なお、先日の[記事](https://qiita.com/tkmry/items/dede9cadc0d2a2df6c1a)にあったイベントリスナの登録は `ExecuteScriptAsync` を使う点で重複し検証の意味がないので省いた。

# ■ XAML で WebView2 を埋め込んだウィンドウを作成

## ◇ ファイル構成
```:構成
xaml_webview01\
  + script01.ps1  (1)
  + ui01.xaml     (2)
  + lib\
    + Microsoft.Web.WebView2.Core.dll
    + Microsoft.Web.WebView2.Wpf.dll
    + WebView2Loader.dll
  + data\  … スクリプト側で自動生成
```
lib 配下のファイルは Nuget ([参考](#参考)#1) よりパッケージをダウンロードして、実行環境のアーキテクチャのDLLファイルを配置する。
なお、当記事の起稿時点では最新版では WebView2 が適切に起動されないためプレリリース版を利用している。

## ◇ 各ファイルの内容
```powershell:(1) script01.ps1
Add-Type -AssemblyName PresentationFramework  # WPF 用

<# WebView2 用アセンブリロード #>
[void][reflection.assembly]::LoadFile((Join-Path $PSScriptRoot "lib\Microsoft.Web.WebView2.Wpf.dll"))  # 本記事時点 microsoft.web.webview2.1.0.1222-prerelease を利用
[void][reflection.assembly]::LoadFile((Join-Path $PSScriptRoot "lib\Microsoft.Web.WebView2.Core.dll")) # 本記事時点 microsoft.web.webview2.1.0.1222-prerelease を利用

<# XAML にて Window 構築 #>
[xml]$xaml  = (Get-Content (Join-Path $PSScriptRoot .\ui01.xaml))
$nodeReader = (New-Object System.XML.XmlNodeReader $xaml)
$window     = [Windows.Markup.XamlReader]::Load($nodeReader)

<# WebView2 初期設定 #>
$webview = $window.findName("webView")
$webview.CreationProperties = New-Object 'Microsoft.Web.WebView2.Wpf.CoreWebView2CreationProperties'
$webview.CreationProperties.UserDataFolder = (Join-Path $PSScriptRoot "data")

[void]$window.ShowDialog()
$window.Close()
```
先日の[記事](https://qiita.com/tkmry/items/dede9cadc0d2a2df6c1a)との相違点として、読み込む DLL を `Microsoft.Web.WebView2.WinForms.dll` から `Microsoft.Web.WebView2.Wpf.dll` に変更している。

```xml:(2) ui01.xaml
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
        Title="MainWindow" Height="450" Width="800"
    >
    <DockPanel x:Name="RootDock">
        <wv2:WebView2 Name="webView" Source="http://abehiroshi.la.coocan.jp/" />
    </DockPanel>
</Window>
```

## ◇ 実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124578/539dad9d-6ff2-2dcc-e1b1-0efd08033fd2.png)
無事、WebView2 を介して阿部寛さんの HP を表示することに成功した。
XAML を併用することで記述量を著しく抑える事が出来るのはありがたい。

# PowerShell から WebVie2 に対して (A)スクリプトの実行、(B)データの送受信 を行う
先日の[記事](https://qiita.com/tkmry/items/dede9cadc0d2a2df6c1a)と同様の実装をこちらでも行う。実装は下記の通り行う。


1. (A) スクリプトの実行
   WebView の `ExecuteScriptAsync` で JavaScript の alert(メッセージボックス) を起動する。
1. (B) データの送受信
   **WebView から PowerShell**:
   　JS の `window.chrome.webview.postMessage(...)` で PowerShell にデータを送信する。
   　PowerShell 側は WebView2 オブジェクトの `add_WebMessageReceived` で受信時の処理を記載する。
   **PowerShell から WebView**:
   　PowerShell の `$webview.CoreWebView2.PostWebMessageAsString` でデータを送信する。
   　JS 側は `window.chrome.webview.addEventListener` で受信時の処理を記載する。    　　

## ◇ ファイル構成

```:構成
xaml_webview01\
  + script02.ps1  (1)
  + ui02.xaml     (2)
  + page02.html   (3)
  + lib\
    + Microsoft.Web.WebView2.Core.dll
    + Microsoft.Web.WebView2.Wpf.dll
    + WebView2Loader.dll
  + data\  … スクリプト側で自動生成
```
lib 配下のファイルは Nuget ([参考](#参考)#1) よりパッケージをダウンロードして、実行環境のアーキテクチャのDLLファイルを配置する。
なお、当記事の起稿時点では最新版では WebView2 が適切に起動されないためプレリリース版を利用している。

## ◇ 各ファイルの内容

```powershell:(1) script02.ps1
Add-Type -AssemblyName PresentationFramework  # WPF 用

<# WebView2 用アセンブリロード #>
[void][reflection.assembly]::LoadFile((Join-Path $PSScriptRoot "lib\Microsoft.Web.WebView2.Wpf.dll"))  # 本記事時点 microsoft.web.webview2.1.0.1222-prerelease
[void][reflection.assembly]::LoadFile((Join-Path $PSScriptRoot "lib\Microsoft.Web.WebView2.Core.dll")) # 本記事時点 microsoft.web.webview2.1.0.1222-prerelease

<# XAML にて Window 構築 #>
[xml]$xaml  = (Get-Content (Join-Path $PSScriptRoot .\ui02.xaml))
$nodeReader = (New-Object System.XML.XmlNodeReader $xaml)
$window     = [Windows.Markup.XamlReader]::Load($nodeReader)

<# WebView2 初期設定 #>
$webview = $window.findName("webView")
$webview.CreationProperties = New-Object 'Microsoft.Web.WebView2.Wpf.CoreWebView2CreationProperties'
$webview.CreationProperties.UserDataFolder = (Join-Path $PSScriptRoot "data")
$webview.Source = ([uri]("file:///" + $PSScriptRoot + "/page02.html"))

<# Contols #>
$alertButton        = $window.findName("alertButton")
$changeHeaderButton = $window.findName("changeHeaderButton")
$pageTitle          = $window.findName("pageTitle")

<# for Events ScriptBlock #>
$clickAlertButton = {
    $webview.ExecuteScriptAsync("alert('Hello, World!');")
}

$clickChangeHeaderButton = {
    $webview.CoreWebView2.PostWebMessageAsString('that is the question.');
    # $webview.CoreWebView2.PostWebMessageAsJson((@{a=10} | ConvertTo-Json))  # JSON データ用サンプル行
}

$webview_MessageReceived = {
    param($webview, $message)
    $json = ($message.WebMessageAsJson | ConvertFrom-Json)
    $pageTitle.Text = $json.PageTitle
}

<# add Event Listeners #>
$alertButton.add_Click($clickAlertButton)
$changeHeaderButton.add_Click($clickChangeHeaderButton)
$webview.add_WebMessageReceived($webview_MessageReceived)

<# Window の表示 #>
[void]$window.ShowDialog()
$window.Close()
```

```xml:(2) ui02.xaml
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
        Title="MainWindow" Height="260" Width="500"
    >
    <DockPanel x:Name="RootDock">
        <DockPanel DockPanel.Dock="Top"  LastChildFill="False">
            <Button x:Name="alertButton"        Content="alert(in WebView2)" Padding="3" Margin="2" />
            <Button x:Name="changeHeaderButton" Content="Change Header"      Padding="3" Margin="2" />
        </DockPanel>
        <DockPanel DockPanel.Dock="Top">
            <TextBox Name="pageTitle" Text="DefaultText" IsReadOnly="True" />
        </DockPanel>
        <wv2:WebView2 Name="webView" />
    </DockPanel>
</Window>
```

```html:(3) page02.html
<!DOCTYPE html>
<html>
  <head>
    <title>Hogeタイトル</title>
    <script>
        window.addEventListener('DOMContentLoaded', () => {
            window.chrome.webview.addEventListener('message', function(event) {
                document.querySelector('h1').innerText = event.data;
            });
            document.querySelector('#sendToPS').addEventListener('click', () => {
                window.chrome.webview.postMessage({PageTitle: document.querySelector('#sendText').value});
            });
        })
    </script>
  </head>
  <body>
    <h1 id="forTitleChange">To be or not to be,</h1>
    <hr />
    <input id="sendText" type="text" value="sample Text" />
    <button id="sendToPS">テキストの内容をPowerShellに送信</button>
  </body>
</html>
```

## ◇ 実行結果
### ・(A) スクリプトの実行
無事WebView2内でメッセージボックスを起動することに成功した。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124578/568489c5-c27a-0e51-e5fb-712dc2048784.png)

### ・(B) データの送受信
#### WebView2 から PowerShell:
"テキストの内容をPowerShellに送信" ボタンを押すことで `window.chrome.webview.postMessage` が実行され、WebView2外部のTextBoxに送信した文字列を代入することに成功した。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124578/d3f75aec-e912-c337-3cbc-77fc6336be1a.png)

#### PowerShell から WebView2:
"Change Header" ボタンを押すことで `$webview.CoreWebView2.PostWebMessageAsString` が実行され、WebView2内部へのデータ送信に成功し、H1要素の値の変更に成功した。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124578/89537f62-1ef5-2887-684b-07de00787a7d.png)

# ■結論と後書き
外部との通信やファイル操作等は PowerShell で行い、アプリケーションとしてのデザインは XAML で行い、取得済みデータのユーザ操作は WebView2 上で実装すれば良い感じに棲み分け出来そうな上、VBS/JScript の中で全てを記述するより簡潔に影響範囲を分離しながら書ける可能性も感じた。

PowerShell + WebView2 に関する日本語の情報が見当たらないため本記事が与することを祈る。

# 参考
記事内で参照しているもの、参照していないが参考とさせていただいたもの。

1. NuGet Gallery | Microsoft.Web.WebView2 1.0.1222-prerelease
   https://www.nuget.org/packages/Microsoft.Web.WebView2/1.0.1222-prerelease
1. 次期HTAとしてのPowerShell+WebView2の利用 - Qiita
   https://qiita.com/tkmry/items/dede9cadc0d2a2df6c1a)
1. 阿部寛のホームページ
   http://abehiroshi.la.coocan.jp/
1. WPF アプリでの WebView2 の使用を開始する - Microsoft Edge Development | Microsoft Docs
   https://docs.microsoft.com/ja-jp/microsoft-edge/webview2/get-started/wpf
1. WebView2 in PowerShell Winform GUI - Stack Overflow
   https://stackoverflow.com/questions/66106927/webview2-in-powershell-winform-gui
1. PowerShellでWPFしたい Part2 - XAML編 - tech.guitarrapc.cóm
   https://tech.guitarrapc.com/entry/2013/03/14/190337
1. WPF WebView2 を試す | のい太ろぐ
   https://noitalog.tokyo/wpf-webview2/
1. WPFでWindows.FormsのWebBrowserを使う - 備忘録
   https://kagasu.hatenablog.com/entry/20150817/1439797327
1. PowerShellでWPFを使う - Qiita
   https://qiita.com/potimarimo/items/1eca0516bd8c690872dc
1. WPFでDockPanelを使用する - プログラムを書こう！
   https://www.paveway.info/entry/2020/05/01/wpf_dockpanel

# 更新履歴
* 2022/05/11:
  - タグに `PS-Edge` を追加