Reboot computer in C# / .NET

.NET doesn’t support rebooting, logging off or shutting down your computer though a managed API. Searching for the best way to do this brings up three options: WMI, shutdown.exe and ExitWindowsEx.

I regard WMI as the last resort of the desperate. Weakly typed magic string juju.

Calling Process.Start(“shutdown.exe /r /t 0”) might work, but how would you know? And you’ve got the overheard of starting a new process just to accomplish a reboot. Lazy.

The best way to reboot is P/Invoke to ExitWindowsEx. Unfortunately there’s some really awful sample code out there which will either fail to do anything or mask any errors. I’ve included a drop-in class below that fixes these problems.

If you read all the way through the documentation for ExitWindowsEx you’ll find this:

To shut down or restart the system, the calling process must use the AdjustTokenPrivileges function to enable the SE_SHUTDOWN_NAME privilege. For more information, see Running with Special Privileges.

So just calling ExitWindowsEx won’t do anything. The sample code below adjusts the process token and then reboots (change the flags passed to ExitWindowsEx to shutdown instead, or to pass in a different reason). You’ll also get a Win32Exception if a failure occurs. Catch this, and you can tell the user that they need to reboot manually.

static class NativeMethods
{
    /// 
    /// Reboot the computer
    /// 
    public static void Reboot()
    {
        IntPtr tokenHandle = IntPtr.Zero;

        try
        {
            // get process token
            if (!OpenProcessToken(Process.GetCurrentProcess().Handle,
                TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES,
                out tokenHandle))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error(),
                    "Failed to open process token handle");
            }

            // lookup the shutdown privilege
            TOKEN_PRIVILEGES tokenPrivs = new TOKEN_PRIVILEGES();
            tokenPrivs.PrivilegeCount = 1;
            tokenPrivs.Privileges = new LUID_AND_ATTRIBUTES[1];
            tokenPrivs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

            if (!LookupPrivilegeValue(null,
                SE_SHUTDOWN_NAME,
                out tokenPrivs.Privileges[0].Luid))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error(),
                    "Failed to open lookup shutdown privilege");
            }

            // add the shutdown privilege to the process token
            if (!AdjustTokenPrivileges(tokenHandle,
                false,
                ref tokenPrivs,
                0,
                IntPtr.Zero,
                IntPtr.Zero))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error(),
                    "Failed to adjust process token privileges");
            }

            // reboot
            if (!ExitWindowsEx(ExitWindows.Reboot,
                    ShutdownReason.MajorApplication | 
            ShutdownReason.MinorInstallation | 
            ShutdownReason.FlagPlanned))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error(),
                    "Failed to reboot system");
            }
        }
        finally
        {
            // close the process token
            if (tokenHandle != IntPtr.Zero)
            {
                CloseHandle(tokenHandle);
            }
        }
    }

    // everything from here on is from pinvoke.net

    [Flags]
    private enum ExitWindows : uint
    {
        // ONE of the following five:
        LogOff = 0x00,
        ShutDown = 0x01,
        Reboot = 0x02,
        PowerOff = 0x08,
        RestartApps = 0x40,
        // plus AT MOST ONE of the following two:
        Force = 0x04,
        ForceIfHung = 0x10,
    }

    [Flags]
    private enum ShutdownReason : uint
    {
        MajorApplication = 0x00040000,
        MajorHardware = 0x00010000,
        MajorLegacyApi = 0x00070000,
        MajorOperatingSystem = 0x00020000,
        MajorOther = 0x00000000,
        MajorPower = 0x00060000,
        MajorSoftware = 0x00030000,
        MajorSystem = 0x00050000,

        MinorBlueScreen = 0x0000000F,
        MinorCordUnplugged = 0x0000000b,
        MinorDisk = 0x00000007,
        MinorEnvironment = 0x0000000c,
        MinorHardwareDriver = 0x0000000d,
        MinorHotfix = 0x00000011,
        MinorHung = 0x00000005,
        MinorInstallation = 0x00000002,
        MinorMaintenance = 0x00000001,
        MinorMMC = 0x00000019,
        MinorNetworkConnectivity = 0x00000014,
        MinorNetworkCard = 0x00000009,
        MinorOther = 0x00000000,
        MinorOtherDriver = 0x0000000e,
        MinorPowerSupply = 0x0000000a,
        MinorProcessor = 0x00000008,
        MinorReconfig = 0x00000004,
        MinorSecurity = 0x00000013,
        MinorSecurityFix = 0x00000012,
        MinorSecurityFixUninstall = 0x00000018,
        MinorServicePack = 0x00000010,
        MinorServicePackUninstall = 0x00000016,
        MinorTermSrv = 0x00000020,
        MinorUnstable = 0x00000006,
        MinorUpgrade = 0x00000003,
        MinorWMI = 0x00000015,

        FlagUserDefined = 0x40000000,
        FlagPlanned = 0x80000000
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct LUID
    {
        public uint LowPart;
        public int HighPart;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct LUID_AND_ATTRIBUTES
    {
        public LUID Luid;
        public UInt32 Attributes;
    }

    private struct TOKEN_PRIVILEGES
    {
        public UInt32 PrivilegeCount;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
        public LUID_AND_ATTRIBUTES[] Privileges;
    }

    private const UInt32 TOKEN_QUERY = 0x0008;
    private const UInt32 TOKEN_ADJUST_PRIVILEGES = 0x0020;
    private const UInt32 SE_PRIVILEGE_ENABLED = 0x00000002;
    private const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege";

    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool ExitWindowsEx(ExitWindows uFlags, 
        ShutdownReason dwReason);

    [DllImport("advapi32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool OpenProcessToken(IntPtr ProcessHandle, 
        UInt32 DesiredAccess, 
        out IntPtr TokenHandle);

    [DllImport("advapi32.dll", SetLastError = true, CharSet=CharSet.Unicode)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool LookupPrivilegeValue(string lpSystemName, 
        string lpName, 
        out LUID lpLuid);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CloseHandle(IntPtr hObject);

    [DllImport("advapi32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool AdjustTokenPrivileges(IntPtr TokenHandle,
        [MarshalAs(UnmanagedType.Bool)]bool DisableAllPrivileges,
        ref TOKEN_PRIVILEGES NewState,
        UInt32 Zero,
        IntPtr Null1,
        IntPtr Null2);
}

Comments

David Rickard



You are using & to combine the shutdown reasons. That's the bitwise AND; that means that they will all zero each other out and you'll end up with 0, which is unplanned non-specified.

July 21, 2011

Rob Ellison



You're right, I've fixed this in the sample above. Thanks for pointing it out.

July 22, 2011

danfansn



thanks a lot for sharing this....quite useful

November 22, 2011

I Thought He Came With You
Robert Ellison's blog.

GetvCard shutting down at the end of 2013

Like Debate is an experiment in civilized online debate. Try a test debate or weigh in on the fate of the Oxford comma.

Blog Archives