The Importance of Being On Time

In an Active Directory environment its typical for client machines to use a local domain controller as their time source, domain controllers with the PDC emulator for the domain and the PDC emulator for the root domain to synchronise time with an external source. In most circumstances the aim is to keep the time synchronised within a 5 minute tolerance level, this will ensure there are no issues with Kerberos authentication which has the 5 minute tolerance as part of its requirements.

For some applications a tolerance of 5 minutes is too large and can cause issues with the application. You may need to adjust items such as the polling interval to ensure that the time is within a lower tolerance, but first of all you need to know how far out of tolerance the servers within your environment are.

The below Get-WindowsTimeDifference function can help you out here. It will take a list of Windows computers as input and then compare their time to either a specified Time Server within the environment, or by default attempt to use the PDC emulator for the Root AD Domain (or failing that, the PDC emulator for the current domain). The default tolerance level is one minute, this can be altered with the ToleranceLevel parameter.

A common cause of time sync issues can be Windows servers in the DMZ, which are not members of the domain. Consequently, by default they will not sync with a local domain controller and may need to be manually configured to use a time server (which doesn’t always happen!). Checking the time for these servers may require alternate credentials, so the Credential parameter is provided for that purpose.

The example below demonstrates using a text file list of Windows Server names piped into Get-WindowsTimeDifference with one server out of synch when the ToleranceLevel has been set to 3 minutes.


.DESCRIPTION Get the time difference between a time server and Windows clients

.PARAMETER  ReferenceComputer Name of the computer to check time difference for

.PARAMETER  TimeServer Name of a TimeServer to use instead of the PDC Emulator

.PARAMETER  ToleranceLevel Amount in minutes to permit as an acceptable time difference. Default is 1

.PARAMETER  Credential Supply a PSCredential to use alternative credentials for the WMI query

.EXAMPLE PS C:\\> Get-WindowsTimeDifference -ReferenceComputer Server01 -ToleranceLevel 2

.EXAMPLE

PS C:\\> Get-Content servers.txt | Get-WindowsTimeDifference -TimeServer TimeServer -ToleranceLevel 3 | Select ComputerName,SynchronisedStatus,TimeDeviation,TimeServer

.EXAMPLE PS C:\\> Get-Content servers.txt | Get-WindowsTimeDifference -Credential (Get-Credential)

.NOTES Author: Jonathan Medd Date: 16/03/2012 #>

\[CmdletBinding()\] param( \[Parameter(Position=0,Mandatory=$true,HelpMessage="Name of the computer to check time difference for", ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$true)\] \[Alias('CN','\_\_SERVER','IPAddress','Server','Computername')\] \[System.String\] $ReferenceComputer = $env:COMPUTERNAME,

\[Parameter(Position=1)\] \[System.String\] $TimeServer,

\[Parameter(Position=2)\] \[int\] $ToleranceLevel = 1,

\[Parameter(Position=3)\] \[System.Management.Automation.PSCredential\] $Credential )

begin {

\# If no TimeServer specified, retrive the PDC Emulator for the Root Domain and the Current Domain if (-not($Timeserver)) {

$Forest = \[System.DirectoryServices.ActiveDirectory.Forest\]::GetCurrentForest() $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain", $Forest.RootDomain) $RootPDCEmulator = (\[System.DirectoryServices.ActiveDirectory.Domain\]::GetDomain($DomainContext)).PdcRoleOwner.Name $CurrentDomainPDCEmulator = (\[System.DirectoryServices.ActiveDirectory.Domain\]::GetCurrentDomain()).PdcRoleOwner.Name

\# Attempt a WMI query to the PDC Emulator for the Root Domain try {

$PDCEmulator = (Get-WmiObject Win32\_OperatingSystem -ComputerName $RootPDCEmulator -ErrorAction Stop).\_\_Server

}

\# If there is an error (such as blocked TCP ports or permissions) try the PDC Emulator for the Current Domain instead catch \[System.Exception\] {

$PDCEmulator = (Get-WmiObject Win32\_OperatingSystem -ComputerName $CurrentDomainPDCEmulator).\_\_Server }

} }

