OpenSSH経由でGUIアプリやシステムクリップボードを使いたい理由

前回、WindowsにOpenSSH経由でリモートログインした段階では、GUIアプリケーションを起動できないと説明した。しかし、起動したいケースは考えられる。

例えば、WSL2やHyper-Vなどの仮想環境でLinuxやほかのOSを実行している際、そこからホスト側のファイルエクスプローラや特定のアプリケーションを起動したいとか、Microsoft Edgeで特定のURLやファイルを開きたいといった場合だ。

また、ほかのOSからOpenSSH経由でWindowsへリモートログインして、テキストデータのコピー&ペースを行いたいこともあるだろう。OpenSSH経由では、システムクリップボードにコピーできないのだ。何とかしてこうしたことをできるようにしたい。そうすれば、複数のPCを使っている場合などは特に、とても便利になるはずだ。

実現方法:あるプロセスとあるプロセスの間の通信

OpenSSH経由でWindowsにリモートログインした状態で、WindowsでGUIアプリケーションを起動したり、システムクリップボードを使ったりするには、GUIアプリケーションを起動したり、システムクリップボードにアクセスしたりできるプロセスを先に起動しておき、そのプロセスに操作を依頼すればよい。

典型的な方法としては、サーバやサービス、デーモンと呼ばれるようなソフトウエアを実行しておき、そこへ仕事を依頼することになる。

OSはあるプロセスから別のプロセスへメッセージを送る(プロセス間通信)方法を複数提供している。ネットワークを使う方法もあるし、専用の通信方法が提供されていることもある。「ファイルシステム」という外部リソース空間を使うのも、プロセス間通信を実現する方法の一つだ。

どの方法を採用しても良いのだが、今回はファイルシステムを介してプロセス間で通信する方法を紹介する。この方法は容易に実現でき、デバッグも行いやすい。仕組みはシンプルだし、個人が使うユーティリティレベルとしては十分だ。企業での利用であっても、仕組みをきちんと整えておけば、十分実用に足りる。

ファイルシステムの名前空間

端末からWindowsへサインインした場合も、リモートデスクトップで接続した場合も、OpenSSHでリモートログインした場合も、基本的にユーザーのホームディレクトリ以下には自由にアクセスできる。ファイルシステムの名前空間はグローバルなリソースの一つであり、結構自由にアクセスできるのだ。

LinuxなどのUNIX系OSでは「chroot(2)」でファイルシステムを区画化して分離することができる。FreeBSDではjailでファイルシステムのみならずプロセス空間などほかのシステムリソースも区画化できる。似たような技術はSolaris Containersなどいくつか存在している。こうした技術を使うとユーザーを閉じ込めることができるのだが、Windowsが提供している機能としてはHyper-Vを使うのが一般的であり、Hyper-Vを使って仮想環境へ分離しない限り、ホストのリソースは結構自由にアクセスできるのだ。

サービス(デーモン)となるプロセスでファイルシステム上のリソース、つまりファイルまたはフォルダを監視し、ここに何らかのアクションが発生した場合にイベントを発生させる。シンプルに特定のファイルをモニタリングして、そのファイルへの書き込みが発生したらアクションを起こす仕組みにすればよい。データのやり取りは、そのファイルへの読み書きで行う。シンプルでわかりやすい方法だ。

UNIX系のファイルシステムでは、ファイルシステムの提供する機能を使ってもっといろいろなことができるのだが、Windowsのファイルシステムでも簡単なものであれば似たようなことはできる。便利な方法なので一度試してみていただきたい。

簡単なスクリプトで動作を確認する

このアイデアが機能するかどうかを確認するスクリプトを作成し、動作をチェックしてみる。スクリプト自体は簡単だ。「while ($true)」でぐるぐる回りながら、ファイルに書き込みがあったらその中身を読み込んで出力する、といったようなものを作ればよい。ここでは、次のようなスクリプト「simple_server.ps1」を作成した。

