DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

コンピュータの電源切る、システムのシャットダウン、再起動、ログオフする

コンピュータの電源を切ったり、システムをシャットダウン、再起動、ログオフさせることは、.NET Frameworkの機能だけではできません。ここでは、Win32 API、WMI、shutdown.exeによる方法を紹介します。

ExitWindowsEx関数で行う

ExitWindowsEx関数を使用するために、まずは次のようなメソッドを記述します。

VB.NET
コードを隠すコードを選択
Public Enum ExitWindows
    EWX_LOGOFF = &H0
    EWX_SHUTDOWN = &H1
    EWX_REBOOT = &H2
    EWX_POWEROFF = &H8
    EWX_RESTARTAPPS = &H40
    EWX_FORCE = &H4
    EWX_FORCEIFHUNG = &H10
End Enum

<System.Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True)> _
Public Shared Function ExitWindowsEx(ByVal uFlags As ExitWindows, _
    ByVal dwReason As Integer) As Boolean
End Function
C#
コードを隠すコードを選択
public enum ExitWindows : uint
{
    EWX_LOGOFF = 0x00,
    EWX_SHUTDOWN = 0x01,
    EWX_REBOOT = 0x02,
    EWX_POWEROFF = 0x08,
    EWX_RESTARTAPPS = 0x40,
    EWX_FORCE = 0x04,
    EWX_FORCEIFHUNG = 0x10,
}

[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
public static extern bool ExitWindowsEx(ExitWindows uFlags,
    int dwReason);

さらにWindows NT系のOSでは、AdjustTokenPrivileges関数によりSE_SHUTDOWN_NAMEセキュリティ特権を有効にする必要があります。これは、例えば次のようなコードになります。

VB.NET
コードを隠すコードを選択
<System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError:=True)> _
Private Shared Function GetCurrentProcess() As IntPtr
End Function

<System.Runtime.InteropServices.DllImport("advapi32.dll", SetLastError:=True)> _
Private Shared Function OpenProcessToken(ByVal ProcessHandle As IntPtr, _
    ByVal DesiredAccess As Integer, _
    ByRef TokenHandle As IntPtr) As Boolean
End Function

<System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError:=True)> _
Private Shared Function CloseHandle(ByVal hHandle As IntPtr) As Boolean
End Function

<System.Runtime.InteropServices.DllImport("advapi32.dll", SetLastError:=True, _
    CharSet:=System.Runtime.InteropServices.CharSet.Auto)> _
Private Shared Function LookupPrivilegeValue(ByVal lpSystemName As String, _
    ByVal lpName As String, _
    ByRef lpLuid As Long) As Boolean
End Function

<System.Runtime.InteropServices.StructLayout( _
    System.Runtime.InteropServices.LayoutKind.Sequential, Pack:=1)> _
Private Structure TOKEN_PRIVILEGES
    Public PrivilegeCount As Integer
    Public Luid As Long
    Public Attributes As Integer
End Structure

<System.Runtime.InteropServices.DllImport("advapi32.dll", SetLastError:=True)> _
Private Shared Function AdjustTokenPrivileges(ByVal TokenHandle As IntPtr, _
    ByVal DisableAllPrivileges As Boolean, _
    ByRef NewState As TOKEN_PRIVILEGES, _
    ByVal BufferLength As Integer, _
    ByVal PreviousState As IntPtr, _
    ByVal ReturnLength As IntPtr) As Boolean
End Function

'シャットダウンするためのセキュリティ特権を有効にする
Public Shared Sub AdjustToken()
    Const TOKEN_ADJUST_PRIVILEGES As Integer = &H20
    Const TOKEN_QUERY As Integer = &H8
    Const SE_PRIVILEGE_ENABLED As Integer = &H2
    Const SE_SHUTDOWN_NAME As String = "SeShutdownPrivilege"

    If Environment.OSVersion.Platform <> PlatformID.Win32NT Then
        Return
    End If

    Dim procHandle As IntPtr = GetCurrentProcess()

    'トークンを取得する
    Dim tokenHandle As IntPtr
    OpenProcessToken(procHandle, _
        TOKEN_ADJUST_PRIVILEGES Or TOKEN_QUERY, tokenHandle)
    'LUIDを取得する
    Dim tp As New TOKEN_PRIVILEGES()
    tp.Attributes = SE_PRIVILEGE_ENABLED
    tp.PrivilegeCount = 1
    LookupPrivilegeValue(Nothing, SE_SHUTDOWN_NAME, tp.Luid)
    '特権を有効にする
    AdjustTokenPrivileges(tokenHandle, False, tp, 0, IntPtr.Zero, IntPtr.Zero)

    '閉じる
    CloseHandle(tokenHandle)
