PowerShell Module for Time Synchronization – PowerShell Workflow for testing and monitoring of Time Sync on remote servers in parallel

This PowerShell Workflow is part of PowerShell module for Time Synchronization on Windows and Windows Server. To get all features you need all PowerShell Workflows from the Time Sync module.

Possibilities

  • Test current state of time synchronization on the local server or on multiple remote servers in parallel.
  • Orchestrate regular monitoring of the time synchronization on multiple remote servers in parallel.
  • Compare time difference between remote server and specified NTP server (it is a good idea to use different NTP server then then NTP server that is used internally)
    • UDP 123 communication between remote server and specified NTP server
    • Accurate but firewall opening is required
  • Compare time difference between remote server and time on the internet.
    • TCP 80 (HTTP) from the server that orchestrate monitoring to internet
    • Not accurate but firewall opening is not required (monitoring server may have access to the internet or could be done via proxy)

Examples

Test remote servers in parallel and compare their times with defined internal NTP server and with time on internet

  • Get information from the remote devices (servers)
  • Invoke correction actions on the remote devices
  • Compare remote device”s time with defined NTP server (UDP 123 from remote server)
    • It is possible to set IPv4 (for example 192.168.3.5), IPv6 (for example fd12:3456::0045) or DNS name (for example 0.pool.ntp.org)
  • Compare remote device”s time with time obtained from website (from the internet) (TCP 80 from local server, inaccurate)’
  • Ignore when it is not possible to connect to get time from internet or from NTP server
Test-VSystemTimeSynchronization `
    -CompareWithNTPServerName 'fd12:3456::0045' `
    -CompareWithNTPServerMaximumTimeDifferenceSeconds 30 `
    -CompareWithWeb:$true `
    -CompareWithWebMaximumTimeDifferenceSeconds 60 `
    -IgnoreError CompareWithNTPServerNoConnection,
        CompareWithWebNoConnection `
    -Verbose

Monitor time synchronization in regular intervals and send e-mail in case of error

  • Send e-mail when the Status is not True
  • Ignore all errors related to network connectivity (devices could be inaccessible)
  • Source has to be uqueal to one of the NTP servers that are configured in Windows Registry
  • Last synchronization cannot be older then 24 hours
