Time Sync in MDT 2010/WinPE, take 3

Third time is the charm? I found after a few passes that the script I put together for syncing system clocks in Windows was not actually working the way that I thought it was. While the system date was getting set correctly, time consistently was off by three hours. WTF?!?!? The problem, as it turns out, is not in coding errors, but rather in a misunderstanding of WinPE system time.

WinPE, as you may know, has no control panels. It has no “Windows Time” service. It has no time service management utilities such as “w32tm.exe”. So since it has no way to display its time zone settings, does that mean that it is not Time Zone aware? The answer is no.

WinPE has a default timezone of PST (GMT-8). This can be changed using the DISM utility, but sadly setting the default timezone for your LiteTouch/MDT boot images is not an option that is exposed in the Deployment Workbench. You would have to update the source winpe.wim image in the Windows AIK “toolspetools” directory it change the default for all future workbench-generated boot images. Rather than do this, and risk forgetting to do it again for all future AIK updates, I decided to set the timezone in my LiteTouch task sequences. It is pretty easy, requiring one additional custom command line in the task sequence:

reg.exe import "%SCRIPTROOT%EST_tz.reg"

The “reg” file referenced here is simply an export of HKLMSystemCurrentControlSetControlTimeZoneInformation, from a system in the correct local timezone.

I then updated the WinPE timesync script to sync directly against our deployment server using “net time /set /y”, since this seems like the most reliable tool in WinPE:

Option Explicit
RunNewInstance

'//—————————————————————————-
'//  Global Constants
'//—————————————————————————-

'const DEPLOY_SERVER = "\sysimg3.campus.ad.uvm.edu"

'//—————————————————————————-
'//  Main Class
'//—————————————————————————-

