The Two Sides of Group Policy Script Extension Processing, Part 2

By Judith Herman, Microsoft Corporation


In Part 2 of this two-part series, Judith Herman explains how to troubleshoot problems with Group Policy logon, logoff, startup, and shutdown scripts.

Have you ever deployed a script using Group Policy and wondered why it didn’t run? Part 1  of this article began to show you how to go about discovering the reason, focusing on how to determine if Group Policy scripts client-side extension (CSE) processed the scripts extension correctly.

Part 1 presented a way to read the registry keys representing Group Policy startup, logon, logoff, and shutdown scripts. This enables you to quickly see a list of the scripts deployed by Group Policy.

Part 2 delves deeper into resolving this issue by extending our Group Policy scripts knowledge past the point where Group Policy processes the client-side extension. In this part we will answer two questions: Why didn’t the script run when it should have, and why didn’t Group Policy report this to me?

For Microsoft Windows Server 2003, Windows XP, and Windows 2000 operating systems, the short answers to these questions are that there could be many reasons why the script didn’t run, but the Group Policy scripts CSE isn’t running the script and therefore can’t report the script failure through the event log.

But there is a process responsible for running the Group Policy startup, logon, logoff, and shutdown scripts that reports script failures: the USERINT process.

The USERINIT process

When Group Policy has processed the Group Policy scripts CSE, Group Policy’s job is done. The USERINIT process owns the job of running the Group Policy scripts for each startup, logon, logoff, and shutdown event.

For each of these events, the USERINIT process will perform the following tasks:

  • Based on the type of event, the USERINIT process will cycle through the corresponding Group Policy registry key representing the startup, logon, logoff, or shutdown scripts associated with each Group Policy object (GPO) applied either to the machine for startup or shutdown scripts or to the user for logon or logoff scripts.
  • The USERINIT process will launch the script along with any parameters using the appropriate privileges associated with either the machine or the user. This information is read from the registry keys.
  • The USERINIT process launches the scripts using calls to the ShellExecuteEx method. This limits the type of script that can be run to what ShellExecuteEx will process. Check out the documentation on MSDN on the ShellExecuteEx method.
  • The USERINIT process will launch all scripts for an event either synchronously, waiting for each script to finish before launching the next, or asynchronously, depending on the type of event and the policy settings affecting this event. For example, all startup scripts will be launched synchronously, by default, on a Microsoft Windows Server 2003, Windows XP, and Windows 2000 operating system. A Group Policy setting can be enabled to change all startup scripts to run asynchronously. The full path of this policy setting, in the Group Policy Object Editor, is Computer Configuration \ Administrative Templates \ System \ Scripts \ Run startup scripts asynchronously. Unsure how to set this policy setting? Check out the documentation about settingadministrative template policy settings.
  • If a script fails to run, USERINIT will log an error. A failed script will not necessarily cause all scripts to fail. The exception to the rule is in the synchronous case when a script fails and hangs. In this situation, the script processing will wait for either the hung script to complete or the Group Policy scripts processing to time out. The default timeout for Group Policy scripts processing is 10 minutes. This timeout value can be modified through Group Policy for each event. Be aware that this timeout value applies to the time it takes to run all scripts for an event. If you set this value too low, the USERINIT process may time out before all the scripts for an event have had time to complete.

Did my Group Policy script run?

Besides the obvious way to know the script ran of checking for the effect of the script, you can use the Resultant Set of Policy (RSoP) information for the Group Policy scripts CSE. This will tell you if the script ran by reporting the time the script ran. If the script has never executed successfully, you will see either a time of “0” or a blank entry in the Last Executed column. Figure 1 shows the RSoP display for a script that has never executed successfully. If it ran successfully at some point but then failed, you will see an old date and time of execution.

Note: The script execution time is reset to 0 if gpupdate /force is run on a machine. Administrators tend to force a Group Policy update when troubleshooting a problem. If you are looking for Group Policy script execution problems, you should reboot instead of forcing the update.

Figure 1 The RSoP display for a failed script.

You can also display this information via a script. There are two important issues to keep in mind when creating a script to gather the scripts extension information from RSoP. First, the information about the scripts extension is completely populated in the RSoP namespace only until the time when the RSoP provider was activated. In the example scripts provided below, the RSoP provider is activated using the following lines in the script.

 

 

Set provider = connection.Get("RsopLoggingModeProvider")



