Windows Server 2022 Remote Desktop Session Host : Перестало открываться меню "Пуск", приложение "Параметры" и другие приложения AppX

Windows Server 2022 Remote Desktop Session Host - The Start menu, Settings app, and other AppX apps no longer open. AppReadiness Event ID 10 and 214 errors - Package could not be registered 0x80073CF6 with 0x800705AAВ пуле серверов Windows Server 2022 с ролью Remote Desktop Session Host (RDSH) в пользовательских сеансах одного из серверов появилась странная проблема - перестало открываться стартовое меню при нажатии кнопки "Пуск" и перестали открываться контекстные меню в некоторых местах на нижней панели задач. Кроме этого перестало запускаться AppX приложение "Параметры" или "Settings" в англоязычном варианте (не путать с классическими апплетами управления *.cpl).

На этом сервере проблема воспроизводилась у разных пользователей, но при попытке проверить соседний сервер, на тот момент времени, проблема не воспроизвелась. Поэтому изначально подумали, что проблема конкретно в этом сервере и начали пытаться купировать проблему такими простыми методами, как временное отключение антивирусного ПО/перезагрузка ОС/актуализация уровня кумулятивного обновления Windows и т.д. Ничего не помогло, но зато заметили, что за то время, пока мы занимались первым сервером, проблема вдруг стала воспроизводиться на соседнем сервере, где ещё недавно её не было. На этот момент времени выяснилось, что проблема проявляется уже на трёх из пяти серверов фермы.

В Event-логах Windows при попытках воспроизведения проблемы заметили повторяющиеся ошибки следующего вида:

Log Name:    Microsoft-Windows-AppReadiness/Admin
Event ID:    10
Level:       Error
User:        SYSTEM
Description: The Appx operation 'RegisterPackageAsync' on 'Microsoft.Windows.StartMenuExperienceHost_10.0.20348.1_neutral_neutral_cw5n1h2txyewy' failed for user 'S-1-5-21-....' - An internal error occurred with error 0x800705AA. (Error: Package could not be registered.)
Log Name:    Microsoft-Windows-AppReadiness/Admin
Event ID:    214
Level:       Error
User:        SYSTEM
Description: 'Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy' install failed for S-1-5-21-.... Error: 'Package could not be registered.'

Проверили от имени пользователя (не с правами администратора) наличие пакета, выполнив в консоли PowerShell:

Get-AppxPackage -Name Microsoft.Windows.StartMenuExperienceHost

В нашем случае в выводе информации о пакете явного криминала не наблюдалось:

Name              : Microsoft.Windows.StartMenuExperienceHost
Publisher         : CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US
Architecture      : Neutral
ResourceId        : neutral
Version           : 10.0.20348.1
PackageFullName   : Microsoft.Windows.StartMenuExperienceHost_10.0.20348.1_neutral_neutral_cw5n1h2txyewy
InstallLocation   : C:\Windows\SystemApps\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy
IsFramework       : False
PackageFamilyName : Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy
PublisherId       : cw5n1h2txyewy
IsResourcePackage : False
IsBundle          : False
IsDevelopmentMode : False
NonRemovable      : True
IsPartiallyStaged : False
SignatureKind     : System
Status            : Ok

Далее, как того рекомендуют разные гайды по диагностике AppX приложений, в контексте этого же пользователя (не с правами администратора) попытались перерегистрировать пакет:

Add-AppxPackage -Path "C:\Windows\SystemApps\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\AppxManifest.xml" -Register -DisableDevelopmentMode

И здесь мы снова словили исключение 0x80073CF6 с внутренним кодом ошибки 0x800705AA, аналогичным тому, что мы уже видели ранее в Event-логах:

Add-AppxPackage : Deployment failed with HRESULT: 0x80073CF6, Package could not be registered.
An internal error occurred with error 0x800705AA. See http://go.microsoft.com/fwlink/?LinkId=235160 for help diagnosing app deployment issues.
NOTE: For additional information, look for [ActivityId] ddc867e4-b522-0002-0590-c8dd22b5dc01 in the Event Log or use the command line Get-AppPackageLog -ActivityID ddc867e4-b52
2-0002-0590-c8dd22b5dc01
At line:1 char:1
+ Add-AppxPackage -Path "C:\Windows\SystemApps\Microsoft.Windows.StartM ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo           : WriteError: (C:\Windows\Syst...ppxManifest.xml:String) [Add-AppxPackage], IOException
    + FullyQualifiedErrorId : DeploymentError,Microsoft.Windows.Appx.PackageManager.Commands.AddAppxPackageCommand