process {

\# If no TimeServer specified, calculate the time difference between the PDC Emulator and the Reference Computer if (-not($Timeserver)) {

try { $PDCEmulatorWMI = Get-WmiObject Win32\_OperatingSystem -ComputerName $PDCEmulator

if ($Credential){

$ReferenceComputerWMI = Get-WmiObject Win32\_OperatingSystem -ComputerName $ReferenceComputer -Credential $Credential -ErrorAction Stop

}

else {

$ReferenceComputerWMI = Get-WmiObject Win32\_OperatingSystem -ComputerName $ReferenceComputer -ErrorAction Stop

}

$PDCEmulatorTime = $PDCEmulatorWMI.ConvertToDateTime($PDCEmulatorWMI.LocalDateTime) $ReferenceComputerTime = $ReferenceComputerWMI.ConvertToDateTime($ReferenceComputerWMI.LocalDateTime) $TimeDeviation = $PDCEmulatorTime - $ReferenceComputerTime }

\# Catch any errors with the WMI query to the Reference Computer catch \[System.Exception\] {

$TimeDeviation = "Unable to contact server"

} }

else {

\# If TimeSever is specified, calculate the time difference between the TimeServer and the Reference Computer try {

$TimeServerWMI = Get-WmiObject Win32\_OperatingSystem -ComputerName $TimeServer

if ($Credential){

$ReferenceComputerWMI = Get-WmiObject Win32\_OperatingSystem -ComputerName $ReferenceComputer -Credential $Credential -ErrorAction Stop

}

else {

$ReferenceComputerWMI = Get-WmiObject Win32\_OperatingSystem -ComputerName $ReferenceComputer -ErrorAction Stop

}

$TimeServerTime = $TimeServerWMI.ConvertToDateTime($TimeServerWMI.LocalDateTime) $ReferenceComputerTime = $ReferenceComputerWMI.ConvertToDateTime($ReferenceComputerWMI.LocalDateTime) $TimeDeviation = $TimeServerTime - $ReferenceComputerTime

}

\# Catch any errors with the WMI query to the Reference Computer catch \[System.Exception\] {

$TimeDeviation = "Unable to contact server"

}

} # Use the TimeDeviation variable to determine SynchronisedStatus and TimeDeviation to report if ($TimeDeviation -eq "Unable to contact server"){

$SynchronisedStatus = "Unable to contact server"

}

elseif (\[Math\]::Abs(($TimeDeviation).Minutes) -ge $ToleranceLevel) {

$SynchronisedStatus = "Not Synchronised" $TimeDeviation = \[string\]$TimeDeviation.Minutes +" Minute(s) " + \[string\]$TimeDeviation.Seconds +" Seconds" }

else {

$SynchronisedStatus = "Synchronised" $TimeDeviation = \[string\]$TimeDeviation.Minutes +" Minute(s) " + \[string\]$TimeDeviation.Seconds +" Seconds" }

\# Create custom objects to store the results New-Object -TypeName PSObject -Property @{ ComputerName = $ReferenceComputer SynchronisedStatus = $SynchronisedStatus TimeDeviation = $TimeDeviation TimeServer = if ($PDCEmulator) {$PDCEmulator} else {$TimeServer}

} }

}

It may also be important to prove that time is synchronised across your VMware servers. The Get-ESXTimeDifference function will do this for you. In the below example a list of VMware servers is obtained froma text file and the time checked against the Root Domain PDC emulator using the default ToleranceLevel of 1 minute.

Note: Get-ESXTimeDifference requires PowerCLI installed and a connection to vCenter having already been made.


