VB.NET/WPF で透明な追従ウィンドウを使った独自カーソルを作ると、タスク マネージャーを開いた瞬間だけマウスフックが効かなくなることがあります。この現象はアプリのバグではなく、Windows のセキュリティ機構 UIPI が原因です。この記事では、なぜフックが止まるのかを表を交えながら整理し、UIAccess を使った実践的な回避策と、代替設計まで詳しく解説します。
症状の概要とよくある再現パターン
まずは、今回の相談内容を整理します。
- VB.NET(WPF)のデスクトップアプリ
- 透明なフローティングウィンドウをマウス位置に追従させて、独自カーソルを表示
WH_MOUSE_LL(低レベル マウスフック)でSetWindowsHookExを使い、マウス移動イベントをグローバルに取得- 通常のアプリやデスクトップ上では問題なく動作
- しかし タスク マネージャー や デバイス マネージャー にフォーカスが移った瞬間、フックが効かなくなる
フックを解除しているわけでも例外が出ているわけでもないのに、イベントだけが来なくなります。これは「たまたまそういう仕様」ではなく、Windows のセキュリティモデル(整合性レベルと UIPI)がきっちり効いている結果です。
原因は UIPI と整合性レベル:マウスフックは「格上プロセス」を覗けない
Windows Vista 以降では、プロセスには「整合性レベル(Integrity Level)」という概念が導入されています。ざっくり言うと、「どれくらい信頼されたプロセスか」を段階で表したものです。
整合性レベルのざっくり整理
代表的な整合性レベルを表にまとめると次のようになります。
| 整合性レベル | 略称 | 代表的な例 |
|---|---|---|
| Low | 低 IL | サンドボックスされたブラウザタブ、一部の UWP アプリ など |
| Medium | 中 IL | 通常起動したデスクトップアプリ(VB.NET / WPF アプリもここ) |
| High | 高 IL | 管理者として実行したアプリ、タスク マネージャー、デバイス マネージャー など |
| System | システム IL | OS の重要コンポーネントやサービス |
通常ユーザーとして起動した VB.NET / WPF アプリは「中 IL」です。一方、タスク マネージャーやデバイス マネージャーは、管理者メンバーでサインインしている環境では自動的に「高 IL」で動作します。
UIPI のルール:「下から上」は触れない
整合性レベルとセットで登場するのが UIPI(User Interface Privilege Isolation) です。これは簡単に言うと、
- 整合性レベルの低いプロセスは、高いプロセスの UI や入力に干渉してはいけない
というルールです。ここでいう「干渉」には、次のようなものが含まれます。
- ウィンドウメッセージの送信(
SendMessage/PostMessage) - 入力の注入(
SendInputなど) - フックを使った入力監視(低レベルマウスフック / キーボードフックなど)
イメージしやすいように、「どの整合性レベルからどこまで触れるか」を簡易表で整理してみます。
| 送信元プロセス | 宛先 | 結果のイメージ |
|---|---|---|
| 中 IL | Low / 中 IL | 基本的に許可(UIPI 制限なし or 緩い) |
| 中 IL | 高 IL / システム IL | UIPI によりブロック(今回のケース) |
| 高 IL | Low / 中 / 高 IL | 原則可能(UIPI の制限を受けにくい) |
今回の VB.NET / WPF アプリは「中 IL」。タスク マネージャーは「高 IL」。そのため、
- 中 IL のアプリから高 IL のタスク マネージャーに対しては、低レベルマウスフックによる入力監視がブロックされる
という挙動になります。コードがどれだけ正しくても、このルールは変えられません。
なぜ「タスク マネージャー」「デバイス マネージャー」でだけ止まるのか
同じマウスカーソルでも、メモ帳やブラウザが前面の時には問題なくフックできるのに、タスク マネージャーに切り替えた瞬間だけイベントが来なくなる理由はここにあります。
- メモ帳や多くの一般アプリは「中 IL」
- タスク マネージャーやデバイス マネージャーは「高 IL」
そのため、
- 中 IL のアプリ → 中 IL のアプリ:フックできる
- 中 IL のアプリ → 高 IL のアプリ:フックできない(UIPI でブロック)
という挙動になります。
「タスク マネージャーが前面に来た瞬間だけ独自カーソルが止まる/透明ウィンドウが動かない」という場合は、ほぼ間違いなくこの整合性レベルの差が原因だと考えてよいでしょう。
対策の選択肢:3パターンを比較する
回避策として代表的なものは次の 3 つです。
- UIAccess アプリとして動作させる(推奨)
- アプリを常に「管理者として実行」する
- そもそも低レベルフックを使わない設計にする
それぞれの特徴をざっくり比較すると、次のようになります。
| 方法 | メリット | デメリット |
|---|---|---|
| UIAccess 化 | 高 IL のウィンドウも安全な範囲で扱える。一般配布にも比較的向く。 | コード署名や配置場所などの要件が厳しい。証明書コストがかかる。 |
| 管理者として実行 | 実装が簡単。UIAccess より理解しやすい。 | 常に UAC プロンプトが出る。一般ユーザー向け配布には不向き。 |
| ポーリングなどに変更 | UIPI の制限を受けにくい。署名などが不要。 | 入力注入はできない。取得できる情報も限定される。 |
以降では、特に現実解になりやすい UIAccess 化 を中心に詳しく解説します。
対策 1:UIAccess アプリとして動かす(推奨)
UIAccess とは何か
UIAccess は、もともとスクリーンリーダーなどの ユーザー補助(アクセシビリティ)アプリ のために用意された仕組みです。UIAccess として認められたアプリは、
- ユーザーのデスクトップ上の他プロセスのウィンドウや入力に対して、一定の範囲でアクセスすることが許可される
- 高 IL のウィンドウに対しても、メッセージ送信やフックなどが可能になる(セキュリティルールの範囲内)
これにより、中 IL のアプリであっても、適切に署名・配置された UIAccess アプリであれば、タスク マネージャーなど高 IL のウィンドウが前面にあっても、低レベルマウスフックが機能するようになります。
UIAccess を有効にするための 3 つの必須条件
UIAccess を有効化するには、次の 3 つを すべて 満たす必要があります。どれか 1 つでも欠けると、uiAccess="true" は 完全に無視 され、通常の中 IL アプリとして扱われます。
| 条件 | 概要 | 補足 |
|---|---|---|
| 1. コード署名 | EXE および同梱 DLL すべてにコード署名を行う。 | テストは自己署名でもよいが、本番は信頼された CA の証明書推奨。 |
| 2. マニフェスト設定 | requestedExecutionLevel に uiAccess="true" を指定。 | 通常は level="asInvoker" のままで OK。 |
| 3. 配置場所 | 保護されたフォルダー(Program Files 等)にインストール。 | インストール自体は管理者権限が必要。 |
マニフェストの設定(Visual Studio からの手順)
まずはマニフェストに UIAccess の設定を追加します。
- Visual Studio でプロジェクトを開く
- プロジェクト > プロパティ > アプリケーション を開く
- 「アプリケーション マニフェスト」 を追加する(既にある場合はそれを編集)
- マニフェスト内の
requestedExecutionLevelを次のように修正
<requestedExecutionLevel level="asInvoker" uiAccess="true" />
ここで注意したいのは、
requireAdministratorにしてしまうと、常に UAC 昇格が必要になり、UIAccess の「中 IL で高 IL に触れる」という利点が薄れるasInvokerのままでuiAccess="true"を付与するのが基本形
という点です。
コード署名のポイント
次に、EXE / DLL にコード署名を行います。代表的な流れは以下の通りです。
- コード署名証明書を用意する
- テスト用:自己署名証明書(テスト環境に信頼ルートとして登録)
- 本番用:認証局(CA)から購入したコードサイニング証明書
signtool.exeを使って EXE / DLL すべてに署名する
署名コマンドのイメージは次のような形です(実際のオプションは環境に合わせて調整してください)。
signtool sign /fd SHA256 /tr http://timestamp.example.com /td SHA256 ^
/f YourCodeSignCert.pfx /p パスワード ^
YourApp.exe
ポイントは、
- タイムスタンプ(
/tr)を必ず付与しておく(証明書期限切れ後も署名の有効性を保つため) - EXE だけでなく、自作 DLL やプラグイン DLL も漏れなく署名する
といった部分です。署名されていないファイルが混ざっていると UIAccess の要件を満たさなくなる可能性があります。
配置場所:保護されたフォルダーにインストール
最後に、アプリの配置場所です。UIAccess アプリは、
C:\Program FilesC:\Program Files (x86)C:\Windows\System32(特別な場合)
といった 保護されたフォルダー に置かれている必要があります。開発中によくあるのが、
bin\Debugフォルダーからそのまま実行して動作確認してしまう
というパターンですが、この状態では UIAccess の条件を満たさないため、いくら uiAccess="true" を書いても まったく効きません。
実際の確認では、
- インストーラー(MSI や Setup プロジェクトなど)で Program Files 配下にインストール
- インストール後の EXE が署名されていることを確認
- その EXE を実行してタスク マネージャー上で挙動を確認
という流れでテストするのがおすすめです。
UIAccess 化したあとに確認すべきこと
UIAccess の条件を満たしているかどうか、手っ取り早く確認する方法としては、次のようなものがあります。
- タスク マネージャーを前面にしても、独自カーソルの追従が止まらないか
- 低レベルマウスフックのコールバックが継続して呼ばれているか
また、プロセスモニタリングツールを使えば、プロセスの整合性レベル(IL)や UIAccess フラグが確認できることもあります。開発中はこうしたツールで実際のトークン情報を眺めてみると理解が深まります。
対策 2:アプリ自体を「管理者として実行」する
もっとも単純な回避策は、アプリを起動するときに 常に管理者として実行する ことです。プロセスが「高 IL」になれば、タスク マネージャーと同じ整合性レベルになりますから、UIPI による「下から上へのアクセス禁止」に引っかからなくなります。
方法
- ショートカットのプロパティから「管理者としてこのプログラムを実行する」にチェック
- マニフェストの
requestedExecutionLevelをrequireAdministratorに変更
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
メリットとデメリット
メリットは、
- UIAccess に比べて理解しやすく、実装も簡単
- コード署名が必須ではない(ただし実務的には署名しておいたほうがよい)
一方、デメリットはかなり大きく、
- 起動のたびに UAC プロンプトが表示される
- 企業環境ではセキュリティポリシー上、そもそも管理者実行が禁止されている場合がある
- ユーザーが「知らないうちに権限の高いアプリを常用している」状態になり、運用上のリスクが高い
といった点が挙げられます。社内ツールとして限定配布する場合など、運用がコントロールしやすい場面では選択肢になり得ますが、一般配布するアプリでは基本的に避けた方が無難です。
対策 3:低レベルマウスフックを使わない設計にする
用途によっては、そもそも低レベルマウスフックを使わない設計に切り替えることで、UIPI の制限を回避できる場合があります。
GetCursorPos をタイマーでポーリングする
「やりたいことはカーソル位置に透明ウィンドウを追従させるだけ」であれば、
GetCursorPosを一定間隔(例:16ms〜33ms)で呼び出す- 取得した座標に WPF ウィンドウを移動させる
というポーリング方式でも十分に実現可能です。
この方式の特徴は次の通りです。
| 項目 | 内容 |
|---|---|
| メリット | フックのようなメッセージ注入を行わないため、UIPI によるブロックの影響を受けにくい。 |
| デメリット | 「ボタンが押された瞬間」などのイベントをトリガーにするのには向かない。あくまで位置追跡が中心。 |
WPF であれば、DispatcherTimer や CompositionTarget.Rendering イベントを使って一定間隔でカーソル位置を取得し、ウィンドウの Left / Top を更新する、といった実装が現実的です。
Raw Input(WM_INPUT)を検討する場合
より低レベルな入力取得方法として、RegisterRawInputDevices と WM_INPUT を使う方法もあります。これにより、特定のデバイス(マウス / キーボード)からの入力をアプリが直接受け取ることができます。
ただし、
- UIPI やフォアグラウンドウィンドウの状況に依存する部分がある
- マルチマウス / 特殊デバイスの扱いなど、検証すべきケースが増える
といった理由から、「確実に Task Manager 上でも動作させたい」という要件には、結局 UIAccess や管理者実行の方が現実解になるケースが多いです。Raw Input を選ぶ場合は、ターゲット環境での徹底的な実機検証が必須になります。
VB.NET / WPF 実装のポイントとチェックリスト
ここからは、VB.NET / WPF の実装視点で、押さえておきたいポイントを整理します。
透明ウィンドウで独自カーソルを描画する基本
WPF で独自カーソルを表示する場合、次のようなウィンドウ設定をよく使います。
WindowStyle="None"AllowsTransparency="True"Background="Transparent"- マウスイベントを透過させるための
IsHitTestVisible="False"指定(ルート要素側)
このウィンドウを低レベルマウスフックまたは GetCursorPos ポーリングで追従させる構成です。
低レベルマウスフックを使う場合、VB.NET からの P/Invoke 宣言の例は以下のようなイメージになります。
<DllImport("user32.dll", SetLastError:=True)>
Private Shared Function SetWindowsHookEx(
idHook As Integer,
lpfn As LowLevelMouseProc,
hMod As IntPtr,
dwThreadId As UInteger) As IntPtr
End Function
ここで idHook に WH_MOUSE_LL を指定し、コールバック内で座標を取得して WPF 側に渡す、という形です。(詳細なコードは環境や設計により大きく変わるため割愛します。)
実装チェックリスト(UIAccess を前提)
UIAccess を前提にした実装を行う際のチェックリストを表にまとめます。リリース前に一つずつ潰していくと、トラブルを減らせます。
| チェック項目 | 内容 | 確認状況 |
|---|---|---|
| マニフェスト設定 | requestedExecutionLevel level="asInvoker" uiAccess="true" が設定されている。 | ☐ |
| コード署名証明書 | テスト/本番用の証明書を準備し、有効期限を管理している。 | ☐ |
| EXE / DLL への署名 | アプリ本体と同梱 DLL 全てに署名済みである。 | ☐ |
| インストール先 | Program Files 等の保護フォルダーにインストールされている。 | ☐ |
| テスト環境 | 開発環境だけでなく、クリーンなテスト端末でも動作確認を行った。 | ☐ |
| タスク マネージャーでの挙動 | タスク マネージャーを前面にしても独自カーソルが止まらないことを確認。 | ☐ |
| 企業ポリシー | ドメイン環境ではグループポリシーで UIAccess が許可されていることを確認。 | ☐ |
よくあるつまずきポイントと対処法
「uiAccess=true を付けたのに効いていない」場合
もっとも多いのが、「マニフェストには書いたのに挙動が変わらない」というケースです。主な原因は次のいずれかであることが多いです。
- EXE にコード署名がされていない
- 自己署名証明書を使っているが、信頼されたルートとして登録されていない
- インストール先が Program Files ではなく、ユーザープロファイル配下になっている
- Visual Studio からデバッグ実行しており、実際に走っているのは
bin\Debugの EXE
この場合の対処としては、
- リリースビルドを作成
- リリース EXE / DLL に署名
- インストーラーで Program Files にインストール
- インストールされた先の EXE を直接ダブルクリックして起動
という手順で再確認してみてください。
「特定の端末だけ動かない」場合
企業ネットワークや学校などの管理下にある PC では、グループポリシーによって UIAccess の動作が制限されていることがあります。この場合、
- 同じバージョンの Windows でも、端末によって UIAccess アプリの挙動が違う
- 管理者が用意した証明書ストアやアプリケーションホワイトリストに依存する
といった状況が起こり得ます。社内配布を前提とする場合は、情報システム部門と連携し、
- 使用するコードサイニング証明書を共有
- 必要に応じてグループポリシーの設定変更を依頼
といった調整を事前に行っておくことが重要です。
「セキュリティ的に大丈夫か?」という疑問
UIAccess は本来、視覚障害者向けのスクリーンリーダーなど ユーザー補助アプリ を想定した仕組みです。そのため、UIAccess を要求するアプリは、
- 署名付きで配布されていること
- 信頼された場所にインストールされていること
- 不要な権限行使をしていないこと
が前提となります。マウスカーソルの拡大表示や、クリック可視化など ユーザー補助に近い用途 であれば比較的受け入れられやすいですが、
- 高 IL のウィンドウに対して勝手にクリックを注入する
- パスワード入力欄を監視する
といった設計は当然ながら望ましくありません。仕様検討の段階で、「どこまでの操作を許すのか」をチーム内で明確に決めておくことをおすすめします。
証明書運用・ビルドパイプラインでの工夫
UIAccess を長期運用する場合、証明書や署名の運用をきちんと設計しておくとトラブルを減らせます。
- コードサイニング証明書の有効期限をカレンダーやタスク管理ツールで管理
- ビルド後イベントや CI/CD パイプラインに
signtoolを組み込み、自動署名する - テスト環境と本番環境で証明書を分ける(誤配布防止)
証明書の失効・更新を怠ると、新しいビルドだけ UIAccess 要件を満たさなくなる、といった事故が起こりがちです。ビルドスクリプト内で署名エラーを検出したらビルドを失敗させるようにしておくと安心です。
どの対策を選ぶべきか:用途別の指針
最後に、用途別にどの対策が向いているかを整理します。
| 用途・前提 | 推奨される対策 | コメント |
|---|---|---|
| 一般ユーザー向けの配布アプリ | UIAccess 化(対策 1) | UAC プロンプトを増やさずに高 IL ウィンドウを扱える。署名コストはかかるが、ユーザー体験と安全性のバランスが良い。 |
| 社内限定ツール(管理者ユーザーのみ使用) | UIAccess または 管理者実行(対策 2) | 運用しやすい方を選択。セキュリティポリシーとの整合性を優先。 |
| カーソル位置の追従だけが目的 | ポーリング方式(対策 3) | クリック可視化・カーソル拡大などであれば、低レベルフックを使わずに実現できるケースも多い。 |
| 入力注入や自動操作が主目的 | UIAccess または 高 IL アプリ | そもそも要件自体がセキュリティにシビアなので、設計段階でリスク評価が必須。 |
まとめ
VB.NET / WPF アプリで低レベルマウスフックを使い、タスク マネージャーやデバイス マネージャーが前面になるとフックが効かなくなる問題は、
- Windows の整合性レベル(中 IL と高 IL)の違い
- UIPI による「下位プロセスから上位プロセスへの UI 干渉禁止」
が原因で発生している、いわば「正しい挙動」です。コードをいくら直しても、このルールそのものを変えることはできません。
実用的な回避策としては、
- コード署名+マニフェスト+インストール場所を整えて UIAccess アプリとして動作させる
- 用途に応じて、「管理者として実行」やポーリング方式などの設計変更を検討する
といったアプローチになります。特に、ユーザー補助的な用途で高 IL のウィンドウにも対応したい場合は、UIAccess 化が最もバランスの取れた解と言えるでしょう。
独自カーソルや入力系ユーティリティを実装する際には、「なぜタスク マネージャーでだけ動かなくなるのか?」という疑問をきっかけに、UIPI と整合性レベルの仕組みを一度しっかり整理しておくと、今後のトラブルシューティングが格段に楽になります。

コメント