provider.RsopCreateSession FL_FORCE_CREATE_NAMESPACE, Null, namespaceLocation, hResult, eInfo

An array, stored in the ScriptList property, is populated with one entry for each Group Policy script that the USERINIT process launches. Each entry will provide information on the script arguments, execution time and script type of each called script in the ScriptList array.

The second issue is that execution time is stored in WMI time format (UTC time) instead of normal human-readable time. The WMIDateToString function is used to convert WMI time to the execution time you expect to see.

An example script to display computer Group Policy script execution times is provided here:

Const FL_FORCE_CREATE_NAMESPACE = 4



Function WMIDateToString(dtmDate)



WMIDateToString = (Mid(dtmDate, 5, 2) & "/" & _

                  Mid(dtmDate, 7, 2) & "/" & _

                  Left(dtmDate, 4) & " " & _

                  Mid(dtmDate, 9, 2) & ":" & _

                  Mid(dtmDate, 11, 2) & ":" & _

                  Mid(dtmDate, 13, 2))



End Function





strComputer = "."



Set locator = CreateObject("WbemScripting.SWbemLocator")



Set connection = locator.ConnectServer( strComputer, "root\rsop", null,null, null, null, 0, null)



Set provider = connection.Get("RsopLoggingModeProvider")



provider.RsopCreateSession FL_FORCE_CREATE_NAMESPACE, null, namespaceLocation, hResult, eInfo



'WScript.Echo "NameSpace: " & namespaceLocation



'WScript.Echo "HRESULT: " & hResult



'WScript.Echo "EInfo: " & eInfo



Set rsopProv = locator.ConnectServer _

    (strComputer, namespaceLocation & "\Computer", null, null, null, null, 0 , null)





Set colItems = rsopProv.ExecQuery("select * from RSOP_ScriptPolicySetting")





For Each objItem in colItems



    WScript.Echo String(50, "=")



    Wscript.Echo "GPO Name: " & objItem.name





    arrScriptList = objItem.ScriptList



    For Each objScript in arrScriptList



       WScript.Echo



      WScript.Echo vbTab & "Script: " & objScript.script



       Select Case objItem.scriptType

           Case 3

            WScript.Echo vbTab & "Type: Startup"

           Case 4

            WScript.Echo vbTab & "Type: Shutdown"

       End Select



       WScript.Echo vbTab & "Arguments: " & objScript.arguments



       WScript.Echo vbTab & "Execution Time: " & WMIDateToString(objScript.executiontime)



       WScript.Echo



    Next



Next



provider.RsopDeleteSession namespaceLocation, hResult

You can create a similar script to check the user Group Policy scripts that the USERINIT process launches. In this case, you need to connect to the RSoP provider for the user namespace. The values for the logon and logoff script types will be 1 and 2, respectively. And now you get the script to display launched user Group Policy scripts:

Const FL_FORCE_CREATE_NAMESPACE = 4



Function WMIDateToString(dtmDate)







WMIDateToString = (Mid(dtmDate, 5, 2) & "/" & _

                  Mid(dtmDate, 7, 2) & "/" & _

                  Left(dtmDate, 4) & " " & _

                  Mid(dtmDate, 9, 2) & ":" & _

                  Mid(dtmDate, 11, 2) & ":" & _

                  Mid(dtmDate, 13, 2))



End Function





strComputer = "."



Set locator = CreateObject("WbemScripting.SWbemLocator")



Set connection = locator.ConnectServer( strComputer, "root\rsop", null,null, null, null, 0, null)



Set provider = connection.Get("RsopLoggingModeProvider")



provider.RsopCreateSession FL_FORCE_CREATE_NAMESPACE, null, namespaceLocation, hResult, eInfo



'WScript.Echo "NameSpace: " & namespaceLocation



'WScript.Echo "HRESULT: " & hResult



'WScript.Echo "EInfo: " & eInfo



Set rsopProv = locator.ConnectServer _

    (strComputer, namespaceLocation & "\user", null, null, null, null, 0 , null)



Set colItems = rsopProv.ExecQuery("select * from RSOP_ScriptPolicySetting")



