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:
- Keep time synchronized from a network service
- Always set the time no matter how far off it is
- 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.
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.