製品コラム
セイテクエンジニアのブログ 製品コラム システム時刻の大きなズレを監視したい-BOM for Windows活用例
2025年11月12日配信
執筆者:セイ・テクノロジーズ エバンジェリスト
「BOM for Windows Ver.8.0」(以下、BOM)の「カスタム監視」を使用すると、BOM標準の監視設定にはない、独自の監視を行えます。先日、VPNで接続するリモートワーク環境で、リモートデスクトップ(RDP)接続が、普段のコンピューター名では認証に失敗し、IPアドレスだと成功するというトラブルの事例報告がありました(画面1)。原因は何らかの原因で仮想マシン(VM)で稼働しているActive Directoryのドメインコントローラーの時刻が大きくズレてしまったことでした。時刻のズレをすばやく検知できれば、問題に早く対処できます。今回は、ローカルサーバーやHyper-V VMのゲストOSのシステム時刻の正常性をBOMで監視する方法を紹介します。
画面1 ある日突然、コンピューター名によるRDP接続が認証に失敗するように。IPアドレス接続だと接続可能。原因はドメインのシステム時刻のずれだった
Active DirectoryドメインのKerberos認証で許容される時刻のずれは最大5分(300秒)であり、許容範囲を超えるとドメイン全体の認証やレプリケーション、グループポリシーの適用、証明書の有効性の確認、監査ログの信頼性の低下など、広範囲に申告な影響が出ます。
Active Directoryドメインでは、ドメインメンバーは認証されたドメインコントローラーと時刻同期を行い、そのドメインコントローラーはフォレストの最初のドメインコントローラーのPDCエミュレーターと時刻同期します。通常、そのPDCエミュレーターを外部のNTPサーバーと時刻同期するように構成して、ドメイン全体で時刻同期されるようにします。
ドメインコントローラーの時刻のズレの原因の1つとして考えられるのが、ドメインコントローラーをHyper-Vなどの仮想化ホストで実行しているケースです。Hyper-Vなどの仮想環境では、仮想マシン(VM)の時刻をHyper-Vホストと同期する機能が提供されていますが、運用向けのドメインコントローラーを仮想化する場合、Hyper-Vホストとの時刻同期をオフにし、外部のNTPサーバーと時刻同期を行うPDCエミュレーターを頂点とした、Active Directoryの時刻同期メカニズムに任せることが推奨されます。
Active Directory Domain Services (AD DS) の安全な仮想化|Windows Server(Microsoft Learn)
しかし、その状況において、ドメインコントローラーのVMの状態を保存して停止し、数日後に再開した場合、時刻のずれが大きすぎて(既定は48時間*1)、Windowsタイムサービス(w32time)による時刻同期が安全のため(時刻のロールフォーワードやロールバックの影響を回避するため)が行われないことがあります(画面2)。その場合、ドメイン全体で正確ではない時刻に同期されることになりますが、リモートワークのためのRDP接続などは時刻のズレの影響を受け、認証に失敗する状況になった可能性があります(前出の画面1)。
*1 参考: 大規模なタイム オフセットに対してWindows タイム サービスを構成する方法|Windows Server(Microsoft Learn)

