Windows Update Agent(WUA)API を用いた資産管理やパッチ監査で、Windows 11 23H2/24H2/25H2 環境に適用されないはずの旧版向け累積更新が「インストール済み」と判定されてしまう――現場で致命的な誤判定を生むこの事象について、再現パターン、推定原因、実務で効くワークアラウンドと監査ロジックの実装例までを一気通貫で解説します。
問題の全体像:WUA API が重複 KB を「インストール済み」と誤認識
WUA API(IUpdateSearcher など)を使って Windows 11 をスキャンすると、同一月に公開された別バージョン(22H2/23H2 など)向けの累積更新が、対象外であるにもかかわらず「検出」され、さらに IsInstalled が true を返すことがあります。結果として、資産管理ツールは「適切にパッチ済み」と誤判断し、実機が未適用のまま運用に入ってしまう危険が生じます。
| 観点 | 内容 |
|---|---|
| 対象 | Windows 11 23H2/24H2/25H2(Windows 10 では非再現) |
| 症状-1 | 当該 PC に適用対象外の旧版向け累積更新(例:22H2/23H2 用)が検出される |
| 症状-2 | IsInstalled プロパティが true を返し、インストール済みと誤報 |
| 症状-3 | 同月内で KBID が重複して返るため、ツール側が「最新が入っている」と判断してしまう |
| 再現条件 | オフライン CAB(wsusscn2.cab)、WSUS、Windows Update オンラインのいずれでも発生 |
この誤検出は スキャン結果(WUA)側だけに現れ、実際のインストール履歴や DISM が示すインストール済みパッケージ一覧には残らない点が大きなヒントになります。
推定原因:Detectoid と累積更新の内包関係が引き起こす誤判定
累積更新(LCU:Latest Cumulative Update)は、同系統の旧版の修正を内包します。例えば 24H2 用の LCU は 23H2/22H2 の内容を包含することが多く、適切な「適用対象判定(Detectoid)」があれば、24H2 を最新化すれば 23H2 相当の修正も満たすという関係になります。
しかし Windows 11 の一部ビルドでは、この Detectoid のロジックが「同月の別バージョン用 LCU を内包=その KB もインストール済み」という短絡的な解釈を行ってしまい、対象外 KB まで IsInstalled=true と誤認する挙動が観測されています。結果として、KBID が同月内で重複していても「全部入っている」と見えてしまうのです。
(概念図)
24H2 用 LCU ──┐
├─ 23H2 の修正を内包
└─ 22H2 の修正を内包
誤判定 Detectoid:
「24H2 用 LCU が入っている」 ⇒ 「23H2/22H2 の KB も“入っている”はず」 ⇒ IsInstalled=true
なお、この誤動作は Windows 10 では報告されていないため、Detectoid の定義や適用順序、Windows 11 特有のバージョニング(DisplayVersion や SSU/LCU の組み合わせ)に起因する可能性が高いと考えられます。
なぜ危険なのか:監査・自動適用のロジックが崩れる
- 監査の誤合格:スキャン上は「インストール済み」に見えるため、未適用端末が監査を通過する。
- 自動適用の取り逃し:
IsInstalled=falseをトリガーに「足りないパッチを入れる」運用では誤判定に気づきにくい。 - 横断レポートのノイズ:KBID 重複により、レポートで“二重適用”や“不一致”として大量にノイズが発生。
特に「KBID 単体での合否判定」「IsInstalled=true/false の二値での自動適用」といった単純化されたロジックは脆弱です。次章以降で、誤判定を排除するための二重化された検証フローと、運用に組み込みやすいスクリプト例を示します。
誤検出の特徴を掴む:タイトル・バージョン・UpdateID を三点照合
誤判定の多くは、以下の三点を併用することで除外できます。
- Title のバージョン文字列(例:「Windows 11 Version 24H2」)。現行ビルドと異なる文字列を含むものは候補から除外。
- UpdateID(GUID):同じ KBID でも UpdateID は一意。月次で重複する KBID を UpdateID で区別する。
- 実測のインストール履歴:
Get-WUHistory(PSWindowsUpdate)またはDISM /Get-Packagesの結果と突合し、実際に入っているものだけを「適用済み」とする。
| 項目 | 取得方法 | 用途 | 注意点 |
|---|---|---|---|
| DisplayVersion / Build | レジストリ(CurrentVersion)、[System.Environment]::OSVersion | バージョンミスマッチの粗取り(22H2/23H2/24H2/25H2) | Title にバージョンが含まれない場合は補助指標が必要 |
| UpdateID(GUID) | WUA API(IUpdate.Identity.UpdateID) | KBID 重複の一意化、改訂版(Revision)の識別 | ツール側のデータ構造に GUID フィールドを用意 |
| インストール履歴 | Get-WUHistory / DISM /Get-Packages | 「実際に入ったものだけ合格」ルールの中核 | PSWindowsUpdate の導入方針、権限、ネットワーク方針に留意 |
再現と検証:オフライン/WSUS/オンラインいずれでも起きる
本事象はスキャンエンジンの判定(Detectoid)に起因するため、参照元がオフライン CAB、WSUS、Windows Update いずれでも再現します。下記のような簡易スクリプトで挙動を観察できます。
PowerShell(WUA COM)での基本スキャン
# 管理者 PowerShell
$session = New-Object -ComObject 'Microsoft.Update.Session'
$searcher = $session.CreateUpdateSearcher()
# インストール済み/未インストールを合わせて取得
$result = $searcher.Search("IsInstalled=0 or IsInstalled=1")
$updates = for ($i=0; $i -lt $result.Updates.Count; $i++) {
$u = $result.Updates.Item($i)
[PSCustomObject]@{
Title = $u.Title
IsInstalled = $u.IsInstalled
KBIDs = ($u.KBArticleIDs -join ',')
UpdateID = $u.Identity.UpdateID
Revision = $u.Identity.RevisionNumber
}
}
$updates | Sort-Object Title | Format-Table -AutoSize
オフライン CAB(wsusscn2.cab)でのスキャン例
# wsusscn2.cab をローカルに保存済みと仮定
$svcMgr = New-Object -ComObject 'Microsoft.Update.ServiceManager'
$service = $svcMgr.AddScanPackageService('Offline Sync Service', 'C:\temp\wsusscn2.cab')
$session = New-Object -ComObject 'Microsoft.Update.Session'
$searcher = $session.CreateUpdateSearcher()
# ServerSelection=ssOthers(3) 相当
$searcher.ServerSelection = 3
$searcher.ServiceID = $service.ServiceID
$result = $searcher.Search("IsInstalled=0 or IsInstalled=1")
# 以下、上と同様に列挙
上記の結果に、現行バージョンとは異なる「Windows 11 Version 22H2/23H2 向け」の KB が IsInstalled=true で混じり込みます。これが本記事のテーマです。
実務で効くワークアラウンド:単独判定をやめ、二重化する
根本修正はベンダー側の対応(Detectoid の更新)を待つしかありません。現場で即効性のある対策は、単独指標を捨て、二重化された検証フローを導入することです。
| 区分 | 内容 | 備考・補足 |
|---|---|---|
| 恒久対応 | ベンダーへのバグ報告:Feedback Hub で詳細手順・ログ(Get-WindowsUpdateLog、DISM /Get-Packages)を送付。必要に応じて有償サポートを活用。 | 根本解決の唯一のルート |
| 一時対応① | IsInstalled だけで判断しない:Title 内のバージョン文字列(22H2/23H2/24H2…)をフィルタし、現行ビルド以外の KB を除外。 | 単純フィルタでも誤認識の大半を排除可能 |
| 一時対応② | Get-WUHistory で履歴を突合:実際に「適用に成功した」更新のみを採択。 | WUA 側の誤検出は履歴に残らない |
| 一時対応③ | DISM のパッケージ一覧で KB を照合:DISM /Online /Get-Packages /Format:Table を機械可読に。 | スクリプトから呼び出しやすい |
| 注意点 | KBID は月次で重複しやすい。UpdateID(GUID)も併用して一意化。 「未適用=IsInstalled=false」だけを頼りに自動適用する運用は危険。 | 誤判定で未適用が見落とされるリスク |
実装レシピ:誤判定を弾く PowerShell スニペット集
OS バージョン(DisplayVersion / Build / UBR)を取得する
$cv = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
$displayVersion = $cv.DisplayVersion # 例: "24H2"
$build = [int]$cv.CurrentBuild
$ubr = [int]$cv.UBR
$buildString = "{0}.{1}" -f $build, $ubr
WUA スキャン結果を「現行バージョンのみ」に絞り込む
$session = New-Object -ComObject 'Microsoft.Update.Session'
$searcher = $session.CreateUpdateSearcher()
$result = $searcher.Search("IsInstalled=0 or IsInstalled=1")
$filtered = @()
for ($i=0; $i -lt $result.Updates.Count; $i++) {
$u = $result.Updates.Item($i)
$title = $u.Title
# "Windows 11 Version 24H2" 等の明示表記で判定(なければ後段で除外)
$isSameVersion = ($title -match "Windows 11.*$displayVersion")
if ($isSameVersion) {
$filtered += [PSCustomObject]@{
Title = $title
KBIDs = ($u.KBArticleIDs -join ',')
IsInstalled = $u.IsInstalled
UpdateID = $u.Identity.UpdateID
Revision = $u.Identity.RevisionNumber
Deployed = $u.LastDeploymentChangeTime
}
}
}
$filtered | Sort-Object Deployed -Descending | Format-Table -AutoSize
KBID を UpdateID で一意化し、最新 Revision だけを残す
# KBID が複数の UpdateID を持つ場合、Revision が最大のものだけ残す
$canonical = $filtered |
ForEach-Object {
$kb = ($_.'KBIDs' -split ',')[0]
[PSCustomObject]@{
KBID = $kb
Title = $_.Title
UpdateID = $_.UpdateID
Revision = $_.Revision
Installed= $_.IsInstalled
}
} |
Group-Object KBID |
ForEach-Object {
$_.Group | Sort-Object Revision -Descending | Select-Object -First 1
}
$canonical | Format-Table -AutoSize
履歴(Get-WUHistory)と突合し、実際に入ったものだけ合格にする
# 事前に PSWindowsUpdate を導入済みであること
$history = Get-WUHistory | Where-Object {$_.Result -eq 'Succeeded'}
$final = foreach ($row in $canonical) {
$kb = $row.KBID
$hit = $history | Where-Object { $_.Title -match $kb }
[PSCustomObject]@{
KBID = $kb
Title = $row.Title
UpdateID = $row.UpdateID
Revision = $row.Revision
# 「履歴で成功」しているものだけ最終的に Installed とみなす
Installed = [bool]$hit
Evidence = if ($hit) { 'WUHistory' } else { 'WUA-only (suspect)' }
}
}
$final | Sort-Object KBID | Format-Table -AutoSize
DISM でパッケージ一覧を取得して KB 照合(履歴の代替・補強)
$tmp = New-TemporaryFile
dism /online /get-packages /format:table | Out-File -FilePath $tmp -Encoding utf8
$installedKB = Select-String -Path $tmp -Pattern 'KB\d+' -AllMatches |
ForEach-Object { $_.Matches.Value } | Sort-Object -Unique
$final2 = foreach ($row in $canonical) {
$kb = $row.KBID
[PSCustomObject]@{
KBID = $kb
Title = $row.Title
UpdateID = $row.UpdateID
Revision = $row.Revision
Installed = $installedKB -contains $kb
Evidence = if ($installedKB -contains $kb) { 'DISM' } else { 'WUA-only (suspect)' }
}
}
$final2 | Sort-Object KBID | Format-Table -AutoSize
最終判定の推奨ルール(擬似コード)
# 1) Title が現行 DisplayVersion と一致しない KB は除外
# 2) KBID ごとに UpdateID/Revision で一意化し、最新のみを候補に
# 3) 候補は WUHistory または DISM のいずれかで「実在」が確認できたものだけ Installed=true
# 4) 3) を満たさない WUA-only の項目は「参考(疑義あり)」と明示し、監査レポートを分離
# 5) 「未適用=IsInstalled=false」だけをトリガーに自動適用しない(人やワークフローの確認を挟む)
監査・レポートの設計指針:同一 KBID の「最新だけ有効」ポリシー
月次で KBID が重複しやすい前提に立ち、「同一 KBID では最新 Revision(または最新の LastDeploymentChangeTime)のみを有効とみなす」ポリシーをレポート側に実装します。これにより、検出ノイズを大幅に減らせます。
- スキーマ設計:KBID、UpdateID(GUID)、Revision、Title、分類、製品、対象バージョン、配布日時のカラムを必須化。
- 重複排除:KBID でグループ化し、Revision が最大のレコードだけを採用。
- 対象バージョンの一致:Title または対象情報に 23H2/24H2/25H2 と現行の
DisplayVersionが一致しないものを除外。 - 証跡種別:WUA-only / WUHistory / DISM の 3 値でフラグ化し、ダッシュボード上で視覚的に区別。
運用に組み込む:CI/CD・自動適用・例外処理のベストプラクティス
自動適用のガードレール
- 二段承認:WUA スキャン結果に対し、履歴または DISM の裏取りが取れた項目のみ自動適用候補へ。
- サージ制御:誤配布防止のため、リング(Pilot→Broad)配布と 24~48 時間の観察期間を設ける。
- ロールバック計画:LCU 適用後の既知不具合に備え、復元ポイント・回復イメージ・アンインストール手順書を整備。
例外ハンドリング
- WUA-only(疑義あり)の項目は自動適用から除外し、週次レビューで手動判断。
- バージョン跨ぎ(23H2 → 24H2 への機能更新)直後は、Detectoid 更新伝播まで数日~数週のラグを想定。
- WSUS/オフライン CAB の更新タイミングがズレていると、誤検出の波形が強く出る。同期ポリシーを見直す。
トラブルシュート:必要なログと採取コマンド
ベンダーへ報告する際は、再現手順・ビルド情報・スキャン結果・実測のインストール履歴の 4 点セットを提出します。
| ログ/情報 | 採取方法(例) | 用途 |
|---|---|---|
| OS 情報 | Get-ComputerInfo、reg query HKLM\...\CurrentVersion | DisplayVersion、Build、UBR の確認 |
| WUA スキャン結果 | 本記事の PowerShell 例で CSV 化 | 誤判定の再現証跡 |
| Windows Update ログ | Get-WindowsUpdateLog -LogPath C:\temp\WindowsUpdate.log | Detectoid 判定の痕跡確認 |
| インストール履歴 | Get-WUHistory、または DISM /Online /Get-Packages | 「実際に入っている」根拠の提示 |
CSV 収集スクリプトのひな形
$out = "C:\temp\wua-scan.csv"
$session = New-Object -ComObject 'Microsoft.Update.Session'
$searcher = $session.CreateUpdateSearcher()
$result = $searcher.Search("IsInstalled=0 or IsInstalled=1")
$rows = for ($i=0; $i -lt $result.Updates.Count; $i++) {
$u = $result.Updates.Item($i)
[PSCustomObject]@{
Title = $u.Title
IsInstalled = $u.IsInstalled
KBIDs = ($u.KBArticleIDs -join ',')
UpdateID = $u.Identity.UpdateID
Revision = $u.Identity.RevisionNumber
Deployed = $u.LastDeploymentChangeTime
}
}
$rows | Export-Csv -NoTypeInformation -Path $out -Encoding UTF8
Write-Host "Saved: $out"
Windows 10 と Windows 11 の比較(挙動差)
| 項目 | Windows 10 | Windows 11(23H2/24H2/25H2) |
|---|---|---|
| Detectoid 誤判定 | 現時点で非再現 | 同月の別バージョン LCU を「内包=Installed」と誤認する事例 |
| KBID 重複の影響 | 限定的 | 誤判定と相まって監査ノイズの主因に |
| 対処の要点 | 通常の判定でも概ね良好 | Title のバージョン一致、UpdateID で一意化、履歴/DISM で裏取り |
FAQ:よくある疑問
Q. IsInstalled=true なのに、なぜ DISM や履歴に出ないの?
WUA のスキャン段階(Detectoid 判定)で「入っているはず」と解釈しているだけで、実体のパッケージが導入されたわけではないためです。実測系(履歴/DISM)では「ない」ので不一致になります。
Q. Title にバージョンが入っていない更新はどう扱う?
UpdateID/Revision と 対象製品・分類を加味して判定します。方針として「現行 DisplayVersion と無関係なものは保留」「履歴または DISM の裏取りありのみ合格」に倒すのが安全です。
Q. KBID と UpdateID はどちらを信頼すべき?
レポートのキーは UpdateID(GUID)を主とし、KBID は表示や人間判読のための補助に回すのが定石です。KBID は月次で重複しうるため、一意性は保証しません。
Q. WSUS/オフライン CAB/オンラインのどれが安全?
本件が Detectoid の解釈に由来する以上、いずれも影響を受けます。同期遅延が長い経路ほど誤判定の「残存時間」が伸びるため、定期同期の見直しは有効です。
リスク低減の運用ガイドライン(チェックリスト)
- スキャン判定は WUA+履歴/DISM の二系統で突合する。
- 同一 KBID は常に「最新 Revision だけ有効」に正規化する。
- Title のバージョン文字列と現行
DisplayVersionが不一致な KB は一律除外。 - IsInstalled=false を自動適用トリガーにしない(人手確認・少数リング配布を挟む)。
- 週次で「WUA-only(疑義あり)」一覧をレビューし、必要に応じて一次情報(ログ)を追加採取。
- Feedback Hub に再現手順とログを添えて報告し、トラッキング番号を運用台帳で管理。
参考:C#(WUA COM)での最小サンプル
// 参照: csc /platform:x64 /unsafe ...
using System;
using WUApiLib;
class Program {
static void Main() {
var session = new UpdateSession();
var searcher = session.CreateUpdateSearcher();
// Installed/NotInstalled をまとめて取得
var result = searcher.Search("IsInstalled=0 or IsInstalled=1");
foreach (IUpdate u in result.Updates) {
var identity = u.Identity;
Console.WriteLine($"{u.Title} | Installed={u.IsInstalled} | " +
$"KB={string.Join(",", (string[])u.KBArticleIDs)} | " +
$"UpdateID={identity.UpdateID} | Rev={identity.RevisionNumber}");
}
}
}
上記の出力から、現行バージョンと異なる Title の KB が Installed=true となっていないかをまずは目視確認します。以降は PowerShell 例と同様に、UpdateID で一意化し履歴/DISM と突合してください。
セキュリティ・コンプライアンス視点の補足
- 脆弱性管理の SLA 逸脱:監査合格と現物不一致は SLA の根幹を揺るがすため、二系統検証は「努力目標」ではなく必須。
- 監査対応の透明性:レポートに「判定根拠(WUA / 履歴 / DISM)」を記録し、監査時に提示可能な形で保管。
- 変更管理:機能更新(Feature Update)直後は Detectoid の更新ラグを想定した暫定ルールを発布し、解除条件を明文化。
追加ヒント:実装をもっと堅牢にするために
- ビルド番号+SSU を基準に:
Servicing Stackのバージョンも併せて採取し、期待値と合致しない組み合わせを警告。 - 「同一 KBID の最新のみ有効」ポリシー:データベース上で KBID, UpdateID, Revision にユニーク制約を設け、古い Revision の採用をロジックでブロック。
- 公式パッチ情報の定期確認:月例・臨時配信の粒度で Known Issues を社内へ展開し、検出ロジックを随時調整。
安全な自動化のための「最終版」アルゴリズム
- WUA で全件スキャン(Installed/NotInstalled)。
- Title に現行
DisplayVersionが含まれないものは除外。 - KBID ごとにグルーピングし、最大 Revision の UpdateID を代表に選定。
- 代表 UpdateID が WUHistory または DISM に存在すれば Installed=true、なければ Suspect。
- 自動適用は NotInstalled かつ Suspect 以外(=裏取り済み)の項目だけを対象。
- Suspect は隔離リストに送り、週次で手動レビュー。
まとめ
- 本件の本質は WUA Detectoid の誤動作。ユーザー側で完全修正は不可能。
- ベンダーへの詳細報告を最優先に、公式修正を待つ。
- それまでの運用は、バージョン判定フィルタ/更新履歴突合/DISM 検証の三点で二重化し、KBID は UpdateID で一意化、同一 KBID は最新だけ有効のルールを徹底する。
IsInstalled単独の判定や自動適用は危険。常に裏取りを。

コメント