The new vbAccelerator Site - more VB and .NET Code and Controls

How to Shutdown the System in Windows 9x and NT

Author:

Steve McMahon(steve@vbaccelerator.com)

Keywords:

API,Windows

Updated:

09/08/98

Other Tips
All Tips
By Date
By Subject


API (33)
Bit
Manipulation (3)

Clipboard (3)
Combo
Box (5)

Desktop (3)
GDI (13)
Graphics (13)
Internet (2)
Interprocess
Comms (3)

Keyboard (2)
Mouse (1)
Shell (1)
Sprites (1)
Subclassing (3)
Text
Box (2)

Windows (11)
Windows
Controls (10)



Submit


This tip demonstrates how to shutdown, logoff and/or reboot a system. Shutting down a Windows 9x system is very straightforward - just one API call to ExitWindowsEx is all that is required. However, under NT it is a little more tricky. By default, no application processes under Windows NT have the privilege to shut the system down, and the call to ExitWindowsEx simply fails on calling it. The trick to make it work is to call the API AdjustTokenPrivileges function to enable the calling process to shut the system down. Note that under NT the user may not have sufficient system privileges to shut down anyway.

Start a new project in VB. Add a new module, then add the following code to it:

' To Shutdown Windows:
Private Declare Function ExitWindowsEx Lib "user32" (ByVal uFlags As Long, ByVal dwReserved As Long) As Long
Private Const ENDSESSION_LOGOFF = &H80000000

Public Enum EShutDownTypes
&nbsp &nbsp [_First] = 0
&nbsp &nbsp EWX_LOGOFF = 0
&nbsp &nbsp EWX_SHUTDOWN = 1&
&nbsp &nbsp EWX_REBOOT = 2&
&nbsp &nbsp EWX_FORCE = 4&
&nbsp &nbsp EWX_POWEROFF = 8&
&nbsp &nbsp &nbsp &nbsp EWX_FORCEIFHUNG = 10&
' NT5 only
&nbsp &nbsp
&nbsp &nbsp EWX_RESET = EWX_LOGOFF Or EWX_FORCE Or EWX_REBOOT
&nbsp &nbsp [_Last] = &H20& - 1
End Enum
Public Enum EShutDownErrorBaseConstant
&nbsp &nbsp eeSSDErrorBase = vbObjectError Or (1048 + &H210)
End Enum

' To Determine if we are running NT or not:
Private Type OSVERSIONINFO
&nbsp &nbsp dwOSVersionInfoSize As Long
&nbsp &nbsp dwMajorVersion As Long
&nbsp &nbsp dwMinorVersion As Long
&nbsp &nbsp dwBuildNumber As Long
&nbsp &nbsp dwPlatformId As Long
&nbsp &nbsp &nbsp &nbsp szCSDVersion As String * 128
' Maintenance string for PSS usage
End Type
Private Declare Function GetVersionEx Lib "kernel32" Alias "GetVersionExA" (lpVersionInformation As OSVERSIONINFO) As Long
Private Const VER_PLATFORM_WIN32_NT = 2
Private Const VER_PLATFORM_WIN32_WINDOWS = 1
Private Const VER_PLATFORM_WIN32s = 0

' To Report API errors:
Private Const FORMAT_MESSAGE_ALLOCATE_BUFFER = &H100
Private Const FORMAT_MESSAGE_ARGUMENT_ARRAY = &H2000
Private Const FORMAT_MESSAGE_FROM_HMODULE = &H800
Private Const FORMAT_MESSAGE_FROM_STRING = &H400
Private Const FORMAT_MESSAGE_FROM_SYSTEM = &H1000
Private Const FORMAT_MESSAGE_IGNORE_INSERTS = &H200
Private Const FORMAT_MESSAGE_MAX_WIDTH_MASK = &HFF
Private Declare Function FormatMessage Lib "kernel32" Alias "FormatMessageA" (ByVal dwFlags As Long, lpSource As Any, ByVal dwMessageId As Long, ByVal dwLanguageId As Long, ByVal lpBuffer As String, ByVal nSize As Long, Arguments As Long) As Long