画面2 ドメインコントローラーの時刻のずれが大きくなると、Windows Timeサービスは時刻同期を行わない。w32tm /resync /forceを実行(1回または複数回)すると強制的に同期できるが、時刻の大きな変更は別のトラブルの原因にもなるので注意
BOMを使用してローカルコンピューターやHyper-V VMのシステム時刻を監視するにあたり、基準となる時刻はBOMサーバーのローカルのシステム時刻ではなく、外部の公開NTPサーバーから取得した正確な時刻にすることにしました。そのために、ChatGPTの協力を得て、PowerShellで利用可能なGet-NtpTime関数を作成しました(画面3)。ファイアウォールでUDPポート123が遮断されていなければ、PowerShellのスクリプト内で再利用できます。
この関数は、指定したNTPサーバー(IPv4またはIPv6に名前解決)に対してUDPパケット(ポート123)を送信し、その応答から正確な時刻(ローカル時刻)を取得するスクリプトです。PowerShell標準機能とNET Frameworkの公開API、RFC 5905(Network Time Protocl Version 4)の仕様に基づいて作成されたもので、特定の著作権やライセンスに縛られたコードではないので、汎用的に利用できるはずです(ChatGPTは問題がないと言っています)。
function Get-NtpTime {
# A function that sends UDP packets to NTP server
# via IPv4 or IPv6 to obtain the precise time.
#
# return: datetime (based local timezone)
# $null (no server or something went wrong)
param([string]$NtpServer)
try {
# NTP packet (48 bytes)
$ntpData = New-Object byte[] 48
$ntpData[0] = 0x1B
# Send to ntp server via 123/UDP
$addresses = [System.Net.Dns]::GetHostAddresses($NtpServer)
$udpClient = $null
foreach ($addr in $addresses) {
try {
$udpClient = New-Object System.Net.Sockets.UdpClient($addr.AddressFamily)
$endpoint = New-Object System.Net.IPEndPoint($addr, 123)
$udpClient.Connect($endpoint)
# Received a response
[void]$udpClient.Send($ntpData, $ntpData.Length)
$udpClient.Client.ReceiveTimeout = 1500
$response = $udpClient.Receive([ref]$endpoint)
if ($response) {
$udpClient.Close()
break
}
} catch {
$udpClient.Close()
$udpClient = $null
}
}
if (-not $udpClient) { return $null }
# NTP timestamps are stored in bytes 40-47 (based on January 1, 1900)
$intPart = [BitConverter]::ToUInt32(($response[43..40]), 0)
$fracPart = [BitConverter]::ToUInt32(($response[47..44]), 0)
$ms = ($fracPart * 1000) / 0x100000000
$secondsSince1900 = $intPart
$unixEpochOffset = 2208988800
$unixSeconds = $secondsSince1900 - $unixEpochOffset
$dateTime = (Get-Date "1970-01-01T00:00:00Z").AddSeconds($unixSeconds).AddMilliseconds($ms)
return $dateTime.ToLocalTime()
}
catch {
return $null
}
}
# End of function Get-NtpTime
# Usage Examples
Get-NtpTime -NtpServer "time.windows.com"
Get-NtpTime -NtpServer "ntp.nict.jp"
(プレーンテキストで表示)

画面3 Get-NtpTimeカスタム関数を使用すると、指定したNTPサーバーにUDPパケットを送信して、その応答から正確な時刻を取得できる。代替関数のGet-NtpTimeByW32tm関数も使い方は同じ
Get-NtpTime関数に何かしら問題があった場合に備えて、もう1つ、Windows標準のw32tmコマンド(NTPサーバーの時刻との差分のデータを取得するw32tm /stripchart ・・・ /dataonly)を使用して同様の機能を実装した、Get-NtpTimeByW32tm関数も作成してみました。この後出てくるスクリプトはGet-NtpTime関数によるNTPサーバー「ntp.nict.jp」からの時刻取得を使用していますが、期待通り動かない場合は別のNTPサーバーを試すか、こちらのGet-NtpTimeByW32tm関数に置き換えてみてください。ただし、こちらの関数は、Windowsの日本語版でのみ正しく機能します。また、NTPサーバー(ntp.nict.jpなど)によってはタイムアウトエラーで失敗することがあります。
function Get-NtpTimeByW32tm {
# A function get time from ntpserver via w32tm command
param([string]$NtpServer)
$ret = & cmd /c "w32tm /stripchart /computer:$($ntpserver) /samples:1 /dataonly"
$localTime = $ret | Select-String "現在の時刻は "
$localTime = $localTime -Replace "現在の時刻は ", ""
$localTime = $localTime -Replace " です。", ""
$diffSec = $ret | Select-String -Pattern '([-+]?\d+\.\d+)s$'
if ($diffSec -and $diffSec.Matches.Count -gt 0) {
$offset = [double]$diffSec.Matches[0].Groups[1].Value
return ([datetime]$localtime).AddSeconds($offset)
$offset
} else {
return "Error: w32tm error"
}
}
# End of Get-NtpTimeByW32tm function
# Usage Examples
Get-NtpTimeByW32tm -NtpServer "time.windows.com"
(プレーンテキスト《UTF-8 BOM付き》で表示)
BOMに監視項目として「カスタム監視」を作成し、次のPowerShellスクリプト「checklocaltime.ps1」を定期的(例、5分間隔)に実行させることで、NTPサーバー(この例ではntp.nict.jp)から取得した時刻に基づいて、ローカルのシステム時刻の正確性を監視することができます(画面4)。外部NTPサーバーを使用しているため、ローカルシステムの時刻同期方法に関係なく、時刻の正確性を確認できます。スクリプトが返す値は、時間差を小数点以下四捨五入した絶対値の秒(整数)です。例えば、しきい値を3分(180秒)以上を注意、5分(300秒)以上を警告するように設定すれば、大きな時刻のずれが生じる前、あるいは直後にそれをメールアラート通知などで把握できます。なお、NTPサーバーから時刻を取得できなかった場合、このスクリプトは文字列“Error: 理由の簡単説明”を返し、BOMは「ステータス: 正常、前回の値:(N/A)」を表示します。
アクション項目を作成して、時刻にずれが生じたときに強制的に時刻同期(w32tm /resync /forceの実行など)させることも可能ですが、システム時刻の大きな変更は意図しない不具合につながるリスクがあるためお勧めしません。Windows標準の時刻同期機能や、Active Directoryの時刻同期メカニズムを使用して時刻同期することをお勧めします。その時刻同期が正常に機能していない問題を解決することが重要です。
[checklocaltime.ps1](プレーンテキストで表示)
$ntpserver = "ntp.nict.jp"
$ntpTime = Get-NtpTime -NtpServer $ntpserver
$localTime = Get-Date
if ($ntpTime) {
$diff = $ntpTime - $localTime
return [int][Math]::Abs($diff.TotalSeconds)
} else {
return "Error: no response from ntp server."
}

