Visual Basic System Services
InitiateSystemShutdown: Terminating Remote Windows Sessions
     
Posted:   Wednesday December 08, 2004
Updated:   Monday December 26, 2011
     
Applies to:   VB4-32, VB5, VB6
Developed with:   VB6, Windows XP
OS restrictions:   Windows NT4, Windows 2000, Windows XP, Windows 2003
Author:   VBnet - Randy Birch
     

Related:  

ExitWindowsEx: Shut Down, Reboot, Log Off or Power Off
     
 Prerequisites
None.

Windows' InitiateSystemShutdown function initiates a shutdown and optional restart of the specified computer. Although the user of the remote machine targeted will receive a message box-like notification of the pending shutdown, the user can not cancel the shutdown from occurring.

As a minimum, the name of the remote machine must be specified, and it can be in the format \\machinename, machinename alone, or the IP address without leading slashes.

The lpMessage parameter allows you to send the user of the remote computer a message indicating why you are shutting down the machine.

The dwTimeout parameter indicates the countdown that the dialog will perform. A reasonable time should be provided to allow the user to save open files, as the shutdown is not delayed while apps are open or a Save As dialog is on-screen. Specifying 0 as dwTimeout causes the shutdown to occur immediately without any warning to the user.

Two flags allow you to perform special actions. Although the API returns a success flag indicating the shutdown has been initiated on the remote machine, there is no message returned to indicate that the shutdown of the remote machine is going to be successful. Therefore when the bForceAppsClosed flag is non-zero Windows shuts down even when encountering hung apps.

The bRebootAfterShutdown flag, when non-zero, causes the system to reboot.

Once a shutdown has been started using InitiateSystemShutdown, its sister API, AbortSystemShutdown, can cancel the action. AbortSystemShutdown requires just the name of the remote machine.

The demo presented enumerates all machines on the domain or workgroup to populate the combo box. Controls are disabled until a combo selection has been made. As a confirmation, on selection the "Shut down selected machine" check box is enabled to confirm the shutdown action is desired. Checking this box enables the Force, Reboot and Delay options, and the Perform command button.

Because you'll probably want to only ensure the functions execute properly, the a successful call will disable the Perform button and enable the Abort button. The machine name combo is also disabled to prevent changing the machine name in case you want to hit Abort. In a real utility this extra code would probably not be needed, as you'd probably want to execute the command to several machines in a loop.

