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.

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

.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}

}
}

}

3 thoughts on “The Importance of Being On Time

  1. very nice !
    in your ps1 :
    how to request the active directory domain instead a list of servers ?

  2. I would recommend using the Microsoft or Quest AD PowerShell cmdlets to query Active Directory for the list of servers, you can then pipe the results directly into the function.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>