VB.NET から Windows を再起動・シャットダウンするとき、ExitWindowsEx の dwReason を正しく指定できているか自信が持てない……というケースは意外と多いです。本記事では、特に「その他(計画済み)」として再起動させるための理由コードの指定方法を、VB.NET の型や数値リテラルの落とし穴まで含めて丁寧に解説します。
ExitWindowsEx と dwReason の基本を整理する
まずは、今回の主役である Win32 API ExitWindowsEx の全体像を整理します。VB.NET から P/Invoke で呼び出す前提で、パラメーターの意味と役割を押さえておきましょう。
| パラメーター名 | 型 | 概要 |
|---|---|---|
uFlags | UINT(VB.NET では UInteger) | 再起動・シャットダウン・サインアウトなどの動作を指定するフラグ。 |
dwReason | DWORD(VB.NET では UInteger) | シャットダウン/再起動の「理由」をビットフラグで指定する。 |
多くのサンプルコードでは dwReason に 0 を入れがちですが、サーバー運用やログ管理にこだわると、この理由コードをきちんと設定しておくことが重要になります。イベントログ上で「どんな理由で再起動されたのか」を判別できるからです。
「その他(計画済み)」で再起動したいケース
Windows Server やクライアント OS では、GUI からシャットダウン/再起動する際に「理由」を求められることがあります。たとえば次のような選択肢です。
- ハードウェア:メンテナンス (計画済み)
- オペレーティング システム:構成の変更 (計画済み)
- アプリケーション:インストール (計画済み)
- その他 (計画済み)
本記事で目指すのは、このうちの「その他(計画済み)」をプログラムから指定することです。つまり、イベントログ上で「Planned(計画済み)」扱いになり、かつ「Other(その他)」として記録されるように dwReason を組み立てることがゴールになります。
dwReason のフラグ構造を理解する
dwReason は「1つの数値」ですが、中身は複数のビットフラグを OR 結合したものです。概ね以下の 3 種類のグループから成り立っています。
- メジャー理由(
SHTDN_REASON_MAJOR_*) - マイナー理由(
SHTDN_REASON_MINOR_*) - フラグ(
SHTDN_REASON_FLAG_*)
| 分類 | 例 | ビット位置のイメージ |
|---|---|---|
| メジャー理由 | SHTDN_REASON_MAJOR_OPERATINGSYSTEM など | 上位 16bit の一部 |
| マイナー理由 | SHTDN_REASON_MINOR_UPGRADE など | 下位 16bit の一部 |
| フラグ | SHTDN_REASON_FLAG_PLANNED など | 最上位ビット(0x80000000)など |
「その他(計画済み)」を指定したい場合に関係する定数は次の 3 つです。
SHTDN_REASON_MAJOR_OTHERSHTDN_REASON_MINOR_OTHERSHTDN_REASON_FLAG_PLANNED
そして、実はこのうち SHTDN_REASON_MAJOR_OTHER と SHTDN_REASON_MINOR_OTHER は、どちらも値が 0x00000000(ゼロ)です。そのため、実際にビットとして意味を持つのは SHTDN_REASON_FLAG_PLANNED だけとなります。
まとめると、「その他(計画済み)」で再起動したい場合の dwReason は、次の式と同じ結果になります。
SHTDN_REASON_MAJOR_OTHER Or
SHTDN_REASON_MINOR_OTHER Or
SHTDN_REASON_FLAG_PLANNED ' => 実質 0x80000000
つまり、dwReason = &H80000000UI(UInt32 の最上位ビットが 1)であれば目的は達成できる、ということになります。
VB.NET での型と数値リテラルの落とし穴
ここからが VB.NET 特有のハマりポイントです。C 言語の 0x80000000 をそのまま VB.NET に持ち込むと、符号付き/符号なしの違いにより挙動が変わってしまう可能性があります。
Integer と UInteger の違い
VB.NET の Integer は 32bit の符号付き整数、UInteger は 32bit の符号なし整数です。
| 型 | ビット数 | 範囲 | 備考 |
|---|---|---|---|
Integer | 32bit | -2,147,483,648 ~ 2,147,483,647 | 符号付き。0x80000000 は負数になる。 |
UInteger | 32bit | 0 ~ 4,294,967,295 | 符号なし。0x80000000 も正の数として扱える。 |
0x80000000 は十進数にすると 2,147,483,648 です。これは Integer の 最大値を 1 だけ超えているため、Integer 型でそのまま表現しようとするとオーバーフロー扱いになったり、負数として扱われてしまいます。
そのため、Win32 API の DWORD や UINT を扱うときは、VB.NET では基本的に UInteger を使うのが安全です。
&H80000000 と UI サフィックス
VB.NET で 16 進リテラルを書くときは &H を使いますが、型サフィックスを付けないと Integer として評価されてしまうことがあります。
| 書き方 | 評価される型 | 意味 |
|---|---|---|
&H80000000 | Integer(環境による) | オーバーフローまたは負数として扱われる可能性。 |
&H80000000UI | UInteger | 0x80000000 を正しい 32bit 符号なし整数として扱える。 |
必ず UI サフィックスを付けて &H80000000UI と書くようにしましょう。こうすることでコンパイラに「これは UInteger だよ」と明示できます。
VB.NET から ExitWindowsEx を呼び出す最小コード
ここまでのポイントを踏まえた上で、「その他(計画済み)」理由で Windows を再起動する VB.NET の最小サンプルコードを示します。
Imports System.Runtime.InteropServices
' --- P/Invoke 宣言 ---
<DllImport("user32.dll", SetLastError:=True)>
Private Shared Function ExitWindowsEx(uFlags As UInteger, dwReason As UInteger) As Boolean
End Function
' --- EWX フラグ ---
Public Const EWX_REBOOT As UInteger = &H2UI ' 再起動
' --- 理由コード ---
Public Const SHTDN_REASON_MAJOR_OTHER As UInteger = &H0UI
Public Const SHTDN_REASON_MINOR_OTHER As UInteger = &H0UI
Public Const SHTDN_REASON_FLAG_PLANNED As UInteger = &H80000000UI
Public Sub RebootPlannedOther()
Dim reason As UInteger =
SHTDN_REASON_MAJOR_OTHER Or
SHTDN_REASON_MINOR_OTHER Or
SHTDN_REASON_FLAG_PLANNED ' => 0x80000000
Dim ok = ExitWindowsEx(EWX_REBOOT, reason)
If Not ok Then
Dim err = Runtime.InteropServices.Marshal.GetLastWin32Error()
' 例:1314 = 必要な特権がありません(SeShutdownPrivilege)
' ここでログ出力やエラーメッセージを表示するなどの処理を行う
End If
End Sub
コードのポイント解説
uFlagsとdwReasonはどちらもUIntegerで宣言。EWX_REBOOTは&H2UIとしてUIntegerに。SHTDN_REASON_FLAG_PLANNEDは&H80000000UIとして最上位ビットを立てる。SetLastError:=Trueを付けることでMarshal.GetLastWin32Error()でエラーコードを取得可能。
この最小コードをベースに、実環境ではログ出力やユーザーへの確認ダイアログなどを追加すれば、実務でも十分に通用する再起動処理になります。
SeShutdownPrivilege(シャットダウン特権)の罠
ExitWindowsEx を呼ぶときにもう一つ重要なのが、プロセスに SeShutdownPrivilege が付与されているかどうかです。これが無い状態で実行すると、関数は False を返し、GetLastError の結果が 1314(ERROR_PRIVILEGE_NOT_HELD) になります。
一番簡単な対策:アプリを「管理者として実行」する
もっとも手軽な方法は、アプリケーション自体を「管理者として実行」することです。UAC が有効な環境では、通常ユーザー権限と管理者権限とで扱える特権が変わるため、まずはこれを試すのが現実的です。
プログラムから特権を有効化する(応用編)
より踏み込んだ対応として、OpenProcessToken や AdjustTokenPrivileges を使って、実行中プロセスのトークンに対して SeShutdownPrivilege を有効化する方法もあります。少し長くなりますが、VB.NET での例を示します。
Imports System.Runtime.InteropServices
Imports System.Security.Principal
Public Class ShutdownHelper
<DllImport("advapi32.dll", SetLastError:=True)>
Private Shared Function OpenProcessToken(
processHandle As IntPtr,
desiredAccess As UInteger,
ByRef tokenHandle As IntPtr
) As Boolean
End Function
<DllImport("advapi32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
Private Shared Function LookupPrivilegeValue(
lpSystemName As String,
lpName As String,
ByRef lpLuid As LUID
) As Boolean
End Function
<DllImport("advapi32.dll", SetLastError:=True)>
Private Shared Function AdjustTokenPrivileges(
tokenHandle As IntPtr,
disableAllPrivileges As Boolean,
ByRef newState As TOKEN_PRIVILEGES,
bufferLength As Integer,
previousState As IntPtr,
returnLength As IntPtr
) As Boolean
End Function
<DllImport("kernel32.dll", SetLastError:=True)>
Private Shared Function GetCurrentProcess() As IntPtr
End Function
<DllImport("kernel32.dll", SetLastError:=True)>
Private Shared Function CloseHandle(hObject As IntPtr) As Boolean
End Function
<StructLayout(LayoutKind.Sequential)>
Private Structure LUID
Public LowPart As UInteger
Public HighPart As Integer
End Structure
<StructLayout(LayoutKind.Sequential)>
Private Structure LUID_AND_ATTRIBUTES
Public Luid As LUID
Public Attributes As UInteger
End Structure
<StructLayout(LayoutKind.Sequential)>
Private Structure TOKEN_PRIVILEGES
Public PrivilegeCount As UInteger
Public Privileges As LUID_AND_ATTRIBUTES
End Structure
Private Const TOKEN_ADJUST_PRIVILEGES As UInteger = &H20UI
Private Const TOKEN_QUERY As UInteger = &H8UI
Private Const SE_PRIVILEGE_ENABLED As UInteger = &H2UI
Private Const SE_SHUTDOWN_NAME As String = "SeShutdownPrivilege"
Public Shared Function EnableShutdownPrivilege() As Boolean
Dim hToken As IntPtr = IntPtr.Zero
Try
Dim hProcess = GetCurrentProcess()
If Not OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES Or TOKEN_QUERY, hToken) Then
Return False
End If
Dim luid As LUID
If Not LookupPrivilegeValue(Nothing, SE_SHUTDOWN_NAME, luid) Then
Return False
End If
Dim tp As New TOKEN_PRIVILEGES()
tp.PrivilegeCount = 1
tp.Privileges = New LUID_AND_ATTRIBUTES() With {
.Luid = luid,
.Attributes = SE_PRIVILEGE_ENABLED
}
If Not AdjustTokenPrivileges(hToken, False, tp, 0, IntPtr.Zero, IntPtr.Zero) Then
Return False
End If
' AdjustTokenPrivileges は成功しても GetLastError が
' ERROR_NOT_ALL_ASSIGNED になるケースがあるので注意。
Return True
Finally
If hToken <> IntPtr.Zero Then
CloseHandle(hToken)
End If
End Try
End Function
End Class
このヘルパーを使えば、再起動前に次のように呼び出せます。
If Not ShutdownHelper.EnableShutdownPrivilege() Then
' 特権を取得できなかった場合の処理
End If
RebootPlannedOther()
実務では「常に管理者実行する」「必要なときだけ特権を上げる」など、運用ポリシーに合わせてどちらの方法を取るか検討しましょう。
再起動以外の EWX フラグの例
ExitWindowsEx の uFlags には、再起動以外にもさまざまな動作を指定できます。用途に応じて適切に選びましょう。
| 定数 | 値 | 動作 | 注意点 |
|---|---|---|---|
EWX_LOGOFF | &H0UI | ログオフ(サインアウト) | システムは継続動作。 |
EWX_SHUTDOWN | &H1UI | シャットダウン(電源オフの準備) | 実際の電源断には ACPI 対応が必要。 |
EWX_REBOOT | &H2UI | 再起動 | 今回の記事で使用。 |
EWX_POWEROFF | &H8UI | 電源断 | ハードウェア側の対応が必要。 |
EWX_FORCE | &H4UI | 強制終了 | 保存されていないデータが失われる可能性。 |
EWX_FORCEIFHUNG | &H10UI | 応答なしアプリのみ強制終了 | なるべくこちらを優先したい。 |
複数のフラグは OR 結合して使用します。たとえば「ハングしているアプリのみ強制終了しつつ再起動したい」場合は、次のように指定します。
Dim uFlags As UInteger = EWX_REBOOT Or EWX_FORCEIFHUNG
ExitWindowsEx(uFlags, reason)
他のシャットダウン理由コードのパターン例
「その他(計画済み)」以外にも、運用ポリシーに応じて理由を細かく分けたい場合があります。代表的なパターンをいくつか挙げておきます。
| 用途の例 | メジャー理由 | マイナー理由 | フラグ |
|---|---|---|---|
| OS のアップデート(計画済み) | SHTDN_REASON_MAJOR_OPERATINGSYSTEM | SHTDN_REASON_MINOR_UPDATE | SHTDN_REASON_FLAG_PLANNED |
| アプリケーションのインストール(計画済み) | SHTDN_REASON_MAJOR_APPLICATION | SHTDN_REASON_MINOR_INSTALLATION | SHTDN_REASON_FLAG_PLANNED |
| ハードウェアメンテナンス(計画済み) | SHTDN_REASON_MAJOR_HARDWARE | SHTDN_REASON_MINOR_MAINTENANCE | SHTDN_REASON_FLAG_PLANNED |
| システム障害による再起動(計画外) | SHTDN_REASON_MAJOR_SYSTEM | SHTDN_REASON_MINOR_BLUESCREEN | フラグなし(計画外) |
イベントログ解析を自動化する場合は、これらの理由コードを自社向けの運用ルールとして整理し、どのケースでどの組み合わせを使うかを事前に決めておくと、後からログを分析しやすくなります。
イベントログで dwReason を確認する方法
dwReason が本当に期待どおりに記録されているかは、イベントビューアーで確認できます。
- 検索ボックスや「ファイル名を指定して実行」で eventvwr.msc を起動。
- 左ペインで 「Windows ログ」→「システム」 を選択。
- ソースが 「USER32」 となっているイベントを探す。
- 対象のイベントを開き、詳細タブやメッセージ内の「理由」や「Reason Code」を確認。
「その他(計画済み)」であれば、メッセージ内に “Planned” であることが示され、かつ分類が “Other” 相当になっているはずです。もし思った通りになっていなければ、dwReason の組み合わせや型定義を再確認してみてください。
よくあるトラブルとチェックリスト
実務でありがちなトラブルと、そのチェックポイントをまとめておきます。実装時のセルフレビューにも使えるチェックリストです。
| 症状 | 原因の候補 | 確認すべきポイント |
|---|---|---|
| 再起動が実行されない | 特権不足、UAC、グループポリシー | 管理者として実行しているか、SeShutdownPrivilege が有効か。 |
| イベントログの理由が「不明」になる | dwReason が 0、または不正値 | dwReason の定数定義、型が UInteger になっているか。 |
| コンパイルエラーや警告が出る | リテラルの型不一致、オーバーフロー | &H80000000UI のように UI サフィックスを付けているか。 |
| 一部のアプリが強制終了される | EWX_FORCE を安易に使用 | EWX_FORCEIFHUNG で代用できないか検討。 |
実務での設計上のポイント
単に API を叩くだけでなく、運用を意識すると次のような工夫が役立ちます。
ユーザーへの事前通知・猶予時間
たとえ「計画済み」の再起動であっても、ユーザー側から見ると「いきなり再起動された」と感じてしまうことがあります。そこで、次のような流れを組むとユーザー体験が向上します。
- トレイアイコンやフォームで「xx 分後に再起動します」と告知。
- カウントダウンを表示し、延期ボタンを用意。
- 最終的に延期上限を超えたら
ExitWindowsExを呼ぶ。
このときも、内部的には dwReason に「その他(計画済み)」、または適切な理由コードを設定しておくと、後から「なぜ再起動されたのか」を説明しやすくなります。
ログ出力と監査対応
監査対応や障害調査を考えると、「アプリケーション自身のログ」と「Windows のイベントログ」の両方に再起動理由が残っていると理想的です。
- アプリ側のログ:
- 誰が(ユーザー名・端末名)
- いつ再起動を要求したか
- どの理由コードを使ったか(
dwReasonの値)
- Windows 側のログ:
- USER32 のイベントに理由コードが記録される
- 他のシステムイベントと突き合わせ可能
このように多層的にログを残しておくと、数ヶ月後に「このとき再起動したのは誰の操作か?」といった質問が来ても説明しやすくなります。
まとめ:VB.NET での dwReason 設定の勘所
最後に、「その他(計画済み)」で再起動するためのポイントを簡単におさらいします。
- 目的の理由コード:
SHTDN_REASON_MAJOR_OTHER Or SHTDN_REASON_MINOR_OTHER Or SHTDN_REASON_FLAG_PLANNED- 実質的には
0x80000000(&H80000000UI)を指定したのと同じ。
- VB.NET での型:
uFlagsとdwReasonはUIntegerを使う。- 16 進リテラルには
UIサフィックスを付ける(例:&H80000000UI)。
- 特権:
- プロセスに
SeShutdownPrivilegeが必要。 - 足りないと
Marshal.GetLastWin32Error()が 1314(特権不足)になる。
- プロセスに
- 検証方法:
- イベントビューアーの「システム」ログ(ソース:USER32)を確認。
- 理由コード・メッセージが想定どおりかをチェック。
これらを押さえておけば、「VB.NET から ExitWindowsEx を呼んだら、意図した理由でイベントログに記録されない」「特権不足で密かに失敗していた」といったトラブルをかなりの確率で防ぐことができます。Windows のシャットダウン/再起動を自動化するツールやメンテナンスバッチを作る際には、本記事のポイントをぜひ取り入れてみてください。

コメント