В выводе ошибки нам дали рекомендацию выполнить запрос логов пакета для получения более развёрнутой информации о событиях:

Get-AppPackageLog -ActivityID "ddc867e4-b522-0002-398f-c8dd22b5dc01"

Но просмотр этого лога не дал большей ясности, так как там фигурировали те же коды ошибок с теми же описаниями.

Код 0x80073CF6, согласно гайда "Troubleshooting packaging..." просто фиксирует факт невозможности регистрации пакета, а внутренний код ошибки 0x800705AA интерпретируется как недостаточность ресурсов для завершения операции: "Insufficient system resources exist to complete the requested service" (ERROR_INSUFFICIENT_RESOURCES). Поиск по этой информации привёл нас к старой статье KB3063843 "Registry bloat causes slow logons or insufficient system resources error 0x800705AA in Windows 8.1", в которой идёт речь о ситуациях с переполнением ключа реестра:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Notifications

Как я понял, на серверах RDS после длительного периода работы, когда на сервер были выполнены уже тысячи входов пользователей, в указанном ключе реестра может происходить переполнение вследствие накапливания "мусорных" записей механизмов "Windows Push Notification" (WPN) и "Windows Notification Facility" (WNF). В результате это приводит к разным проблемам в пользовательской среде – начиная с задержек входа в систему и заканчивая странностями в работе приложений в графической оболочке. Всё это безобразие может сопровождаться повышенной нагрузкой на процессорные ресурсы, так как внутренние механизмы ОС периодически пытаются перечитывать ключ реестра с сотнями тысяч записей.

Когда я попытался для проверки открыть указанный ключ реестра в графическом редакторе regedit, то сразу стало понятно, что с этим ключом что-то нездоровое, так как редактор завис на несколько минут.
Обнаружилось, что внутри этого ключа 261 тысяча записей типа REG_BINARY:

