Scripting. Powershell, VMware, Windows, Active Directory & Exchange. All that kind of stuff…..
RSS icon Email icon Home icon
  • The Importance of Being On Time

    Posted on April 2nd, 2012 Jonathan Medd 3 comments

    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 responses to “The Importance of Being On Time” RSS icon

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

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

    • This script does not detect time-zone difference. but nice anyway…


    Leave a reply