画面4 BOMによるローカルシステム時刻の監視
次のPowerShellスクリプト「checkvmtime.ps1」は、BOMで監視しているサーバーのHyper-V上で稼働している、特定のWindows VM(Hyper-Vホストとの時刻同期をオフにしていることを想定)のシステム時刻を監視できるように作成したものです。スクリプト内でNTPサーバー、監視対象のVMの名前、PowerShell Direct*2でVMのゲストOSに接続するための資格情報をカスタマイズすることで、実行中の特定のVMのシステム時刻を監視できます(画面5)。

画面5 BOMサーバーのローカルで実行されるHyper-V VMの情報を指定して監視する
[checkvmtime.ps1](プレーンテキストで表示)
$vmname = "###YOUR VM NAME###"
$password = ConvertTo-SecureString "###USERS PASSWORD###" -AsPlainText -Force
$username = "###COMPUTERNAME(or DOMAIN)\USERNAME###"
$cred = New-Object System.Management.Automation.PSCredential($username , $password)
$ntpserver = "ntp.nict.jp"
# Check Hyper-V module
Import-Module Hyper-V -ErrorAction SilentlyContinue
if (!(Get-Command Get-VM -ErrorAction SilentlyContinue)) {
return "Error: no hyper-v role or hyper-v cmdlet."
}
# Check VM status is running
$vmstatus = Get-VM -Name $vmname -ErrorAction SilentlyContinue
if ($vmstatus -eq $null) {
return "Error: no such vm."
} elseif ($vmstatus.State -ne "Running") {
return 0 # vm state not running
}
$baseTime = Get-NtpTime -NtpServer $ntpserver
if ($basetime) {
# Write-Host "$($basetime) is get from $($ntpserver)"
} else {
$basetime = Get-Date
# Write-Host "$($basetime) is get from local pc time"
}
$vmTime = Invoke-Command -VMName $vmname -ScriptBlock {Get-Date} -Credential $cred -ErrorAction SilentlyContinue
if ($vmTime) {
$diff = $baseTime - $vmTime
return [int][Math]::Abs($diff.TotalSeconds)
} else {
retnru "Error: could not get time from vm."
}
$vmTime = Invoke-Command -VMName $vmname -ScriptBlock {Get-Date} -Credential $cred -ErrorAction SilentlyContinue
if ($vmTime) {
$diff = $baseTime - $vmTime
return [int][Math]::Abs($diff.TotalSeconds)
} else {
retnrn "Error"
}
なお、このスクリプトは、Get-VMコマンドレットを使用できない場合(Hyper-Vがインストールされていない場合)や対象のVMが存在しない場合は文字列“Error”を返し、BOMは「ステータス: 正常、前回の値:(N/A)」と表示します。対象のVMが実行中でない場合は0を返します。また、BOMサーバーがNTPサーバーに接続できない場合、NTPサーバーに代わってBOMサーバーのローカルのシステム時刻を代わりの基準にしてVMのシステム時刻を監視します。しきい値や、時刻のずれが大きい場合の対処については、ローカルシステム時刻の監視と同様です(画面5)。

