Содержание
<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 свободных мегабайт и репутация этой причины. Правдоподобие - не доказательство, и чем убедительнее выглядит версия без подтверждения, тем дороже она обходится.