function Get-ESXTimeDifference { <# .SYNOPSIS Get the time difference between a time server and ESX clients

.DESCRIPTION Get the time difference between a time server and ESX clients

.PARAMETER  ReferenceComputer Name of the computer to check time difference for

.PARAMETER  TimeServer Name of a TimeServer to use instead of the PDC Emulator

.PARAMETER  ToleranceLevel Amount in minutes to permit as an acceptable time difference. Default is 1

.EXAMPLE PS C:\\> Get-ESXTimeDifference -ReferenceComputer ESX01 -ToleranceLevel 2

.EXAMPLE

PS C:\\> Get-VMHost ESX01 | Get-ESXTimeDifference -TimeServer TimeServer -ToleranceLevel 3 | Select ComputerName,SynchronisedStatus,TimeDeviation,TimeServer

.NOTES Author: Jonathan Medd Date: 16/03/2012 #>

\[CmdletBinding()\] param( \[Parameter(Position=0,Mandatory=$true,HelpMessage="Name of the computer to check time difference for", ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$true)\] \[Alias('CN','\_\_SERVER','IPAddress','Server','Computername','VMHost','Name')\] \[System.String\] $ReferenceComputer,

\[Parameter(Position=1)\] \[System.String\] $TimeServer,

\[Parameter(Position=2)\] \[int\] $ToleranceLevel = 1 )

begin {

\# If no TimeServer specified, retrive the PDC Emulator for the Root Domain and the Current Domain if (-not($Timeserver)) {

$Forest = \[System.DirectoryServices.ActiveDirectory.Forest\]::GetCurrentForest() $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain", $Forest.RootDomain) $RootPDCEmulator = (\[System.DirectoryServices.ActiveDirectory.Domain\]::GetDomain($DomainContext)).PdcRoleOwner.Name $CurrentDomainPDCEmulator = (\[System.DirectoryServices.ActiveDirectory.Domain\]::GetCurrentDomain()).PdcRoleOwner.Name

\# Attempt a WMI query to the PDC Emulator for the Root Domain try {

$PDCEmulator = (Get-WmiObject Win32\_OperatingSystem -ComputerName $RootPDCEmulator -ErrorAction Stop).\_\_Server

}

\# If there is an error (such as blocked TCP ports or permissions) try the PDC Emulator for the Current Domain instead catch \[System.Exception\] {

$PDCEmulator = (Get-WmiObject Win32\_OperatingSystem -ComputerName $CurrentDomainPDCEmulator).\_\_Server }

} }

process {

\# If no TimeServer specified, calculate the time difference between the PDC Emulator and the Reference Computer if (-not($Timeserver)) {

$PDCEmulatorWMI = Get-WmiObject Win32\_OperatingSystem -ComputerName $PDCEmulator

$PDCEmulatorTime = $PDCEmulatorWMI.ConvertToDateTime($PDCEmulatorWMI.LocalDateTime)

$ReferenceComputerTime = ((Get-View ((Get-VMHost $ReferenceComputer).ExtensionData.ConfigManager.DateTimeSystem)).QueryDateTime()).ToLocalTime() $TimeDeviation = $PDCEmulatorTime - $ReferenceComputerTime

}

else {

\# If TimeSever is specified, calculate the time difference between the TimeServer and the Reference Computer

$TimeServerWMI = Get-WmiObject Win32\_OperatingSystem -ComputerName $TimeServer

$TimeServerTime = $TimeServerWMI.ConvertToDateTime($TimeServerWMI.LocalDateTime) $ReferenceComputerTime = ((Get-View ((Get-VMHost $ReferenceComputer).ExtensionData.ConfigManager.DateTimeSystem)).QueryDateTime()).ToLocalTime() $TimeDeviation = $TimeServerTime - $ReferenceComputerTime

} # Use the TimeDeviation variable to determine SynchronisedStatus and TimeDeviation to report

if (\[Math\]::Abs(($TimeDeviation).Minutes) -ge $ToleranceLevel) {

$SynchronisedStatus = "Not Synchronised" $TimeDeviation = \[string\]$TimeDeviation.Minutes +" Minute(s) " + \[string\]$TimeDeviation.Seconds +" Seconds" }

else {

$SynchronisedStatus = "Synchronised" $TimeDeviation = \[string\]$TimeDeviation.Minutes +" Minute(s) " + \[string\]$TimeDeviation.Seconds +" Seconds" }

\# Create custom objects to store the results New-Object -TypeName PSObject -Property @{ ComputerName = $ReferenceComputer SynchronisedStatus = $SynchronisedStatus TimeDeviation = $TimeDeviation TimeServer = if ($PDCEmulator) {$PDCEmulator} else {$TimeServer}

} }

}