画面5 しきい値「300(秒)以上警告」で監視中のVMで、Hyer-Vホストとの時刻同期をオフにし、日付を前日に変更したところ。BOMが1日(86400秒)以上の時刻のずれを警告
*2 PowerShell Directは、Enter-PSSessionやInvoke-Commandを使用して、Hyper-VホストからVMのゲストOSに対して、PowerShellのコマンドやスクリプトを実行できる機能です。PowerShell Directは、Windows 10およびWindows Server 2016以降のHyper-Vを実行するホストから、同バージョンをゲストOSとして実行するVMに対して使用できます。VMのゲスト側でPowerShell Directを受け付けるための設定は必要なく、VMBus経由で実行されるため、VMのネットワーク接続も使用しません
PowerShell を使用した仮想マシンの自動化と管理|Windows Server(Microsoft Learn)
「checkvmtime.ps1」はBOMサーバーのローカルのHyper-V環境の指定したVMのシステム時刻を監視しますが、ローカルまたはリモートの任意の仮想環境(Hyper-VやVMware)で稼働するVMや、リモートのWindowsマシン(例えば、BOMで代理監視しているサーバー)のシステム時刻を監視することもできます。それには、PowerShell Remotingを利用します。以下の「checkpctime.ps1」は、「checkvmtime.ps1」のPowerShell Directを、PowerShell Remotingに書き換えたスクリプトであり、リモートのVMや物理マシンを指定して実行できます。
[checkpctime.ps1](プレーンテキストで表示)
function Get-NtpTime {
# 省略
A function that sends UDP packets to NTP server
# 省略
}
# End of function Get-NtpTime
$pcname = "ws2025vm03"
$password = ConvertTo-SecureString "P@ssw0rd" -AsPlainText -Force
$username = "ws2025vm03\administrator"
$cred = New-Object System.Management.Automation.PSCredential($username , $password)
$ntpserver = "ntp.nict.jp"
$baseTime = Get-NtpTime -NtpServer $ntpserver
if ($basetime) {
# Write-Host "$($basetime) is get from $($ntpserver)"
} else {
$basetime = Get-Date
# Write-Host "$($basetime) is get from local pc time"
}
$pcTime = Invoke-Command -ComputerName $pcname -ScriptBlock {Get-Date} -Credential $cred -ErrorAction SilentlyContinue
if ($pcTime) {
$diff = $baseTime - $pcTime
return [int][Math]::Abs($diff.TotalSeconds)
} else {
return "Error: could not get time from pc."
}
PowerShell Remotingを利用可能にするには、接続される側で以下のコマンドラインを実行して、PowerShell Remotingを有効化しておく必要があります。
| Enable-PSRemoting -Force |
また、Hyper-VホストとVMのゲストがActive Directoryドメインのメンバーでない場合(ワークグループ構成の場合)、接続元のコンピューターに信頼されたホストにリモートコンピューター名またはIPアドレスまたは*(すべてを信頼)を追加しておく必要があります(画面7)。
| Set-Item WSMan:\localhost\Client\TrustedHosts -Value "コンピューター名 または IPアドレス または *" |

画面7 「checkpctime.ps1」による監視を行うには、スクリプト内にコンピューター名と資格情報を書き込んでおくことに加え、PowerShell Remotingの利用環境を準備しておく必要がある