🎚️

ブラウザを PowerShell の UI にする - 4

2024/09/04に公開

これまで

これまでになぜブラウザをUIにするのか(その1)、ブラウザ側の js コード(その2)、サーバー側として動作する PowerShell のコード(その3)を見てきました。

今回は残りの PowerShell コードを見ていきます。この回で最後(のはず)。

その他の PowerShell コード部分

UIDemoWorker.psm1

最初はワーカーモジュールと名付けている、UI からのコマンドを処理する部分です。いわゆる Model。

今回はデモ用に簡単にコマンド1つになっているので対応する関数も1つ。UI から複数のコマンドが来る場合、たとえば、追加、削除、保存などとなる場合にはここで対応関数を分けることになります。

UIDemoWorker.psm1
#
# UI Demo API Worker module
#
using module .\lib.pwsh\stdps.psm1
using module .\WSCommandDispatcher.psm1

class DemoWorker {
    $OOBSender;

    Init([WSCommandDispatcher]$dispatcher) {
        $dispatcher.RegisterCommand("Run", $this.RunCommand)
        $this.OOBSender = $dispatcher.SendOOBMessage;
    }

Init() はインスタンスが生成された直後に呼ばれ、コマンド処理関数の登録をします。今回はデモとしてコマンド実行中のステータス更新のための通信を行うので、その送信関数 SendOOBMessage を覚えておきます。

UIDemoWorker.psm1
    [Object] RunCommand($p) {
        <#
            p.param.Id <-- paramId
            p.param.action <-- action code (there should be no Noop)
        #>
        log "RunCommand: Id=$($p.param.Id) Action=$($p.param.action)"

        $w = 5;
        log "RunCommand: Processing now... ($w sec. 1/2)"
        Start-Sleep -s $w

        $this.SendStatus("ただいま処理中...")

        log "RunCommand: Processing now... ($w sec. 2/2)"
        Start-Sleep -s $w

        $rc =  @{
            success = 1;
            msg = "処理は正常に完了しました。終了ボタンを押してブラウザのタブを閉じてください"
        }
        return $rc
    }

    SendStatus([string]$m) {
        $this.OOBSender.Invoke($m)
    }
}

コマンド処理関数は、今回デモ用なので何もしません。コマンドのパラメータを申し訳程度にログ出力し、5秒まってステータス更新メッセージ(out-of-band での通信)を送信し、さらに5秒待ってコマンド実行結果(今回はダミー)を返すようにしています。

実際のツールでは、ここでパラメータに応じていろいろ処理を行い、必要に応じてステータス更新などなどをしていくことになります。

メイン UIDemo.ps1

このスクリプトがユーザから実行される ps1 ファイルとなります。

UIDemo.ps1
class Main {
    $Browser;
    $DemoHelper;
    $Dispatcher;

    Run() {
        $this.Init()

        $p = [UIGenParams]::New()
        $p.Title = 'Pwsh UI Demo'
        $p.OutPath = 'demo.html'
        $p.Port = $this.GetAvailablePort()
        $g = [UIGen]::New()
        $g.Generate($p)

        & $this.Browser ([IO.Path]::GetFullPath($p.OutPath))

        $server = [WebSocketServer]::New()
        $this.Dispatcher.RegisterOOBSender($server.SendMessage);
        $server.Run($p.Port, $this.Dispatcher, 'UIDemo1')
    }

クラス Main は文字通りプログラムのメイン部分となるクラスでライブラリ関数によって Run() が呼び出されます。

Run() は、全体の流れの設計で議論した通り、UIを構成するHTMLファイルを生成し、そのブラウザを別プロセスで起動して UI HTML を表示、WebSocket サーバーを起動する、という全体の流れを実装しています。

HTML生成は自作ライブラリの中の HTMLGenerator クラスとそのパラメータを保持する HTMLGenParams クラスを継承した UIGen と UIGenParams クラスを使って生成します。このあたりの記事はまた気が向いたら(ニーズがあれば)。

WebSocket のサーバー機能は前回の WebSocketServer クラスでさらっと。

UIDemo.ps1
    [int] GetAvailablePort() {
        return $script:Port
    }

今回のデモコードで一番の問題部分がここで、 本来であればコマンドラインあるいは設定ファイルで指定された TCP ポートが使用中だった場合に開いているポートを探しに行くということをするべきなのですが実装していません。この GetAvailablePort() で未使用ポートを探す仕様というか前提なわけですが、探す部分はいれていません(おい)。

UIDemo.ps1
    Init() {
        [AppEnv]::Init($script:ConfigFile)

        $this.Browser = [AppEnv]::Get('Browser')
        $this.Dispatcher = [WSCommandDispatcher]::New()
        $this.Dispatcher.Init()

        $this.DemoHelper = [DemoWorker]::New()
        $this.DemoHelper.Init($this.Dispatcher)
    }

初期化を行う Init() ですが、[AppEnv]::Init() による設定ファイルの読み込み、設定ファイルで指定されたパラメータの取得、Dispatcher の生成と初期化、コマンド実行モジュール(Modelに相当)である Worker の生成と初期化をして終わりです。

終わりに

シリーズ冒頭でも書きましたが、ちょっとしたことや PowerShell でサクッとやると簡単に終わることは結構多くあります。Windowsやクラウド関連の設定や、Office系ファイルの操作などは良い例でしょう。

監視ツールだったりバッチ処理だったり、エンジニアが直接使うものであれば CLI で十分ですし、監視ツールのような場合には UI はそもそも不要ということもあるでしょう。

ですが、Office系ファイルの操作など、非技術者の方が日常に行っている作業が実は PowerSehell でやれば一瞬で終わるということも少なくありません。そのようなケースでは UI(GUI)はほぼ必須なのでそのためにツール作成に踏み出せなかったりすることもあるのではないでしょうか?

WebSocket を開通させてブラウザ側の js と手元の PowerShell を連携させるのは、はじめこそフレームワーク部分のコードが必要になるので面倒ですが、一度それができてしまえば再利用性はかなり高いということが今回のデモコードの作成で良くわかりました。

似たようなニーズに直面するようなことがあれば、ぜひ使っていただいて、あるいはもっと良いフレームワークを作っていただいて、それによって PowerShell の活用が広がるといいですね!

Discussion