PowerShellからmsg.exeでWindowsセッションへメッセージを送る方法

PowerShellから別のWindowsコンピュータの利用者に短い通知を出すには、Windowsのmsg.exeを使う方法があります。送信先はコンピュータに存在するユーザーセッションです。メールのように後で読むための通知や、ネットワーク上の全PCへ配信する仕組みとしては使いません。

この記事では、quser.exeで対象を目視確認し、サーバー名とセッションIDを指定して1つのセッションだけに送る手順を説明します。自動抽出したIDに送る処理は使いません。コードは管理者の承認がある環境で、値を実際の対象に置き換えて実行してください。

目次

msg.exeの対象と必要な権限

Microsoftの説明では、msgはRemote Desktop Session Host上のユーザーにメッセージを送信するコマンドです。/server:はそのセッションがあるサーバーを指定します。RDPで利用者が接続している場合は、手元の接続元PCではなく、通知したいセッションがある接続先を選びます。

  • 実行するWindows環境でquser.exeとmsg.exeを利用できることを確認します。PowerShell自体のコマンドレットではないため、PowerShellがあるだけで利用できるとは限りません。
  • quserにはFull Controlまたは特別なアクセス権限、msgにはMessage特別アクセス権限が必要です。照会できても送信できるとは限りません。送信元で管理者として起動しただけで、接続先の必要権限が得られるわけではありません。
  • 宛先のセッションはアクティブなものを選びます。切断中のセッションやログオンしていない人に確実に届ける必要がある場合は、組織で承認されたメールやチャットなど別の手段を使います。

権限や通信の問題がある場合は管理者に確認してください。送信するためだけに、ファイアウォールの無効化、レジストリ変更、広いアクセス権の付与を先に行う手順にはしません。

手順1:サーバーを指定してquserの一覧を表示する

次の例は照会だけで、メッセージは送りません。SERVER01を対象サーバー名へ置き換え、ブロック全体を実行してください。

& {
    $ErrorActionPreference = 'Stop'
    $PSNativeCommandUseErrorActionPreference = $false
    $server = 'SERVER01'
    $quser = (Get-Command quser.exe -CommandType Application -ErrorAction Stop).Source

    & $quser "/server:$server"
    $queryExitCode = $LASTEXITCODE
    if ($queryExitCode -ne 0) {
        throw "quserに失敗しました。終了コード: $queryExitCode。表示されたエラーを確認してください。"
    }
}

一覧では、同じ行にあるユーザー名、セッション名、ID、状態を見比べます。サーバー名はコマンドに指定した値と照合してください。ログオン時刻も判断材料になります。行頭の>は現在のセッションを示す印であり、送信先として選んでよいという意味ではありません。

  • 対象のログオン名とIDを、列見出しに対応させて読み取ります。同じ利用者が複数のセッションを持つ場合は、本人または担当者と対象を確認します。先頭の行を自動採用しません。
  • 表示言語や空欄のある列に左右されないよう、この手順では出力を空白で分割したり、行内の数字を探したりしてIDを抽出しません。状態の文字列を英語のActiveに固定して判定する処理も使いません。
  • 対象が見つからない、同名アカウントの区別がつかない、状態が判断できない場合はここで止めます。エラーを「0件」と読み替えたり、宛先を*へ広げたりしません。

手順2:対象を再照会し、本文を確認して1回だけ送る

次のコードは、確認文字列を入力すると実際に送信します。まず$serverと$expectedUserを確認済みのサーバー名とログオン名へ、$sessionIdを一覧で選んだIDへ、$messageを承認済みの本文へ変更してください。例のIDや本文をそのまま本番利用者へのテストに使わないでください。

& {
    # このブロック内のエラー処理。起動失敗は停止し、終了コードは自分で確認する。
    $ErrorActionPreference = 'Stop'
    $PSNativeCommandUseErrorActionPreference = $false
    $server = 'SERVER01'
    $expectedUser = 'user01'
    [int]$sessionId = 4   # 例。直前の一覧で確認したIDに変更する。
    $message = '18:00から保守を開始します。作業中のファイルを保存してください。'
    if ([string]::IsNullOrWhiteSpace($message)) {
        throw '本文が空なので中止しました。'
    }

    $quser = (Get-Command quser.exe -CommandType Application -ErrorAction Stop).Source
    $msg = (Get-Command msg.exe -CommandType Application -ErrorAction Stop).Source

    # 送信候補を再照会し、出力をそのまま表示する。
    & $quser $sessionId "/server:$server"
    $queryExitCode = $LASTEXITCODE
    if ($queryExitCode -ne 0) {
        throw "再照会に失敗しました。終了コード: $queryExitCode。送信しません。"
    }

    # ExpectedUserは確認用の値。quser出力から自動検証した値ではない。
    [pscustomobject]@{
        Server       = $server
        ExpectedUser = $expectedUser
        SessionId    = $sessionId
        Message      = $message
    } | Format-List

    $confirmation = "SEND $server/$sessionId/$expectedUser"
    $answer = Read-Host "上のquser出力でユーザー・ID・アクティブ状態と本文を確認後、$confirmation を入力。それ以外は中止"
    if ($answer -cne $confirmation) {
        throw '送信を中止しました。'
    }

    # 実際の送信はこの1回のみ。文字列を実行するInvoke-Expressionは使わない。
    & $msg $sessionId "/server:$server" /time:60 /v $message
    $msgExitCode = $LASTEXITCODE
    Write-Host "msg.exeの終了コード: $msgExitCode"
    if ($msgExitCode -ne 0) {
        Write-Warning '送信処理でエラーが報告されました。自動再送せず、表示された内容を確認してください。'
    }
}