Class ZUVMsetTimePE

    '//—————————————————————————-
    '//  Global constant and variable declarations
    '//—————————————————————————-

    Dim iRetVal

    '//—————————————————————————-
    '//  Constructor to initialize needed global objects
    '//—————————————————————————-

    Private Sub Class_Initialize

    End Sub
    '//—————————————————————————-
    '//  Main routine
    '//—————————————————————————-

    Function Main
	
		' setTimePE.vbs
		' J. Greg Mackinnon, 2010-10-12
		' Sets time from within a WinPE 3.0 environment.
		' ASSUMES:  Local timezone is set correctly in the WinPE system registry.
		' ASSUMES:  Time on the deployment server is set correctly.

		' Declare objects and variants
		Dim oExec
		Dim sDPServ
		
		' Initiate variants
		sDPServ = oEnvironment.Item("SMSDP")

		' Procedure: Display current time:
		oLogging.CreateEntry "setTime> " & "Current Time is: " & Date & " " & Time, LogTypeInfo
		
		' Procedure: Set local time against time on deployment server:
		oLogging.CreateEntry "setTime> About to run command: " & "net.exe time \" & sDPServ & " /set /y", LogTypeInfo
		set oExec = oShell.Exec("net.exe time \" & sDPServ & " /set /y")
		Do While oExec.Status = 0
			 WScript.Sleep 100
		Loop
		do while not oExec.StdOut.AtEndOfStream
			oLogging.CreateEntry "setTime> " & oExec.StdOut.ReadLine, LogTypeInfo
		loop
		oLogging.CreateEntry "setTime> " & "Current Time is now: " & Date & " " & Time, LogTypeInfo

	End Function

End Class

   

ApplicationXtender: 5.4 to 6.5 Upgrade Notes

Time again for another adventure in upgrading ApplicationXtender… this time to the much awaited 6.x release family.  Although not a revolutionary release, it is much welcome owing to the following architecture changes:

  1. Support for Server 2008 and Server 2008 R2 OS platforms (and thus, support for 64-bit operating systems).
  2. Integrated Adobe PDF 9.x libraries, solving compatibility problems with Adobe Reader.
  3. Web Access component now compiled in ASP.NET 2.0 (instead of the retired ASP.NET v1).
  4. Web Access Thumbnail viewer is now a Silverlight control instead of a Java applet (a dubious improvement).
  5. The DiskXtender image repository now implements (and AX now supports) the use of Authenticated RPC calls, instead of the unforgivable unauthenticated and unencrypted RPCs used in previous versions of the software.  However, this “improvement” not withstanding, we will be discontinuing the use of DiskXtender with this upgrade.  When you look at actual implementation details, use of CIFS/SMB2 offers us equivalent security (or more security, if implemented in conjunction with IPsec) at a lower cost, and with more configuration options.
  6. Oracle 11g databases are now supported, for what that is worth (we will be using 10g R2 for a bit longer).

I am not going to go though a step-by-step here, but I will note the most significant configuration quirks that either were not documented in the deployment guides, or that were inadequately documented.  These settings are necessary if running AX in the “least privilege” mode, instead of the moronic “make all of your service accounts domain admins” mode:

  • All AX components (except licensing) are all still 32-bit applications.  As a result, they require the availability of 32-bit libraries for interacting with external (third party components).  As a result, we cannot expect 64-bit Oracle database clients to be of any use.  We have developed 32-bit only Oracle InstantClient v11.2.0.1 installers for use with this product.
  • If installing the license service on a 64-bit host, be aware that EMC has released a new version of the license server specifically for 64-bit computers.  You should not use the version that ships with the initial 6.5 release.
    • The license service has been rewritten since AX 5.4… it now requires client access to TCP port 9251 only.  Fortunately, we no longer need to muck with DCOM component security settings to get licensing to work.
  • After installing the WebAccess.NET components into IIS, be sure to grant the Service Account rights to .NET framework temp files directory (In “%windir%.NET Frameworkv2.xxxxx.NET Framework Temporary Files” (I think that is the correct path)).
  • After installing the AX Rendering service, you need to make the following additional changes:
    • In the properties of the Rendering service (services.msc control panel), you must clear the “Allow Service to Interact with Desktop” option.
    • You must grant the service account read/write access to the Rendering service installation directory in “%ProgramFiles%XtenderSolutions”.
  • After installing the Indexing Server (if you are using it), and before configuration, you must ensure that your Indexing service account has a password that is fewer than 24 characters in length.  The configuration utility will not complain that your password is too long, but it will truncate your password before encrypting it for storage, and the service will then fail to start (silently, and without generating any useful log information… aargh!).
Grant Service Account rights to .NET framework temp files directory

Time Sync Scripts… updated for MDT 2010/LiteTouch

Update: Although the scripts in this post work, a correction is required for time to sync properly in the winPE environment. An updated WinPE script can be found here:
http://blog.uvm.edu/jgm/2010/10/14/time-sync-in-mdt-2010winpe-take-3/

With some help from the indispensable MDT Debugger, I have managed to get my quick Time Sync VBScript into MDT 2010: http://blogs.technet.com/b/deploymentguys/archive/2010/03/22/mdt-debugger.aspx

The script I posted previously needed a few quick adjustments to enable proper logging to the “MININT” deployment directories. Here is the updated version. If you want to use it, give it a name starting with “Z” and drop it in the “Scripts” directory of your deployment share:


   
   

Option Explicit
RunNewInstance

'//—————————————————————————-
'//  Global Constants
'//—————————————————————————-

'//—————————————————————————-
'//  Main Class
'//—————————————————————————-

Class ZUVMsetTime

    '//—————————————————————————-
    '//  Global constant and variable declarations
    '//—————————————————————————-

    Dim iRetVal

    '//—————————————————————————-
    '//  Constructor to initialize needed global objects
    '//—————————————————————————-

    Private Sub Class_Initialize

    End Sub
    '//—————————————————————————-
    '//  Main routine
    '//—————————————————————————-

    Function Main

	' SetTime.vbs script
	' J. Greg Mackinnon, 2010-10-04
	' Actions: Syncs local system clock to "ntp.pool.org" time using the NTP protocol.
	'          Will change registry values for maximum time skew correction If necessary, Then revert to original values
	'          ReSets the w32time service during execution, but NOT at the End of the script.  An manual restart is required to 
	'            revert Domain-joined systems to defaults.
	' Requires: w32tm.exe, net.exe.  Both should be present on all Vista/Win7 systems.
	' Tested on: WinDows 7.  Should work on Vista as well... NOT intEnded for XP systems.

		Dim oExec
		Dim iPosRegVal, iNegRegVal
		Dim strKeyPath, strPosValueName, strNegValueName

		strKeyPath = "HKLMSYSTEMCurrentControlSetservicesW32TimeConfig"
		strPosValueName = "MaxPosPhaseCorrection"
		strNegValueName = "MaxNegPhaseCorrection"

		oLogging.CreateEntry "setTime> " &  "Current Time is: " & Date & " " & Time, LogTypeInfo

		'This works, If you can understand the screwball interger value that gets returned.  
		'Everything over hex 0x0fffffff is listed as a negative interger.
		'0xffffffff returns -1.
		iPosRegVal = oShell.RegRead(strKeyPath & "" & strPosValueName)
		iNegRegVal = oShell.RegRead(strKeyPath & "" & strNegValueName)
		oLogging.CreateEntry "setTime> " &  "strNegValueName value is: " & iNegRegVal, LogTypeInfo
		oLogging.CreateEntry "setTime> " &  "StrPosValueName value is: " & iPosRegVal, LogTypeInfo

		If iPosRegVal  -1 Then
			oLogging.CreateEntry "setTime> " &  "Maximum allowed clock skew correction is NOT large enough... Setting to maximum value."
			'Setting the Max Phase Correction registry values to "-1" (or 0xffffffff in hex), 
			'which will allow correction of local time by any amount.
			oShell.RegWrite strKeyPath & "" & strPosValueName, -1, "REG_DWORD"
			oShell.RegWrite strKeyPath & "" & strNegValueName, -1, "REG_DWORD"
			oLogging.CreateEntry "setTime> " &  strPosValueName & " is now Set to: " & oShell.RegRead(strKeyPath & "" & strPosValueName), LogTypeInfo
			oLogging.CreateEntry "setTime> " &  strNegValueName & " is now Set to: " & oShell.RegRead(strKeyPath & "" & strNegValueName), LogTypeInfo
		Else
			oLogging.CreateEntry "setTime> " &  "This system already already is configured to allow large clock skew corrections.", LogTypeInfo
		End If 

		oLogging.CreateEntry "setTime> " &  "Setting WinDows Time service Manual-sync NTP Server to ""pool.ntp.org""", LogTypeInfo
		' Pool.ntp.org is a collection of Internet NTP time servers.  
		' It is the default time source for stand-alone RedHat installs,
		' and apparently it is a but more reliable than "time.winDows.com"
		Set oExec = oShell.Exec("w32tm.exe /config /manualpeerlist:pool.ntp.org /update")
		Do While oExec.Status = 0
			 WScript.Sleep 100
		Loop
		Do While NOT oExec.StdOut.AtEndOfStream
			oLogging.CreateEntry "setTime> " &  oExec.StdOut.ReadLine, LogTypeInfo
		Loop

		'Stopping the w32time service.  
		'Necessary because changes to the w32time service will NOT take effect until service restart.
		Set oExec = oShell.Exec("net.exe stop w32time")
		Do While oExec.Status = 0
			 WScript.Sleep 100
		Loop
		Do While NOT oExec.StdOut.AtEndOfStream
			oLogging.CreateEntry "setTime> " &  oExec.StdOut.ReadLine, LogTypeInfo
		Loop

		'Starting the w32time service
		Set oExec = oShell.Exec("net start w32time")
		Do While oExec.Status = 0
			 WScript.Sleep 100
		Loop
		Do While NOT oExec.StdOut.AtEndOfStream
			oLogging.CreateEntry "setTime> " &  oExec.StdOut.ReadLine, LogTypeInfo
		Loop

		'Forcing a time service resync
		'Time would resync on its own soon enough, but we are impatient and want to see results immediately.
		Set oExec = oShell.Exec("w32tm.exe /resync")
		Do While oExec.Status = 0
			 WScript.Sleep 100
		Loop
		Do While NOT oExec.StdOut.AtEndOfStream
			oLogging.CreateEntry "setTime> " &  oExec.StdOut.ReadLine, LogTypeInfo
		Loop

		oLogging.CreateEntry "setTime> " &  "Current Time is: " & Date & " " & Time, LogTypeInfo

		If iPosRegVal  -1 Then
			oLogging.CreateEntry "setTime> " &  "ReSetting registry maximum allowed clock skew correction Settings to their original values...", LogTypeInfo
			oShell.RegWrite strKeyPath & "" & strPosValueName, iPosRegVal, "REG_DWORD"
			oShell.RegWrite strKeyPath & "" & strNegValueName, iNegRegVal, "REG_DWORD"
			oLogging.CreateEntry "setTime> " &  strPosValueName & " is now Set to: " & oShell.RegRead(strKeyPath & "" & strPosValueName), LogTypeInfo
			oLogging.CreateEntry "setTime> " &  strNegValueName & " is now Set to: " & oShell.RegRead(strKeyPath & "" & strNegValueName), LogTypeInfo
		End If
		
	End Function

End Class

   

But wait! This does not really work very… Time only gets fixed after the computer logs in after mini-setup completes. By this time, the initial Windows Activation attempt will have failed. Let’s take care of time synchronization in the WinPE environment, before we even lay down the Windows OS image. The following script is added as a custom action during the “pre-install” phase of LiteTouch deployment:


   
   

Option Explicit
RunNewInstance

'//—————————————————————————-
'//  Global Constants
'//—————————————————————————-

'const DEPLOY_SERVER = "\sysimg3.campus.ad.uvm.edu"

'//—————————————————————————-
'//  Main Class
'//—————————————————————————-

Class ZUVMsetTimePE

    '//—————————————————————————-
    '//  Global constant and variable declarations
    '//—————————————————————————-
	
    Dim iRetVal

    '//—————————————————————————-
    '//  Constructor to initialize needed global objects
    '//—————————————————————————-

    Private Sub Class_Initialize
    End Sub
	
	Function RegExpFind(patrn, strng)
	  Dim regEx, oMatch, oMatches, iPos

	  ' Create the regular expression.
	  Set regEx = New RegExp
	  regEx.Pattern = patrn
	  regEx.IgnoreCase = False
	  regEx.Global = False

	  ' Do the search.
	  Set oMatches = regEx.Execute(strng)
	  
	  iPos = "0"
	  
	  For Each oMatch in oMatches
		iPos = oMatch.FirstIndex
	  Next
	  
	  RegExpFind = iPos
	End Function

    '//—————————————————————————-
    '//  Main routine
    '//—————————————————————————-

    Function Main
	
		' setTimePE.vbs
		' J. Greg Mackinnon, 2010-10-07
		' Sets time from within a WinPE 3.0 environment.
		
		Dim sDATESEARCH
		Dim sTIMESEARCH
		
		Dim oExec
		Dim sDSTime, sExecOut, sDate, sTime, sDateTime, sCmd, sDPServ
		Dim iPos1, iPos2, iLength
		
		sDATESEARCH = "[0-9]*/"
		sTIMESEARCH = "[0-9]*:"
		
		sDPServ = oEnvironment.Item("SMSDP")
		'sDPServ = "sysimg3.campus.ad.uvm.edu"

		oLogging.CreateEntry "Current Time on localhost is: " & Date & " " & Time, LogTypeInfo

		set oExec = oShell.Exec("net.exe time \" & sDPServ)
		Do While oExec.Status = 0
			 WScript.Sleep 100
		Loop
		Do Until oExec.StdOut.AtEndOfStream
			sExecOut = oExec.StdOut.ReadLine
			oLogging.CreateEntry "setTime> Output from net time: " & sExecOut, LogTypeInfo
			iPos1 = RegExpFind(sDATESEARCH, sExecOut)
			If iPos1  0 then
				sDateTime = Mid(sExecOut,iPos1)
				iPos2 = RegExpFind(sTIMESEARCH, sDateTime)
				If  iPos2  0 then
					sTime = Mid(sDateTime, iPos2)
					sDate = Left(sDateTime, iPos2)
					Exit Do
				End If
			End If
		Loop

		oLogging.CreateEntry "Current Time on " & sDPServ & " is: " & sDateTime, LogTypeInfo


		REM set oExec = oShell.Exec("%comspec% /c time " & sTime)
		sCMD = """time " & sTime & """"
		oShell.Run "%comspec% /c " & sCMD

		sCMD = """date " & sDate & """"
		oShell.Run "%comspec% /c " & sCMD

		oLogging.CreateEntry "Current Time on localhost now is: " & Date & " " & Time, LogTypeInfo

	End Function

End Class

   


One-time time synchronization in Windows

UPDATE: While the script outlined below works well enough, I have taken a different approach in LiteTouch/WinPE. You can see the final script here:
https://blog.uvm.edu/jgm/2010/10/14/time-sync-in-mdt-2010winpe-take-3/

Recently our clients have started to complain about Windows product activation errors. I think this is less likely caused by an increased per-capita error rate, and more by an upswing in deployment of KMS-dependent operating systems (such as Windows 7).

Some investigation reveals that the root cause is incorrect system time on the Windows clients that are requesting KMS licenses. I am not sure how much time skew the KMS can tolerate between itself and license-requesting clients, but I do know that it is less than 24 hours.

Unfortunately, the license activation errors are not overly helpful in explaining to client machines that time synchronization is the problem. I plan to reduce the occurrence of this problem by scripting a one-time time sync in out LiteTouch task sequences.

How?
Easy… sort of.

' SetTime.vbs script
' J. Greg Mackinnon, 2010-10-04
' Actions: Syncs local system clock to "ntp.pool.org" time using the NTP protocol.
'          Will change registry values for maximum time skew correction If necessary, Then revert to original values
'          ReSets the w32time service during execution, but NOT at the End of the script.  An manual restart is required to 
'            revert Domain-joined systems to defaults.
' Requires: w32tm.exe, net.exe.  Both should be present on all Vista/Win7 systems.
' Tested on: WinDows 7.  Should work on Vista as well... NOT intEnded for XP systems.

Option explicit

Dim oShell, oExec
Dim iPosRegVal, iNegRegVal
Dim strKeyPath, strPosValueName, strNegValueName

strKeyPath = "HKLMSYSTEMCurrentControlSetservicesW32TimeConfig"
strPosValueName = "MaxPosPhaseCorrection"
strNegValueName = "MaxNegPhaseCorrection"

Set oShell = WScript.CreateObject("WScript.Shell") 

WScript.Echo "Current Time is: " & Date & " " & Time

'This works, If you can understand the screwball interger value that gets returned.  
'Everything over hex 0x0fffffff is listed as a negative interger.
'0xffffffff returns -1.
iPosRegVal = oShell.RegRead(strKeyPath & "" & strPosValueName)
iNegRegVal = oShell.RegRead(strKeyPath & "" & strNegValueName)
WScript.Echo "strNegValueName value is: " & iNegRegVal
WScript.Echo "StrPosValueName value is: " & iPosRegVal

If iPosRegVal  -1 Then
	WScript.Echo "Maximum allowed clock skew correction is NOT large enough... Setting to maximum value."
	'Setting the Max Phase Correction registry values to "-1" (or 0xffffffff in hex), 
	'which will allow correction of local time by any amount.
	oShell.RegWrite strKeyPath & "" & strPosValueName, -1, "REG_DWORD"
	oShell.RegWrite strKeyPath & "" & strNegValueName, -1, "REG_DWORD"
	WScript.Echo strPosValueName & " is now Set to: " & oShell.RegRead(strKeyPath & "" & strPosValueName)
	WScript.Echo strNegValueName & " is now Set to: " & oShell.RegRead(strKeyPath & "" & strNegValueName)
Else
	WScript.Echo "This system already already is configured to allow large clock skew corrections."
End If 

WScript.Echo "Setting WinDows Time service Manual-sync NTP Server to ""pool.ntp.org"""
' Pool.ntp.org is a collection of Internet NTP time servers.  
' It is the default time source for stand-alone RedHat installs,
' and apparently it is a but more reliable than "time.winDows.com"
Set oExec = oShell.Exec("w32tm.exe /config /manualpeerlist:pool.ntp.org /update")
Do While oExec.Status = 0
     WScript.Sleep 100
Loop
Do While NOT oExec.StdOut.AtEndOfStream
	WScript.Echo oExec.StdOut.ReadLine
Loop

'Stopping the w32time service.  
'Necessary because changes to the w32time service will NOT take effect until service restart.
Set oExec = oShell.Exec("net.exe stop w32time")
Do While oExec.Status = 0
     WScript.Sleep 100
Loop
Do While NOT oExec.StdOut.AtEndOfStream
	WScript.Echo oExec.StdOut.ReadLine
Loop

'Starting the w32time service
Set oExec = oShell.Exec("net start w32time")
Do While oExec.Status = 0
     WScript.Sleep 100
Loop
Do While NOT oExec.StdOut.AtEndOfStream
	WScript.Echo oExec.StdOut.ReadLine
Loop

'Forcing a time service resync
'Time would resync on its own soon enough, but we are impatient and want to see results immediately.
Set oExec = oShell.Exec("w32tm.exe /resync")
Do While oExec.Status = 0
     WScript.Sleep 100
Loop
Do While NOT oExec.StdOut.AtEndOfStream
	WScript.Echo oExec.StdOut.ReadLine
Loop

WScript.Echo "Current Time is: " & Date & " " & Time

If iPosRegVal  -1 Then
	WScript.Echo "ReSetting registry maximum allowed clock skew correction Settings to their original values..."
	oShell.RegWrite strKeyPath & "" & strPosValueName, iPosRegVal, "REG_DWORD"
	oShell.RegWrite strKeyPath & "" & strNegValueName, iNegRegVal, "REG_DWORD"
	WScript.Echo strPosValueName & " is now Set to: " & oShell.RegRead(strKeyPath & "" & strPosValueName)
	WScript.Echo strNegValueName & " is now Set to: " & oShell.RegRead(strKeyPath & "" & strNegValueName)
End If