Содержание<nav class="toc-nav"> <ul> <li><a href="#где-я-искал-и-ничего-не-нашёл">Где я искал и ничего не нашёл</a></li> <li><a href="#как-достать-настоящий-код">Как достать настоящий код</a> <ul> <li><a href="#вызвать-установку-через-com-и-прочитать-hresult">Вызвать установку через COM и прочитать HRESULT</a></li> <li><a href="#прочитать-updateagentlog">Прочитать UpdateAgent.log</a></li> </ul> </li> <li><a href="#причина">Причина</a></li> <li><a href="#лечение">Лечение</a></li> <li><a href="#грабли-по-пути">Грабли по пути</a></li> <li><a href="#три-ловушки-поиска-по-логам">Три ловушки поиска по логам</a></li> <li><a href="#что-я-вынес">Что я вынес</a></li> </ul> </nav>

Бывают поломки, которые трудно починить. А бывают такие, где чинить-то нечего, просто непонятно, что сломалось. Этот случай из вторых.

Домашняя рабочая машина, Windows 11 25H2, сборка 26200.8655. Центр обновления предлагает 26H2. Нажимаю «установить». Скачивается. Не ставится. Нажимаю ещё раз - то же самое. За сутки четыре полных цикла загрузки, сборка не меняется.

Самое неприятное: в журналах нет ошибки. Не «непонятная ошибка», не «ошибка без описания». Ошибки нет вообще.

Где я искал и ничего не нашёл

Прошёл по всем очевидным местам. Вот честный результат:

