Sunday, July 26, 2015

Setting NTP server and time in Windows using Powershell

And here we have yet another Windows-related post! Yes, I too make fun of Windows as much as required to be in the IT business... ok sometimes more. But, as I have said again and again, being able to solve problems using command line (powershell specifically) makes it feel more like Unix. I can handle that and so can you!

Most of the Windows boxes I met that use a time server to set their time use the Microsoft one, time.windows.com, no matter if they are the sole computer in a car shop or one of the thousands desktop and servers in an university. That is nice until you have to move away from local-only user accounts and deal with Kerberos and, by extension, Active Directory. You see, Kerberos likes to have its clients to be within 5 minutes of the authentication servers (KDCs). Syncing against the Microsoft time server assumes your machine is in a network that can access the Internet. Well, I have 8 of them which are in a vlan that can't (and really shouldn't). Updates to them are pushed through SCCM (when it feels like working, but I digress) and AD.

On the top of that, I have a perfectly good ntp server in my network this vlan can reach anyway. And its address is passed by dhcp. To add insult to injury, Microsoft does not support the dhcp option to care about ntp servers. Here is a list of the DHCP options supported right from their official docs.

So, as always, I need to do something to make it stop pissing me off. And, it will be in a script of some sort. This is Windows so bash is out and Powershell is in.

The plan is to be able to find which ntp server the Windows host is using and change it if we do not like it. And, while we are there, make sure the host's time is in sync with that of the ntp server. Windows uses W32Time and stores all of that in the registry, namely in HKLM:\SYSTEM\CurrentControlSet\services\W32Time, so if you want you can unleash regedit and go at it. Taking a cue from Unix and Linux, powershell treats the registry as a file tree. So, as far as it is concerned, the above is just a path which can be accessed and modified using Get-ItemProperty and Set-ItemProperty. Let's try it out by taking a look on what we have currently defined:

PS C:\> $timeRoot = "HKLM:\SYSTEM\CurrentControlSet\services\W32Time"
PS C:\> Get-ItemProperty  -path "$timeroot\parameters"


PSPath                 : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE
                         \SYSTEM\CurrentControlSet\services\W32Time\parameters
PSParentPath           : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE
                         \SYSTEM\CurrentControlSet\services\W32Time
PSChildName            : parameters
PSDrive                : HKLM
PSProvider             : Microsoft.PowerShell.Core\Registry
ServiceDll             : C:\Windows\system32\w32time.dll
ServiceMain            : SvchostEntry_W32Time
ServiceDllUnloadOnStop : 1
Type                   : NT5DS
NtpServer              : time.windows.com,0x9



PS C:\>

The 3 blank lines below NtpServer are not a typo; don't ask me why it spits those lines because they add absolutely no value to the output besides wasting screen real state. As you can see, it wants to use time.windows.com as the NtpServer. But, what is this 0x9 on the end of the name of the ntp server? Well, here is what I know about what 0x flags mean

  • 0x01 SpecialInterval: interval in seconds between when W32Time pools for time. Requires HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpClient\SpecialPollInterval to be setup. By default W32Time checks the time at intervals based on the network speed, traffic, and phases of the moon. But, if you turn SpecialInterval on, it will check evet SpecialPoolInterval seconds. So, SpecialPoolInterval = 3600 means it will check time ever 3600s (or 1h).
  • 0x02 UseAsFallbackOnly
  • 0x04 SymmatricActive
  • 0x08 Client
  • 0x09 = 0x01 + 0x08. Yes, we can do math.

If we want to change it to, say, ntp.example.com, in powershell we would begin by

PS C:\> Set-ItemProperty  -path "$timeroot\parameters" -name NtpServer -Value "n
tp.example.com,0x9"
PS C:\>
And then checking again
PS C:\> Get-ItemProperty  -path "$timeroot\parameters"


PSPath                 : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE
                         \SYSTEM\CurrentControlSet\services\W32Time\parameters
PSParentPath           : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE
                         \SYSTEM\CurrentControlSet\services\W32Time
PSChildName            : parameters
PSDrive                : HKLM
PSProvider             : Microsoft.PowerShell.Core\Registry
ServiceDll             : C:\Windows\system32\w32time.dll
ServiceMain            : SvchostEntry_W32Time
ServiceDllUnloadOnStop : 1
Type                   : NT5DS
NtpServer              : ntp.example.com,0x9



PS C:\>

We changed the config, but we then need to restart the time server for that to take effect

Restart-Service -Name w32Time -Force

Let's see if we can put some of that together in a script, which I shall call ntpTime.ps1:

# SEE ALSO
# https://technet.microsoft.com/en-us/library/ee176960.aspx

$timeRoot = "HKLM:\SYSTEM\CurrentControlSet\services\W32Time"

# Name of ntp server(s) currently known by this host
function Get-NTPServer {
   $ntpserver = (Get-ItemProperty  -path "$timeroot\parameters" `
                 -name NtpServer).NtpServer -replace ",.*"
   return $ntpserver
}

# So we do not like the ntp servers this host knows and want to change them.
# Remember the 0x flags!
#
# 0x01 SpecialInterval    
# 0x02 UseAsFallbackOnly  
# 0x04 SymmatricActive
# 0x08 Client
#
function Set-NTPServer ($ntpServer) {
   Set-ItemProperty -path "$timeroot\parameters" -name NtpServer -Value $ntpServer
}

function Restart-Time {
   Restart-Service -Name w32Time -Force
}

# How far off are our time (in seconds) from the one in our ntp server?
function Get-NTPOffset ($ntpServer) {
   (w32tm /stripchart /computer:$ntpServer /samples:1)[-1].split("[")[0] `
   -replace ".*:" -replace "s.*"
}

# Adjust time by using the offset
function SetTime ($offsetSeconds) {
   set-date (Get-Date).AddSeconds($offsetSeconds)
}

## Using those silly functions ----------------------------------
$myNTP = "ntp.example.com"
$leserver = Get-NTPServer
if ( $leserver -eq $myNTP ){
   Set-NTPServer("$($myNTP),0x9")
}
SetTime(Get-NTPOffset($myNTP))
Restart-Time

I will put a more complete version in my github account, but the above is good enough to be productive. So, what it does is first see whether we are using the right ntp server ($myNTP since I needed a lame variable name). If not, it changes it. And then it adjust time as needed. Script can then be run (schtasks anyone?) at regular intervals or when the machine wakes up if it is a vm or laptop.

No comments: