比较 PowerShell 中所有域控制器的时间偏移

Compare time offset of all domain-controllers in PowerShell

我想通过 PowerShell 比较所有域控制器的时间差异。

我发现了这样的东西:

$output1 = & w32tm /monitor /domain:mydomain.local /threads:5
    $stdOutStart = 8
    $output = $output1[$stdOutStart..$output1.Length]

我想提取$output的数据,只比较ntp的偏移时间,如果超过一秒就发邮件提醒。

$output 不是对象,它只是文本,我怎样才能提取所需的字段?

$output looks like
server2.mydomain.local[192.168.22.22:123]:
    ICMP: 1ms delay
    **NTP: +0.0017247s offset** from server1.mydomain.local
        RefID: server1.mydomain.local [192.168.22.122.]
        Stratum: 3

您可以将 $output 中的 NTP 偏移量解析为 [Double],如果它大于 1,则发送电子邮件警报。

像这样:

if ($output -match 'NTP:\s+([+-]?\d(?:\.\d+)?)s') {
    $seconds = [double]::Parse($matches[1], [cultureinfo]::InvariantCulture)
    if ($seconds -gt 1) {
        # send an email alert
    }
}

正则表达式详细信息:

NTP:             Match the characters “NTP:” literally
\s               Match a single character that is a “whitespace character” (spaces, tabs, line breaks, etc.)
   +             Between one and unlimited times, as many times as possible, giving back as needed (greedy)
[+-]             Match a single character present in the list below
                 The character “+”
                 The character “-”
   ?             Between zero and one times, as many times as possible, giving back as needed (greedy)
(                Match the regular expression below and capture its match into backreference number 1
   \d            Match a single digit 0..9
   (?:           Match the regular expression below
      \.         Match the character “.” literally
      \d         Match a single digit 0..9
         +       Between one and unlimited times, as many times as possible, giving back as needed (greedy)
   )?            Between zero and one times, as many times as possible, giving back as needed (greedy)
)               
s                Match the character “s” literally

根据您评论中的要求,您可以将我的代码与 合并。
由于您已经在名为 $output 的数组变量中包含这些行,我将使用该名称。

示例:

$output = @"
server2.mydomain.local[192.168.22.22:123]:
    ICMP: 1ms delay
    NTP: +0.0017247s offset from server1.mydomain.local
        RefID: server1.mydomain.local [192.168.22.122.]
        Stratum: 3
server3.mydomain.local[192.168.22.23:123]:
    ICMP: 1ms delay
    NTP: +1.0017555s offset from server1.mydomain.local
        RefID: server1.mydomain.local [192.168.22.122.]
        Stratum: 3
server4.mydomain.local[192.168.22.24:123]:
    ICMP: 1ms delay
    NTP: +21.095731s offset from server1.mydomain.local
        RefID: server1.mydomain.local [192.168.22.122.]
        Stratum: 3
"@ -split '\r?\n'

代码可以是:

$AlertThreshold = 1 # Number of seconds before an alert...

for( $i = 0; $i -lt $output.Count; $i++ ) {
    if ($output[$i] -match 'NTP:\s+([+-]?\d+(?:\.\d+)?)s') {
        $seconds = [double]::Parse($matches[1], [cultureinfo]::InvariantCulture)
        if ($seconds -gt $AlertThreshold) {
            # prepare to send an send an email alert
            $currentServer = $output[$i - 2].TrimEnd(":")
            $refServer = ($output[$i] -split ' ')[-1]
            $message = "Alert: $currentServer time offset $seconds seconds from $refServer !"
            Write-Host $message
            # send the message
            $mailParams = @{
                To         = 'someone@yourdomain.com'
                From       = 'ntpchecker@yourdomain.com'
                SmtpServer = 'mailserver.yourdomain.com'
                Subject    = 'Alert Server Time Difference'
                Body       = $message
                Priority   = 'High'
                # etc.
            }
            # Send-MailMessage @mailParams
        }
    }
}

结果:

Alert: server3.mydomain.local[192.168.22.23:123] time offset 1.0017555 seconds from server1.mydomain.local !
Alert: server4.mydomain.local[192.168.22.24:123] time offset 21.095731 seconds from server1.mydomain.local !

感谢您的回复,这看起来是一个有趣的解决方案。不确定如何获取 ntp 编号,对我来说,如果不匹配是在几秒钟内,如果第一个编号 -ne"0" NTP: -0.0022387s 从完整的输出。 (在输出中将有大约 5 个服务器..)

$output1 = & w32tm /monitor /domain:mydomain.local /threads:5

$stdOutStart = 8
$output = $output1[$stdOutStart..$output1.Length]
$timeInfos = @()

for ($i = 0 ; $i -lt $output.Length ; $i+=4) {
        $server = $output[$i].Split(' ')[0]
        $icmp = $output[$i+1].Trim().Split(' ')[1]
        $offset = $output[$i+2].Trim().Split(' ')[1]
        $timeInfos += New-Object PsObject -Property @{
            Server = $server
            ICMP = $icmp
            Offset = $offset
        }
}

$timeInfos

if ($output -match 'NTP:\s+([+-]?\d(?:\.\d+)?)s') {
    $seconds = [double]::Parse($matches[1], [cultureinfo]::InvariantCulture)
    if ($seconds -gt 1) {
        Write-host " send an email alert..#mail script here"
    }

    else
    {
    Write-host " everything ok"

    }
    }


@Theo RegEx 技能是我无法匹配的...看看我在那里做了什么。

说真的,总有一天我会更流利地使用 RegEx,但现在我只能用困难的方式做事。在这种情况下,我认为您希望在警报中获得更多信息。对我来说,这意味着通过输出返回以获取与另一台服务器的偏移量超出所需阈值的服务器。我通常使用传统的 For 循环,因为我可以更改迭代变量的值来回顾或回顾数组。

    $Arr =
@(
'server2.mydomain.local[192.168.22.22:123]:'
'    ICMP: 1ms delay'
'    NTP: +0.0017247s offset from server1.mydomain.local'
'        RefID: server1.mydomain.local [192.168.22.122.]'
'        Stratum: 3'
'server2.mydomain.local[192.168.22.22:123]:'
'    ICMP: 1ms delay'
'    NTP: +0.0017247s offset from server1.mydomain.local'
'        RefID: server1.mydomain.local [192.168.22.122.]'
'        Stratum: 3'
'server2.mydomain.local[192.168.22.22:123]:'
'    ICMP: 1ms delay'
'    NTP: +0.0017247s offset from server1.mydomain.local'
'        RefID: server1.mydomain.local [192.168.22.122.]'
'        Stratum: 3'
)

$AlertThreshold = .0016 # Number of seconds before an alert...

For( $i = 0; $i -lt $Arr.Length; ++$i )
{
    $CurrentLine = $Arr[$i].Trim()
    # $CurrentLine

    If( $CurrentLine -match '^NTP: ')
    {
        # Break up the NTP line, as long as the output is predictable this should work.
        $CurrentLineFields = $CurrentLine.Split(' ')

        # The second index should be the offset, clean it up to be a number...
        [Double]$OffSet = $CurrentLineFields[1].Replace('s','')

        # Last thing on the current line should be the server is offset from.
        $DiffServer = $CurrentLineFields[-1]

        # Look back through the output array to get the current server.
        $CurrentServer = $Arr[$i-2] #May need some more work here to polish up the output.

        # Logic for echoing and/or email alerting:
        If( $OffSet -ge $AlertThreshold -or $OffSet -le -$AlertThreshold )
        {
            Write-Host "Alert : $CurrentServer time offset $Offset seconds from $DiffServer !"
            # You can send the email here of build up an email body for a single email alert that will
            # all the curious offsets...
        }
    }
}

输出:

