Windows Time Synchronization The Battle Continues

While trying to implement the time changes discussed earlier I discovered that Windows time is a bit more complicated than I at first believed. Let's recap my requirements:

  1. Keep time synchronized from a network service
  2. Always set the time no matter how far off it is
  3. Set the time on system startup

It seems that requirements 1 and 2 are discussed last time and should be easily set using registry keys. What I discovered during implementation is that while ignoring the 15 hour drift limit is easy actual ongoing time synchronization is disabled by default on systems that are not domain joined.

In Windows all NTP work is handled using the W32Time service. By default the service is set to start automatically and, as shown, has no dependencies.

Time Service Dependencies

This would indicate to me that the service will launch on system start-up. Unfortunately I was quite wrong. In addition to the service dependencies each service has what's called a "trigger". When a service is set to start "Automatically" that doesn't mean "On Boot", instead it means "When My Trigger Occurs". I've never dealt with Windows services outside of the snap-in, where the triggers are not shown, so needless to say I was slightly perturbed to find this out. The W32Time service's trigger is set to be "Domain Join" meaning the service only launches if the system is domain joined, which is pretty lame. We can correct this one easily enough with the following command:

sc triggerinfo w32time start/networkon stop/networkoff

This will change the startup parameters of the service to launch whenever the network comes up and stop whenever the network goes down, pretty much exactly what we want. Very nearly. My primary impetus for the work was a slew of systems with faulty BIOS batteries. In my testing the w32time service would launch on boot but does not synchronize immediately. The service has two separate settings MaxPollInterval which defines the longest time between polls and SpecialPollTimeRemaining which is described in the documentation as:

This entry is maintained by W32Time. It contains reserved data that is used by the Windows operating system. It specifies the time in seconds before W32Time will resynchronize after the computer has restarted. Any changes to this setting can cause unpredictable results. The default value on both domain members and on stand-alone clients and servers is left blank.

This means the system will perform its first poll eventually but we don't know how long and we can't control when. This leaves me a little unsettled.

When adding your NTP servers every piece of documentation I came across included an extra field in the server name, i.e. time.microsoft.com,0x1. When we add the 0x1 we are telling W32Time to use the registry key SpecialPollInterval instead of the automagic MinPollInterval and MaxPollInterval. The downside is that the default, for non-domain joined systems, is 604,800 seconds (7 days). Personally I'm a bit unhappy about that since drifts of even as little as 5 minutes can cause problem. I could change this to 5 minutes, so it would resync relatively quickly after boot, but that seems a bit much. I'm quite a lot more comfortable with 1 hour.

This still doesn't solve my problem of fixing the time at boot. According to my research w32time has no notion of immediately sync on launch so the recommendations are to either join the systems do a domain or to set a scheduled task to sync the time. Given the constraints of the systems involved this leaves us with no choice but to use a scheduled task.

The schedule tasked command line interface schtasks actually gives quite a lot of ability to customize the task. One of the standard triggers is "ONSTART" which sounds awesome because that will run the task when the system boots up, whether someone is logged in or not. I like this but the NTP service requires networking and I couldn't figure out a way to set that as a delay condition, i.e. "Run this task when the system boots unless networking isn't up in which case wait and run it then." Instead I used an EventLog trigger. Whenever any network interface comes up, with a network connection, the provider Network Profile generates eventID 10000 which is stored in Microsoft-Windows-NetworkProfile/Operational. We can use this entry as the trigger for our time syncs. The downside is that if the network drops out it will resync when it comes back up. Considering how minimal the network impact of NTP is I'm actually pretty ok with it.

Taking all that together we can add the new task to the end of my batch file as:

schtasks /create /ru "SYSTEM" /tn "Initial Time Synchronization" /tr "w32tm /resync" /sc onevent /ec "Microsoft-Windows-NetworkProfile/Operational" /mo "*[System[Provider[@Name='Microsoft-Windows-NetworkProfile'] and (EventID=10000)]]"

I tested it by making a bunch of crazy changes to my time. Things like moving it forward 30 years or backwards 5. In every trial the time was correctly set as soon as the network plumbed whether as part of a normal boot or me manually disconnecting/reconnecting the interface.