Best Practice for the Windows Time (W32TIME) Service for RDSH and VDI workloads

When using image deployment mechanisms for RDSH and VDI workloads, such as Citrix PVS, Citrix MCS and VMware View Composer, it’s extremely important to reconfigure the Windows Time (w32time) Service to ensure that the LastBootUpTime is accurate. If it’s not accurate, it effects monitoring, the correlation of log data, event triggers, etc.

The default Manual trigger of the Windows Time service is based on domain membership, which seems to cause issues as the image boots and changes name to the correct target name. Without having a deep enough understanding on the inner workings of these technologies, I can only assume that the default trigger of the Windows Time service is not compatible with the way the imaging mechanisms work on boot up to change the computer name and join to an Active Directory computer object. There may be a point during startup where Windows detects that it’s not domain joined and therefore stops the Windows Time service, which seems to throw the time out.

Here’s a screen shot from an image I built in Perth, Australia. Notice how the LastBootUpTime is several hours into the future as Perth is GMT +8.

Incorrect Windows Time At Boot - Australia

Here’s a screen shot from an image I built in Saskatoon, Canada. Notice how the LastBootUpTime is several hours into the past as Saskatoon is GMT -6.

Incorrect Windows Time At Boot - Canada

So to fix this we simply run a script in the master/gold image to…

  • Remove the trigger from the Windows Time (w32time) Service

sc triggerinfo w32time delete

  • Set the Windows Time (w32time) Service to start automatically

sc config w32time start= auto

Then the LastBootUpTime is accurate as seen in the following screen shot.

Correct Windows Time At Boot

Microsoft provides some different options here.

  • Method 1 is preferred and provides accuracy, which is what I’ve discussed here.
    • remove the trigger from the Windows Time (w32time) Service
      • sc triggerinfo w32time delete
    • set the Windows Time (w32time) Service to start automatically
      • sc config w32time start= auto
  • Method 2 is better than default, but is not as accurate as method 1
    • Change it to start when the network is ready (has an IP address)
      • sc triggerinfo w32time start/networkon stop/networkoff
    • Set the Windows Time (w32time) Service to start manually
      • sc config w32time start= manual
  • Method 3 is not a workable solution

I’ve documented this to provide discussion and guidance on how the Windows Time service should be configured for these use cases. From my findings tools such as the Citrix Optimizer and VMware OS Optimization Tool do not contain changes to this service.

Here is the ConfigureWindowsTimeService.ps1 (1271 downloads ) script

<#
  This script will configure the Windows Time (w32time) Service

  We need to reconfigure the Windows Time (w32time) Service to ensure that the
  LastBootUpTime is accurate, as the default Manual trigger based on domain
  membership seems to cause issues when using image deployment mechanisms for
  RDSH and VDI workloads, such as Citrix PVS, Citrix MCS, View Composer, etc.
  The default trigger stops (or does not start) the Windows Time service when
  it's not domain joined. Without having a deep enough understanding on the
  inner workings of these technologies, I assume that the default trigger on
  the Windows Time service is not compatible with the way the imaging software
  works on bootup to change the computer name and join to an Active Directory
  computer object, as there may be a point during startup when it's not domain
  joined.

  Microsoft documents the issue and different options here:
  - https://support.microsoft.com/en-au/help/2385818/windows-time-service-doesn-t-start-automatically-on-a-workgroup-comput
  - https://learn.microsoft.com/en-au/troubleshoot/windows-client/active-directory/w32time-not-start-on-workgroup

  As per the Microsoft documentation:
  - Method 1 is preferred and provides accuracy, which is what this script provides.
    - remove the trigger from the Windows Time (w32time) Service
      sc triggerinfo w32time delete
    - set the Windows Time (w32time) Service to start automatically
       sc config w32time start= auto
  - Method 2 is better than default, but is not as accurate as method 1
    - Change it to start when the network is ready (has an IP address)
      sc triggerinfo w32time start/networkon stop/networkoff
    - Set the Windows Time (w32time) Service to start manually
      sc config w32time start= manual
  - Method 3 is not a workable solution

  To query the existing triggers...
  - sc qtriggerinfo W32Time

  To add the Domain Join trigger back in and set startup to manual...
  - sc triggerinfo w32time start/domainjoin
  - sc config w32time start= manual

  Script name: ConfigureWindowsTimeService.ps1
  Release 1.0
  Written by Jeremy Saunders (jeremy@jhouseconsulting.com) 14th April 2019

#>

#-------------------------------------------------------------
# Set Powershell Compatibility Mode
Set-StrictMode -Version 2.0

# Enable verbose, warning and error mode
$VerbosePreference = 'Continue'
$WarningPreference = 'Continue'
$ErrorPreference = 'Continue'

#-------------------------------------------------------------

$Service = "w32time"

# We can use the sc.exe command line utility
# Possible results using the sc.exe command line tool:
# [SC] ChangeServiceConfig SUCCESS
# [SC] OpenSCManager FAILED 5:  Access is denied.
# [SC] OpenSCManager FAILED 1722:  The RPC server is unavailable." --> Computer shutdown
# [SC] OpenService FAILED 1060:  The specified service does not exist as an installed service." --> Service not installed

write-verbose "Removing the trigger for the `"$Service`" service..." -verbose
Invoke-Command {cmd /c sc.exe triggerinfo $Service delete} | out-null

write-verbose "Setting the `"$Service`" service to Automatic..." -verbose
Invoke-Command {cmd /c sc.exe config $Service start= auto} | out-null

# Unfortunately we need to force the $ExitCode variable to 0 when using Set-StrictMode
# in PowerShell scripts used in MDT/SCCM Task Sequences. It's a known bug.
$ExitCode = 0
Write-Verbose "Completed with an exit code of $ExitCode"
Exit $ExitCode

I hope this helps.

Jeremy Saunders

Jeremy Saunders

Delivering customer success through tech: IT Infrastructure | Citrix | End User Computing | Platform Engineering | DevOps | Full Stack Developer | Technical Architect | Improvisor | Aspiring Comedian | Midlife Adventurer at J House Consulting
Jeremy Saunders is the Problem Terminator; the MacGyver of IT. Views and Intellectual Property (IP) published on this site belong to Jeremy. Please refer to the About page for more information about Jeremy.