' ============================================================================================
' NT Only
Private Type LARGE_INTEGER
&nbsp &nbsp LowPart As Long
&nbsp &nbsp HighPart As Long
End Type
Private Type LUID
&nbsp &nbsp LowPart As Long
&nbsp &nbsp HighPart As Long
End Type
Private Type LUID_AND_ATTRIBUTES
&nbsp &nbsp pLuid As LUID
&nbsp &nbsp Attributes As Long
End Type
Private Type TOKEN_PRIVILEGES
&nbsp &nbsp PrivilegeCount As Long
&nbsp &nbsp Privileges(0 To 0) As LUID_AND_ATTRIBUTES
End Type
Private Declare Function GetCurrentProcess Lib "kernel32" () As Long
Private Declare Function OpenProcessToken Lib "advapi32.dll" (ByVal ProcessHandle As Long, ByVal DesiredAccess As Long, TokenHandle As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function GetTokenInformation Lib "advapi32.dll" (ByVal TokenHandle As Long, TokenInformationClass As Integer, TokenInformation As Any, ByVal TokenInformationLength As Long, ReturnLength As Long) As Long
Private Declare Function AdjustTokenPrivileges Lib "advapi32.dll" (ByVal TokenHandle As Long, ByVal DisableAllPrivileges As Long, NewState As TOKEN_PRIVILEGES, ByVal BufferLength As Long, PreviousState As TOKEN_PRIVILEGES, ReturnLength As Long) As Long
Private Declare Function LookupPrivilegeValue Lib "advapi32.dll" Alias "LookupPrivilegeValueA" (ByVal lpSystemName As String, ByVal lpName As String, lpLuid As LUID) As Long
Private Const SE_SHUTDOWN_NAME = "SeShutdownPrivilege"
Private Const SE_PRIVILEGE_ENABLED = &H2

Private Const READ_CONTROL = &H20000
Private Const STANDARD_RIGHTS_ALL = &H1F0000
Private Const STANDARD_RIGHTS_EXECUTE = (READ_CONTROL)
Private Const STANDARD_RIGHTS_READ = (READ_CONTROL)
Private Const STANDARD_RIGHTS_REQUIRED = &HF0000
Private Const STANDARD_RIGHTS_WRITE = (READ_CONTROL)

Private Const TOKEN_ASSIGN_PRIMARY = &H1
Private Const TOKEN_DUPLICATE = (&H2)
Private Const TOKEN_IMPERSONATE = (&H4)
Private Const TOKEN_QUERY = (&H8)
Private Const TOKEN_QUERY_SOURCE = (&H10)
Private Const TOKEN_ADJUST_PRIVILEGES = (&H20)
Private Const TOKEN_ADJUST_GROUPS = (&H40)
Private Const TOKEN_ADJUST_DEFAULT = (&H80)
Private Const TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED Or _
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp TOKEN_ASSIGN_PRIMARY Or _
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp TOKEN_DUPLICATE Or _
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp TOKEN_IMPERSONATE Or _
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp TOKEN_QUERY Or _
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp TOKEN_QUERY_SOURCE Or _
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp TOKEN_ADJUST_PRIVILEGES Or _
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp TOKEN_ADJUST_GROUPS Or _
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp TOKEN_ADJUST_DEFAULT)
Private Const TOKEN_READ = (STANDARD_RIGHTS_READ Or _
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp TOKEN_QUERY)
Private Const TOKEN_WRITE = (STANDARD_RIGHTS_WRITE Or _
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp TOKEN_ADJUST_PRIVILEGES Or _
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp TOKEN_ADJUST_GROUPS Or _
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp TOKEN_ADJUST_DEFAULT)
Private Const TOKEN_EXECUTE = (STANDARD_RIGHTS_EXECUTE)

