とりあえずブログ作ってみた、的なブログ

このブログの99.99%は「与太話」でできています。

Windows10でサインイン、サインアウト、シャットダウン時のサウンドを鳴らしてみた

前回の記事で触れたように、Windows10(正確にはWindows8かららしいが)では、サインイン(ログオン)、サインアウト(ログアウト)、シャットダウン時のイベントサウンドが鳴らないようになっている。で、これまた前回触れたように、ネット上で書かれている対処法(レジストリのある部分を編集する事で、コントロールパネルの「サウンド」設定に「ログオン」「ログオフ」「Windowsの終了」が表示/編集できるようになる)を試しても、確かに表示はされるが実際には音声が再生されないため、別の方法を試してみた。

その方法は、「ローカルグループポリシー」を使用するもので、これによってサインイン、サインアウト、シャットダウン時にスクリプトを実行できる。そのためにサウンド(WAVファイル)を再生するスクリプトを作成する必要はあるが。なお、「ローカルグループポリシーエディター」は「Professional」以上のエディションにしか入っていないので、「Home」などの下位のエディションでは実施できない(外部からローカルグループポリシーエディターを持ってくればHomeでも使えるような情報もあるが、真偽の程は定かではない)。

イメージ 9

前置きはそのぐらいにして、まずはスクリプトの作成から。

前述のグループポリシーエディターで使用できるスクリプトとしては、Windows Script Host(WSH)に対応したもの(VBScriptなど)かPowerShellがあるが、今回はPowerShellで作成することにした。

スクリプトの要件としては、
 ・コマンドラインパラメータで「ログオン」「ログオフ」「シャットダウン」を指定できること
 ・各イベントに対応した、現在Windowsに設定されているサウンドを取得できること
 ・前述で取得したサウンドを再生できること
の3つ。これらを満たすスクリプトを作成していく。

まず前準備として、上記2番目の要件である「各イベントに対応した、現在Windowsに設定されているサウンドを取得」する方法を知る必要がある。設定自体はレジストリに格納されていて、格納場所は

HKEY_CURRENT_USER\AppEvents\Schemes\Apps\.Default\(イベント名)\.Current

になる。この中の「(規定)」に設定された値が、そのイベントに割り当てられたイベントサウンドになる。

イメージ 1

今回使う「(イベント名)」は次の通り。
 ・サインイン ... WindowsLogon
 ・サインアウト ... WindowsLogoff
 ・シャットダウン ... SystemExit

前準備もできたところで、スクリプトの作成に取りかかる。

コマンドライン引数の取得】
今回作成するスクリプトは、各イベントに対応したサウンドを再生するというものなので、それぞれのイベント名を引数として与えることにする。(ちょっと余談)この記事を読んで頂いている方の多くはプログラミングの心得があると思うので、そういう方には「釈迦に説法」だが、「とりあえずログオン用のプログラムを作って、それをコピーしてログオフ用、シャットダウン用のプログラムを作る(=合計3つのプログラムを作る)」的なやり方でも目的は果たせるが、それをやるといざ修正が発生した場合にすべてのプログラムに手を入れなければならなくなるので、「同じ処理をするプログラムは1つだけ作成して、動作の異なる部分はパラメータ(引数)で制御する」というのが「効率の良い」プログラミングの基本的な考え方になる。

話が横道に逸れたが、PowerShellではコマンドライン引数は「$Args[]」という配列に格納される。
配列の添え字は「0」から始まり、0が1番目の引数、1が2番目の引数・・・となる。今回は引数は一つだけなので、決め打ちで「$Args[0]」の値を取得する。引数としては、レジストリキーの値をそのまま使うことにして、「WindowsLogon」「WindowsLogoff」「SystemExit」の3つを想定している。

実際のプログラムはこんな感じ。

$EventName = $Args[0]

Switch ($EventName){
    "WindowsLogon"{}
    "WindowsLogoff"{}
    "SystemExit"{}
    Default
        {$EventName = ".Default"}
}

まず1行目で1番目のコマンドライン引数を取得して「$EventName」という名前の変数に代入している。

次の「Switch ($EventName)」で始まるブロック(「{」から「}」までのブロック)は、必須という訳ではないが、想定外の引数が与えられた場合を考慮して入れてある(PowerShellのエラー回避のために、誤った引数が与えられた場合に対する何らかの処理は入れておいた方がいい)。引数が「WindowsLogon」「WindowsLogoff」「SystemExit」以外の場合(「Default」の部分)、「.Default(既定のサウンド=一般の警告音)」を指定したと見なすようにしている。正しい引数が与えられた場合は「{}」として、ここでは何も処理を行わない。
参考までに、PowerShellの「Switch」文では、文字列の比較はデフォルトでは大文字/小文字の区別はないので、例えば「windowslogon」というようにすべて小文字で引数を与えても問題なく文字列を判別する。