($k = Get-Item "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Notifications").Property `
| Where-Object { $k.GetValueKind($_) -eq 'Binary' } | Measure-Object | % Count

261024

Точно такое значение было обнаружено на трёх серверах пула RDSH из пяти и на них на всех проблемы воспроизводились одинаково. На других двух цифра была тоже ощутимой - свыше 200k, но меньше, чем обозначенный предел 261024 и там проблемы не воспроизводились. Тут стало понятно, почему сначала на одном из соседних проверяемых ранее серверов ошибки интерфейса не воспроизводились, а позже они появились. Проблема имеет накопительный эффект и проявляет себя со временем.

На GitHub можно найти проект Lazy-256/clnotifications, где предлагается утилита clnotifications.exe для опроса и очистки проблемного ключа реестра. Но если нет желания прибегать к помощи сторонних инструментов, можно выполнить задачу с помощью встроенных в Windows средств. Приведём пример нехитрого PowerShell скрипта, который сначала сохранит резервную копию содержимого ключа реестра, а затем пересоздаст его пустым и восстановит вложенный ключ "Data", который может быть важен для корректной работы некоторых AppX приложений.

# Require Administrator
$principal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
    Write-Error "Script must be run as Administrator."
    exit 1
}

$BackupFolder="C:\Temp\Cleanup-Notifications-Backup"
$KeyPath="HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Notifications"
$RegPath="HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Notifications"
$DataPath="$KeyPath\Data"
$DataReg="$RegPath\Data"

$timestamp=Get-Date -Format "yyyyMMdd_HHmmss"
$FullBackup=Join-Path $BackupFolder ("Notifications_Full_{0}.reg"-f $timestamp)
$DataBackup=Join-Path $BackupFolder ("Notifications_Data_{0}.reg"-f $timestamp)
$LogFile=Join-Path $BackupFolder ("Cleanup-Notifications_{0}.log"-f $timestamp)

try {
    if (-not (Test-Path $BackupFolder)) {
        New-Item -Path $BackupFolder -ItemType Directory -Force | Out-Null
    }
}
catch {
    Write-Error ("Failed to create backup folder {0}: {1}"-f $BackupFolder,$_)
    exit 1
}

Start-Transcript -Path $LogFile -Force

Write-Host "=== Cleanup-Notifications started ==="
Write-Host ("Backup folder : {0}"-f $BackupFolder)
Write-Host ("Full backup   : {0}"-f $FullBackup)
Write-Host ("Data backup   : {0}"-f $DataBackup)

$confirm=Read-Host "Registry export and deletion will be performed. Continue? (Y/N)"
if ($confirm -notin @("Y","y","Yes","yes")) {
    Write-Warning "Operation cancelled."
    Stop-Transcript
    exit 0
}

try {

    Write-Host "1) Exporting full registry key..."
    & reg.exe export $RegPath $FullBackup /y 2>$null

    if (-not (Test-Path $FullBackup) -or ((Get-Item $FullBackup).Length -eq 0)) {
        throw "Full registry export failed"
    }

    Write-Host "Full backup created"

    Write-Host "2) Exporting Data subkey (if present)..."
    if (Test-Path $DataPath) {
        & reg.exe export $DataReg $DataBackup /y 2>$null
        if (-not (Test-Path $DataBackup) -or ((Get-Item $DataBackup).Length -eq 0)) {
            throw "Data subkey export failed"
        }
        Write-Host "Data backup created"
    }
    else {
        Write-Host "Data subkey not present"
        $DataBackup=$null
    }

    Write-Host "3) Deleting Notifications registry key..."
    & reg.exe delete $RegPath /f 2>$null
    Write-Host "Registry key deleted"

    Write-Host "4) Recreating empty Notifications key..."
    try {
        New-Item -Path $KeyPath -Force | Out-Null
    }
    catch {
        throw ("Failed to create key {0}: {1}"-f $KeyPath,$_)
    }
    Write-Host "Empty key created"

    if ($DataBackup) {
        Write-Host "5) Restoring Data subkey..."
        & reg.exe import $DataBackup 2>$null
        Write-Host "Data subkey restored"
    }

    Write-Host "Cleanup completed successfully."
    Write-Host "Server restart is recommended."
    Write-Host ("Log file: {0}"-f $LogFile)

}
catch {

    Write-Error $_

    if (Test-Path $FullBackup) {
        Write-Host "Attempting restore from full backup..."
        & reg.exe import $FullBackup 2>$null
        Write-Host "Restore attempted"
    }

}

Stop-Transcript
exit 0

После того, как ключ реестра очищен, можно выполнить перезагрузку сервера, если есть ранее открытые и некорректно работающие пользовательские сессии. Полагаю, что более предпочтительный путь – это сначала закрыть все пользовательские сессии и перевести сервер в обслуживание во избежание появления новых сессий, а затем уже спокойно чистить реестр с последующей перезагрузкой системы.

В нашем случае после перезагрузки не потребовалось никаких дополнительных манипуляций и все ранее неработающие элементы графической среды снова заработали.

Не нужно забывать про последующее удаление созданных в ходе работы скрипта файлов резервных копий и логов в каталоге "C:\Temp\Cleanup-Notifications-Backup".

Стоит отметить, что в поиске описания проблем, симптоматично схожих с нашей ситуацией, я неоднократно находил рекомендации, типа этой, о проверке ключа реестра на предмет переполнения тысячами правил Windows Firewall, которые могут генерироваться для работы AppX приложений:

HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\FirewallRules
HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\RestrictedServices\AppIso\FirewallRules

Но это явно был не наш случай, так как у нас в этих ключах ничего лишнего нет. Мы заметили эту "фичу" с генерацией множества "мусорных" правил ещё во времена, когда только начинали использовать Windows Server 2016 для многопользовательских сред RDS. Тогда мы решили эту проблему путём настройки несложного бэйзлайна в SCCM, который периодически чистит лишние правила:

$RulesNames = @("Desktop App Web Viewe?", `
"Microsoft Edge (mDNS-In?", "Windows Securit?", "Windows Searc?", `
"@{Microsoft.Windows.Cortana_*", "@{Microsoft.Windows.CloudExperienceHost_*", `
"@{Microsoft.AAD.BrokerPlugin_*", "@{Microsoft.Windows.StartMenuExperienceHost_*", `
"Cortan?", "Your accoun?", "Work or school accoun?", "Star?", `
"Учетная запись компании или учебного заведени?", "Запустит?", `
"@{Microsoft.Win32WebViewHost_*", "@{Microsoft.Windows.SecHealthUI_*", "True?Vector")

$RulesScope = Get-NetFirewallRule -DisplayName $RulesNames  `
| Where-Object {$_.Direction -eq "Inbound" -and $_.Enabled -eq "True"}
$RulesScope | Remove-NetFirewallRule

Добавить комментарий