🛠️
[HTA代替]PowerShellを実行できるローカルHTML製のツールを作る
PowerShellを実行する社内ツールをHTA(HTMLアプリケーション)で作っていたが、さすがに2025年にIEベースでHTMLを書くのに限界を感じたので、Edge/Chromeへの移行をチャレンジした
結論としては、カスタムURLとjsファイル読込を駆使することで、おおむねやりたいこと(HTA相当のことをEdgeで行う)が実現することができた
先行事例として次期HTAとしてのPowerShell+WebView2の利用があり、とても参考になったが、今回はWebView2ランタイムを不要とする方法を追求した
実現したいこと
以下の要件を満たすツールを作りたい
- HTA相当のことを、画面をHTML5で作って実現したい
- オフラインで、追加のインストール不要で動く
- 画面操作からPowerShellが実行できる、PowerShellの結果を画面に表示できる
- ツールのインストーラーが不要で、ファイルをコピーするだけで動く
- コンパイル不要で、ソースが直接編集できる
- 管理者権限不要
- Windows11のデフォルト設定で動く(FWの穴あけ等が不要)
- WebView2ランタイム不要
実現方法概要
- ツールの起動はPowershellから行う
- このシェルは、①画面から実行されるPowershellをカスタムURLに登録 と ②htmlファイルをEdgeで起動する を実行する
- 画面はHTMLで作成したものをEdgeで表示する
- 画面操作からPowerShellの実行は、登録したカスタムURLにアクセスすることで行う
- PowerShellの実行結果を画面に表示する処理は、PowerShellの実行結果をjavascript形式でreturn.jsというローカルファイルに保存し、そのjsファイルを画面で読み込むことで実現する
サンプルファイル
全体構成
ツールは以下のファイルで構成される(すべて同じフォルダに置く)
Register-UrlScheme.ps1
index.html
myapp.ps1
return.js
-
Register-UrlScheme.ps1:ツール起動用のシェル
- カスタムURLへのmyapp.ps1の登録と、index.htmlの起動を行う
-
index.html:画面表示用のHTML
- ツール起動用のシェルから起動される
- PowerShellを呼び出したいときは、カスタムURL(myapp://{id}/{value})にアクセスし、シェルの実行結果をローカルのjsファイル(return.js)経由で受け取る
-
myapp.ps1:画面処理用のシェル
- カスタムURLから呼び出されるコマンドとして登録される
- シェルの実行結果はreturn.jsに保存する
-
return.js:シェルの実行結果受け渡し用のjs
- myapp.ps1から生成されて、index.htmlに読み込まれる
- ファイルの中の処理で「globalShellReturn」という変数を更新することで、画面に情報を渡す
ツール起動用のシェル
Register-UrlScheme.ps1
# カスタムURLスキーム 'myapp://' を登録
$scheme = "myapp"
$command = """"+ $PSScriptRoot +"\myapp.ps1""" +" `"%1`""
$regPath = "HKCU:\Software\Classes\$scheme"
New-Item -Path $regPath -Force | Out-Null
Set-ItemProperty -Path $regPath -Name "(default)" -Value "URL:$scheme Protocol" -Force
Set-ItemProperty -Path $regPath -Name "URL Protocol" -Value "" -Force
$commandPath = "$regPath\shell\open\command"
New-Item -Path $commandPath -Force | Out-Null
Set-ItemProperty -Path $commandPath -Name "(default)" -Value "powershell.exe -ExecutionPolicy Bypass -File $command" -Force
# ツールの画面を起動
$html =""""+ $PSScriptRoot +"\index.html" + """"
Start-Process "msedge" $html
- ダブルクオーテーションがいっぱい並んでるのは、ファイルパスにスペースが含まれた場合の対策
画面用HTML
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<script type="text/javascript">
// グローバル変数 シェルの実行結果保存変数
var globalShellReturn = {}
// 実行ボタンの処理
async function execute() {
const id = new Date().getTime() // 現在時刻(ミリ秒)をidにする
const value = encodeURIComponent("Hello")
const url = `myapp://${id}/${value}`
// powershell実行用のカスタムURLを呼び出し
window.location.href = url
// shellの完了を待機
let returnValue = await waitShell(id)
// shellの実行結果を画面に表示
document.getElementById("result").innerHTML = returnValue
}
// グローバル変数を監視することでshellの実行完了まで待機し、完了したらシェルの戻り値を返す
async function waitShell(id) {
return new Promise((resolve) => {
const check = () => {
fileload()
let value = globalShellReturn[id]
if (value) {
resolve(value)
} else {
setTimeout(check, 100)// 0.1秒単位でshellの完了をチェック
}
}
check()
})
}
// scriptタグを追加することでjsファイルを読み込む
function fileload() {
const el = document.createElement("script")
el.src = "return.js"
const sc = document.getElementById("forLoad")
sc.innerHTML = ""
sc.appendChild(el)
}
</script>
</head>
<body>
<button onclick="execute()">実行</button>
<p>実行結果:<span id="result"></span></p>
<div id="forLoad"></div>
</body>
</html>
- return.jsに前の実行結果が残っていた場合の対策として、リクエスト単位でidを生成し、id指定でデータを読み込むようにしている
画面処理実行用のシェル
myapp.ps1
param ($url)
# URLデコード(%20 → スペース など)
$decodedUrl = [System.Uri]::UnescapeDataString($url)
# 出力ファイルのパス
$returnFilePath =$PSScriptRoot+"\return.js"
# "myapp://xxx" の "xxx" 部分を抽出
if ($decodedUrl -match "myapp://(.+)") {
$data = $matches[1]
$parts = $data -split "/", 2
# URLを解析してidと引数に分割
$id = $parts[0]
$input = $parts[1]
# 例:引数に対応した処理
$result = $id + "-" + $input + " World"
# javascriptの変数を上書きする形で結果を保存
$returnValue = "globalShellReturn = { "+$id +": """ + $result +""" }"
# 実行結果を.jsに保存
Set-Content -Path $returnFilePath -Value $returnValue
}
シェルの実行結果受け渡し用のjs
初期状態の中身は空でいい。値が入った場合は下記のような内容になる。
return.js
globalShellReturn = { 1740923674497: "1740923674497-Hello World" }
実行サンプル
①powershellで、ツール起動用のシェルを実行する
cmd
powershell .\Register-UrlScheme.ps1
②Edgeでツールの画面が表示される
③実行ボタンを選択すると、Powershellの実行結果が画面に表示される
補足
- サンプルではHTMLとjsを同じファイルにしているが、別ファイルにすることも可能(画面はただのローカルHTML)
- 画面から呼び出すシェルの内容が複数パターンある場合は、呼び出すURLで分岐すれば、1つのカスタムURL(と1つの登録用のシェルファイル)で実現可能
- 1回カスタムURLを登録してしまえば、以降は、ローカルHTMLを直接立ち上げても動作する
- シェルを複数並列で実行したい場合は、結果受け取り用に複数のjsファイルが必要
- PowerShellで画面操作を行うことで、ローカルHTMLの画面からWeb画面を操作するツールを作れる
Discussion