snyderkv
asked on
SCOM Alerts not showing with powershell script
Hello,
Although the script works, SCOM doesn't create an alert for it. I set the monitors unhealthy value to less than 0 targeted to Windows Computers but no alerts. The Powershell command below gets the time offset of the NTP server and puts it into variable $TIME. Then SCOM reads $TIME via the MOMAPI/Property bag statement around line 9, sees that it's greater than 0 and should send an alert. But doesn't. I believe the script is incorrect although it works on the server itself. Maybe the MOMAPI/Propertybag statement is in the wrong spot?
[CmdletBinding()]
Param (
[String]$Server = "Domain.com",
[Int]$MaxOffset = 100, # (Milliseconds) Throw exception if network time offset is larger
[Switch]$NoDns # Do not attempt to lookup V3 secondary-server referenceIdentifier
)
$api=New-Object -comObject "MOM.ScriptAPI"
$PropertyBag = $api.CreatePropertyBag()
Set-StrictMode -Version 3
# NTP Times are all UTC and are relative to midnight on 1/1/1900
$StartOfEpoch=New-Object DateTime(1900,1,1,0,0,0,[D ateTimeKin d]::Utc)
Function OffsetToLocal($Offset) {
# Convert milliseconds since midnight on 1/1/1900 to local time
$StartOfEpoch.AddMilliseco nds($Offse t).ToLocal Time()
}
# Construct a 48-byte client NTP time packet to send to the specified server
# (Request Header: [00=No Leap Warning; 011=Version 3; 011=Client Mode]; 00011011 = 0x1B)
[Byte[]$NtpData = ,0 * 48
$NtpData[0] = 0x1B # NTP Request header in first byte
$Socket = New-Object Net.Sockets.Socket([Net.So ckets.Addr essFamily] ::InterNet work,
[Net.Sockets.SocketType]:: Dgram,
[Net.Sockets.ProtocolType] ::Udp)
$Socket.SendTimeOut = 2000 # ms
$Socket.ReceiveTimeOut = 2000 # ms
Try {
$Socket.Connect($Server,12 3)
}
Catch {
Write-Error "Failed to connect to server $Server"
Throw
}
# NTP Transaction -------------------------- ---------- ---------- ---------
$t1 = Get-Date # t1, Start time of transaction...
Try {
[Void]$Socket.Send($NtpDat a)
[Void]$Socket.Receive($Ntp Data)
}
Catch {
Write-Error "Failed to communicate with server $Server"
Throw
}
$t4 = Get-Date # End of NTP transaction time
# End of NTP Transaction -------------------------- ---------- ---------- --
$Socket.Shutdown("Both")
$Socket.Close()
# We now have an NTP response packet in $NtpData to decode. Start with the LI flag
# as this is used to indicate errors as well as leap-second information
# Check the Leap Indicator (LI) flag for an alarm condition - extract the flag
# from the first byte in the packet by masking and shifting
$LI = ($NtpData[0] -band 0xC0) -shr 6 # Leap Second indicator
If ($LI -eq 3) {
Throw 'Alarm condition from server (clock not synchronized)'
}
# Decode the 64-bit NTP times
# The NTP time is the number of seconds since 1/1/1900 and is split into an
# integer part (top 32 bits) and a fractional part, multipled by 2^32, in the
# bottom 32 bits.
# Convert Integer and Fractional parts of the (64-bit) t3 NTP time from the byte array
$IntPart = [BitConverter]::ToUInt32($ NtpData[43 ..40],0)
$FracPart = [BitConverter]::ToUInt32($ NtpData[47 ..44],0)
# Convert to Millseconds (convert fractional part by dividing value by 2^32)
$t3ms = $IntPart * 1000 + ($FracPart * 1000 / 0x100000000)
# Perform the same calculations for t2 (in bytes [32..39])
$IntPart = [BitConverter]::ToUInt32($ NtpData[35 ..32],0)
$FracPart = [BitConverter]::ToUInt32($ NtpData[39 ..36],0)
$t2ms = $IntPart * 1000 + ($FracPart * 1000 / 0x100000000)
# Calculate values for t1 and t4 as milliseconds since 1/1/1900 (NTP format)
$t1ms = ([TimeZoneInfo]::ConvertTi meToUtc($t 1) - $StartOfEpoch).TotalMillis econds
$t4ms = ([TimeZoneInfo]::ConvertTi meToUtc($t 4) - $StartOfEpoch).TotalMillis econds
# Calculate the NTP Offset and Delay values
$Offset = (($t2ms - $t1ms) + ($t3ms-$t4ms))/2
$Delay = ($t4ms - $t1ms) - ($t3ms - $t2ms)
# Make sure the result looks sane...
If ([Math]::Abs($Offset) -gt $MaxOffset) {
# Network server time is too different from local time
Throw "Network time offset exceeds maximum ($($MaxOffset)ms)"
}
# Decode other useful parts of the received NTP time packet
# We already have the Leap Indicator (LI) flag. Now extract the remaining data
# flags (NTP Version, Server Mode) from the first byte by masking and shifting (dividing)
$LI_text = Switch ($LI) {
0 {'no warning'}
1 {'last minute has 61 seconds'}
2 {'last minute has 59 seconds'}
3 {'alarm condition (clock not synchronized)'}
}
$VN = ($NtpData[0] -band 0x38) -shr 3 # Server version number
$Mode = ($NtpData[0] -band 0x07) # Server mode (probably 'server')
$Mode_text = Switch ($Mode) {
0 {'reserved'}
1 {'symmetric active'}
2 {'symmetric passive'}
3 {'client'}
4 {'server'}
5 {'broadcast'}
6 {'reserved for NTP control message'}
7 {'reserved for private use'}
}
# Other NTP information (Stratum, PollInterval, Precision)
$Stratum = [UInt16]$NtpData[1] # Actually [UInt8] but we don't have one of those...
$Stratum_text = Switch ($Stratum) {
0 {'unspecified or unavailable'}
1 {'primary reference (e.g., radio clock)'}
{$_ -ge 2 -and $_ -le 15} {'secondary reference (via NTP or SNTP)'}
{$_ -ge 16} {'reserved'}
}
$PollInterval = $NtpData[2] # Poll interval - to neareast power of 2
$PollIntervalSeconds = [Math]::Pow(2, $PollInterval)
$PrecisionBits = $NtpData[3] # Precision in seconds to nearest power of 2
# ...this is a signed 8-bit int
If ($PrecisionBits -band 0x80) { # ? negative (top bit set)
[Int]$Precision = $PrecisionBits -bor 0xFFFFFFE0 # Sign extend
} else {
# ..this is unlikely - indicates a precision of less than 1 second
[Int]$Precision = $PrecisionBits # top bit clear - just use positive value
}
$PrecisionSeconds = [Math]::Pow(2, $Precision)
If ($Stratum -le 1) {
# Response from Primary Server. RefId is ASCII string describing source
$ReferenceIdentifier = [String]([Char[]$NtpData[1 2..15] -join '')
}
Else {
# Response from Secondary Server; determine server version and decode
Switch ($VN) {
3 {
# Version 3 Secondary Server, RefId = IPv4 address of reference source
$ReferenceIdentifier = $NtpData[12..15] -join '.'
If (-Not $NoDns) {
If ($DnsLookup = Resolve-DnsName $ReferenceIdentifier -QuickTimeout -ErrorAction SilentlyContinue) {
$ReferenceIdentifier = "$ReferenceIdentifier <$($DnsLookup.NameHost)>"
}
}
Break
}
4 {
# Version 4 Secondary Server, RefId = low-order 32-bits of
# latest transmit time of reference source
$ReferenceIdentifier = [BitConverter]::ToUInt32($ NtpData[15 ..12],0) * 1000 / 0x100000000
Break
}
Default {
# Unhandled NTP version...
$ReferenceIdentifier = $Null
}
}
}
# Calculate Root Delay and Root Dispersion values
$RootDelay = [BitConverter]::ToInt32($N tpData[7.. 4],0) / 0x10000
$RootDispersion = [BitConverter]::ToUInt32($ NtpData[11 ..8],0) / 0x10000
# Finally, create output object and return
$NtpTimeObj = [PSCustomObject]@{
NtpServer = $Server
NtpTime = OffsetToLocal($t4ms + $Offset)
Offset = $Offset
OffsetSeconds = [Math]::Round($Offset/1000 , 3)
Delay = $Delay
t1ms = $t1ms
t2ms = $t2ms
t3ms = $t3ms
t4ms = $t4ms
t1 = OffsetToLocal($t1ms)
t2 = OffsetToLocal($t2ms)
t3 = OffsetToLocal($t3ms)
t4 = OffsetToLocal($t4ms)
LI = $LI
LI_text = $LI_text
NtpVersionNumber = $VN
Mode = $Mode
Mode_text = $Mode_text
Stratum = $Stratum
Stratum_text = $Stratum_text
PollIntervalRaw = $PollInterval
PollInterval = New-Object TimeSpan(0,0,$PollInterval Seconds)
Precision = $Precision
PrecisionSeconds = $PrecisionSeconds
ReferenceIdentifier = $ReferenceIdentifier
RootDelay = $RootDelay
RootDispersion = $RootDispersion
Raw = $NtpData # The undecoded bytes returned from the NTP server
}
# Set the default display properties for the returned object
[String[]$DefaultPropertie s = 'NtpServer', 'NtpTime', 'OffsetSeconds', 'NtpVersionNumber',
'Mode_text', 'Stratum', 'ReferenceIdentifier'
# Create the PSStandardMembers.DefaultD isplayProp ertySet member
$ddps = New-Object Management.Automation.PSPr opertySet( 'DefaultDi splayPrope rtySet', $DefaultProperties)
# Attach default display property set and output object
$PSStandardMembers = [Management.Automation.PSM emberInfo[ ]$ddps
$NtpTimeObj | Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $PSStandardMembers -PassThru
$a=$NtpTimeObj.offsetsecon ds
if($a -lt 0){$b=[decimal]$a * 100 * -1}else {$b=[decimal]$a * 100}
$TIME=[int]$b
write-host $TIME
$PropertyBag.AddValue("TIM E",$TIME)
$PropertyBag
Although the script works, SCOM doesn't create an alert for it. I set the monitors unhealthy value to less than 0 targeted to Windows Computers but no alerts. The Powershell command below gets the time offset of the NTP server and puts it into variable $TIME. Then SCOM reads $TIME via the MOMAPI/Property bag statement around line 9, sees that it's greater than 0 and should send an alert. But doesn't. I believe the script is incorrect although it works on the server itself. Maybe the MOMAPI/Propertybag statement is in the wrong spot?
[CmdletBinding()]
Param (
[String]$Server = "Domain.com",
[Int]$MaxOffset = 100, # (Milliseconds) Throw exception if network time offset is larger
[Switch]$NoDns # Do not attempt to lookup V3 secondary-server referenceIdentifier
)
$api=New-Object -comObject "MOM.ScriptAPI"
$PropertyBag = $api.CreatePropertyBag()
Set-StrictMode -Version 3
# NTP Times are all UTC and are relative to midnight on 1/1/1900
$StartOfEpoch=New-Object DateTime(1900,1,1,0,0,0,[D
Function OffsetToLocal($Offset) {
# Convert milliseconds since midnight on 1/1/1900 to local time
$StartOfEpoch.AddMilliseco
}
# Construct a 48-byte client NTP time packet to send to the specified server
# (Request Header: [00=No Leap Warning; 011=Version 3; 011=Client Mode]; 00011011 = 0x1B)
[Byte[]$NtpData = ,0 * 48
$NtpData[0] = 0x1B # NTP Request header in first byte
$Socket = New-Object Net.Sockets.Socket([Net.So
[Net.Sockets.SocketType]::
[Net.Sockets.ProtocolType]
$Socket.SendTimeOut = 2000 # ms
$Socket.ReceiveTimeOut = 2000 # ms
Try {
$Socket.Connect($Server,12
}
Catch {
Write-Error "Failed to connect to server $Server"
Throw
}
# NTP Transaction --------------------------
$t1 = Get-Date # t1, Start time of transaction...
Try {
[Void]$Socket.Send($NtpDat
[Void]$Socket.Receive($Ntp
}
Catch {
Write-Error "Failed to communicate with server $Server"
Throw
}
$t4 = Get-Date # End of NTP transaction time
# End of NTP Transaction --------------------------
$Socket.Shutdown("Both")
$Socket.Close()
# We now have an NTP response packet in $NtpData to decode. Start with the LI flag
# as this is used to indicate errors as well as leap-second information
# Check the Leap Indicator (LI) flag for an alarm condition - extract the flag
# from the first byte in the packet by masking and shifting
$LI = ($NtpData[0] -band 0xC0) -shr 6 # Leap Second indicator
If ($LI -eq 3) {
Throw 'Alarm condition from server (clock not synchronized)'
}
# Decode the 64-bit NTP times
# The NTP time is the number of seconds since 1/1/1900 and is split into an
# integer part (top 32 bits) and a fractional part, multipled by 2^32, in the
# bottom 32 bits.
# Convert Integer and Fractional parts of the (64-bit) t3 NTP time from the byte array
$IntPart = [BitConverter]::ToUInt32($
$FracPart = [BitConverter]::ToUInt32($
# Convert to Millseconds (convert fractional part by dividing value by 2^32)
$t3ms = $IntPart * 1000 + ($FracPart * 1000 / 0x100000000)
# Perform the same calculations for t2 (in bytes [32..39])
$IntPart = [BitConverter]::ToUInt32($
$FracPart = [BitConverter]::ToUInt32($
$t2ms = $IntPart * 1000 + ($FracPart * 1000 / 0x100000000)
# Calculate values for t1 and t4 as milliseconds since 1/1/1900 (NTP format)
$t1ms = ([TimeZoneInfo]::ConvertTi
$t4ms = ([TimeZoneInfo]::ConvertTi
# Calculate the NTP Offset and Delay values
$Offset = (($t2ms - $t1ms) + ($t3ms-$t4ms))/2
$Delay = ($t4ms - $t1ms) - ($t3ms - $t2ms)
# Make sure the result looks sane...
If ([Math]::Abs($Offset) -gt $MaxOffset) {
# Network server time is too different from local time
Throw "Network time offset exceeds maximum ($($MaxOffset)ms)"
}
# Decode other useful parts of the received NTP time packet
# We already have the Leap Indicator (LI) flag. Now extract the remaining data
# flags (NTP Version, Server Mode) from the first byte by masking and shifting (dividing)
$LI_text = Switch ($LI) {
0 {'no warning'}
1 {'last minute has 61 seconds'}
2 {'last minute has 59 seconds'}
3 {'alarm condition (clock not synchronized)'}
}
$VN = ($NtpData[0] -band 0x38) -shr 3 # Server version number
$Mode = ($NtpData[0] -band 0x07) # Server mode (probably 'server')
$Mode_text = Switch ($Mode) {
0 {'reserved'}
1 {'symmetric active'}
2 {'symmetric passive'}
3 {'client'}
4 {'server'}
5 {'broadcast'}
6 {'reserved for NTP control message'}
7 {'reserved for private use'}
}
# Other NTP information (Stratum, PollInterval, Precision)
$Stratum = [UInt16]$NtpData[1] # Actually [UInt8] but we don't have one of those...
$Stratum_text = Switch ($Stratum) {
0 {'unspecified or unavailable'}
1 {'primary reference (e.g., radio clock)'}
{$_ -ge 2 -and $_ -le 15} {'secondary reference (via NTP or SNTP)'}
{$_ -ge 16} {'reserved'}
}
$PollInterval = $NtpData[2] # Poll interval - to neareast power of 2
$PollIntervalSeconds = [Math]::Pow(2, $PollInterval)
$PrecisionBits = $NtpData[3] # Precision in seconds to nearest power of 2
# ...this is a signed 8-bit int
If ($PrecisionBits -band 0x80) { # ? negative (top bit set)
[Int]$Precision = $PrecisionBits -bor 0xFFFFFFE0 # Sign extend
} else {
# ..this is unlikely - indicates a precision of less than 1 second
[Int]$Precision = $PrecisionBits # top bit clear - just use positive value
}
$PrecisionSeconds = [Math]::Pow(2, $Precision)
If ($Stratum -le 1) {
# Response from Primary Server. RefId is ASCII string describing source
$ReferenceIdentifier = [String]([Char[]$NtpData[1
}
Else {
# Response from Secondary Server; determine server version and decode
Switch ($VN) {
3 {
# Version 3 Secondary Server, RefId = IPv4 address of reference source
$ReferenceIdentifier = $NtpData[12..15] -join '.'
If (-Not $NoDns) {
If ($DnsLookup = Resolve-DnsName $ReferenceIdentifier -QuickTimeout -ErrorAction SilentlyContinue) {
$ReferenceIdentifier = "$ReferenceIdentifier <$($DnsLookup.NameHost)>"
}
}
Break
}
4 {
# Version 4 Secondary Server, RefId = low-order 32-bits of
# latest transmit time of reference source
$ReferenceIdentifier = [BitConverter]::ToUInt32($
Break
}
Default {
# Unhandled NTP version...
$ReferenceIdentifier = $Null
}
}
}
# Calculate Root Delay and Root Dispersion values
$RootDelay = [BitConverter]::ToInt32($N
$RootDispersion = [BitConverter]::ToUInt32($
# Finally, create output object and return
$NtpTimeObj = [PSCustomObject]@{
NtpServer = $Server
NtpTime = OffsetToLocal($t4ms + $Offset)
Offset = $Offset
OffsetSeconds = [Math]::Round($Offset/1000
Delay = $Delay
t1ms = $t1ms
t2ms = $t2ms
t3ms = $t3ms
t4ms = $t4ms
t1 = OffsetToLocal($t1ms)
t2 = OffsetToLocal($t2ms)
t3 = OffsetToLocal($t3ms)
t4 = OffsetToLocal($t4ms)
LI = $LI
LI_text = $LI_text
NtpVersionNumber = $VN
Mode = $Mode
Mode_text = $Mode_text
Stratum = $Stratum
Stratum_text = $Stratum_text
PollIntervalRaw = $PollInterval
PollInterval = New-Object TimeSpan(0,0,$PollInterval
Precision = $Precision
PrecisionSeconds = $PrecisionSeconds
ReferenceIdentifier = $ReferenceIdentifier
RootDelay = $RootDelay
RootDispersion = $RootDispersion
Raw = $NtpData # The undecoded bytes returned from the NTP server
}
# Set the default display properties for the returned object
[String[]$DefaultPropertie
'Mode_text', 'Stratum', 'ReferenceIdentifier'
# Create the PSStandardMembers.DefaultD
$ddps = New-Object Management.Automation.PSPr
# Attach default display property set and output object
$PSStandardMembers = [Management.Automation.PSM
$NtpTimeObj | Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $PSStandardMembers -PassThru
$a=$NtpTimeObj.offsetsecon
if($a -lt 0){$b=[decimal]$a * 100 * -1}else {$b=[decimal]$a * 100}
$TIME=[int]$b
write-host $TIME
$PropertyBag.AddValue("TIM
$PropertyBag
ASKER
Thanks for looking into it. I realized that my $TIME variable was already set from other attempts making me think it was working. I cleared the variable data and tried again but I still get a lot of errors. I'll report back.
What I'd do in your situation is start fresh.. and do the following
install-module ntptime
install-module ntptime
[CmdletBinding()]
[OutputType('NtpTime')]
Param (
[String]$Server = 'pool.ntp.org',
[Int]$MaxOffset = 100, # (Milliseconds) Throw exception if network time offset is larger
[Switch]$NoDns # Do not attempt to lookup V3 secondary-server referenceIdentifier
)
import-module ntptime -ErrorAction stop
$api=New-Object -comObject "MOM.ScriptAPI"
$PropertyBag = $api.CreatePropertyBag()
$values = get-ntptime -Server 'pool.ntp.org' -MaxOffset 10000 -NoDNS
$PropertyBag.AddValue("TIME",$TIME)
$PropertyBag
ASKER
Thanks again. With this script, I have no NTPTIME module to import.
import-module : The specified module 'ntptime' was not loaded because no valid module file was found in any module
directory.
At line:8 char:1
+ import-module ntptime -ErrorAction stop
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~ ~~~
+ CategoryInfo : ResourceUnavailable: (ntptime:String) [Import-Module], FileNotFoundException
+ FullyQualifiedErrorId : Modules_ModuleNotFound,Mic rosoft.Pow erShell.Co mmands.Imp ortModuleC ommand
import-module : The specified module 'ntptime' was not loaded because no valid module file was found in any module
directory.
At line:8 char:1
+ import-module ntptime -ErrorAction stop
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ResourceUnavailable: (ntptime:String) [Import-Module], FileNotFoundException
+ FullyQualifiedErrorId : Modules_ModuleNotFound,Mic
ASKER
But I do get data when I type ntptime from the prompt
ASKER
So I just omitted the import-module line and this is the error.
The variable '$TIME' cannot be retrieved because it has not been set.
At line:4 char:30
+ $PropertyBag.AddValue("TIM E",$TIME)
+ ~~~~~
+ CategoryInfo : InvalidOperation: (TIME:String) [], RuntimeException
+ FullyQualifiedErrorId : VariableIsUndefined
The variable '$TIME' cannot be retrieved because it has not been set.
At line:4 char:30
+ $PropertyBag.AddValue("TIM
+ ~~~~~
+ CategoryInfo : InvalidOperation: (TIME:String) [], RuntimeException
+ FullyQualifiedErrorId : VariableIsUndefined
did you
'install-module ntptime' ??
'install-module ntptime' ??
ASKER
No such command install-module. I imported module but ntptime doesn't exist. Server2012 has ntptime already in the shell so I just type it and I have lots of data populated.
[CmdletBinding()]
[OutputType('NtpTime')]
Param (
[String]$Server = 'pool.ntp.org',
[Int]$MaxOffset = 100, # (Milliseconds) Throw exception if network time offset is larger
[Switch]$NoDns # Do not attempt to lookup V3 secondary-server referenceIdentifier
)
import-module ntptime -ErrorAction stop
<#
$api=New-Object -comObject "MOM.ScriptAPI"
$PropertyBag = $api.CreatePropertyBag()
#>
$ntpTimeObj = get-ntptime -Server 'pool.ntp.org' -MaxOffset 10000 -NoDNS
$a=$NtpTimeObj.offsetseconds
if($a -lt 0){$b=[decimal]$a * 100 * -1}else {$b=[decimal]$a * 100}
$TIME=[int]$b
write-host $TIME
<#
$PropertyBag.AddValue("TIME",$TIME)
$PropertyBag
#>
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Nice I get an output now.
ASKER
I get either a 1 or more depending on the milliseconds. That's what SCOM reads. Then I modify the monitor parameters to send an alert if it's above or below that number.
ASKER
Let me try this now within SCOM.
ASKER
Well the script works but SCOM doesn't create the alerts so I'll close the thread and post on a SCOM site.
Thanks again for breaking the code down to and getting it to work. ;)
Thanks again for breaking the code down to and getting it to work. ;)
I think you started off with https://www.powershellgallery.com/packages/NtpTime/1.1/Content/NtpTime.psm1
Open in new window
fixed mismatched brackets
Open in new window