Private Const TokenDefaultDacl = 6
Private Const TokenGroups = 2
Private Const TokenImpersonationLevel = 9
Private Const TokenOwner = 4
Private Const TokenPrimaryGroup = 5
Private Const TokenPrivileges = 3
Private Const TokenSource = 7
Private Const TokenStatistics = 10
Private Const TokenType = 8
Private Const TokenUser = 1
' ============================================================================================

Public Function WinError(ByVal lLastDLLError As Long) As String
Dim sBuff As String
Dim lCount As Long
&nbsp &nbsp
&nbsp &nbsp
' Return the error message associated with LastDLLError:
&nbsp &nbsp sBuff = String$(256, 0)
&nbsp &nbsp lCount = FormatMessage( _
&nbsp &nbsp FORMAT_MESSAGE_FROM_SYSTEM Or FORMAT_MESSAGE_IGNORE_INSERTS, _
&nbsp &nbsp 0, lLastDLLError, 0&, sBuff, Len(sBuff), ByVal 0)
&nbsp &nbsp If lCount Then
&nbsp &nbsp WinError = Left$(sBuff, lCount)
&nbsp &nbsp End If
&nbsp &nbsp
End Function

Public Function IsNT() As Boolean
Static bOnce As Boolean
Static bValue As Boolean

&nbsp &nbsp
' Return whether the system is running NT or not:
&nbsp &nbsp If Not (bOnce) Then
&nbsp &nbsp Dim tVI As OSVERSIONINFO
&nbsp &nbsp tVI.dwOSVersionInfoSize = Len(tVI)
&nbsp &nbsp If (GetVersionEx(tVI) 0) Then
&nbsp &nbsp &nbsp &nbsp bValue = (tVI.dwPlatformId = VER_PLATFORM_WIN32_NT)
&nbsp &nbsp &nbsp &nbsp bOnce = True
&nbsp &nbsp End If
&nbsp &nbsp End If
&nbsp &nbsp IsNT = bValue
&nbsp &nbsp
End Function

Private Function NTEnableShutDown(ByRef sMsg As String) As Boolean
Dim tLUID As LUID
Dim hProcess As Long
Dim hToken As Long
Dim tTP As TOKEN_PRIVILEGES, tTPOld As TOKEN_PRIVILEGES
Dim lTpOld As Long
Dim lR As Long

&nbsp &nbsp
' Under NT we must enable the SE_SHUTDOWN_NAME privilege in the
&nbsp &nbsp ' process we're trying to shutdown from, otherwise a call to
&nbsp &nbsp ' try to shutdown has no effect!