#!/usr/bin/env pwsh

#========================================================================
# 特定のファイルに書き込まれた文字列を出力する
#========================================================================

#========================================================================
# 文字列が書き込まれるファイル
#========================================================================
$msgFilePath = "${HOME}/.simple_server_msg"

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

#========================================================================
# 文字列が書き込まれるファイルを初期化
#========================================================================
Write-Output $null > $msgFilePath

#========================================================================
# ファイルを監視して、書き込みが行われた場合に、中身を表示する
#========================================================================
while ($true) {
    # ファイルが存在し、かつ、中身があるときに中身を表示する
    if (Test-Path "$msgFilePath") {

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

            # 時刻を付加して中身を出力
            $timestamp = Get-Date -format "yyyy/MM/dd HH:mm:ss"
            $timestamp + " - " + (cat "$msgFilePath")
        }
    }

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

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

スクリプトを読めば動作内容は理解できると思うが、以下に簡単に説明しておく。

ステップ 内容
1 ファイルを初期化する(0バイトで新規作成)
 2 ファイルが存在し、かつ、サイズが0バイトよりも大きかった場合、その中身を時刻を付加した状態で出力する
 3 ファイルを初期化する(0バイト化)
 4 1秒間待機する
 5 2へ戻る

実際に動作を確認してみよう。まず、Windowsで上記スクリプト(simple_server.ps1)を実行する。

  • Windowsでsimple_server.ps1を実行

    Windowsでsimple_server.ps1を実行

次に、Windows以外のホストからスクリプトsimple_server.ps1を実行したホストへOpenSSH経由でリモートログインする。

  • MacからWindowsへOpenSSH経由でリモートログイン

    MacからWindowsへOpenSSH経由でリモートログイン

この状態で、OpenSSHでログインしているところから~/.simple_server_msgファイルへ文字列を書き込む。

  • OpenSSHでログインしているところからら~/.simple_server_msgファイルへ文字列を書き込み

    OpenSSHでログインしているところからら~/.simple_server_msgファイルへ文字列を書き込み

スクリプトを実行しているWindows側の出力を確認すると、次のように書き込まれた文字列を取り出して出力していることを確認できる。

  • simple_server.ps1がファイルへの書き込みを検出して中身を取り出していることを確認

    simple_server.ps1がファイルへの書き込みを検出して中身を取り出していることを確認

もう一度OpenSSHでリモートログインしている方へ移動し、さらに別の文字列を~/.simple_server_msgファイルへ書き込んでみる。

  • OpenSSHでログインしているところからら~/.simple_server_msgファイルへ文字列を書き込み

    OpenSSHでログインしているところからら~/.simple_server_msgファイルへ文字列を書き込み

するとWindowsで実行したスクリプト側で、新たに書き込まれたメッセージが出力されることを確認できる。

  • simple_server.ps1がファイルへの書き込みを検出して中身を取り出していることを確認

    simple_server.ps1がファイルへの書き込みを検出して中身を取り出していることを確認

このようにシンプルなスクリプトで、OpenSSH経由でログインしている状態から、WindowsのGUIアプリケーションを起動できる状態で動作しているプロセスにメッセージを送れることが確認できた。このスクリプトを拡張していけば、目的とするサービスもすぐに作れることになる。

小さく始めて、少しずつ育てる

simple_server.ps1にさまざまなことをさせたいなら、通信用にプロトコルを開発して利用すればよいし、コマンドだけ実行すればよいなら、通信用のファイルに実行したいコマンドを書き込んでもよいだろう。

ここで大切なのは、シンプルなスクリプトでも実用的な仕組みを作れるという点にある。まずは簡単なスクリプトを作って、必要に応じて拡張していく。これが実用的なスクリプト開発の第1段階だ。本連載では次回以降、このスクリプトをブラッシュアップして、より汎用的に使えるものに仕上げていこうと思う。