Alert : server2.mydomain.local[192.168.22.22:123]: time offset 0.0017247 seconds from server1.mydomain.local !
Alert : server2.mydomain.local[192.168.22.22:123]: time offset 0.0017247 seconds from server1.mydomain.local !
Alert : server2.mydomain.local[192.168.22.22:123]: time offset 0.0017247 seconds from server1.mydomain.local !
  • 显然我只是重复了你提供的数据。我现在不在 AD 环境中。在您的情况下,您想将变量分配给 w32tm.exe
  • 的输出
  • 我不知道偏移量是否可能为负,所以我适应了这种情况。
  • 根据需要调整阈值变量。我明明改成test了
  • 评论是为了你的利益,如果你删除它们,它会更简洁。

告诉我这是否疯狂。让我知道进展如何。

我并不是要继续这样做,但有几点我无法在评论中提出。

w32tm.exe 确实可以 return 一个负数,例如 -1.001755 。这没有包含在最新的答案中。另外,我会使用 -ge & -le 而不是 -gt & -lt,因为在极端情况下,差异为 +- 恰好 1 秒。我知道这在像这样的脚本中可能是微不足道的,但它也不需要任何成本就可以做到彻底。

我会在 For 循环之前建立 $mailParams 散列,并且只在 if 块中重置 body 的值。

我不太清楚我们为什么要使用 [Double]::Parse() 方法。我在美国工作,那里对全球化功能的需求较少。但是,在快速测试中,我看不出结果有任何差异。很高兴有进一步的解释。

这样做的关键是能够相对于 NTP 线向后查看阵列。我想我们如何匹配/识别线并在之后拆分是一个偏好问题。但是,我确实发现了我的方法中的一个缺陷,因为 "NTP: error" 在某些情况下可能会得到回应。不用更复杂的 RegEx 就可以很简单地解决这个问题。我将错误行添加到示例输出中只是为了在更多示例中覆盖它。

我认为您喜欢 RegEx 更密集的方法,当然使用它有助于我们学习。因此,我将在下面给出两个调整后的示例。

您还应该决定是要接收多封电子邮件警报,还是只接收一封包含所有警报数据的电子邮件 per/run。由于我们都受到某种程度的电子邮件过载的困扰,我想是后者,但我在其中放置了一个布尔变量,因此您可以自己决定。

我在原来的回答中做了评论,以润色$CurrentServer。 @Theo 使用 .TrimEnd(':') 做到了这一点,但我认为我们可以更进一步。起初我以为这是一个端口号,我可以做一些像 -replace ":\d{1,5}:" 这样的事情,它将把尾随的冒号截掉 1-5 位冒号。它有效但感觉不对。 NTP 偏离众所周知的端口 123 的可能性非常小。所以,让我们使用像 -replace ":123]:","]" 这样的文字匹配来切碎它 注意:.Replace(":123]:", "]") 可能也同样有效。

w32tm 的示例输出:

# Sample Output, in practice set this to the output of w32tm.exe
$W32t_Output =
@(
'server2.mydomain.local[192.168.22.22:123]:'
'    ICMP: 1ms delay'
'    NTP: +0.0017247s offset from server1.mydomain.local'
'        RefID: server1.mydomain.local [192.168.22.122.]'
'        Stratum: 3'
'server2.mydomain.local[192.168.22.22:123]:'
'    ICMP: 1ms delay'
'    NTP: -1.0017247s offset from server1.mydomain.local'
'        RefID: server1.mydomain.local [192.168.22.122.]'
'        Stratum: 3'
'server2.mydomain.local[192.168.22.22:123]:'
'    ICMP: 1ms delay'
'    NTP: +2.0017247s offset from server1.mydomain.local'
'        RefID: server1.mydomain.local [192.168.22.122.]'
'        Stratum: 3'
'server2.mydomain.local[192.168.200.200:123]:'
'    ICMP: 0ms delay'
'    NTP: error ERROR_TIMEOUT - no response from server in 1000ms'
'DownServer.mydomain.local [error WSAHOST_NOT_FOUND]'
)