途中の行だけを選んで実行せず、& {から最後の}までをひとまとまりとして実行してください。この確認は人が行うものです。スクリプトは、表示された行のユーザー名や状態が期待値と一致することまでは検証していません。再照会結果が違う、複数候補が残る、読み取れない場合は確認文字列を入力せず中止してください。

照会と送信は別の操作なので、その間のログオフや再接続などによる変化を完全には防げません。確認中に時間が空いた場合や利用者から接続変更の連絡があった場合は、再照会からやり直します。セッションの変化が頻繁な環境や、誤送信を許容できない通知には別の連絡手段を選んでください。

よく使う引数と、避けたい宛先指定

指定意味・この手順での扱い
$sessionIdそのサーバー上の数値のセッションIDを指定します。ユーザー名だけを宛先にすると、そのユーザーの複数セッションが対象になり得るため、この例では使いません。
/server:SERVER01送信先セッションがあるサーバーです。省略時は現在ログオンしているサーバーが使われるため、リモート宛てでは明示します。
/time:60メッセージを画面に表示する時間を秒で指定します。配信を繰り返す間隔ではありません。
/v実行中の処理に関する情報を表示します。利用者が内容を理解したことを示すものではありません。
/w利用者の受信確認応答を待ちます。使う場合は長時間の待機を避けるため/time:も指定します。上の例では付けていません。
*、@ファイル名全ユーザー宛て、またはファイルに並べた複数宛先への指定です。1セッションへの通知では使いません。

msg.exeにPowerShellの-WhatIfはありません。送信前の確認は上の表示と入力で行います。また、本文を含むコマンド文字列を組み立てて再実行するのではなく、例のように実行ファイルへ引数を直接渡します。機密情報、パスワード、確認コードは本文へ入れないでください。

本文は例のような短い1行を想定しています。文字列内の半角二重引用符などの扱いは、Windows PowerShell 5.1とPowerShell 7.3以降のネイティブ引数処理で差があるため、複雑な本文をそのまま扱えるとは限りません。必要な場合は承認された検証用セッションで表示を確認し、本番利用者をテスト相手にしないでください。

終了コードと利用者の受領を分けて確認する

この例のエラー設定はブロック内だけに適用します。実行ファイルが見つからない・起動できない場合はその場で止め、コマンドが動いていないのに以前の終了コードを流用しないようにしています。PowerShell 7のネイティブコマンド用エラー設定もブロック内で明示し、非0の終了コードはコード中で扱います。

$LASTEXITCODEは最後に実行したネイティブプログラムの終了コードなので、msg.exeの直後に別変数へ保存します。0はコマンドが成功終了したことを確認する材料ですが、それだけで本人が文章を読み、理解したと判断しないでください。0以外の場合は、番号だけで原因を決めず、同時に表示されたエラーを残します。

/wで確認応答を待つことと、保守日時や作業内容が伝わったことの確認も分けて扱います。重要な保守連絡は、承認された別経路で利用者の返答を確認してください。返答がない、結果が不明、待機が終わったという理由だけで、同じ通知を自動再送しない運用にします。

届かない場合の切り分けと誤送信時の対応

症状確認すること
コマンドが見つからない実行するWindows環境に対象の実行ファイルがあるか確認します。出所不明の実行ファイルをダウンロードして補いません。
quserが失敗するサーバー名、接続先への到達性、照会権限を確認します。照会失敗と、正常に照会できたが対象がいない状態は区別します。
対象がいない/切断中サーバーの取り違え、ログオフ、接続状態の変化を確認します。古いIDを再利用したり、全員宛てへ切り替えたりしません。
照会はできるがmsgが失敗するMessageアクセス権限と、直前に確認した対象の状態を管理者に確認します。照会の成功は送信権限の証明ではありません。
成功終了したが本人の返答がない別経路で受領を確認します。通知が未読なのか、表示を見逃したのかを終了コードだけで断定しません。

送ったメッセージを取り消す手順はありません。誤送信が分かった場合は、まず追送を止めて担当者へ報告し、必要な訂正文と宛先を確認してから対応します。機密情報が含まれた場合は、組織の情報漏えい対応手順に従ってください。

保守連絡として残す記録

通知日時、送信元と実行者、対象サーバー、確認したユーザー名とセッションID、必要な範囲の照会結果、承認済み本文、msg.exeの出力と終了コードを記録します。本人から受領確認を得た場合は、その時刻と確認方法を別項目で残すと、コマンド実行と実際の連絡完了を混同せずに済みます。記録に含まれる他の利用者情報は、組織の保存・閲覧ルールに従って扱ってください。

公式情報・参考資料

コマンドの構文、引数と必要権限は次のMicrosoft公式資料に基づき、2026年10月20日に確認しました。本記事のコードは説明用の例です。Windows実機での送受信試験は実施しておらず、利用環境での到達や表示を保証するものではありません。コマンドのローカルヘルプはquser.exe /?、msg.exe /?で確認できます。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次