ИсточникЧто показал
Microsoft-Windows-WindowsUpdateClient/Operationalтолько уровень «Сведения», ни одной ошибки
журнал Setupни одной записи выше информационной за сутки
Microsoft-Windows-UpdateOrchestrator/*журнала на этой машине не существует
CBS.log и все CbsPersist_*.logноль строк уровня Error и FatalError
DISM /Online /Cleanup-Image /RestoreHealthкод 0, хранилище компонентов чистое
sfc /scannowкод 0, нарушений целостности нет
политики HKLM:\SOFTWARE\Policies\...\WindowsUpdateключей нет, никто ничего не запрещает
раздел восстановления795 МБ, свободно 87 МБ
системный EFI-раздел196 МБ, свободно 162 МБ

На раздел восстановления я потратил больше всего времени и зря. 87 свободных мегабайт выглядят как классическая причина отказов обновлений, версия правдоподобная, и я в неё поверил. Она оказалась ложным следом.

Зато в журнале Setup нашлась механика происходящего. За сутки:

  • 74 раза «пакет успешно переведён в состояние Staged»
  • 68 раз Current state is Staged. Target state is Staged. Client id: Arbiter
  • и ни одного раза целевым состоянием не ставилось Installed

То есть система по кругу подтверждала то, что и так было сделано. А тишина в CBS объясняется просто: установщик падал до обслуживания компонентов. Каталог C:\$WINDOWS.~BT не создавался вовсе, значит setup даже не начинал работу. CBS честно молчал, потому что его время не наступало.

Как достать настоящий код

Два способа, и нужны оба.

Вызвать установку через COM и прочитать HRESULT

Окно Центра обновления кода не показывает. А вот API показывает:

$s = New-Object -ComObject Microsoft.Update.Session
$r = $s.CreateUpdateSearcher().Search('IsInstalled=0 and IsHidden=0')
$coll = New-Object -ComObject Microsoft.Update.UpdateColl
foreach ($u in $r.Updates) { if (-not $u.EulaAccepted) { $u.AcceptEula() }; $coll.Add($u) | Out-Null }

$dl = $s.CreateUpdateDownloader(); $dl.Updates = $coll
$dl.Download()

$i = $s.CreateUpdateInstaller(); $i.Updates = $coll
$res = $i.Install()
'0x{0:X8}' -f $res.HResult
'0x{0:X8}' -f $res.GetUpdateResult(0).HResult

Получил:

install: ResultCode=4  HResult=0x80240022  RebootRequired=False
  Windows 11, version 26H2  ->  код 4  HRESULT 0x800700CB

0x80240022 - это обёртка «все обновления не установились», в ней смысла нет. А внутри лежит настоящее: 0x800700CB, то есть ERROR_ENVVAR_NOT_FOUND, «указанная переменная среды не найдена».

Код странный. Первая мысль была про битую ссылку %VAR% в системных переменных, и я проверил их все, включая каждый элемент системного PATH на существование каталога. Всё разрешалось. Переменные были в порядке.

Прочитать UpdateAgent.log

Вот это и есть журнал обновлений, приходящих из Центра, и про него почему-то мало кто помнит:

C:\Windows\Logs\MoSetup\UpdateAgent.log

Там лежала вся цепочка:

Failed to start ETW tracing FetchBaseImagesLayerIds: HRESULT_FROM_WIN32(ERROR_DISK_FULL)
Container installation failed for command line: ...servicingstack_...
Servicing of container failed in the silo.: HRESULT_FROM_WIN32(203)
Failed to service container base images: HRESULT_FROM_WIN32(203)
Failed to recreate container base layer: {GUID}: HRESULT_FROM_WIN32(203)
Failed to recreate container base images: HRESULT_FROM_WIN32(203)
CScenarioCtrlBase::Install(459): Result = 0x800700CB
Error is fatal: [TRUE]

HRESULT_FROM_WIN32(203) и 0x800700CB - одно и то же число, просто записанное по-разному. Падало обслуживание базовых образов контейнеров, в «силосе», то есть внутри изолированного контейнерного окружения.

Про ERROR_DISK_FULL на трассировке ETW: это спутник, а не причина. На системном диске было свободно 650 ГБ. Фатальной отмечена именно 203-я.

Причина

Посмотрел на состояние контейнерных компонентов:

Get-WindowsOptionalFeature -Online |
  Where-Object { $_.FeatureName -match 'Container|Hyper-V|VirtualMachinePlatform|Linux' } |
  Select-Object State, FeatureName

Все включены: Containers, Containers-DisposableClientVM (это песочница Windows), Containers-HNS, Containers-SDN, плюс Hyper-V целиком, WSL и VirtualMachinePlatform.

А дальше главное:

C:\ProgramData\Microsoft\Windows\Containers - 24,5 ГБ
  BaseImages          0 ГБ   <- пусто
  ContainerStorages   1,15 ГБ
  Layers              основная масса
  Snapshots
  Dumps

Слои есть, а базового образа под ними нет. Двадцать четыре гигабайта надстройки над пустотой - наследие Docker Desktop, который когда-то стоял на этой машине и больше не использовался.

Установщик 26H2 обязан обслужить базовые образы контейнеров. Он идёт пересоздавать базовый слой, не находит его, и падает фатально. Обновление откатывается целиком, в CBS ни строчки, в Центре обновления - ошибка без кода.

Лечение

Docker я к этому моменту уже удалил, Windows-контейнеры и песочница мне не нужны. Поэтому решение простое:

Disable-WindowsOptionalFeature -Online -FeatureName Containers-DisposableClientVM -NoRestart
Disable-WindowsOptionalFeature -Online -FeatureName Containers -NoRestart

Перезагрузка, и после неё:

итог: ResultCode=2  HResult=0x00000000  ТребуетсяПерезагрузка=True
Windows 11, version 26H2 -> код 2, HRESULT 0x00000000

ResultCode=2 - успех. Десять минут, нулевой HRESULT, с первой попытки.

Приятный побочный эффект: при удалении компонента Windows вычистила все 24,5 ГБ сама. Каталог Containers после перезагрузки оказался пустым.

Грабли по пути

Дочерние компоненты гаснут вместе с родителем. Я собирался отключить только Containers и сохранить Containers-HNS - это Host Network Service, на нём исторически работает сеть WSL2. Не вышло: Containers-HNS и Containers-SDN дочерние к Containers и ушли в Disabled автоматически. Сеть хоста при этом не пострадала, все адаптеры остались в работе, но риск был реальный и о нём стоит знать заранее.

Каталог не переименовать, пока загружен wcifs. До перезагрузки я попробовал отставить папку с контейнерами в сторону и получил «Отказано в доступе». Полез проверять права:

icacls 'C:\ProgramData\Microsoft\Windows\Containers'

У Администраторов полный доступ, (I)(OI)(CI)(F). То есть дело не в ACL вообще. Дело в фильтре файловой системы:

fltmc filters

Filter Name       Num Instances    Altitude
bindflt                  1          409800
wcifs                    2          189900

wcifs - это Windows Container Isolation File System, и у него два живых подключения. Он и держал каталог. Побочный признак того же: при попытке перечислить Layers вылезает замечательное This operation cannot be performed in a transaction - внутри не обычные папки, а управляемые фильтром слои.

Get-HotFix не показывает накопительные обновления. Я по привычке проверял установку пакетов через него и получал «не установлен» там, где это ничего не доказывает. Надёжный признак - ревизия сборки:

$k = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
"$($k.DisplayVersion) сборка $($k.CurrentBuild).$($k.UBR)"

Сброс SoftwareDistribution обнуляет историю обновлений. Я сбросил кеш Центра обновления на раннем этапе - штатное действие, которое ничем не помогло, зато QueryHistory после него стал возвращать ноль записей. Диагностику это ослабило, и об этом надо помнить: сначала читаем историю, потом сбрасываем кеш, а не наоборот.

DISM /RestoreHealth выметает staged-пакеты в Absent. В журнале Setup это выглядит как Target state is Absent. Client id: DISM Package Manager Provider. Я сначала принял эти записи за часть поломки, пока не сопоставил время со своим же запуском DISM. Свои следы за чужие принимать - отдельный жанр самообмана.

Три ловушки поиска по логам

Все три пойманы за одно утро, на одних и тех же файлах CBS.

Первая. Select-String регистронезависим по умолчанию. Я искал строки уровня Error шаблоном ,\s+Error\s+ и получил пятьдесят «ошибок». Все до одной оказались безобидными информационными строками вида "Counter", error code 2. - запятая, пробел, слово error. Шаблон совпал идеально. Нужен -CaseSensitive.

Вторая. Слово error встречается во фразах, которые означают обратное. Поиск по подстроке выдал пачку строк does not have an install time property yet, ignoring any errors. Это не ошибка, это сообщение о том, что ошибки игнорируются. Искать надо по полю серьёзности с привязкой к началу строки:

Select-String -Path 'C:\Windows\Logs\CBS\*.log' `
  -Pattern '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}, (Error|FatalError)' `
  -CaseSensitive

Вот после этого и выяснилось, что настоящих ошибок уровня Error в CBS ровно ноль - и это был содержательный результат, а не отсутствие результата.

Третья, самая подлая. Запрос по несуществующему журналу падает, а с -EA 0 это выглядит как «ошибок нет».

Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-UpdateOrchestrator/Operational'; ...} -EA 0

Такого журнала на машине не существует. Команда возвращает The parameter is incorrect, подавленное исключение глотает сообщение, переменная остаётся пустой, и отчёт получается бодрый: «в журнале диспетчера обновлений ошибок нет». Проверять надо существование:

Get-WinEvent -ListLog 'Microsoft-Windows-UpdateOrchestrator/*'

Что я вынес

Две вещи.

Первая, техническая. Если обновление Windows падает, а в CBS пусто - значит падает не обслуживание компонентов, а что-то до него. Идти надо в C:\Windows\Logs\MoSetup\UpdateAgent.log, а код доставать через COM-установщик, а не из окна Параметров. Эти два приёма сэкономили бы мне половину дня, если бы я начал с них.

Вторая, про себя. Я потратил больше всего времени на раздел восстановления - на версию, которая была правдоподобной, но ни на чём не стояла. У меня не было ни кода ошибки, ни записи в логе, только 87 свободных мегабайт и репутация этой причины. Правдоподобие - не доказательство, и чем убедительнее выглядит версия без подтверждения, тем дороже она обходится.