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' } ))
}
}