One thing I noticed when bRebootAfterShutdown was False is that instead of the remote computer powering off, it went to its "It is now safe to turn off your computer" screen. (I didn't know XP still had this screen!)  This may be just an anomaly with my setup; sure would have been nice to have the machine actually power off. When bRebootAfterShutdown is non-zero the machine shuts right down then restarts Windows.

InitiateSystemShutdown can not be used to shut down the machine executing the call (the local machine) -- for this ExitWindowsEx must be used. In addition, InitiateSystemShutdown does not cause an entry to be added to the remote machine's event log. If event recording is required, use InitiateSystemShutdownEx which accommodates a DWORD value to indicate the reason for the shutdown.

 BAS Module Code
None.

 Form Code
To a form, add a combo (Combo1) for the machine names, and a second combo (Combo2) for the delay duration. Also add three check boxes (Check1, Check2, Check3), a multi-line text box (Text1), and a label (Label1). The code below will fill in the control captions.

Add two command buttons (Command1, Command2) along with the following code:


Option Explicit
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Copyright ©1996-2011 VBnet/Randy Birch, All Rights Reserved.
' Some pages may also contain other copyrights by the author.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Distribution: You can freely use this code in your own
'               applications, but you may not reproduce 
'               or publish this code on any web site,
'               online service, or distribute as source 
'               on any media without express permission.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Windows type used to call the Net API
Private Const MAX_PREFERRED_LENGTH As Long = -1
Private Const NERR_SUCCESS As Long = 0&
Private Const ERROR_MORE_DATA As Long = 234&
Private Const SV_TYPE_ALL As Long = &HFFFFFFFF
Private Const MAX_COMPUTERNAME As Long = 16

Private Type SERVER_INFO_100
  sv100_platform_id As Long
  sv100_name As Long
End Type

Private Declare Function NetServerEnum Lib "Netapi32" _
  (ByVal servername As Long, _
   ByVal level As Long, _
   buf As Any, _
   ByVal prefmaxlen As Long, _
   entriesread As Long, _
   totalentries As Long, _
   ByVal servertype As Long, _
   ByVal domain As Long, _
   resume_handle As Long) As Long

Private Declare Function NetApiBufferFree Lib "netapi32.dll" _
   (ByVal Buffer As Long) As Long

Private Declare Sub CopyMemory Lib "kernel32" _
   Alias "RtlMoveMemory" _
  (pTo As Any, uFrom As Any, _
   ByVal lSize As Long)
   
Private Declare Function lstrlenW Lib "kernel32" _
  (ByVal lpString As Long) As Long

Private Declare Function InitiateSystemShutdown Lib "advapi32.dll" _
   Alias "InitiateSystemShutdownA" _
  (ByVal lpMachineName As String, _
   ByVal lpMessage As String, _
   ByVal dwTimeout As Long, _
   ByVal bForceAppsClosed As Long, _
   ByVal bRebootAfterShutdown As Long) As Long

Private Declare Function AbortSystemShutdown Lib "advapi32.dll" _
   Alias "AbortSystemShutdownA" _
  (ByVal lpMachineName As String) As Long

Private Declare Function GetComputerName Lib "kernel32" _
   Alias "GetComputerNameA" _
  (ByVal lpBuffer As String, _
   nSize As Long) As Long



Private Sub Form_Load()

   Dim sLocalMachine As String
   
   sLocalMachine = GetLocalComputerName()
  
   Call GetServers(sLocalMachine, Combo1)
   
   With Combo1
      .ListIndex = 0
      .ListIndex = -1
   End With
   
   With Combo2
      .AddItem "(immediate)"
      .AddItem "5"
      .AddItem "10"
      .AddItem "15"
      .AddItem "20"
      .AddItem "30"
      .AddItem "45"
      .AddItem "60"
      .ListIndex = -1
   End With
   
   Text1.Text = "The VBnet InitiateSystemShutdown Demo is " & _
                 "closing the current session. Kiss your apps goodbye!"
   
   Command1.Caption = "Perform Shutdown"
   Command2.Caption = "Abort Shutdown"
   
   Check1.Caption = "Shut down selected machine"
   Check2.Caption = "Force open apps closed if needed"
   Check3.Caption = "Reboot the computer"
   
   Label1.Caption = "Delay shutdown for               seconds"
   
  'invoke the check1 click event to set
  'the initial control states
   Check1.Value = vbChecked
   Check1.Value = vbUnchecked
   
End Sub


Private Sub Command1_Click()

   Dim sMachine As String
   Dim sAlertMessage As String
   
   Dim dwDelay As Long
   Dim dwForce As Long
   Dim dwReboot As Long
   Dim dwSuccess As Long
   
  'set up the parameters
   sMachine = "\\" & Combo1.List(Combo1.ListIndex)
  'alternate formats: 
  'sMachine = "192.168.1.101"
  'sMachine = Combo1.List(Combo1.ListIndex)
  
   sAlertMessage = Text1.Text & vbNullChar
   dwForce = Abs(Check2.Value = vbChecked)
   dwReboot = Abs(Check3.Value = vbChecked)
   
  'cause you're bound to forget!
   If Combo2.ListIndex > -1 Then
      dwDelay = Val(Combo2.List(Combo2.ListIndex))
   Else
      dwDelay = 30
   End If

  'success will be non-zero if successful.
  'Err.LastDllError will return the error
  'code if a problem, eg 5 - access denied.
   dwSuccess = InitiateSystemShutdown(sMachine, sAlertMessage, dwDelay, dwForce, dwReboot)
   
  'prevent changing the machine name in case
  'an abort is desired, and enable the abort button
   Combo1.Enabled = dwSuccess = 0
   Command1.Enabled = dwSuccess = 0
   Command2.Enabled = dwSuccess <> 0
   
End Sub


Private Sub Command2_Click()

   Dim sMachine As String
   
   sMachine = "\\" & Combo1.List(Combo1.ListIndex)
   AbortSystemShutdown sMachine
   
   Combo1.Enabled = True
   Command1.Enabled = True
   Command2.Enabled = False
   
End Sub


Private Sub Check1_Click()

   Check2.Enabled = Check1.Value = vbChecked
   Check3.Enabled = Check1.Value = vbChecked
   Combo2.Enabled = Check1.Value = vbChecked
   Label1.Enabled = Check1.Value = vbChecked
   Command1.Enabled = Check1.Value = vbChecked
   Command2.Enabled = False
   
End Sub


Private Sub Combo1_Click()

   Check1.Enabled = Combo1.ListIndex <> -1
   
End Sub


Private Function GetServers(sLocalMachine As String, ctl As Control) As Long

  'list all machines of the specified type
  'that are visible in the domain/workgroup
   Dim bufptr          As Long
   Dim dwEntriesread   As Long
   Dim dwTotalentries  As Long
   Dim dwResumehandle  As Long
   Dim se100           As SERVER_INFO_100
   Dim success         As Long
   Dim nStructSize     As Long
   Dim cnt             As Long
   Dim tmp             As String
   
   nStructSize = LenB(se100)
   
  'Call passing MAX_PREFERRED_LENGTH to have the
  'API allocate required memory for the return values.
  'The MSDN states servername must be NULL (0&).
   success = NetServerEnum(0&, _
                           100, _
                           bufptr, _
                           MAX_PREFERRED_LENGTH, _
                           dwEntriesread, _
                           dwTotalentries, _
                           SV_TYPE_ALL, _
                           0&, _
                           dwResumehandle)

  'if all goes well
   If success = NERR_SUCCESS And _
      success <> ERROR_MORE_DATA Then
      
    'loop through the returned data, adding
    'each machine to the list
      For cnt = 0 To dwEntriesread - 1
         
        'get one chunk of data and cast
        'into an LOCALGROUP_INFO_1 type
        'in order to add the name to a list
         CopyMemory se100, ByVal bufptr + (nStructSize * cnt), nStructSize
            
        'if the machine is the local machine, don't bother
        'adding it as you can't shut down your own machine
        'using InitiateSystemShutdown
         tmp = LCase$(GetPointerToByteStringW(se100.sv100_name))
         If tmp <> sLocalMachine Then
            ctl.AddItem tmp
         End If
         
      Next
      
   End If
   
  'clean up, regardless of success
   Call NetApiBufferFree(bufptr)

End Function


Private Function GetLocalComputerName() As String

   Dim tmp As String

  'return the name of the computer
   tmp = Space$(MAX_COMPUTERNAME)

   If GetComputerName(tmp, Len(tmp)) <> 0 Then
      GetLocalComputerName = LCase$(TrimNull(tmp))
   End If

End Function


Private Function GetPointerToByteStringW(ByVal dwData As Long) As String
  
   Dim tmp() As Byte
   Dim tmplen As Long
   
   If dwData <> 0 Then
   
      tmplen = lstrlenW(dwData) * 2
      
      If tmplen <> 0 Then
      
         ReDim tmp(0 To (tmplen - 1)) As Byte
         CopyMemory tmp(0), ByVal dwData, tmplen
         GetPointerToByteStringW = tmp
         
     End If
     
   End If
    
End Function


Private Function TrimNull(startstr As String) As String

   TrimNull = Left$(startstr, lstrlenW(StrPtr(startstr)))
   
End Function
 Comments
From the MSDN:

To shut down the local computer, the calling thread must have the SE_SHUTDOWN_NAME privilege. To shut down a remote computer, the calling thread must have the SE_REMOTE_SHUTDOWN_NAME privilege on the remote computer. By default, users can enable the SE_SHUTDOWN_NAME privilege on the computer they are logged onto, and administrators can enable the SE_REMOTE_SHUTDOWN_NAME privilege on remote computers. For more information, see Running with Special Privileges.

Common reasons for failure include an invalid or inaccessible computer name or insufficient privilege. The error ERROR_SHUTDOWN_IN_PROGRESS is returned if a shutdown is already in progress on the specified computer.

A non-zero return value does not mean the logoff was or will be successful. The shutdown is an asynchronous process, and it can occur long after the API call has returned, or not at all. Even if the timeout value is zero, the shutdown can still be aborted by applications, services, or even the system. The non-zero return value indicates that the validation of the rights and parameters was successful and that the system accepted the shutdown request.

When this function is called, the caller must specify whether or not applications with unsaved changes should be forcibly closed. If the caller chooses not to force these applications to close and an application with unsaved changes is running on the console session, the shutdown will remain in progress until the user logged into the console session aborts the shutdown, saves changes, closes the application, or forces the application to close. During this period the shutdown may not be aborted except by the console user, and another shutdown may not be initiated.

Note that calling this function with the value of the bForceAppsClosed parameter set to TRUE avoids this situation. Remember that doing this may result in loss of data.

Windows Server 2003, Windows XP: If the computer is locked and the bForceAppsClosed parameter is FALSE, the last error code is ERROR_MACHINE_LOCKED. If the system is not ready to handle the request, the last error code is ERROR_NOT_READY. The application should wait a short while and retry the call.


 
 

PayPal Link
Make payments with PayPal - it's fast, free and secure!

 
 
 
 

Copyright ©1996-2011 VBnet and Randy Birch. All Rights Reserved.
Terms of Use  |  Your Privacy

 

Hit Counter