&nbsp &nbsp
' Find the LUID of the Shutdown privilege token:
&nbsp &nbsp lR = LookupPrivilegeValue(vbNullString, SE_SHUTDOWN_NAME, tLUID)
&nbsp &nbsp
&nbsp &nbsp
' If we get it:
&nbsp &nbsp If (lR 0) Then
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp
&nbsp &nbsp
' Get the current process handle:
&nbsp &nbsp hProcess = GetCurrentProcess()
&nbsp &nbsp If (hProcess 0) Then
&nbsp &nbsp &nbsp &nbsp
' Open the token for adjusting and querying (if we can - user may not have rights):
&nbsp &nbsp &nbsp &nbsp lR = OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES Or TOKEN_QUERY, hToken)
&nbsp &nbsp &nbsp &nbsp If (lR 0) Then
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp
' Ok we can now adjust the shutdown priviledges:
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp With tTP
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp .PrivilegeCount = 1
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp With .Privileges(0)
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp .Attributes = SE_PRIVILEGE_ENABLED
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp .pLuid.HighPart = tLUID.HighPart
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp .pLuid.LowPart = tLUID.LowPart
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp End With
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp End With
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp
' Now allow this process to shutdown the system:
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp lR = AdjustTokenPrivileges(hToken, 0, tTP, Len(tTP), tTPOld, lTpOld)
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp If (lR 0) Then
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp NTEnableShutDown = True
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp Else
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp &nbsp Err.Raise eeSSDErrorBase + 6, App.EXEName & ".mShutDown", "Can't enable shutdown: You do not have the privileges to shutdown this system. [" & WinError(Err.LastDllError) & "]"
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp End If
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp
' Remember to close the handle when finished with it:
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp CloseHandle hToken
&nbsp &nbsp &nbsp &nbsp Else
&nbsp &nbsp &nbsp &nbsp &nbsp &nbsp Err.Raise eeSSDErrorBase + 6, App.EXEName & ".mShutDown", "Can't enable shutdown: You do not have the privileges to shutdown this system. [" & WinError(Err.LastDllError) & "]"
&nbsp &nbsp &nbsp &nbsp End If
&nbsp &nbsp Else
&nbsp &nbsp &nbsp &nbsp Err.Raise eeSSDErrorBase + 5, App.EXEName & ".mShutDown", "Can't enable shutdown: Can't determine the current process. [" & WinError(Err.LastDllError) & "]"
&nbsp &nbsp End If
&nbsp &nbsp Else
&nbsp &nbsp Err.Raise eeSSDErrorBase + 4, App.EXEName & ".mShutDown", "Can't enable shutdown: Can't find the SE_SHUTDOWN_NAME privilege value. [" & WinError(Err.LastDllError) & "]"
&nbsp &nbsp End If

End Function

Public Function ShutdownSystem( _
&nbsp &nbsp Optional ByVal eType As EShutDownTypes = EWX_RESET _
&nbsp &nbsp ) As Boolean
Dim lR As Long
Dim sMsg As String

&nbsp &nbsp
' Validate shutdown type:
&nbsp &nbsp If (eType EShutDownTypes.[_Last]) Then
&nbsp &nbsp Err.Raise eeSSDErrorBase + 7, App.EXEName & ".mShutDown", "Invalid parameter to ShutdownSystem: " & eType, vbInformation
&nbsp &nbsp Exit Function
&nbsp &nbsp End If

&nbsp &nbsp
' Make sure we have enabled the privilege to shutdown
&nbsp &nbsp ' for this process if we're running NT:
&nbsp &nbsp If (IsNT) Then
&nbsp &nbsp If Not (NTEnableShutDown(sMsg)) Then
&nbsp &nbsp &nbsp &nbsp Exit Function
&nbsp &nbsp End If
&nbsp &nbsp End If

&nbsp &nbsp
' This is the code to shut down
&nbsp &nbsp lR = ExitWindowsEx(eType, &HFFFFFFFF)
&nbsp &nbsp If (lR = 0) Then
&nbsp &nbsp Err.Raise eeSSDErrorBase + 3, App.EXEName & ".mShutDown", "ShutdownSystem failed: " & WinError(Err.LastDllError)
&nbsp &nbsp Else
&nbsp &nbsp
' Remember that shutdown will proceed on another
&nbsp &nbsp ' thread to this one, so code may continue to
&nbsp &nbsp ' execute after this.
&nbsp &nbsp ShutdownSystem = True
End If

End Function

To try out a shutdown, add a Command button to the project's form and then paste in the following code. Note you should save your work before running it, because the shutdown will kill VB without asking you to save any changes! Clicking on the Command button will cause the system to shutdown and reboot.

Private Sub Command1_Click()
&nbsp &nbsp
' Don't do this unless you've saved the code!
&nbsp &nbsp If (MsgBox("Are you sure you want to shutdown?", vbYesNo Or vbQuestion) = vbYes) Then
&nbsp &nbsp ShutdownSystem
End If
End Sub


&nbsp

Related Tips and Articles:

&nbsp
 

About  Contribute  Send Feedback  Privacy

Copyright © 1998-1999, Steve McMahon ( steve@vbaccelerator.com). All Rights Reserved.
Last updated: 09/08/98