余談だが、個人的には上記のエラー回避処理は100点満点の対応とは思っていない。今回はコマンドライン引数が3種類しかないという前提なので「ま、いっか」とお手軽な方法を取ったが、もしこれが100種類とかになったらこのやり方ではかなり効率が悪い。他の方法としては、とりあえずコマンドライン引数はそのまま取得しておいて、レジストリの値を取得する時にエラー(指定されたイベントがなければエラーになるはずなので)をキャッチして、エラーだったら改めて「.Default」の値を取得し直す対応なども考えられるので、そこはケースバイケースで臨機応変に対応すれば良いかと。今回のやり方は、あくまでも「方法の一つ」に過ぎない。

次に、与えられた引数を元に、レジストリの位置を特定する。

$RegPath = "HKCU:\AppEvents\Schemes\Apps\.Default\" + $EventName + "\.Current"

これは単純に文字列を連結して、「$RegPath」という変数に格納しているだけ。
ちなみに、「HKEY_CURRENT__USER」はPowerShellでは「HKCU:」と記述する。また、文字列の連結には「+」を用いる。上記の処理で、例えば与えられた引数が「WindowsLogon」の場合には「$RegPath」の値は
 HKCU:\AppEvents\Schemes\Apps\.Default\WindowsLogon\.Current
となる。

レジストリの値の取得】
次に、指定されたレジストリキーの中身を取得する。

$Sound = Get-ItemProperty -Path $RegPath -Name "(Default)"

$SoundPath = $Sound."(Default)"

1行目の処理を実行すると、下記のような感じの値が変数「$Sound」に格納される

PSPath       : Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\AppEvents\Schemes\Apps\.Default\WindowsLogon\.Current
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\AppEvents\Schemes\Apps\.Default\WindowsLogon
PSChildName  : .Current
PSDrive      : HKCU
PSProvider   : Microsoft.PowerShell.Core\Registry
(default)    : C:\Windows\media\Windows Logon Sound.wav

これは変数「$RegPath」で指定したレジストリキーの内容なのだが、要は指定したレジストリキーの配下全ての値が取得される。

そこで2行目で必要な値を取得する。
必要なのは、「(既定)」の値(「(default)」で示される値)のみなので、2行目のような処理を記述することによって、必要な値のみが出力される。これを「$SoundPath」という名前の変数に代入する。これで目的のサウンドファイルのパスを取得できた。

確認のため、変数「$SoundPath」の値を表示してみる。

echo $SoundPath

「echo」命令を使うことで、続く文字列をコンソールに表示することができる。これも「釈迦に説法」かもだが、この手のスクリプトの場合、ちゃんと想定した動作をしているかどうかが外部から分かりづらいので、スクリプト作成時には要所要所にこのような出力処理をいれて確認するとよい。

サウンドの再生】
最後に、前述の処理で取得したパスのファイル(WAVファイル)を再生する。

$SoundPlayerObj = New-Object Media.SoundPlayer($SoundPath)
$SoundPlayerObj.PlaySync()

1行目で.NET Frameworkの「Media.SoundPlayer」クラスのオブジェクトを作成して変数「$SoundPlayerObj」に代入している。平たく言えば「サウンドを再生する準備ができた」ということ。
2行目で実際にサウンドを再生している。サウンドの再生には、「Play()」メソッドと「PlaySync()」メソッドがあるが、違いは前者はサウンドの再生が終わるまで次の処理を待たない(なので、サウンド再生中に次の処理が実行されて、サウンドの再生が中断される可能性がある)のに対して、後者はサウンドの再生が完了するまで処理を待つ。今回は、「Play()」メソッドを使うと上手くサウンドが再生されなかったので、「PlaySync()」メソッドを使っている。

スクリプトはここまで。
完成したスクリプトはこんな感じになる。

# コマンドライン引数を取得
$EventName = $Args[0]

# 想定外の引数が与えられた場合を考慮した対応
Switch ($EventName){
    "WindowsLogon"{}
    "WindowsLogoff"{}
    "SystemExit"{}
    Default
        {$EventName = ".Default"}
}

# レジストリのパス名を生成
$RegPath = "HKCU:\AppEvents\Schemes\Apps\.Default\" + $EventName + "\.Current"

# 指定されたレジストリの内容を取得する
$Sound = Get-ItemProperty -Path $RegPath -Name "(Default)"

# 上記で取得したレジストリ値から、必要なキーの値を取得する
$SoundPath = $Sound."(Default)"

# 確認用(別になくても動作に影響はない)
echo $SoundPath

# WAVサウンドを再生
$SoundPlayerObj = New-Object Media.SoundPlayer($SoundPath)
$SoundPlayerObj.PlaySync()


スクリプトの作成が終わったら、適当な名前で保存する。なお、PowerShellスクリプトの拡張子は「.ps1」にすること。今回は「PlaySystemSound.ps1」という名前で保存した。

スクリプトができたので、ちゃんと動くかテストをしてみる。そのためにPowerShellのコンソールを起動する。
PowerShellのコンソールの起動方法は2つある。
一つは、スタートメニューから起動する方法、もう一つは、コマンドプロンプトから起動する方法だ。

まずは、スタートメニューから起動する方法。
イメージ 2