End Sub
C#
コードを隠すコードを選択
[System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetCurrentProcess();

[System.Runtime.InteropServices.DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(IntPtr ProcessHandle,
    uint DesiredAccess,
    out IntPtr TokenHandle);

[System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);

[System.Runtime.InteropServices.DllImport("advapi32.dll", SetLastError = true,
    CharSet = System.Runtime.InteropServices.CharSet.Auto)]
private static extern bool LookupPrivilegeValue(string lpSystemName,
    string lpName,
    out long lpLuid);

[System.Runtime.InteropServices.StructLayout(
   System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)]
private struct TOKEN_PRIVILEGES
{
    public int PrivilegeCount;
    public long Luid;
    public int Attributes;
}

[System.Runtime.InteropServices.DllImport("advapi32.dll", SetLastError = true)]
private static extern bool AdjustTokenPrivileges(IntPtr TokenHandle,
    bool DisableAllPrivileges,
    ref TOKEN_PRIVILEGES NewState,
    int BufferLength,
    IntPtr PreviousState,
    IntPtr ReturnLength);

//シャットダウンするためのセキュリティ特権を有効にする
public static void AdjustToken()
{
    const uint TOKEN_ADJUST_PRIVILEGES = 0x20;
    const uint TOKEN_QUERY = 0x8;
    const int SE_PRIVILEGE_ENABLED = 0x2;
    const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege";

    if (Environment.OSVersion.Platform != PlatformID.Win32NT)
        return;

    IntPtr procHandle = GetCurrentProcess();

    //トークンを取得する
    IntPtr tokenHandle;
    OpenProcessToken(procHandle,
        TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out tokenHandle);
    //LUIDを取得する
    TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES();
    tp.Attributes = SE_PRIVILEGE_ENABLED;
    tp.PrivilegeCount = 1;
    LookupPrivilegeValue(null, SE_SHUTDOWN_NAME, out tp.Luid);
    //特権を有効にする
    AdjustTokenPrivileges(
        tokenHandle, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);

    //閉じる
    CloseHandle(tokenHandle);
}

システムをシャットダウンして電源を切るには、次のようにEWX_POWEROFFを指定してExitWindowsExメソッドを呼び出します。

VB.NET
コードを隠すコードを選択
'シャットダウンする
AdjustToken()
ExitWindowsEx(ExitWindows.EWX_POWEROFF, 0)
C#
コードを隠すコードを選択
//シャットダウンする
AdjustToken();
ExitWindowsEx(ExitWindows.EWX_POWEROFF, 0);

上記のようにExitWindowsExを呼び出したとき、例えば、アプリケーションで文章を作成中であれば、文章を保存するか破棄するかキャンセルするかを問うダイアログが表示されることがあります。もしここでユーザーがキャンセルを選択すれば、通常シャットダウンもキャンセルされます。

シャットダウンを確実に実行するために、開いているアプリケーションを強制的に終了させるには、次のようにEWX_FORCEを付加します。

VB.NET
コードを隠すコードを選択
'強制的にシャットダウンする
AdjustToken()
ExitWindowsEx(ExitWindows.EWX_POWEROFF Or ExitWindows.EWX_FORCE, 0)
C#
コードを隠すコードを選択
//強制的にシャットダウンする
AdjustToken();
ExitWindowsEx(ExitWindows.EWX_POWEROFF | ExitWindows.EWX_FORCE, 0);

上記のようにすると、現在実行されているアプリケーションにWM_QUERYENDSESSIONやWM_ENDSESSIONメッセージを送信しないで終了されます。よって、アプリケーションで作成中のデータが失われてしまう可能性があります。

EWX_FORCEの代わりにEWX_FORCEIFHUNGを指定すると、Windows 2000以降では、WM_QUERYENDSESSION、WM_ENDSESSIONメッセージに応答しないアプリケーションが強制的に終了されます。

EWX_POWEROFFの代わりにEWX_SHUTDOWNを指定すると、シャットダウン後も電源が切られなくなります。ただし、Windows XP SP1およびWindows Server 2003では、EWX_SHUTDOWNでも電源が切られます(私の環境では、XP SP2でも電源が切られました)。詳しくは、以下のリンク先をご覧ください。

システムを再起動されるには、EWX_POWEROFFの代わりにEWX_REBOOTを指定します。

ログオフするには、EWX_POWEROFFの代わりにEWX_LOGOFFを指定します。ログオフではセキュリティ特権は必要ありませんので、上記のAdjustTokenメソッドを呼び出す必要はありません。

WMIで行う

WMIのWin32_OperatingSystemクラスにより、シャットダウン、再起動、ログオフすることもできます。この方法については、「How to Shutdown My Computer using C#?」で説明されています。この方法はWindows NT系でのみ使えます。

以下に、Win32_OperatingSystemクラスのWin32Shutdownメソッドにより、システムをシャットダウンするコードを示します。なお、System.Management.dllを参照設定に追加しておく必要があります。

VB.NET
コードを隠すコードを選択
'ユーザー特権を有効にするための設定を作成
Dim co As New System.Management.ConnectionOptions()
co.Impersonation = System.Management.ImpersonationLevel.Impersonate
co.EnablePrivileges = True
'ManagementScopeを作成
Dim sc As New System.Management.ManagementScope("\ROOT\CIMV2", co)

'リモートコンピュータをシャットダウンするには次のようにする
'co.Username = "username"
'co.Password = "password"
'Dim sc As New System.Management.ManagementScope("\\MachineName\ROOT\CIMV2", co)

'接続
sc.Connect()

Dim oq As New System.Management.ObjectQuery( _
    "select * from Win32_OperatingSystem")
Dim mos As New System.Management.ManagementObjectSearcher(sc, oq)

'Shutdownメソッドを呼び出す
Dim mo As System.Management.ManagementObject
For Each mo In mos.Get()
    'パラメータを指定
    Dim inParams As System.Management.ManagementBaseObject = _
        mo.GetMethodParameters("Win32Shutdown")
    inParams("Flags") = 1
    inParams("Reserved") = 0
    'Win32Shutdownメソッドを呼び出す
    Dim outParams As System.Management.ManagementBaseObject = _
        mo.InvokeMethod("Win32Shutdown", inParams, Nothing)
    mo.Dispose()
Next mo

mos.Dispose()
C#
コードを隠すコードを選択
//ユーザー特権を有効にするための設定を作成
System.Management.ConnectionOptions co =
    new System.Management.ConnectionOptions();
co.Impersonation = System.Management.ImpersonationLevel.Impersonate;
co.EnablePrivileges = true;
//ManagementScopeを作成
System.Management.ManagementScope sc =
    new System.Management.ManagementScope("\\ROOT\\CIMV2", co);

//リモートコンピュータをシャットダウンするには次のようにする
//co.Username = "username";
//co.Password = "password";
//System.Management.ManagementScope sc =
//    new System.Management.ManagementScope("\\\\MachineName\\ROOT\\CIMV2", co);

//接続
sc.Connect();

System.Management.ObjectQuery oq =
    new System.Management.ObjectQuery("select * from Win32_OperatingSystem");
System.Management.ManagementObjectSearcher mos =
    new System.Management.ManagementObjectSearcher(sc, oq);

//Shutdownメソッドを呼び出す
foreach (System.Management.ManagementObject mo in mos.Get())
{
    //パラメータを指定
    System.Management.ManagementBaseObject inParams =
        mo.GetMethodParameters("Win32Shutdown");
    inParams["Flags"] = 1;
    inParams["Reserved"] = 0;
    //Win32Shutdownメソッドを呼び出す
    System.Management.ManagementBaseObject outParams =
        mo.InvokeMethod("Win32Shutdown", inParams, null);
    mo.Dispose();
}

mos.Dispose();

上記の例ではWin32ShutdownのFlagsパラメータに1を指定していますが、この値は、先に紹介したExitWindowsEx関数のuFlagsパラメータに指定する値と同じ意味となります。つまり、電源を切るには8、シャットダウンするには1、再起動するには2、ログオフするには0を指定します。さらに、この値に4を足すことにより、EWX_FORCEと同じように強制することもできます。

補足:私が試した限りでは、Flagsパラメータに8を指定すると、強制的にシャットダウンと電源オフが行われるようでした。

また、Win32Shutdownメソッドの代わりにShutdownメソッドを使うこともできます(先に紹介したリンク先では、Shutdownメソッドを使用しています)。Shutdownメソッドは、Win32ShutdownのFlagsパラメータに1を指定したのと同じではないかと思われます。

shutdown.exeで行う

shutdown.exeというツールを使用して行うこともできます。このツールはWindows XP以上では標準で用意されており、それ以下(Windows 2000、NT4.0)ではResource Kitを導入することにより使えるようになります。

shutdown.exeを使用してシャットダウンする例を示します。Process.Startメソッドで呼び出すだけなので、簡単です。なおProcess.Startメソッドについて詳しくは、「DOSコマンドを実行し出力データを取得する」をご覧ください。

VB.NET
コードを隠すコードを選択
Dim psi As New System.Diagnostics.ProcessStartInfo()
psi.FileName = "shutdown.exe"
'コマンドラインを指定
psi.Arguments = "/s"
'ウィンドウを表示しないようにする
psi.UseShellExecute = False
psi.CreateNoWindow = True
'起動
Dim p As System.Diagnostics.Process = System.Diagnostics.Process.Start(psi)
C#
コードを隠すコードを選択
System.Diagnostics.ProcessStartInfo psi =
    new System.Diagnostics.ProcessStartInfo();
psi.FileName = "shutdown.exe";
//コマンドラインを指定
psi.Arguments = "/s";
//ウィンドウを表示しないようにする
psi.UseShellExecute = false;
psi.CreateNoWindow = true;
//起動
System.Diagnostics.Process p = System.Diagnostics.Process.Start(psi);

Windows XPで「shutdown /?」を実行したときに表示されるshutdown.exeの使用法は、以下の通りです。

使用法: shutdown [/i | /l | /s | /r | /a] [/f] [/m \\コンピュータ名] [/t xx] [/c
 "コメント"] [/d up:xx:yy]

        引数なし                このメッセージを表示します (/? と同じです)
        /i                      GUI インターフェイスを表示します。このオプ
                                ションは最初に指定する必要があります
        /l                      ログオフ (/m オプションとは併用できません)
        /s                      コンピュータをシャットダウンします
        /r                      コンピュータをシャットダウンして再起動します
        /a                      システム シャットダウンを中止します
        /m \\コンピュータ名     シャットダウン/再起動/中止するリモート コン
                                ピュータの名前です
        /t xx                   シャットダウンのタイムアウトを xx 秒に設定
                                します
        /c "コメント"           シャットダウンのコメントです (127 文字まで)
        /f                      実行中のアプリケーションを警告なしに閉じます
        /d [u][p]:xx:yy         シャットダウンの理由コードです
                                u = ユーザー コード
                                p = 計画されたシャットダウンのコード
                                xx = 重大な理由コード (255 以下の正の整数)
                                yy = 重大ではない理由コード (65535 以下の正の
                                整数)

このように、シャットダウンするときは引数に"/s"を、再起動するときは"/r"を、ログオフするときは"/l"を指定します。強制的にシャットダウンするには、"/f"パラメータも付けます。

さらに、"/m"引数により、リモートコンピュータをシャットダウンすることもできます。

補足:「c# - Process of Shutdown.exe with multiple arguments, not working」によると、shutdown.exeの引数に「/」ではなく「-」を使うと、複数の引数を指定した時に正常に動作しなくなるということです。
  • 履歴:
  • 2013/7/5 「ExitWindowsEx関数で行う」のAdjustTokenメソッドで、tokenHandleをCloseHandleで閉じるようにした。
  • 2014/5/27 shutdown.exeの引数に「-」ではなく「/」を使うように変更。「psi.UseShellExecute = False」を追加。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • このサイトで紹介されているコードの多くは、例外処理が省略されています。例外処理については、こちらをご覧ください。
  • 「???を参照に追加します」の意味が分からないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。