ドメインの時刻ズレは、Kerberos 認証失敗や監査ログの整合性崩れ、証明書検証エラーなど運用リスクに直結します。本記事では Active Directory 環境で「ドメイン既定の NTP(通常は PDC エミュレーター/組織の時刻基準サーバー)」と同期していない端末だけを抽出し、CSV で取得する実践手順をまとめます。PowerShell 推奨のうえで、CMD(バッチ)版も用意し、現場導入の勘所や運用設計まで具体的に解説します。
目的とゴール
組織全体の端末(サーバー/クライアント)を横断し、次の条件に該当するマシンだけを CSV で洗い出します。
- ドメイン時刻階層(
NT5DS)に従い、ドメイン コントローラー(DC)を時刻源にしていない端末 - または、許可した NTP サーバー(PDC エミュレーターや運用で定めた上位 NTP)以外を参照している端末
- 到達不可・権限不足などで確認できない端末も 要調査 として記録
出力 CSV は Excel でフィルタしやすい列構成(端末名/検出した時刻ソース/判定理由など)とし、日次・週次の定期レポートにも使える形にします。
前提・背景の整理
- ドメインの既定:クライアントとメンバー サーバーは既定で
NT5DSモード(ドメイン階層同期)です。各端末は所属サイトの DC を参照し、最上位のドメインでは PDC エミュレーターが「権威的な時刻源」として振る舞います。 - PDC エミュレーター:フォレスト/ドメイン階層の根に近い位置で外部 NTP を参照し、配下 DC とメンバーへ時刻を伝播します。
- ズレ許容:Kerberos は通常 ±5 分の時刻スキューが閾値ですが、ポリシー変更やアプリ要件に左右されるため、実運用では「同期元が適正か」を先に担保するのが有効です。
判定ロジック(OK/NGの考え方)
「同期していない」の判断は単純な文字列一致では誤判定の原因になります。そこで、w32tm /query /source の結果を正規化し、以下のように判定します。
| ケース | 例(w32tm /query /source) | 判定 | 解説 |
|---|---|---|---|
| ドメイン階層に準拠 | DC01.corp.local | OK | DC 名(FQDN/NetBIOS いずれも可)が時刻源。許容集合に含める。 |
| PDC の外部 NTP(PDC自身) | ntp1.example.net,0x9 | OK(※PDC / 例外許可時) | フラグ ,0x9 などは除去して比較。PDC のみ許容、ほかは原則 NG。 |
| 一般インターネット NTP | time.windows.com,0x9 | NG | ドメイン既定から逸脱。構成誤りの可能性が高い。 |
| 仮想化ホストの時刻提供 | VM IC Time Synchronization Provider | NG(原則) | Hyper-V の統合サービスによりドメイン外同期。GPO で無効化が基本。 |
| 未同期・自走 | Local CMOS Clock / Free-running System Clock | NG | 同期が切れている状態。至急の是正が必要。 |
要件・実行環境
- 管理端末(もしくは管理サーバー)に RSAT の Active Directory モジュール を導入済み
- 実行アカウントはドメイン内で端末情報にアクセスできる管理者権限(読み取り&リモート RPC 権限)
- ファイアウォールで リモート サービス管理(RPC) が許可(PowerShell リモーティングは不要な版も用意)
推奨:PowerShell スクリプト(リモーティング不要版)
管理端末から w32tm /query /computer:<対象> /source を呼び出す方式です。PowerShell Remoting(WinRM)が無効でも動作しやすく、導入が容易です。許容時刻源には 自動収集した DC 群+PDC を登録し、必要に応じて「追加で許可する NTP サーバー」を渡せるようにしています。
スクリプト全文
#requires -Modules ActiveDirectory
# Active Directory 環境で「ドメイン既定の NTP と同期していない端末」を抽出するレポート
# 実行は管理者 PowerShell を推奨
Import-Module ActiveDirectory
# === 設定パラメータ(必要に応じて編集) =========================
# 追加で許容したい NTP ソース(組織の上位 NTP など)。未使用なら空配列で可。
$AdditionalAllowedNtp = @() # 例: @('ntp1.example.net','ntp2.example.net')
# 出力ファイル
$OutCsv = '.\TimeSyncReport.csv'
# AD 検索フィルタ(停止済みや非対象を除外したい場合に調整)
$AdFilter = "Enabled -eq 'True'"
# タイムアウト(1 台あたり秒)
$PerHostTimeoutSec = 8
# ===============================================================
# 許容ソース(大文字小文字を無視して一致判定)
$allowed = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
# PDC と全 DC を収集(FQDN/短縮名の両方を許容に登録)
$domain = Get-ADDomain
$pdcFqdn = $domain.PDCEmulator
$allDcFqdns = (Get-ADDomainController -Filter *).HostName | Sort-Object -Unique
foreach ($name in ($allDcFqdns + $pdcFqdn + $AdditionalAllowedNtp | Where-Object { $_ })) {
[void]$allowed.Add($name)
$short = $name.Split('.')[0]
if ($short) { [void]$allowed.Add($short) }
}
# AD から対象コンピューター一覧(FQDN でなく NetBIOS 名でもOK)
$computers = Get-ADComputer -LDAPFilter $AdFilter -Properties DNSHostName,OperatingSystem |
Select-Object @{n='Name';e={$*.DNSHostName -ne $null ? $*.DNSHostName : $_.Name}},
OperatingSystem
# 実行結果格納
$rows = New-Object System.Collections.Generic.List[object]
# タイムアウト管理(Start-Job を使わず同期実行で安定性重視)
$swGlobal = [System.Diagnostics.Stopwatch]::StartNew()
foreach ($c in $computers) {
$name = $c.Name
$os = $c.OperatingSystem
# 端末の Windows Time サービス状態を把握(Get-Service は RPC ベース、WinRM 不要)
try {
$svc = Get-Service -ComputerName $name -Name 'W32Time' -ErrorAction Stop
$svcStatus = $svc.Status.ToString()
} catch {
$svcStatus = 'Unknown'
}
# w32tm /query /computer:<host> /source で現在ソースを取得
$sourceRaw = $null
$ok = $false
$reason = 'Unknown'
$cmd = "w32tm /query /computer:$name /source"
try {
$jobSw = [System.Diagnostics.Stopwatch]::StartNew()
$p = Start-Process -FilePath 'cmd.exe' -ArgumentList "/c $cmd" -NoNewWindow -PassThru -RedirectStandardOutput ([System.IO.Path]::GetTempFileName())
# 簡易タイムアウト
while (-not $p.HasExited) {
Start-Sleep -Milliseconds 200
if ($jobSw.Elapsed.TotalSeconds -ge $PerHostTimeoutSec) {
try { $p.Kill() } catch {}
throw "Timeout"
}
}
$sourceRaw = Get-Content -Path $p.RedirectStandardOutput -ErrorAction SilentlyContinue | Select-Object -First 1
Remove-Item -Path $p.RedirectStandardOutput -Force -ErrorAction SilentlyContinue
} catch {
$sourceRaw = $null
}
# 正規化:末尾の ,0x9 などのフラグや括弧表示を除去
$sourceClean = $null
if ([string]::IsNullOrWhiteSpace($sourceRaw)) {
$reason = 'Unreachable or AccessDenied or Timeout'
} else {
$sourceClean = ($sourceRaw.Trim() -replace ',0x[0-9a-fA-F]+$','' -replace '\s+\(.*\)$','')
$short = $sourceClean.Split('.')[0]
switch -Regex ($sourceClean) {
'^(Local CMOS Clock|Free-running System Clock)$' { $ok = $false; $reason = 'FreeRunningOrCMOS'; break }
'^VM IC Time Synchronization Provider$' { $ok = $false; $reason = 'HyperVTimeProvider'; break }
default {
if ($allowed.Contains($sourceClean) -or $allowed.Contains($short)) {
$ok = $true
$reason = 'OK'
} else {
$ok = $false
$reason = 'SourceNotAllowed'
}
}
}
}
# 必要なら未同期のみ記録。ここでは「未同期/要調査のみ」を CSV に出す方針。
if (-not $ok) {
$rows.Add([pscustomobject]@{
ComputerName = $name
OperatingSystem = $os
TimeSource = $sourceRaw
ServiceW32Time = $svcStatus
InSync = $ok
Reason = $reason
}) | Out-Null
}
}
# CSV 出力(UTF-8 BOM なし)
$rows | Sort-Object ComputerName | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $OutCsv
Write-Host "Report written to $OutCsv. Count = $($rows.Count)" -ForegroundColor Cyan
ポイント
- ロケール非依存:
w32tm /query /sourceは 「Source:」 などのラベル解析が不要で、OS 言語に左右されません。 - 許容ソースの自動化:AD から DC 群(FQDN と短縮名の両方)と PDC を収集し、比較用にハッシュセット化。大小文字差や FQDN/短縮名差を吸収します。
- Hyper-V 例外の明示:VM IC Time Synchronization Provider は原則 NG として扱い、構成ミス検知を優先します(必要に応じて
$AdditionalAllowedNtpに追加許容も可能)。 - サービス状態も併記:
Get-Service -ComputerNameでW32Timeが停止・無効の端末を同時に可視化できます。 - 未同期のみ出力:レポートは「対処が必要な端末だけ」になるため、確認の手間を最小化できます。
実行手順(最短)
- 管理者 PowerShell を開く(RSAT の ActiveDirectory モジュールが使える環境)。
- 上記スクリプトを
Check-TimeSource.ps1として保存し、必要に応じて$AdditionalAllowedNtpを編集。 - PowerShell から実行:
.\Check-TimeSource.ps1 - カレントに
TimeSyncReport.csvが生成されます(未同期または要調査のみ)。
CSV 出力例
ComputerName,OperatingSystem,TimeSource,ServiceW32Time,InSync,Reason
APP01.corp.local,Windows 10 Enterprise,DC02.corp.local,Running,False,SourceNotAllowed
SRV-HV01.corp.local,Windows Server 2019,VM IC Time Synchronization Provider,Running,False,HyperVTimeProvider
VPNGW01.corp.local,Windows Server 2022,,Unknown,False,Unreachable or AccessDenied or Timeout
拡張:厳格な NT5DS 準拠+スキュー測定(Remoting 版)
より厳密に判定したい場合は、PowerShell Remoting(WinRM)を前提に以下の改良を加えます。
- 構成タイプの確認:リモートで
HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\ParametersのTypeを読み、NT5DS以外(例:NTP)を NG と判定(例外許可を除く)。 - 時刻スキューの実測:対象端末から PDC に対して
w32tm /stripchart /dataonly /samples:3を実行し、オフセットの最大値がしきい値(例:5 秒)を超えたら NG。
# 厳格判定(WinRM 必須)
param(
[int]$SkewThresholdSec = 5,
[string]$OutCsv = '.\TimeSyncStrict.csv'
)
Import-Module ActiveDirectory
$domain = Get-ADDomain
$pdc = $domain.PDCEmulator
$allowed = (Get-ADDomainController -Filter *).HostName + $pdc | ForEach-Object {
$_
$_.Split('.')[0]
} | Sort-Object -Unique
$targets = Get-ADComputer -Filter "Enabled -eq 'True'" | Select-Object -ExpandProperty Name
$rows = foreach ($t in $targets) {
try {
$data = Invoke-Command -ComputerName $t -ScriptBlock {
param($pdc, $allowed)
# 構成タイプ(レジストリ)
$type = (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Parameters' -Name Type -ErrorAction Stop).Type
$source = (w32tm /query /source)
# PDC とのオフセット(3 サンプルの最大値)
$offsets = (w32tm /stripchart /computer:$pdc /dataonly /samples:3 2>&1) |
ForEach-Object {
if ($_ -match '([-+]*\d+(?:[.,]\d+)?)s') {
[double]($_.Matches[0].Groups[1].Value.Replace(',', '.'))
}
} | Where-Object { $_ -ne $null }
$maxOffset = ($offsets | Measure-Object -Maximum).Maximum
[pscustomobject]@{
ComputerName=$env:COMPUTERNAME
Type=$type
Source=$source
MaxOffsetSec=[Math]::Round($maxOffset,3)
}
} -ArgumentList $pdc, $allowed -ErrorAction Stop
# 判定
$srcClean = ($data.Source -replace ',0x[0-9a-fA-F]+$','' -replace '\s+\(.*\)$','')
$short = $srcClean.Split('.')[0]
$isSourceOk = $allowed -contains $srcClean -or $allowed -contains $short
$isTypeOk = $data.Type -eq 'NT5DS'
$isSkewOk = ($null -ne $data.MaxOffsetSec) -and ($data.MaxOffsetSec -le $SkewThresholdSec)
$ok = ($isSourceOk -and $isTypeOk -and $isSkewOk)
if (-not $ok) {
[pscustomobject]@{
ComputerName = $t
ConfigType = $data.Type
TimeSource = $data.Source
MaxOffsetSec = $data.MaxOffsetSec
InSync = $ok
Reason = ("{0}/{1}/{2}" -f
($(if($isSourceOk){'SrcOK'}else{'SrcNG'})),
($(if($isTypeOk){'TypeOK'}else{'TypeNG'})),
($(if($isSkewOk){'SkewOK'}else{'SkewNG'})))
}
}
} catch {
[pscustomobject]@{
ComputerName = $t
ConfigType = ''
TimeSource = ''
MaxOffsetSec = ''
InSync = $false
Reason = 'Unreachable or AccessDenied'
}
}
}
$rows | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $OutCsv
Write-Host "Strict report: $OutCsv (Count=$($rows.Count))"
この「厳格判定版」は WinRM が利用できる環境で威力を発揮します。構成タイプを NT5DS に統一したい環境や、NTP 参照先が許容でも実オフセットが大きい端末を掘り起こしたい場合に有効です。
代替:CMD(バッチ)版
PowerShell の導入が難しい現場向けに、コンピューター名のリスト(computers.txt) を用意して回す単純なバッチ例を掲載します。各行に 1 台ずつホスト名(FQDN/短縮名)を記載してください。
computers.txt の作成例
- PowerShell あり:
Get-ADComputer -Filter "Enabled -eq 'True'" | Select -Expand Name > computers.txt - または管理台帳からエクスポート、資産管理のリストを流用
バッチ ファイル
@echo off
setlocal enabledelayedexpansion
set OUTPUT=TimeSyncReport.csv
REM ここに許容する時刻源(DC 名や PDC、上位 NTP)を登録(FQDN と短縮名を混在可)
set "EXPECTED=DC01,DC01.corp.local,DC02,DC02.corp.local,PDC01.corp.local,ntp1.example.net"
echo ComputerName,TimeSource,Status>"%OUTPUT%"
for /f "usebackq delims=" %%C in ("computers.txt") do (
set "PC=%%~C"
set "SOURCE="
for /f "usebackq tokens=* delims=" %%S in (`w32tm /query /computer:%%~C /source 2^>nul`) do (
set "SOURCE=%%S"
)
if not defined SOURCE (
echo %%~C,,Unreachable>>"%OUTPUT%"
) else (
REM 末尾の ,0x9 等のフラグを除去
for /f "tokens=1 delims=," %%A in ("!SOURCE!") do set "CLEAN=%%~A"
set "MATCH=0"
for %%E in (!EXPECTED!) do (
if /I "%%~E"=="!CLEAN!" set "MATCH=1"
)
if /I "!CLEAN!"=="Local CMOS Clock" set "MATCH=0"
if /I "!CLEAN!"=="Free-running System Clock" set "MATCH=0"
if /I "!CLEAN!"=="VM IC Time Synchronization Provider" set "MATCH=0"
if "!MATCH!"=="0" (
echo %%~C,"!CLEAN!",NotInSync>>"%OUTPUT%"
)
)
)
echo Done. Output: %OUTPUT%
endlocal
バッチ版は w32tm のリモート照会(RPC)に依存します。到達不可やアクセス拒否は Unreachable として残るため、別途ポートや権限の是正を行ってください。
運用設計:定期レポート化と是正フロー
スケジュール実行
PowerShell 版を週次などで自動実行する場合のタスク登録例です。
# 例:毎日 07:30 に実行、CSV を共有フォルダーへ
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-NoProfile -ExecutionPolicy Bypass -File C:\Scripts\Check-TimeSource.ps1'
$trigger = New-ScheduledTaskTrigger -Daily -At 7:30
Register-ScheduledTask -TaskName 'TimeSyncReport' -Action $action -Trigger $trigger -Description 'NTP未同期端末の抽出'
是正の優先順位
- CMOS/Free-running:即時対応。
w32timeの再構成と同期実施。 - Hyper-V Provider:該当 VM の統合サービスの時刻同期を 無効 にし、ドメイン同期に戻す。
- インターネット系 NTP:GPO/ローカル ポリシーの誤設定を是正。
NT5DS/PDC 経由へ。 - Unreachable:ネットワーク/ファイアウォール/電源状態/権限を確認。
是正コマンドのチートシート
現地またはリモートでの是正に使える最小コマンド例です。
# 既定(ドメイン階層)に戻す
w32tm /config /syncfromflags:DOMHIER /update
net stop w32time & net start w32time
w32tm /resync /nowait
# PDC (または上位 NTP)を直接指定する場合(PDC サーバー上など環境に応じて)
w32tm /config /manualpeerlist:"ntp1.example.net ntp2.example.net" /syncfromflags:MANUAL /reliable:yes /update
net stop w32time & net start w32time
w32tm /resync /nowait
精度と誤判定を左右する要素
- 表示名の揺れ:
DC01とDC01.corp.localが混在します。本記事のスクリプトは FQDN と短縮名を双方許容する設計です。 - フラグの存在:
,0x9は NTP クライアント フラグ です。比較前に除去しています。 - OS ロケール:
w32tm /query /statusはラベルがローカライズされます。/sourceを使うことでロケール非依存にしています。 - Hyper-V の時刻同期:仮想マシンではホストからの時刻供給が優先されることがあります。ドメイン環境ではホスト側で VM 時刻同期を無効化するのが推奨です。
- マルチドメイン/サイト:別サイトの DC 名でも許容ですが、別フォレストの DC を参照していないか注意してください。
トラブルシューティング
| 症状 | 考えられる原因 | 確認/対処 |
|---|---|---|
| Unreachable/AccessDenied が多数 | RPC/ファイアウォール遮断、資格情報不足 | 管理ネットワークの疎通、Windows ファイアウォールの「リモート サービス管理」許可、管理者権限で実行 |
| Hyper-V Provider が出る | VM の時刻同期(統合サービス)が有効 | VM 設定で「時刻の同期」を無効化し、NT5DS へ戻す |
| time.windows.com 等が混在 | ローカル ポリシー/GPO の分断、手作業の設定残り | GPO の統一(コンピューターの構成 > 管理用テンプレート > システム > Windows Time サービス) |
| CMOS/Free-running の端末 | W32Time 停止、ドメイン未参加、電源断後のズレ | サービス再構成、ドメイン再参加、PDC からの強制再同期 |
よくある質問
- 「Type が NTP だが Source は DC」:例外的に NTP 指定で DC を指している構成です。同期源としては問題ないものの、設計上は
NT5DSへ統一するのが運用保守に優れます(厳格版で TypeNG と判定可能)。 - スキュー(秒数)も一緒に出したい:Remoting 版で
/stripchartの/dataonlyを用い、PDC との最大オフセットを採用するのが簡便です。 - ドメイン外端末も拾いたい:AD 取得を IP スキャンに置換し、
w32tm /query /computerを回す方式に変更してください。
まとめ
本記事の方針は、まず現実に参照している時刻源(source)を正しく拾い、ロケール差や表記揺れを吸収したうえで NG だけを CSV に落とすことです。PowerShell 版(リモーティング不要)は配布直後から動かしやすく、厳格版は GPO 準拠や実オフセットまで含めた品質管理に向きます。週次の定期レポートと是正フローをセットにし、ドメインの基盤品質(認証・ログ・監査)の土台となる「正しい時刻」を継続的に保全してください。
付録:シンプル版(最短 5 行)
小規模検証やスポット確認向けのワンライナーです。未同期だけ抽出します。
Import-Module ActiveDirectory
$allowed = (Get-ADDomainController -Filter *).HostName + (Get-ADDomain).PDCEmulator | % { $_; $_.Split('.')[0] } | Sort-Object -Unique
Get-ADComputer -Filter "Enabled -eq 'True'" | Select-Object -Expand Name | ForEach-Object {
$s=(w32tm /query /computer:$_ /source 2>&1); $c=$s -replace ',0x[0-9a-fA-F]+$','' -replace '\s+\(.*\)$',''; $sh=$c.Split('.')[0]
if(-not ($allowed -contains $c -or $allowed -contains $sh) -or $c -match 'Local CMOS Clock|Free-running System Clock|VM IC Time Synchronization Provider'){[pscustomobject]@{ComputerName=$_;TimeSource=$s}}
} | Export-Csv -NoTypeInformation -Encoding UTF8 .\TimeSyncMin.csv
付録:列の意味(推奨 CSV)
| 列名 | 説明 |
|---|---|
| ComputerName | 端末の FQDN または NetBIOS 名 |
| OperatingSystem | OS 種別(是正の優先度や担当振り分けに利用) |
| TimeSource | w32tm /query /source の生値(監査用に加工前を保持) |
| ServiceW32Time | Windows Time サービスの状態(Running/Stopped/Unknown) |
| InSync | 許容ソースかどうかの真偽 |
| Reason | NG 理由(SourceNotAllowed/HyperVTimeProvider/FreeRunningOrCMOS/Unreachable など) |
付録:よく使うコマンド集(確認用)
# ローカルの時刻情報
w32tm /query /status
w32tm /query /source
w32tm /query /peers
w32tm /query /configuration
# ドメインの PDC や DC を確認
(Get-ADDomain).PDCEmulator
Get-ADDomainController -Filter *

コメント