シェルスクリプト&PowerShell Advent Calendar 2024 の 15日目 の記事だ。
ついでに、 Windows Advent Calendar 2024 15日目 の記事としても登録させてもらっている。
電子タイムカードの打刻など、毎日出勤時に PC で特定の Web サイトを操作する事を強要されることは無いだろうか?
その作業習慣が血肉となるまで魂に刻み込まれるまで飼い慣らされていないと、頻繁に操作を忘れてしまう。
普段 PC をシャットダウンしているのであれば、スタートアップにその Web サイトの URL を登録しておくことで、 PC ログイン時に自動的にそのページが立ち上げられるので、やり忘れを防止できる。
しかし、普段 PC をスリープしっぱなしな運用にしている場合、朝初めてPCを立ち上げ(スリープ解除)した際に、特定のページを開かせるのは意外と難しい。
そこで、 PC のタスクトレイに PC のロック解除イベントを監視する PowerShell スクリプトをタスクトレイに常駐させ、「その日最初のロック解除」の時に特定の URL を開く仕組みを作成してみた。
導入手順
以下のファイルを、 ファイル名の拡張子を .jse
にして、 BOM付きUTF-16 の文字コードで、 Windows のスタートアップフォルダ 1 に保存する。
'\'>$null;'; var WshShell = WScript.CreateObject("WScript.Shell"); var fso = WScript.CreateObject("Scripting.FileSystemObject"); WshShell.CurrentDirectory = fso.GetParentFolderName(WScript.ScriptFullName); WshShell.Environment("Process").item("SCRIPT_PATH") = WScript.ScriptFullName; WshShell.Run("powershell.exe -NoL -Sta -NoP -Win Hidden \"&{gc -li $env:SCRIPT_PATH -Raw -Enc Unicode|iex}\"", 0); /* '>$null;
<###############################################################
以下のタイミングで特定の URL を起動させる機能をタスクトレイに常駐させるPowerShell スクリプト。
* スクリプト起動時
* スクリプト起動セッション内の毎日一回目のロック解除時
1行目は WSH JScript スクリプトとなっていて、2行目以降を PowerShell v5 スクリプトと解釈して実行する。
このため、このファイルをダブルクリックしたり、スタートアップに置くだけで実行できる。
###############################################################>
#Requires -Version 5
$SessionVariable = [PSCustomObject][ordered]@{
LastLogon = [System.DateTimeOffset]::MinValue;
# 実行する URL 等
# Start-Process で起動するため、 URL 以外でも Windows のシェルが認識できるコマンドなら何でも指定できる。
InvokeUris = [string[]]@(
'https://www.yahoo.co.jp/';
);
# 日付変更と見なす時刻
TodayStartTime = '04:00:00';
# アイコンのツールチップテキスト
ToolHintText = $null;
};
$OnUnlockScript = {
$now = [System.DateTimeOffset]::Now;
$todayStart = $now - $now.TimeOfDay + $SessionVariable.TodayStartTime;
if ($todayStart -ge $SessionVariable.LastLogon) {
$SessionVariable.InvokeUris | %{
Start-Process $_;
};
}
$SessionVariable.LastLogon = $now;
};
<###############################################################
タスクトレイに常駐し、クリックされると特定のスクリプトを実行するコード
<https://aquasoftware.net/blog/?p=1244>
###############################################################>
# アイコンの色。
# 同じ色を指定した場合、同時に起動できなくなる。
# ARGB<31-24> (不透明度) は無視される
# ARGB<23-16> は R
# ARGB<15-8> は G
# ARGB<7-0> は B
[uint32]$ARGB = 0x00007F3F;
Add-Type -AssemblyName System.Windows.Forms;
# PowerShellっぽい アイコン画像バイナリ。
# 0x3f から 3バイト (PNG の PLTE チャンク) に、 RGB の順番で入っている背景色の色情報を書き換える。
$mems = New-Object System.IO.MemoryStream(,[System.Convert]::FromBase64String('AAABAAEAEBAQAAEABAB4AAAAFgAAAIlQTkcNChoKAAAADUlIRFIAAAAQAAAAEAEDAAAAJT1tIgAAAAZQTFRFAAB/////8DxOgwAAAC1JREFUCNdjYGBgYGFg4GNgYGdgYG5gYGxgYHgARcwHQIJyDAwWNQwGNQxgAACDjAYG7YuK+QAAAABJRU5ErkJggg=='));
$mems.Seek(0x3f, [System.IO.SeekOrigin]::Begin) > $null;
$ARGB = $ARGB -band 0x00ffffff;
$mems.WriteByte($ARGB -shr 16 -band 0xff);
$mems.WriteByte($ARGB -shr 8 -band 0xff);
$mems.WriteByte($ARGB -band 0xff);
$mems.Seek(0x0, [System.IO.SeekOrigin]::Begin) > $null;
$icon = New-Object System.Drawing.Icon($mems);
$MUTEX_NAME = '866cff49-a2fc-4f0f-ae6c-6db049b28d7a: ' + [System.BitConverter]::ToString([System.BitConverter]::GetBytes($ARGB));
$mutex = New-Object System.Threading.Mutex($false, $MUTEX_NAME);
try {
# 多重起動チェック
if ($mutex.WaitOne(0, $false)) {
try {
# 初回起動時動作
&$OnUnlockScript;
# コンテキスト作成
$appContext = New-Object System.Windows.Forms.ApplicationContext;
# 通知アイコン作成
$notifyIcon = [System.Windows.Forms.NotifyIcon]@{
Icon = $icon;
Text = $ToolHintText;
BalloonTipIcon = 'Error';
BalloonTipTitle = 'Error';
};
# アイコン左クリック時
$notifyIcon.add_Click({
if ($_.Button -eq [System.Windows.Forms.MouseButtons]::Left) {
$notifyIcon.BalloonTipText = '終了時は右クリックメニューを使用してください';
$notifyIcon.ShowBalloonTip(5000);
}
});
# Exit メニュー
$menuItem = [System.Windows.Forms.ToolStripMenuItem]@{ Text = 'Exit' };
$notifyIcon.ContextMenuStrip = New-Object System.Windows.Forms.ContextMenuStrip;
[void]$notifyIcon.ContextMenuStrip.Items.Add($menuItem);
$menuItem.add_Click({
$appContext.ExitThread();
});
# ロック解除時動作
[Microsoft.Win32.SystemEvents]::Add_SessionSwitch({
param($sender, [Microsoft.Win32.SessionSwitchEventArgs]$e);
if ($e.Reason -eq [Microsoft.Win32.SessionSwitchReason]::SessionUnlock) {
&$OnUnlockScript;
}
});
# アイコンの表示
$notifyIcon.Visible = $true;
[void][System.Windows.Forms.Application]::Run($appContext);
$notifyIcon.Visible = $false;
} finally {
$notifyIcon.Dispose();
$mutex.ReleaseMutex();
}
$retcode = 0;
} else {
$retcode = 255;
}
} finally {
$mutex.Dispose();
}
exit $retcode;
# JScript として解釈した時にエラーとならないように、末尾に以下が必要
# */
InvokeUris プロパティの文字列配列に、ロック解除時に開きたい URL やプログラムを 1つ 以上記述しよう。
わざわざタスクトレイに常駐させているのは、アプリケーションコンテキストを回す等してウィンドウメッセージを処理させないと、ロック解除の検知に使っている Microsoft.Win32.SystemEvents クラス でイベントが発生しないためだ。
タスクトレイへのアイコンの登録は、当ブログの以下の記事を参考にしている。
また、ファイルをスタートアップにファイルを一つ置くだけでコンソールウィンドウ無しの PowerShell 起動を実現するため、 JScript に偽装した Polyglot にしている。
それについての解説も、当ブログの以下の記事を参照のこと。
JScript への偽装を避けたい場合
もし JScript に埋め込む方法が、気持ち悪かったり組織のセキュリティポリシーに引っかかる場合は、ファイルの拡張子を *.ps1
にリネームしてショートカット経由で起動させよう。
具体的には、 *.ps1
ファイルをスタートアップディレクトリ以外に移動させ、替わりにスタートアップディレクトリには以下のようなリンク先のショートカットを作成しよう。
それにより、 PowerShell の仕組みの範囲内でウィンドウの作成を抑制した状態でタスクトレイに常駐させられる。
powershell.exe -NoL -Sta -NoP -Win Hidden -ExecutionPolicy RemoteSigned "<*.ps1 へのフルパス>"
但しこのショートカットを使った方法だと、 Set-ExecutionPolicy
で署名されていないスクリプトの実行を許可しておく 必要があったり、ショートカットが起動する一瞬だけコンソールウィンドウが表示されることには注意だ。
おわりに
「日付変更」と見なす時間は 4:00 にしているので、 0:00 過ぎまでお仕事する皆さんへの配慮もバッチリだ。
各自よいように改造して快適なしゃちくライフを!
-
エクスプローラのアドレスバーに
shell:startup
を入力すると、ユーザーのスタートアップフォルダが開く ↩