Auditing the System Time of Windows Servers
SystemTimeAudit.vbs checks for the switch to and from Daylight Saving Time
November 6, 2007
Executive Summary:
Run the SystemTimeAudit.vbs script before and after the switch to and from Daylight Saving Time to ensure that all your Microsoft Windows Server 2003 and Windows 2000 Server systems are time synchronized. You can also run SystemTimeAudit.vbs to check the time synchronization of your servers if you need a tool to help troubleshoot time-related Kerberos authentication problems in your Active Directory domain. SystemTimeAudit.vbs builds on the ScriptTemplate.vbs script to check which servers might be out of sync. |
The earlier shift to Daylight Saving Time (DST) that occurred last spring in the U.S. posed some concern for system administrators because Kerberos, the preferred protocol used for authentication by computer systems in Active Directory (AD) domains, relies upon time synchronization between computers to work properly. Therefore, I wrote a script called SystemTimeAudit.vbs to audit the system time of the servers in my company's AD domains and validate their change to and from DST.
The Importance of Time Synchronization
Not only can SystemTimeAudit.vbs be used to validate DST change, but it can also be used to help troubleshoot time-related Kerberos authentication issues in AD domains because Kerberos relies on timestamps as one of its elements for network authentication. By default, the time skew allowed by Kerberos authentication between two computers over the network is 5 minutes, although the value is configurable in Group Policy.
Some of the domain operations that use Kerberos authentication include:
Client-server authentication
AD replication
Print services
Remote server or workstation management using RPC calls
AD queries using LDAP
Remote file access using Common Internet File System/Server Message Block (CIFS/SMB)
DFS management and referrals
Intranet authentication to Microsoft IIS
Security authority authentication for IPsec
Certificate requests to Certificate Services for domain users and computers
For more information about Kerberos authentication, please refer to the Microsoft TechNet article "How the Kerberos Version 5 Authentication Protocol Works" (technet2.microsoft.com/windowsserver/en/library/4a1daa3e-b45c-44ea-a0b6-fe8910f92f281033.mspx).
Out-of-sync clocks between clients and servers can cause Kerberos authentication to fail or extra traffic during the Kerberos authentication exchange. Kerberos also uses timestamps to protect against replay attacks. With so many operations relying on Kerberos authentication and Kerberos relying on system time, you can see that having an easy way to check that server times are in sync is a good idea.
SystemTimeAudit.vbs
The SystemTimeAudit.vbs script comprises the code from the ScriptTemplate.vbs script that I wrote for “Don't Let Your AD Scripts Hang on You” (www.scriptingprovip.com/articles/articleid/96423/96423.html) and some custom functions that I added to the section of ScriptTemplate.vbs that's designed to contain custom functions and subroutines specific to a particular script’s purpose. You can download SystemTimeAudit.vbs and ScriptTemplate.vbs by clicking Download the Code Here at the top of the article. ScriptTemplate.vbs's ProcessServers subroutine calls the custom functions.
SystemTimeAudit.vbs uses seven custom functions to audit the system time:
getPDCEmulator obtains the server name of the PDC emulator Flexible Single-Master Operation (FSMO) of the domain.
checkAutoDST checks whether a computer's Automatically adjust clock for daylight saving changes setting is enabled.
getTimeZoneName obtains the time zone of the target server.
getDiffTime obtains the times from the time source server and the target server and compares the two.
WMIDateStringToDate converts the UTC date-time format from Windows Management Instrumentation (WMI) to the standard date-time format.
setUTCForAutoDST corrects the UTC time value for the Automatically adjust clock for daylight saving changes setting that's used to correct for the time zone differences.
checkDSTInEffect checks whether DST is in effect for a particular time zone.
The getPDCEmulator Function
Listing 1 shows the getPDCEmulator function. As callout A in Listing 1 shows, after declaring its variables, the getPDCEmulator function uses the GetObject call to bind to the domain controller’s (DC's) RootDSE object. It uses a second GetObject call and the Get method to read the information from the domain’s defaultNamingContext attribute. The function uses the second Get method to retrieve the value of the fSMORoleOwner attribute and sets it to the strPdcEmulator variable.
Next, the function uses a third GetObject call to connect to the NT Directory Services (NTDS) settings from strPdcEmulator and uses another GetObject call to bind to the Parent property of the NTDS settings. The next line of code obtains the Common Name (CN) of the PDC emulator and returns the result to the function. The value returned from this function is assigned to the strTSServer variable in the ProcessServers subroutine, as callout A in Listing 8 shows.
The checkAutoDST Function
As Listing 2 shows, the checkAutoDST function obtains the Automatically adjust clock for daylight saving changes setting by querying the registry of the target machine. The function accepts one input parameter, which is the computer name of the target machine.
The function starts by defining the constant for the HKEY_LOCAL_MACHINE registry subtree and declaring the variables. Next, it sets the strKeyPath and strValueName variables for the registry subkey path and value name respectively. The registry subkey path that contains the Automatically adjust clock for daylight saving changes setting is HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlTimeZoneInformation, and the value name is DisableAutoDaylightTimeSet with a value type of REG_DWORD.
The returned value from the registry query is stored in the strValue variable. As shown in callout A in Listing 2, when the value of the strValue variable is 1, the setting for Automatically adjust clock for daylight saving changes is disabled, and checkAutoDST is set as “Disabled.” Otherwise, the checkAutoDST is set to “Enabled.” The ProcessServers subroutine sets the strAutoDST variable equal to the checkAutoDST value and logs the returned value to the main report log file, as shown at callout B in Listing 8.
The getTimeZoneName Function
Listing 3 shows the getTimeZoneName function, which obtains the time zone name by querying the Win32_TimeZone class of WMI. The function accepts one input parameter—the computer name of the target machine. After declaring its variables, the function sets the objWMIService variable to create an object instance of WMI to connect to the target machine and then sets the colTimeZone variable to query the data from the Win32_TimeZone class. Next, the function uses a For loop to enumerate the time zone name by querying the StandardName property of the Win32_TimeZone class. The strTZName variable is set to the value returned from this function within the ProcessServers subroutine and logged to the main report log file as shown at callout B in Listing 8.
The getDiffTime Function
The most important of the custom functions in SystemTimeAudit.vbs is the getDiffTime function in Listing 4. This function accepts two input parameters that are passed from the ProcessServers subroutine. The first parameter is the computer name of the time source server. (For the purposes of this article, the time source server is the PDC emulator whose name was obtained by the getPDCEmulator function. I explain more about which server is used as time source for the AD domain in the last section of the article.) The second parameter is the computer name of the target server.
Like all other functions, the getDiffTime function begins by declaring its variables. As callout A in Listing 4 shows, the getDiffTime function sets the objWMIService1 variable to create an object instance to establish the WMI connection to the time source server and sets the objWMIService2 variable to create an object instance to establish the WMI connection to the target server. Next, the function sets the colItems1 variable to query the Win32_OperatingSystem class from the time source server and the colItems2 variable to query the Win32_OperatingSystem class of the target server.
As shown at callout B in Listing 4, the getDiffTime function continues with a double For loop statement to enumerate the local time of the time source server and the target server by querying the LocalDateTime property of the Win32_OperatingSystem class of each server. Querying the local times of the two servers by using one For loop within another ensures the smallest possible delay between the two queries of the local time on the two servers. The date-time values returned by the two queries are then set to the dtmDateTime1 variable for the time source server and the dtmDateTime2 variable for the target server.
The date-time values from these two queries are in the Coordinated Universal Time (UTC) date-time format yyyymmddHHMMSS.xxxxxx±UUU, where
yyyy is the year
mm is the month
dd is the day
HH is the hour (in 24-hour format)
MM is the minutes
SS is the seconds
xxxxxx is the milliseconds
UUU is the difference, in minutes, between the local time zone and UTC time. A minus sign (-) in front of this number means that the local time in that particular time zone is that many minutes behind the UTC time; a plus sign (+) means that the local time in that time zone is that many minutes ahead of the UTC time.
For example, in the UTC date-time of 20070309133915.186000-300,
2007 is the year
03 (March) is the month
09 is the day
13 (1 p.m.) is the hour
39 is the minutes
15 is the seconds
186000 is the milliseconds
-300 is 300 minutes behind UTC time
UTC is the standard, or reference, time zone that Windows Time Service and the Network Time Protocol (NTP) use to synchronize clocks on the systems in a Windows domain across a network. Therefore, the getDiffTime function uses UTC time for time synchronization between the source and the target servers.
It's important to note that in a time zone in which DST is in effect, the UTC time offset value from a computer with the Automatically adjust clock for daylight saving changes option enabled is 60 minutes ahead of a computer with the Automatically adjust clock for daylight saving changes option disabled. The UTC time, which is used by the computer to synchronize its clock, never changes, even when DST is in effect. The OS adjusts the system time by adding 60 minutes to the UTC time offset to reflect the time for DST.
Because the UTC date-time format can't be used with the DateDiff function, the getDiffTime function calls the WMIDateStringToDate function to convert the UTC date-time result of the above queries to the standard date-time format. The returned values from the WMIDateStringToDate function are then set to the strDateTime1 variable for the time source server and the strDateTime2 variable for the target server. With the date-time values in readable format, the getDiffTime function calls the DateDiff function to obtain the time difference between the two values and sets the result to the strTimeDelta variable with the time value in seconds, as shown at callout C in Listing 4.
As callout D in Listing 4 shows, the getDiffTime function uses the Right function to obtain the UTC time offsets from the UTC date-time values from the two machines and sets the values to the strTZUTC1 and strTZUTC2 variables for the time source server and the target server, respectively. Next, the function calls the setUTCForAutoDST function to correct the UTC time offset with four parameters passed to the setUTCForAutoDST function. The first parameter is the computer name of the time source server. The second parameter is the computer name of the target server. The third parameter is the strTZUTC1 variable. The fourth parameter is the strTZUTC2 variable. The strTZUTC variable is set to the return value from this function call, which is in minutes. The getDiffTime function then converts this value to seconds by multiplying it by 60. The getDiffTime function corrects the time for the UTC time offset by subtracting the strTZUTC value from the strTimeDelta value and then sets the strTimeDelta variable to the result of the subtraction.
Callout E in Listing 4 shows the If ... Then ... ElseIf statement that's used to determine the value of strTimeDelta to see whether it's less than 0, greater than 0, or equal to 0. If it’s less than 0, the function sets a minus sign (-) to the strSign variable. If it’s greater than 0, the function sets a positive sign (+) to the strSign variable. If it’s equal to 0, the value of strSign is set to null. strSign will be used to correct the final value of strTimeDelta after the time conversion.
The next line of code at callout E in Listing 4 uses the Abs function to obtain the absolute value of the strTimeDelta variable and sets the result to the same variable name as strTimeDelta. A positive time value is needed in order for the convertTime function to properly convert the time in seconds to hh:mm:ss format. (Remember that the absolute value of any number is always positive, whether the number is positive or negative.) The getDiffTime function then calls the convertTime function to convert the value of the strTimeDelta variable to hh:mm:ss format and sets strTimeDelta to the converted value. Next, the strSign variable is added to strTimeDelta to indicate whether the time of the target server is the same, slower, or faster than that of the source server.
As callout F in Listing 4 shows, the getDiffTime function uses the Mid function to remove the date from the strDateTime1 and strDateTime2 variables and sets the returned values to the strTime1 and strTime2 variables, respectively. Finally, the strTimeDelta variable is appended to the strTime1 and strTime2 variables with a tab between each variable to form a string that's the function's returned value. The ProcessServers subroutine sets the strTimeDelta variable to the returned value and logs to the main report log file, as shown at callout B in Listing 8. When the value of the strTimeDelta is zero, the target server’s time is synchronized with the time source server’s time. When the value of strTimeDelta is positive, the time source server’s time is faster than that of the target server. When strTimeDelta is negative, the target server’s time is slower than the time source server’s time.
The WMIDateStringToDate Function
As shown in Listing 5, the WMIDateStringToDate function accepts one input parameter, which is the UTC time from the WMI query. The WMIDateStringToDate function converts the UTC date-time format to the standard date-time format as follows. First, it uses the Mid function to grab the two characters that begin at position 5 in the string—Mid(dtmDate,5,2)—for the month, adds a forward slash character (/), grabs the two characters that begin at position 7 in the string—Mid(dtmDate,7,2)—for the day, adds another forward slash character (/), and uses the Left function to grab the four leftmost characters for the year.
Next, the WMIDateStringToDate function adds a blank space and uses the Mid function to grab the two characters that begin at position 9 in the string—Mid(dtmDate,9,2)—for the hour, adds a colon character (:), grabs the two characters that begin at position 11 in the string—Mid(dtmDate,11,2)—for the minutes, adds another colon character (:), and grabs the two characters that begin at position 13 in the string—Mid(dtmDate,13,2)—for seconds. Using the UTC date-time sample of 20070309133915, the result of the conversion would be 03/09/2007 13:39:15. WMIDateStringToDate uses the CDate function to convert the 24-hour-clock time to the standard format of 3/9/2007 1:39:15 PM.
The setUTCForAutoDST Function
As shown in Listing 6, the setUTCForAutoDST function corrects the UTC time offset for the getDiffTime function by calling the checkDSTInEffect function and the checkAutoDST function. As mentioned previously, the setUTCForAutoDST function accepts four parameters from the getDiffTime function.
As shown at callout A in Listing 6, checkDSTInEffect first checks the time source server and the target server to see whether both servers are in a time zone where DST is currently in effect. If they are, checkAutoDST uses the If ... Then ... ElseIf statement to check both servers' Automatically adjust clock for daylight saving changes setting and setUTCForAutoDST adjusts the UTC time offset accordingly as follows:
If both servers have the Automatically adjust clock for daylight saving changes option enabled, setUTCForAutoDST subtracts the time value of strTZUTC1 from strTZUTC2, returns the result to the function, and exits.
If both servers have the setting unchecked, setUTCForAutoDST subtracts the time value of strTZUTC1 from strTZUTC2, returns the result to the function, and exits.
If the source server has the option checked and the target server has the option unchecked, the script checks strTZUTC2 to see whether it's Auckland, Wellington (New Zealand Standard Time), which has a value of +720. If it is, setUTCForAutoDST simply subtracts strTZUTC1 from strTZUTC2, returns the result to the function, and exits. Otherwise, setUTCForAutoDST adds 60 to strTZUTC2 first, then subtracts the value of strTZUTC1 from strTZUTC2, returns the result to the function, and exits.
If the source server has the option unchecked and the target server has the option checked, the script checks strTZUTC1 to see whether it's Auckland, Wellington (New Zealand Standard Time), which has a value of +720. If it is, setUTCForAutoDST simply subtracts strTZUTC1 from strTZUTC2, returns the result to the function, and exits. Otherwise, setUTCForAutoDST adds 60 to strTZUTC1 first, then subtracts the value of strTZUTC1 from strTZUTC2, returns the result to the function, and exits.
Remember that adding 60 minutes to the UTC time is the same as enabling the Automatically adjust clock for daylight saving changes option on a machine, which also moves the clock one hour forward during DST. However, time in the Auckland, Wellington (New Zealand Standard Time) time zone doesn't advance one hour when Automatically adjust clock for daylight saving changes is enabled, even though New Zealand does observe DST. Therefore, the UTC value from this time zone is treated differently from other time zones in the third and fourth bulleted items above.
Callout B in Listing 6 shows the ElseIf statement that runs when DST is not in effect. In this case, the setUTCForAutoDST function simply subtracts strTZUTC1 from strTZUTC2, returns the result to the function, and exits.
Callout C in Listing 6 shows the ElseIf statement that determines the correct UTC when the time source server is in a time zone with DST currently in effect and the target server is not in a time zone with DST. In this instance, if the time source server has the Automatically adjust clock for daylight saving changes option checked, the script subtracts strTZUTC1 from strTZUTC2, returns the result to the function, and exits. Otherwise, the script adds 60 to strTZUTC1, then subtracts strTZUTC1 from strTZUTC2, returns the result to the function, and exits.
Finally, as shown at callout D in Listing 6, the script uses the ElseIf statement to determine the correct UTC when the time source server is not in the DST time zone and the target server is in the DST time zone. In this situation, if the target server has the Automatically adjust clock for daylight saving changes option checked, the setUTCForAutoDST function simply subtracts strTZUTC1 from strTZUTC2, returns the result to the function, and exits. Otherwise, the script adds 60 to strTZUTC2, then subtracts strTZUTC1 from strTZUTC2, returns the result to the function, and exits. As stated previously, the value returned from the setUTCForAutoDST function is set to the strTZUTC variable within the getDiffTime function to adjust for the UTC time value to correct for the zone differences between the time source and target servers.
It's important to note that the setUTCForAutoDST function doesn't just correct the UTC value for the time zone differences. It also corrects the UTC value for a machine that has the Automatically adjust clock for daylight saving changes setting disabled in a time zone when DST is in effect. If a machine observing DST in a time zone with DST doesn't have its Automatically adjust clock for daylight saving changes option enabled (due to neglect or for some purpose), but the system clock has been manually set by the administrator or advanced by Windows Time Service to the correct time for that time zone, the script is still able to correctly compare the times between the source and target servers. If the machine in a time zone with DST has the Automatically adjust clock for daylight saving changes option disabled, and the system clock has not been advanced by an administrator or Windows Time Service, then the report will show the target server one hour slower than the current time for that time zone when DST is in effect. In the latter case, the report also shows that the Auto DST option is disabled on that particular machine. One of the purposes of the setUTCForAutoDST function is to capture mistakes made by administrators who have forgotten to enable the Automatically adjust clock for daylight saving changes option.
The checkDSTInEffect Function
As shown in Listing 7, the checkDSTInEffect function discovers whether DST is in effect by querying the WMI Win32_ComputerSystem class. The function accepts one input parameter—the computer name of the target machine. After declaring its variables, the function sets the objWMIService variable to create an object instance of WMI to connect to the target machine and then sets the colItems variable to query the DaylightInEffect property of the Win32_ComputerSystem class in a For loop statement. As callout A in Listing 7 shows, if the If statement within the For loop is evaluated to true, the function’s return value is set to True and the function exits. Otherwise, the return value is set to False.
The ProcessServers Subroutine
For more information about the ProcessServers subroutine, please refer to the article “Don't Let Your AD Scripts Hang on You.” In this article, I describe only the sections of the ProcessServers subroutine that I adapted to use with SystemTimeAudit.vbs. As stated previously, the time source server used for SystemTimeAudit.vbs is the PDC emulator of the domain obtained by the getPDCEmulator function. This is shown at callout A in Listing 8. Callout B in Listing 8 shows the section of the ProcessServers subroutine that calls three functions to obtain the data for logging and writes the data to the main report log file.
How the Script Handles Time Zone Differences
Because SystemTimeAudit.vbs can audit the system time of a remote machine from a central location, the time zone of the computer in which the script is launched might differ from that of the remote machine, especially in a large enterprise environment with Windows servers located worldwide. The script has to know how to handle the time zone difference so that it can correctly audit the system time of the remote machine. I want to elaborate on how the functions used in SystemTimeAudit.vbs know which time value to subtract from which other time value in order to correct for the time zone difference.
For example, suppose the script is launched from a machine in the Central Time zone. Assume that the remote server located in the Eastern Time zone doesn't gain or lose time and DST isn't in effect. The UTC time offset for the server in the Central Time zone is -360 minutes. This time offset is set to the strTZUTC1 variable, which belongs to the time source server. The UTC time offset for the server located in the Eastern Time zone is -300, which is set to the strTZUTC2 variable that belongs to the target server. The values of these two variables are passed to the setUTCForAutoDST function for a UTC time offset correction. Subtracting the value of strTZUTC1 from strTZUTC2 yields 60 because the result of subtracting a larger negative number from a smaller negative number is a positive number. This value is the value of the strTZUTC variable returned from the setUTCForAutoDST function. Convert this value (60) to seconds by multiplying it by 60 to yield 3600. The return value of the strTimeDelta variable from the DateDiff function between the two servers is 3600. Subtracting the time value of the strTZUTC variable from the strTimeDelta variable yields 0. Sample 1 in Figure 1 shows these calculations.
Let's look at another example for a target server located in the Pacific Time zone. For this example, assume that the time of the target server is 5 minutes slower than the current time for that time zone, and DST is in effect. Both the source and the target servers have the Automatically adjust clock for daylight saving changes option enabled. Sample 2 in Figure 1 shows these calculations.
Using the Script
Before you use SystemTimeAudit.vbs, you need to know the time source server that's used as the reference clock in your domain. In a Windows 2003 or Win2K Server domain forest, the default time convergence hierarchy follows the following rules:
All client desktops and member servers nominate their authenticating DC as their inbound time source.
All DCs in a domain nominate the PDC emulator FSMO to be the inbound time partner.
All PDC emulator FSMOs in the enterprise follow the hierarchy of the domains in their selection of an inbound time partner.
The PDC emulator FSMO at the root of the forest is authoritative and can be manually set to synchronize with an outside time source.
As stated previously, the getPDCEmulator function can automatically obtain the server name of the PDC emulator of the domain to be used as a time source server. You don’t need to modify SystemTimeAudit.vbs before you use it if the time convergence hierarchy of your domain follows the default settings. Otherwise, you need to modify the code strTSServer = getPDCEmulator() in the ProcessServers subroutine to point to your designated time source server. Also, because SystemTimeAudit.vbs uses WMI components to compare the times of the time source and target servers, the OS of your time source server must be Windows 2003 or Win2K Server.
To validate the DST change, you typically run SystemTimeAudit.vbs once before the change and then again immediately after the change. Then verify the result by reviewing the Time Delta field of the Domain_System_Time_Servers_Date.xls report log file.
As I indicated previously, SystemTimeAudit.vbs can also be used to help troubleshoot time-related Kerberos authentication issues in Windows AD domains. In this instance, the administrator of the domain might need to modify the Kerberos time window allowed for authentication in Group Policy to be longer than 5 minutes, the default setting. In fact, the Access is denied error that some of the servers show in the Domain_Error_Servers_Date.xls report log file generated by the script might be caused by time synchronization problems between the source and target servers.
About the Author
You May Also Like