For Each objItem in colItems



    WScript.Echo String(50, "=")



    Wscript.Echo "GPO Name: " & objItem.name





    arrScriptList = objItem.ScriptList



    For Each objScript in arrScriptList



       WScript.Echo



       WScript.Echo vbTab & "Script: " & objScript.script



       Select Case objItem.scriptType

           Case 1

            WScript.Echo vbTab & "Type: Logon"

           Case 2

            WScript.Echo vbTab & "Type: Logoff"

       End Select



       WScript.Echo vbTab & "Arguments: " & objScript.arguments



       WScript.Echo vbTab & "Execution Time: " & WMIDateToString(objScript.executiontime)



       WScript.Echo



    Next



Next



provider.RsopDeleteSession namespaceLocation, hResult

The script didn't run—what do I do now?

Understanding that the USERINIT process—and not Group Policy—launches the Group Policy scripts provides the first step in troubleshooting why a Group Policy script didn’t run. You will want to look for any error messages in the application event viewer with USERINIT as the source. The error message will provide the path and name of the script along with the reason for failure to run the script. The typical failures you will see with running the Group Policy scripts are bad script path, a hung script, or access to the script is restricted via ACLS. An example of an event viewer error message from a failed logon script is shown in Figure 2.

Figure 2 The event message for a failed logon script

This same information can be displayed via a script that displays the application event viewer messages with USERINIT as a source. Below is an example of a script provides this information using the WMI service to query for the application Logfile with a SourceName of USERINIT.

strComputer = "."

Set objWMIService = GetObject("winmgmts:" _

    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")



Set colLoggedEvents = objWMIService.ExecQuery _

    ("Select * from Win32_NTLogEvent Where Logfile = 'Application'and " _

            & "SourceName = 'USERINIT'")



For Each objEvent in colLoggedEvents

    Wscript.Echo "Category: " & objEvent.Category

    Wscript.Echo "Computer Name: " & objEvent.ComputerName

    Wscript.Echo "Event Code: " & objEvent.EventCode

    Wscript.Echo "Message: " & objEvent.Message

    Wscript.Echo "Record Number: " & objEvent.RecordNumber

    Wscript.Echo "Source Name: " & objEvent.SourceName

    Wscript.Echo "Time Written: " & objEvent.TimeWritten

    Wscript.Echo "Event Type: " & objEvent.Type

    Wscript.Echo "User: " & objEvent.User

Next

So now you have some information about which script failed and possibly why the script failed. But say you get an "Access Denied" message for the script. You log on as administrator, and it runs just fine. What do you do now? Be aware that the USERINIT process will try to launch the script using the appropriate privileges. For startup or shutdown scripts, the USERINIT process will impersonate system privileges. For logon or logoff scripts, the USERINIT process will impersonate the user logging on. To test if the script is running under the appropriate privileges, make sure you are logged on either as that user, if they do not have full local administrator rights on the machine, or from a command window with system privileges.

One scenario usually not tested correctly is verifying a script with machine privileges. You can use the AT command to open a command window with system or machine privileges. To do so, type the following in a command window:

AT <current time + 1 min>/INTERACTIVE CMD

This sample AT command will cause a new window to open in one minute. The time must be in the future for the job to run. Also, the time is specified in 24-hour format. For information about the AT command, type the following text in a command window:

AT /?

What else could go wrong?

We’ve looked at a way to start troubleshooting the Group Policy scripts CSE when you suspect a script has failed. There is one scenario in which you can avoid having to troubleshoot later if you do some extra planning ahead of time when deploying scripts through Group Policy.

When you have mobile devices, such as laptops, you must take into account that the users will want to take their devices off the corporate network, such as to their homes.As we mentioned, when you set up a script to run synchronously, the script could fail to launch in such a way that it will hang until the timeout period has expired. Remember this timeout is set to 10 minutes by default. In the case of startup scripts, the machine will not fully boot until all the startup scripts have run or the Group Policy scripts extension has reached its timeout period for executing all startup scripts. And 10 minutes can be a very long time to wait for the machine to finish booting when you don’t know why it’s taking so long.

For this reason, it is best to be cautious when deploying startup scripts to mobile devices. When administrators create a Group Policy script policy setting they typically will locate the script on the SYSVOL directory of the domain controller for their domain. This works fine as long as the machine can get to a domain controller on startup. However, when the laptop is not on the corporate network, it will not be able to do this. In the case of mobile devices, it works best to either not deploy startup scripts or place the script in a standard location for each machine.

Final thoughts

To sum up, we’ve looked at how the USERINIT process actually launches Group Policy scripts and how you can start thinking about troubleshooting the problem of Group Policy scripts not running.