wincmdserver.ps1 - 動くバージョン

前回作成したスクリプトで、サーバとして機能するための基本的な動きはしてくれるようになった。今回はこれをベースに、「外部からGUIコマンドを起動できるサーバ」として動くように書き換えていく。

先に、今回の成果物を以下に示す。

wincmdserver.ps1

#!/usr/bin/env pwsh

#========================================================================
# 特定のファイルに書き込まれたコマンドを実行する簡易サーバ
#========================================================================

#========================================================================
# コマンドが書き込まれるファイル
#========================================================================
$commandFilePath = "${HOME}/.wincmdserver_cmd"

#========================================================================
# ファイルをチェックするインターバル時間[秒]
#========================================================================
$fileCheckInterval = 1.0

#========================================================================
# コマンドが書き込まれるファイルを初期化
#========================================================================
Write-Output $null > $commandFilePath

#========================================================================
# コマンドが書き込まれるファイルを監視して、コマンドが書き込まれた場合には
# コマンドを実行してファイルをクリアする
#========================================================================
while ($true) {
    # コマンドファイルが存在し、かつ、中身があるときに中身を実行する
    if (Test-Path "$commandFilePath") {

        if (0 -lt (Get-ChildItem "$commandFilePath").Length) {

            # ファイルに書き込まれたコマンドを実行
            Invoke-Expression (cat "$commandFilePath") 

            # ログ出力
            $timestamp = Get-Date -format "yyyy/MM/dd HH:mm:ss"
            $timestamp + " - " + (cat "$commandFilePath")

            # ファイルの中身をクリア
            Clear-Content "$commandFilePath"
        }
    }

    # 次のチェックまで指定秒間待機
    Start-Sleep $fileCheckInterval
}

処理の流れを簡単に整理しておくすると、次のようになる。

ステップ 内容
1     ファイルを初期化する(0バイトで新規作成)
2     ファイルが存在し、かつ、サイズが0バイトよりも大きい場合にステップ3~5を実行する。そうでない場合にはステップ6へ移動
3     ファイルに書き込まれたコマンドを実行する
4     ログを出力する
5     ファイルを初期化する(0バイト化)
6 1秒間待機する。
7 2へ戻る

変数名やファイル名などが前回のスクリプトとは変わっているが、基本的に内容は同じだ。1つだけ、「ファイルに書き込まれたコマンドを実行する」という処理が新しく追加されている。次の部分だ。

# ファイルに書き込まれたコマンドを実行
Invoke-Expression (cat "$commandFilePath")

「(cat "$commandFilePath")」でファイルの中身を取り出しており、これがInvoke-Expressionによって評価される。つまり、ファイルの中に書いてある文字列がコマンドとして実行される、ということになる。

動作を確認する

それでは早速動作を確認してみよう。まず、Windows側で次のように作成した「wincmdserver.ps1」を実行する。

  • Windows側でwincmdserver.ps1を実行

    Windows側でwincmdserver.ps1を実行

次に、wincmdserver.ps1を実行したWindowsとは別のホストまたは仮想環境から、Windows側にsshでアクセスしてホームディレクトリの.wincmdserver_cmdというファイルに実行したいコマンドを書き込む。

  • ssh経由でホームディレクトリの.wincmdserver_cmdに実行したいコマンドを書き込む

    ssh経由でホームディレクトリの.wincmdserver_cmdに実行したいコマンドを書き込む

ssh経由で書き込む方法は何でもよい。例えば「ssh IPアドレス 'echo コマンド > $HOME/.wincmdserver_cmd'」のような感じでコマンドを実行してもよいだろう。上記の画像ではエラーが出ているが、これは実験中の機能を使っているためであり、ここでは特に気にする必要はない。

sshコマンドは、リモートからホストにリモートログインしてインタラクティブな操作をする際に使うものだが、前述したように任意のコマンドを実行する目的でも使えるし、ポートフォワーディングなどの目的でも使える。sshコマンドは高い汎用性を備えたコマンドなのだ。

wincmdserver.ps1を実行したターミナルには、次のようにファイルへの書き込みがあったことを示すログが出力される。

  • wincmdserver.ps1にはファイルへの書き込みを示すログが出力される

    wincmdserver.ps1にはファイルへの書き込みを示すログが出力される

Windowsでは次のようにメモ帳が起動する。

  • ssh経由で最終的にメモ帳が起動してくる

    ssh経由で最終的にメモ帳が起動してくる

ssh経由でただログインしただけでは、この動作はできない。GUIアプリケーションを起動することができるプロセス(wincmdserver.ps1)に処理を依頼したことで、実現できたわけだ。

こんな感じで、試作の段階ではとりあえずサーバ側は手動で起動して動作確認しておくとよいと思う。何度か使っていって問題ないことがわかったら、自動的に起動する仕組みに登録すればよいのだ。

次のステップはタスクスケジューラ

Windowsには特定の条件に合わせてアクションを実行する機能として「タスクスケジューラ」と呼ばれる機能が用意されている。時刻、システムイベント、起動、サインイン、セッション状態の変更などいくつかの条件があり(これらは「トリガー」と呼ばれている)、それらに対して何らかの動作、例えばプログラムの実行などを指定できるというものだ。

先ほど作成したwincmdserver.ps1をタスクスケジューラで登録しておけば、「ユーザーがサインインした段階で起動させる」といったことができるようになる。今回のような用途であれば、これで十分だ。

PowerShellを活用する場合、何もPowerShellだけで全てを完結させる必要はない。OSが提供するほかの機能をうまく組み合わせることも大切だ。タスクスケジューラはPowerShellスクリプトで処理を自動化したい場合に使うと便利な機能なので、今後もう少し詳しく見ていこうと思う。