イメージ 3

次に、コマンドプロンプトから起動する方法。
コマンドプロンプトを起動して、「powershell」と入力すると、プロンプトの先頭に「PS」が付く。これでPowerShellが使えるようになる。

イメージ 4

画面の見た目は違うが、どちらの方法でもできることに変わりはない。

では、実際にスクリプトを実行してみる。

初めてPowerShellスクリプトを実行すると、次のようなエラーが表示される。
イメージ 5

初期状態では、PowerShellの実行が許可されていないため、このようなエラーが表示される。「Get-ExecutionPolicy」と入力してみると「Restricted」となっているはずだ。

イメージ 6

そのため、初回のみ以下のコマンドを実行してPowerShellの実行を許可する必要がある。コマンドプロンプト(管理者)」を起動して、ここからPowerShellを起動後、下記のコマンドを入力する。
 Set-ExecutionPolicy RemoteSigned

イメージ 7

これによって、PowerShellスクリプトの実行が可能になる。
なお、「Set-ExecutionPolicy RemoteSigned」を入力した後に、

実行ポリシーの変更
実行ポリシーは、信頼されていないスクリプトからの保護に役立ちます。実行ポリシーを変更すると、about_Execution_Policies のヘルプ トピック (http://go.microsoft.com/fwlink/?LinkID=135170) で説明されているセキュリティ上の危険にさらされる可能性があります。実行ポリシーを変更しますか?
[Y] はい(Y)  [N] いいえ(N)  [S] 中断(S)  [?] ヘルプ (既定値は "Y"): 

というメッセージが表示される場合は、「Y」またはEnterキーを押すことで実行許可状態になる。

念のため「Get-ExecutionPolicy」と入力してみて「RemoteSigned」と表示されていればOKだ。

この状態で、スクリプトを実行してみる。
イメージ 8

スクリプトに問題がなければ、指定したイベントのサウンドが再生されるはずだ(もちろん、レジストリに登録された場所にWAVファイルが存在することが前提だが)。ちなみに上図の「PlaySystemSound.ps1」の1回目の実行時にあえてコマンドライン引数を入れずに実行しているが、ちゃんとデフォルトのサウンド(「一般の警告音」)が再生されていることが分かる。

これでスクリプトは完成したので、次に「ローカルグループポリシーエディター」にスクリプトを登録する。
「ローカルグループポリシーエディター」を起動するには、コマンドプロンプトまたは「ファイル名を指定して実行」にて「gpedit.msc」と入力する。

イメージ 10


サインインとサインアウト時に実行するスクリプトは、左側のペインの「ユーザーの構成」→「Windowsの設定」→「スクリプト(ログオン/ログオフ)」にて指定する。

イメージ 11

シャットダウン時に実行するスクリプトは、左側のペインの「コンピューターの構成」→「Windowsの設定」→「スクリプト(スタートアップ/シャットダウン)」にて指定する。

イメージ 12

右側のペインの該当項目をダブルクリックすると、下図のウィンドウが表示される。なお、以下の例ではシャットダウンのスクリプト設定を例にしているが、ログオン、ログオフも同じ手順で実行する。

イメージ 13

今回はPowerShellスクリプトを実行するので、上図ウィンドウの「PowerShell スクリプト」タブをクリック後、「追加」ボタンを押す。ちなみに間違って「スクリプト」タブ側にPowerShellスクリプトを登録すると、正しく実行されないのはもちろんのこと、場合によっては(エラーリトライを繰り返しているのか)制御が戻ってくるまでに相当待たされることになるので注意。

イメージ 14

スクリプト名(スクリプトの実行パス)および、スクリプトのパラメーター(コマンドライン引数)を入力して「OK」ボタンを押すと、下図のようにスクリプトが追加される。

イメージ 15

最後に、「OK」ボタンを押して設定を保存する。
同様の設定を「ログオン」「ログオフ」に対しても行う。

これで、Windowsのサインイン/サインアウト/シャットダウン時に、現在システムに設定されているイベントサウンドが再生されるようになった・・・はずなのだが、ここで一つ問題が。

サインインとサインアウトのサウンドは問題なく再生されるのだが、シャットダウン時にはサインアウト用のサウンドが再生されてシャットダウン用のサウンドが再生されない。サインアウト用のサウンドが再生されるのは何となく理解できる(シャットダウン時には最初にサインアウトしてからシステムをシャットダウンしているだろうから)のだが、シャットダウン時のサウンドが再生されない理由が分からない。推測するに、シャットダウンが早すぎてサインアウト時のサウンドを再生し終わった時点でシャットダウンが完了してしまっているのではないかと思うが、確証はない。

とりあえず、個人的な目的としてはサインイン時とシャットダウン時のサウンドさえ再生されれば問題ない(ログオフなど滅多にしない)ので、サインアウト用スクリプトコマンドライン引数も「SystemExit」にすることで凌ぐことにした。あくまでも「応急処置」的な対応だが、多分「恒久対応」化してしまうのだろう・・・。ま、いっか。

おしまい。