BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Windows Programming
.NET 1.1+

Exiting Windows and Logging Off Programmatically

If you develop software that updates key Windows configuration settings, you may require that the system be restarted or that the users logs out and performs a new login process. Using Windows API functions, this can be controlled programmatically in C#.

Adding a Privilege Elevation Method

We will now add a new method to the sample program that assigns the privilege to the current process before attempting any shutdown procedure. Before we can add this method, the four API calls must be declared. These API calls also use some constant values and a structure. Declare these as follows within the form's class code block.

const int PrivilegeEnabled = 0x00000002;
const int TokenQuery = 0x00000008;
const int AdjustPrivileges = 0x00000020;
const string ShutdownPrivilege = "SeShutdownPrivilege";

[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct TokenPrivileges
{
    public int PrivilegeCount;
    public long Luid;
    public int Attributes;
}

[DllImport("kernel32.dll")]
internal static extern IntPtr GetCurrentProcess();

[DllImport("advapi32.dll", SetLastError = true)]
internal static extern int OpenProcessToken(
    IntPtr processHandle, int desiredAccess, ref IntPtr tokenHandle);

[DllImport("advapi32.dll", SetLastError = true)]
internal static extern int LookupPrivilegeValue(
    string systemName, string name, ref long luid);

[DllImport("advapi32.dll", SetLastError = true)]
internal static extern int AdjustTokenPrivileges(
    IntPtr tokenHandle, bool disableAllPrivileges, ref TokenPrivileges newState,
    int bufferLength, IntPtr previousState, IntPtr length);

With these declarations made, the new method can be added to the code. This method uses the four API functions in turn to identify the current process, retrieve the current privileges, identify the shutdown privilege and grant it to the user. Add the following method to the form's code:

private void ElevatePrivileges()
{
    IntPtr currentProcess = GetCurrentProcess();
    IntPtr tokenHandle = IntPtr.Zero;

    int result = OpenProcessToken(
        currentProcess, AdjustPrivileges | TokenQuery, ref tokenHandle);
    if (result == 0) throw new Win32Exception(Marshal.GetLastWin32Error());

    TokenPrivileges tokenPrivileges;
    tokenPrivileges.PrivilegeCount = 1;
    tokenPrivileges.Luid = 0;
    tokenPrivileges.Attributes = PrivilegeEnabled;

    result = LookupPrivilegeValue(null, ShutdownPrivilege, ref tokenPrivileges.Luid);
    if (result == 0) throw new Win32Exception(Marshal.GetLastWin32Error());

    result = AdjustTokenPrivileges(
        tokenHandle, false, ref tokenPrivileges, 0, IntPtr.Zero, IntPtr.Zero);
    if (result == 0) throw new Win32Exception(Marshal.GetLastWin32Error());
}

Exiting Windows

With the API referenced, the flag and reason code enumerations available and a method that elevates the program's privileges to permit shutdowns, we can now add functionality to the form's buttons to demonstrate the use of the ExitWindowsEx method.

The first piece of code to add to the program will cause Windows to power off when the Shut Down button is clicked. To cause Windows to exit completely, the Shutdown flag is used. On Windows XP service pack 1 operating systems and later this causes Windows to terminate and the computer's power to be switched off (if supported by the machine's hardware). However, on earlier operating systems the Shutdown flag alone will exit Windows but leave the machine powered on. This can be overcome by combining the Shutdown and PowerOff flags.

When the ExitWindowsEx function is called, it returns an integer value indicating success or failure. If the call fails, the return value is zero and the error details are stored. The error can be retrieved using the GetLastWin32Error method of the Marshal class. Although this method returns an integer, the details of the exception can be seen by throwing a new Win32Exception, passing the error number as a parameter.

To add the shutdown and error detection code to the Shut Down button, add the following code to the button's Click event. This sample combines two reason codes to indicate to the operating system that the system was powered off due to a hardware issue but that this was planned.

private void ShutdownButton_Click(object sender, EventArgs e)
{
    ElevatePrivileges();

    int result = ExitWindowsEx(
        (uint)(ExitFlags.Shutdown | ExitFlags.PowerOff), (uint)(Reason.HardwareIssue
            | Reason.PlannedShutdown));
    if (result == 0) throw new Win32Exception(Marshal.GetLastWin32Error());
}

You can test the program by executing it and pressing the Shut Down button. Be sure to save the program and any other files that you are working on first.

Forcing a Shutdown

The shutdown procedure described above sends a message to all of the current user's processes requesting that they close. This is similar to closing Windows via the Start menu. Each program that is running will close normally, giving the user the option to save any open work where appropriate. If any of the running software fails to close within a timeout period, the user is given the option to terminate the programs forcibly or cancel the shutdown procedure.

In some emergency cases, such as when programs hang, you may want to force the system to close all of the active processes. By including the Force flag, all processes are forced to close and the user is not given the option to cancel the process. The ForceIfHung flag can also be used to force applications to terminate if they do not respond within the timeout period. Either option should be used with care as they can close applications without giving the user the option to save their work first.

To create a forced shutdown, adjust the ShutdownButton_Click event as follows:

private void ShutdownButton_Click(object sender, EventArgs e)
{
    ElevatePrivileges();

    int result = ExitWindowsEx(
        (uint)(ExitFlags.Shutdown | ExitFlags.PowerOff | ExitFlags.Force),
        (uint)(Reason.HardwareIssue | Reason.PlannedShutdown));
    if (result == 0) throw new Win32Exception(Marshal.GetLastWin32Error());
}
17 December 2007