WSL やターミナル環境から Windows のネイティブ通知・タスクバー連携を行う方法をまとめる。
WSL から powershell.exe を呼び出し、WinRT の Toast Notification API を使う方法。Windows のアクションセンターにバナーが表示される。
powershell.exe -NoProfile -Command "
[Windows.UI.Notifications.ToastNotificationManager,
Windows.UI.Notifications,
ContentType = WindowsRuntime] | Out-Null;
\$xml = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent(1);
\$nodes = \$xml.GetElementsByTagName('text');
\$nodes.Item(0).AppendChild(\$xml.CreateTextNode('Title'));
\$nodes.Item(1).AppendChild(\$xml.CreateTextNode('Body'));
\$toast = [Windows.UI.Notifications.ToastNotification]::new(\$xml);
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('WSL').Show(\$toast)
"| 処理 | 説明 |
|---|---|
ContentType = WindowsRuntime |
WinRT 型を PowerShell セッションにロードする指定 |
GetTemplateContent(1) |
ToastText02(タイトル+本文)テンプレートの XML を取得。0 で ToastText01(本文のみ) |
CreateToastNotifier('WSL') |
引数は AppUserModelID(AUMID)。通知のグループ化に使われる |
win_notify() {
local title="${1:-Notification}"
local body="${2:-}"
powershell.exe -NoProfile -Command "
[Windows.UI.Notifications.ToastNotificationManager,
Windows.UI.Notifications,
ContentType = WindowsRuntime] | Out-Null;
\$t = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent(1);
\$n = \$t.GetElementsByTagName('text');
\$n.Item(0).AppendChild(\$t.CreateTextNode('$title'));
\$n.Item(1).AppendChild(\$t.CreateTextNode('$body'));
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('WSL').Show(
[Windows.UI.Notifications.ToastNotification]::new(\$t))
"
}
# 使用例
make build && win_notify "Build Complete" "All tests passed" \
|| win_notify "Build Failed" "Check output for errors"powershell.exeのプロセス起動コストがあるため高頻度の呼び出しには不向き- 引数にシングルクォートや特殊文字を含む場合はエスケープが必要
Windows 側でビルドする .NET コンソールアプリケーション。イベントハンドリングやカスタム XML による高度な通知が可能。
dotnet new console -n WslToast
cd WslToast<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0-windows10.0.17763.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>-windows10.0.17763.0 は TFM の Windows 固有サフィックスで、WinRT API(Windows.UI.Notifications)への直接アクセスを有効にする。内部的には CsWinRT が .NET と WinRT の間を橋渡しする。
using Windows.UI.Notifications;
using Windows.Data.Xml.Dom;
var title = args.Length > 0 ? args[0] : "Notification";
var body = args.Length > 1 ? args[1] : "";
var appId = args.Length > 2 ? args[2] : "WSL";
var xml = ToastNotificationManager.GetTemplateContent(
ToastTemplateType.ToastText02);
var textNodes = xml.GetElementsByTagName("text");
textNodes[0].AppendChild(xml.CreateTextNode(title));
textNodes[1].AppendChild(xml.CreateTextNode(body));
var toast = new ToastNotification(xml);
toast.Activated += (_, _) => Console.WriteLine("Toast clicked");
toast.Dismissed += (_, e) => Console.WriteLine($"Dismissed: {e.Reason}");
toast.Failed += (_, e) => Console.WriteLine($"Failed: {e.ErrorCode}");
ToastNotificationManager
.CreateToastNotifier(appId)
.Show(toast);
Thread.Sleep(500); // イベント受信用。不要なら削除可using Windows.UI.Notifications;
using Windows.Data.Xml.Dom;
var xmlString = @"
<toast activationType='foreground'>
<visual>
<binding template='ToastGeneric'>
<text>Deploy to production?</text>
<text>main branch, commit abc1234</text>
</binding>
</visual>
<actions>
<action content='Deploy' activationType='foreground' arguments='deploy'/>
<action content='Cancel' activationType='foreground' arguments='cancel'/>
</actions>
</toast>";
var xml = new XmlDocument();
xml.LoadXml(xmlString);
var toast = new ToastNotification(xml);
toast.Activated += (_, args) =>
{
if (args is ToastActivatedEventArgs a)
Console.WriteLine($"Action: {a.Arguments}");
};
ToastNotificationManager
.CreateToastNotifier("WSL")
.Show(toast);
Thread.Sleep(30_000); // ユーザー操作待ち/mnt/c/path/to/WslToast.exe "Build Complete" "All tests passed"追加の依存なしにターミナルから直接使える。Windows Terminal が OSC シーケンスを解釈して Windows API を呼び出す仕組み。
タスクバーアイコン上にプログレスバーを表示する。内部的に ITaskbarList3::SetProgressState / SetProgressValue が呼ばれる。
フォーマット: ESC ] 9 ; 4 ; <state> ; <value> ST
| state | 表示 | 対応する COM 定数 |
|---|---|---|
| 0 | 非表示(クリア) | TBPF_NOPROGRESS |
| 1 | 通常(緑) | TBPF_NORMAL |
| 2 | エラー(赤) | TBPF_ERROR |
| 3 | 警告(黄) | TBPF_PAUSED |
| 4 | 不確定(パルス) | TBPF_INDETERMINATE |
# 進捗 50%
printf '\e]9;4;1;50\e\\'
# 完了
printf '\e]9;4;1;100\e\\'
# エラー表示
printf '\e]9;4;2;100\e\\'
# クリア
printf '\e]9;4;0;0\e\\'
# 不確定(所要時間不明の処理向き)
printf '\e]9;4;4;0\e\\'Windows Terminal が非フォーカス時にタスクバーアイコンを点滅させる。アクションセンターには表示されない。
printf '\e]9;9;"Build finished"\e\\'notify() {
printf '\e]9;9;"%s"\e\\' "${1:-Done}"
}
progress() {
local state="${1:-1}" pct="${2:-0}"
printf '\e]9;4;%d;%d\e\\' "$state" "$pct"
}
progress_clear() {
printf '\e]9;4;0;0\e\\'
}
done_notify() {
local rc=$?
if [ $rc -eq 0 ]; then
progress 1 100
notify "Success"
else
progress 2 100
notify "Failed (exit $rc)"
fi
(sleep 3 && progress_clear) &
}
# 使用例
cargo build --release ; done_notifybuild_with_progress() {
local total=5 current=0
for step in configure compile link test install; do
current=$((current + 1))
pct=$((current * 100 / total))
printf '\e]9;4;1;%d\e\\' "$pct"
echo "[$pct%] $step..."
# 実際のビルド処理
done
printf '\e]9;4;0;0\e\\'
}tmux 内では OSC シーケンスが外側のターミナルに届かないため、パススルーが必要。
printf '\ePtmux;\e\e]9;4;1;50\e\e\\\e\\'Windows Terminal は全タブの状態を集約してタスクバーに表示する。優先順位はエラー(赤) > 通常(緑) > 不確定(パルス)の順。
| PowerShell | C# (.NET) | OSC シーケンス | |
|---|---|---|---|
| 通知の種類 | Toast(アクションセンター) | Toast(アクションセンター) | タスクバー点滅・プログレス |
| 追加依存 | なし(WSL 標準) | .NET SDK + ビルド | なし(Windows Terminal のみ) |
| 起動コスト | 高(ps プロセス) | 中(.NET ランタイム) | なし |
| ボタン等の対話 | 可(XML カスタマイズ) | 可(イベントハンドリング付き) | 不可 |
| プログレスバー | 不可 | ITaskbarList3 で可能 | OSC 9;4 で対応 |
| 用途 | 手軽なワンショット通知 | 高度な通知アプリ | ターミナル内完結の軽い通知 |