示例 1 使用我原来的方法:

# > Set $OneAlert to true if you want 1 alert for all the interesting time variances per run.
# > Set to false if you want a single alert for each interesting variance encountered per run.
$OneAlert       = $true
$Body           = [Collections.ArrayList]@()
$AlertThreshold = 1 # Number of seconds before an alert...

$mailParams = 
@{
    To         = 'someone@yourdomain.com'
    From       = 'ntpchecker@yourdomain.com'
    SmtpServer = 'mailserver.yourdomain.com'
    Subject    = 'Alert Server Time Difference'
    Body       = ''
    Priority   = 'High'
}

For( $i = 0; $i -lt $W32t_Output.Length; ++$i )
{
    $CurrentLine = $W32t_Output[$i].Trim()

    If( $CurrentLine -match '^NTP: ' -and $CurrentLine -notmatch 'error' )
    {
        $CurrentLineFields = $CurrentLine.Split(' ')                    # Break up the NTP line, as long as the output is predictable this should work.
        [Double]$OffSet    = $CurrentLineFields[1].Replace('s','')      # The second index should be the offset, clean it up to be a number...
        $DiffServer        =  $CurrentLineFields[-1]                    # Last thing on the current line should be the server is offset from.
        $CurrentServer     = $W32t_Output[$i-2] -replace ":123]:","]"   # Look back through the output array to get the current server. 

        # Logic for echoing and/or email alerting:
        If( $OffSet -ge $AlertThreshold -or $OffSet -le -$AlertThreshold )
        {
            If($OneAlert)
            {
                [Void]$Body.Add( "Alert : $CurrentServer time offset $Offset seconds from $DiffServer !" )
            }
            Else
            {
                $mailParams['Body'] = "Alert : $CurrentServer time offset $Offset seconds from $DiffServer !"
                Send-MailMessage @mailParams
            }
        }
    }
}

# $Body will only be populated if $OneAlert is true
If( $Body )
{
    $mailParams['body'] = $Body -join "`r`n"
    Send-MailMessage @mailParams   
}

示例2进一步结合了@Theo的方法:

# > Set $OneAlert to true if you want 1 alert for all the interesting time variances per run.
# > Set to false if you want a single alert for each interesting variance encountered per run.
$OneAlert       = $false
$Body           = [Collections.ArrayList]@()
$AlertThreshold = 1 # Number of seconds before an alert...

$mailParams = 
@{
    To         = 'someone@yourdomain.com'
    From       = 'ntpchecker@yourdomain.com'
    SmtpServer = 'mailserver.yourdomain.com'
    Subject    = 'Alert Server Time Difference'
    Body       = ''
    Priority   = 'High'
}

for( $i = 0; $i -lt $output.Count; $i++ ) {
    if ($output[$i] -match 'NTP:\s+([+-]?\d+(?:\.\d+)?)s') {
        $seconds = [double]::Parse($matches[1], [cultureinfo]::InvariantCulture)

        # Adjusted the if condition a little:
        if ( $seconds -ge $AlertThreshold -or $seconds -le -$AlertThreshold ) {
            $currentServer = $output[$i - 2] -replace ":123]:","]" # changed a little
            $refServer = ($output[$i] -split ' ')[-1]

            If($OneAlert) # prepare to send an email alert
            {
                [Void]$Body.Add( "Alert: $currentServer time offset $seconds seconds from $refServer !" )
            }
            Else
            {
                $mailParams['body'] = "Alert: $currentServer time offset $seconds seconds from $refServer !"
                Send-MailMessage @mailParams
            }
        }
    }
}

# $Body will only be populated if $OneAlert is true
If( $Body )
{
    $mailParams['body'] = $Body -join "`r`n"
    Send-MailMessage @mailParams   
}

很明显这是一片蓝天,我可能是被带走了。无论如何让 me/us 知道你的想法。