try
{
    $results = Test-VSystemTimeSynchronization `
        -ComputerName hyperv-host0, hyperv-host1, hyperv-host2 `
        -RequiredSourceTypeConfiguredInRegistry:$true `
        -LastTimeSynchronizationMaximumNumberOfSeconds 86400 `
        -RepetitionCount 3 `
        -RepetitionDelaySeconds 5 `
        -IgnoreError DeviceIsNotAccessible,
            CompareWithNTPServerNoConnection,
            CompareWithWebNoConnection `
        -ComputerName contoso0, contoso1 `
        -Verbose `
        -ErrorAction Stop

    $wrongResult = $results | Where-Object -Property Status -NE -Value $true

    if ($wrongResult)
    {
        Send-MailMessage `
            -From 'User01 <user01@example.com>' `
            -To 'User02 <user02@example.com>', 'User03 <user03@example.com>' `
            -Subject 'Time synchronization error' `
            -Body ('Error on following servers: {0}' -f ($wrongResult.ComputerNameBasic -join ', ')) `
            -Priority High `
            -SmtpServer smtp.contoso.com
    }
}
catch
{
    Write-Warning -Message 'Exception that is not related to network accessibility.'

    if ($wrongResult)
    {
        Send-MailMessage `
            -From 'User01 <user01@example.com>' `
            -To 'User02 <user02@example.com>', 'User03 <user03@example.com>' `
            -Subject 'Time synchronization error' `
            -Body ('Exception: {0}' -f $_.Exception.Message) `
            -Priority High `
            -SmtpServer smtp.contoso.com
    }

    'Some action in case of unknwon exception...'
}

Code

Workflow Test-VSystemTimeSynchronization
{
    <#
    .SYNOPSIS
        Test time synchronization of multiple devices in parallel. Get information about their currect state, do correction actions and compare their current time with specific NTP server or with time on the internet.

    .DESCRIPTION
        Developer
            Developer: Rudolf Vesely, http://rudolfvesely.com/
            Copyright (c) Rudolf Vesely. All rights reserved
            License: Free for private use only

            "V" is the first letter of the developer's surname. The letter is used to distingue Rudolf Vesely's cmdlets from the other cmdlets.

        Description
            Test time synchronization of multiple devices in parallel. Get information about their currect state, do correction actions and compare their current time with specific NTP server or with time on the internet.

        Requirements
            Developed and tested using PowerShell 4.0.

    .PARAMETER ComputerName
        Computer names of remote devices. If not set then local device will be processed.

    .PARAMETER CompareWithNTPServerName
        Compare time difference between device and specified NTP server and raise error when the time difference has exceeded defined maximum.

        It is handy to use different NTP server then server that is used internally.

        Type of comparison: Between remote device (target) and NTP server (UDP 123).

            Advantages: Accurate

            Disadvantages: Remote device has to have opened UDP 123 to remote NTP server.

    .PARAMETER CompareWithNTPServerMaximumTimeDifferenceSeconds
        Defined maximum of seconds of time difference.

    .PARAMETER CompareWithWeb
        Compare time difference between device and external website and raise error when the time difference has exceeded defined maximum.

        Type of comparison: Between local device (device that run the Workflow against remote device) and NTP server (TCP 80, proxy could be used).

            Advantages: Remote device does not have opened firewall (comparison is done on the device that run the Workflow).

            Disadvantages: Inaccurate

    .PARAMETER CompareWithWebMaximumTimeDifferenceSeconds
        Defined maximum of seconds of time difference.

    .PARAMETER IgnoreError
        Ignore some specific errors.

        Possibilities
            WrongComputerName
                Device does not exists (not in DNS).

            DeviceIsNotAccessible
                Device exists (defined in DNS) but it is not reachable (not running, FW issue, etc.).

            CompareWithNTPServerNoConnection
                Error when the remote device cannot connect NTP server.

            CompareWithWebNoConnection
                Error when the local device cannot connect web to get time.

    .EXAMPLE
        '    - Get information from the remote devices (servers)'
        '    - Invoke correction actions on the remote devices'
        '    - Compare remote device''s time with defined NTP server (UDP 123 from remote server)'
        '        - It is possible to set IPv4 (for example 192.168.3.5), IPv6 (for example fd12:3456::0045) or DNS name (for example 0.pool.ntp.org)'
        '    - Compare remote device''s time with time obtained from website (from the internet) (TCP 80 from local server, inaccurate)'
        '    - Ignore when it is not possible to connect to get time from internet or from NTP server'
        Test-VSystemTimeSynchronization `
            -CompareWithNTPServerName 'fd12:3456::0045' `
            -CompareWithNTPServerMaximumTimeDifferenceSeconds 30 `
            -CompareWithWeb:$true `
            -CompareWithWebMaximumTimeDifferenceSeconds 60 `
            -IgnoreError CompareWithNTPServerNoConnection,
                CompareWithWebNoConnection `

    .EXAMPLE
        '    - Send e-mail when the Status is not True'
        '    - Ignore all errors related to network connectivity (devices could be inaccessible)'
        '    - Source has to be uqueal to one of the NTP servers that are configured in Windows Registry'
        '    - Last synchronization cannot be older then 24 hours'
        try
        {
            $results = Test-VSystemTimeSynchronization `
                -ComputerName hyperv-host0, hyperv-host1, hyperv-host2 `
                -RequiredSourceTypeConfiguredInRegistry:$true `
                -LastTimeSynchronizationMaximumNumberOfSeconds 86400 `
                -RepetitionCount 3 `
                -RepetitionDelaySeconds 5 `
                -IgnoreError DeviceIsNotAccessible,
                    CompareWithNTPServerNoConnection,
                    CompareWithWebNoConnection `
                -Verbose `
                -ErrorAction Stop

            $wrongResult = $results | Where-Object -Property Status -NE -Value $true

            if ($wrongResult)
            {
                Send-MailMessage `
                    -From 'User01 <user01@example.com>' `
                    -To 'User02 <user02@example.com>', 'User03 <user03@example.com>' `
                    -Subject 'Time synchronization error' `
                    -Body ('Error on following servers: {0}' -f ($wrongResult.ComputerNameBasic -join ', ')) `
                    -Priority High `
                    -SmtpServer smtp.contoso.com
            }
        }
        catch
        {
            Write-Warning -Message 'Exception that is not related to network accessibility.'

            if ($wrongResult)
            {
                Send-MailMessage `
                    -From 'User01 <user01@example.com>' `
                    -To 'User02 <user02@example.com>', 'User03 <user03@example.com>' `
                    -Subject 'Time synchronization error' `
                    -Body ('Exception: {0}' -f $_.Exception.Message) `
                    -Priority High `
                    -SmtpServer smtp.contoso.com
            }

            'Some action in case of unknwon exception...'
        }

    .INPUTS

    .OUTPUTS
        System.Management.Automation.PSCustomObject

    .LINK
        https://techstronghold.com/
    #>

    [CmdletBinding(
        DefaultParametersetName = 'ComputerName',
        HelpURI = 'https://techstronghold.com/',
        ConfirmImpact = 'Medium'
    )]

    Param
    (
        [Parameter(
            Mandatory = $false,
            Position = 0,
            ParameterSetName = 'ComputerName'
        )]
        [ValidateLength(1, 255)]
        [string[]]$ComputerName,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [AllowNull()]
        [AllowEmptyCollection()]
        [string[]]$RequiredSourceName,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [bool]$RequiredSourceTypeConfiguredInRegistry,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [bool]$RequiredSourceTypeNotLocal,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [bool]$RequiredSourceTypeNotByHost,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [AllowNull()]
        [int]$LastTimeSynchronizationMaximumNumberOfSeconds,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [string]$CompareWithNTPServerName,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [int]$CompareWithNTPServerMaximumTimeDifferenceSeconds = 10,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [switch]$CompareWithWeb,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [int]$CompareWithWebMaximumTimeDifferenceSeconds = 30,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [bool]$CorrectiveActions = $true,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [AllowNull()]
        [int]$RepetitionCount = 3,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [int]$RepetitionDelaySeconds = 5,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [AllowNull()]
        [AllowEmptyCollection()]
        [ValidateSet(
            'WrongComputerName',
            'DeviceIsNotAccessible',
            'CompareWithNTPServerNoConnection',
            'CompareWithWebNoConnection'
        )]
        [string[]]$IgnoreError
    )

    # Configurations
    $ErrorActionPreference = 'Stop'
    $ProgressPreference = 'SilentlyContinue'



    <#
    Time from internet
    #>

    $dateTimeInternetUtc          = $null
    $dateTimeInternetUtcObtained  = $null

    if ($CompareWithWeb)
    {
        try
        {
            $dateTimeInternetUtc          = Get-VDateTimeInternetUtc -Verbose:$false
            $dateTimeInternetUtcObtained  = (Get-Date).ToUniversalTime()
        }
        catch
        {
            if ($IgnoreError -contains 'CompareWithWebNoConnection')
            {
                Write-Warning -Message '[Test] [Compare with web] [Error] Cannot obtain date and time from the internet'
            }
            else
            {
                Write-Error -Exception $_
            }
        }
    }



    <#
    Gathering data and correction actions
    #>

    $outputItems = Wait-VSystemTimeSynchronization `
        -ComputerName                                      $ComputerName                                      `
        -RequiredSourceName                                $RequiredSourceName                                `
        -RequiredSourceTypeConfiguredInRegistry            $RequiredSourceTypeConfiguredInRegistry            `
        -RequiredSourceTypeNotLocal                        $RequiredSourceTypeNotLocal                        `
        -RequiredSourceTypeNotByHost                       $RequiredSourceTypeNotByHost                       `
        -LastTimeSynchronizationMaximumNumberOfSeconds     $LastTimeSynchronizationMaximumNumberOfSeconds     `
        -CompareWithNTPServerName                          $CompareWithNTPServerName                          `
        -CompareWithNTPServerMaximumTimeDifferenceSeconds  $CompareWithNTPServerMaximumTimeDifferenceSeconds  `
        -CorrectiveActions                                 $CorrectiveActions                                 `
        -RepetitionCount                                   $RepetitionCount                                   `
        -RepetitionDelaySeconds                            $RepetitionDelaySeconds                            `
        -IgnoreError                                       $IgnoreError
    $outputItemsObtainedDateTimeUtc = (Get-Date).ToUniversalTime()



    <#
    Processing gathered data
    #>

    foreach -parallel ($outputItem in $outputItems)
    {
        $errorItems  = $outputItem.ErrorEvents
        $statusItems = $outputItem.StatusEvents



        <#
        Error handling: Time from internet
        #>

        $comparisonWebTimeDifferenceSeconds = $null
        $statusComparisonWeb = $null

        if ($dateTimeInternetUtc)
        {
            # Correct time from the internet that was obtained a couple seconds ago
            $dateTimeInternetUtcWithCorrection = $dateTimeInternetUtc + ($outputItemsObtainedDateTimeUtc - $dateTimeInternetUtcObtained)

            $comparisonWebTimeDifferenceSeconds = [int]($dateTimeInternetUtcWithCorrection - $outputItem.DateTimeUtc).TotalSeconds

            if ($comparisonWebTimeDifferenceSeconds -eq $null -or
                $comparisonWebTimeDifferenceSeconds -lt ($CompareWithWebMaximumTimeDifferenceSeconds * -1) -or
                $comparisonWebTimeDifferenceSeconds -gt $CompareWithWebMaximumTimeDifferenceSeconds)
            {
                $statusComparisonWeb = $false

                $errorItems += ('[Test] [Compare with web] [Error] Elapsed: {0} seconds; Defined maximum: {1} seconds' -f
                    $comparisonWebTimeDifferenceSeconds, $CompareWithWebMaximumTimeDifferenceSeconds)
            }
            else
            {
                $statusComparisonWeb = $true
            }
        }
        else
        {
            if ($CompareWithWeb)
            {
                $statusItems += '[Test] [Compare with web] [Error] Cannot obtain date and time from the internet'
            }
        }



        <#
        Return
        #>

        [PsCustomObject]@{
            DateTimeUtc                                     = $outputItem.DateTimeUtc
            DateTimeInternetUtc                             = $dateTimeInternetUtcWithCorrection
            ComputerNameBasic                               = $outputItem.ComputerNameBasic
            ComputerNameNetBIOS                             = $outputItem.ComputerNameNetBIOS
            ComputerNameFQDN                                = $outputItem.ComputerNameFQDN
            ConfiguredNTPServerName                         = $outputItem.ConfiguredNTPServerName
            ConfiguredNTPServerNameRaw                      = $outputItem.ConfiguredNTPServerNameRaw
            ConfiguredNTPServerByPolicy                     = $outputItem.ConfiguredNTPServerByPolicy
            SourceName                                      = $outputItem.SourceName
            SourceNameRaw                                   = $outputItem.SourceNameRaw
            LastTimeSynchronizationDateTime                 = $outputItem.LastTimeSynchronizationDateTime
            LastTimeSynchronizationElapsedSeconds           = $outputItem.LastTimeSynchronizationElapsedSeconds
            ComparisonNTPServerName                         = $outputItem.ComparisonNTPServerName
            ComparisonNTPServerTimeDifferenceSeconds        = $outputItem.ComparisonNTPServerTimeDifferenceSeconds

            ComparisonWebTimeDifferenceSeconds              = $comparisonWebTimeDifferenceSeconds

            StatusRequiredSourceName                        = $outputItem.StatusRequiredSourceName
            StatusRequiredSourceType                        = $outputItem.StatusRequiredSourceType
            StatusDateTime                                  = $outputItem.StatusDateTime
            StatusLastTimeSynchronization                   = $outputItem.StatusLastTimeSynchronization
            StatusComparisonNTPServer                       = $outputItem.StatusComparisonNTPServer

            StatusComparisonWeb                             = $statusComparisonWeb

            Status                                          = $(if ($errorItems) { $false } else { $true })
            StatusEvents                                    = $statusItems
            Error                                           = $(if ($errorItems) { $true } else { $false })
            ErrorEvents                                     = $errorItems
        }

        Write-Verbose -Message ('[Test: {0}] [Verbose]: Finished with additional details: {1}' -f
            $outputItem.ComputerNameBasic, $(if ($statusItems) { $statusItems -join '; ' } else { 'None' } ))
    }
}

Leave a Reply

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