PowerShell Time Sync: Orchestrate Monitoring of Remote Servers


Summary: Guest blogger, Rudolf Vesely discusses how to monitor remote servers.

Microsoft Scripting Guy, Ed Wilson, is here. Today is Part 3 of a 3-part series by guest blogger, Rudolf Vesely. To read the first 2 posts in this series, please see:

In previous posts, I described my Time Sync module. I explained how to handle exceptions in parallel operations and how the Start-VSystemTimeSynchronization and Wait-VSystemTimeSynchronization workflows work. Those who have followed along may realize that there are two more workflows in the module.

Get current time from the Internet     

The first workflow is very simple. It is able to get the current time from the Internet using HTTP protocol. I spent some time searching, and I found that it is possible to get the number of microseconds since 1970 from nist.time.gov:

$webRequest = Invoke-WebRequest -Uri 'http://nist.time.gov/actualtime.cgi?lzbc=siqm9b'

Please realize that you cannot use Invoke-WebRequest directly in SMA in Microsoft Azure or the Server Core installation option. The reason is that these servers do not have the Internet Explorer engine; and therefore, you need to simplify the web request and use basic parsing:

$webRequest = Invoke-WebRequest -UseBasicParsing -Uri 'http://nist.time.gov/actualtime.cgi?lzbc=siqm9b'

The easiest option for converting the returned value is to use a regular expression:

$milliseconds = [int64](($webRequest.Content -replace '.*time="|" delay=".*') / 1000)

Now I instantiate the object by using one of the constructors and then add milliseconds. The problem is that it cannot be done in a workflow, so I am forced to do it in an inline script. As I wrote in the first post, you should always try to avoid inline scripts, but now it is needed.

InlineScript

{

    (New-Object -TypeName DateTime -ArgumentList (1970, 1, 1)).AddMilliseconds($Using:milliseconds)

}

Let’s describe the last Test-VSystemTimeSynchronization workflow. This is the final workflow and it uses all the other workflows from the module. The workflow orchestrates all the testing, reporting, and monitoring actions from a single point (SMA server, management server), and it checks or corrects time synchronization on a large number of remote servers. Technically, it is not a problem to use Test-VSystemTimeSynchronization against a local server, but there is no reason to do that.

First, let me describe one additional feature that I implemented and that uses a previous workflow to get the current time using HTTP.

In Get-VSystemTimeSynchronization, you can compare time between a target server and any other specified NTP server. This could be handy as a second test because you can compare time, for example, against a public NTP server while you use an internal NTP server for regular time synchronization. If you run Get-VSystemTimeSynchronization against a remote server, the comparison is done between two remote servers (target and NTP) via NTP protocol (UDP 123). The problem is that in most production environments, servers cannot access the Internet.

For these reasons, I implemented another check that is done via Test-VSystemTimeSynchronization. the Test-VSystemTimeSynchronization workflow compares time that is obtained from Get-VSystemTimeSynchronization and the current time on the nist.time.gov website. If you run tests using Test-VSystemTimeSynchronization from a management server (with HTTP Internet access) against remote servers, the remote serves do not have to have HTTP Internet access. Only the management server has to be able to access Internet.

Of course, it is possible to access the Internet through a proxy server (this is very common for secured enterprise environments). The only drawback to this approach is that the check is inaccurate. That means you can choose to be notified when the time difference is larger than 10 minutes. However, you cannot measure the real time difference between the nist.time.gov website and a remote server.

Test-VSystemTimeSynchronization attempts to get time from the web, and you can specify in the –IgnoreError parameter that you do not want to be informed about errors when the website is inaccessible.

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 -ErrorRecord $_

        }

    }

}

You have probably noticed that I also saved the current time on the server. I use it later to calculate the time duration between the moments when you get data from the web and when you do the comparison. This improves the precision of the check.

Coordinate time sync

Now let‘s run Wait-VSystemTimeSynchronization (described in the previous post) and get data from it. I will again save the time when all the data is obtained to increase the precision of the time comparison between the servers and nist.time.gov website.

$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()

Now it is possible to process all the custom objects that we obtained from Wait-VSystemTimeSynchronization and that were originally generated in Get-VSystemTimeSynchronization. It is possible to process them in parallel:

foreach -parallel ($outputItem in $outputItems)

{

The first thing is to get the current ErrorEvents and StatusEvents properties of the custom objects from Get-VSystemTimeSynchronization. These properties are used as a log of warnings and errors. That means these properties can contain, for example, information about failed synchronization or that the current source is not equal to the specified one in the desired state.

$errorItems  = $outputItem.ErrorEvents

$statusItems = $outputItem.StatusEvents

The next step is to compare the time between servers and the nist.time.gov website (if it was specified by parameters). The first operation is a correction. However, even this correction does not ensure a precise comparison because I did not save when the particular custom objects were obtained from Wait-VSystemTimeSynchronization. I only saved the time when the process finished.

I believe that improving this correction is not important because this check with the nist.time.gov website cannot be technically precise, and it should be used only for simple verification when the difference between the time on the nist.time.gov website and the server time is not too large (for example, more than 10 minutes).

If you want to improve this part of code, then go ahead.

$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'

    }

}

Finally, I modify the object from Wait-VSystemTimeSynchronization that was generated in Get-VSystemTimeSynchronization, and I add properties related to check the time difference between the server and the nist.time.gov website. If I did the same modification in a standard function (not in a workflow), I would have used following projection:

# Example:

$someObjects |

    Select-Object -Property * ,

    @{ Expression = { (55 * 12345) }; Label = 'SomeProperty' }

    @{ Expression = { $var }; Label = 'SomethingElse' }

The problem is that workflows do not support this way, so I decided to create a new custom object. As you can see in last rows, I do a new evaluation of the Status property because if the nist.time.gov website time comparison fails, I have to change the status from $true to $false.

[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 = ![bool]$errorItems

    StatusEvents = $statusItems

    Error = [bool]$errorItems

    ErrorEvents = $errorItems

}

The output is shown here:

Image of command output

And that is finally all.

Remember that there are two ways to get my free code:

If you have any questions, please post them in the Comments section, and I will reply as soon as possible. Have a nice day.

~Rudolf

Thank you, Rudolf, for a great module and an excellent blog series. I look forward to hearing from you again in the future.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Comments (2)

  1. Dan_IT says:

    Hello, is it possible download all code? Br, Dan

Skip to main content