Files
ROMFASTSQL/proxmox/vm109-windows-dr/scripts/install_updates.ps1
Marius 94758421c5 fix(dr): Windows Update rebota VM 109 in mijlocul testului DR
Testul DR din 2026-08-08 a raportat "Restore failed" dupa 11 secunde, fara
niciun log RMAN. Cauza nu a fost restore-ul: KB5101001 fusese descarcat in
timpul testului din 2026-08-01 (singurul moment in care VM 109 e pornit),
a ramas staged dupa qm stop si s-a finalizat la boot-ul testului urmator.

Cronologie din Event Log-ul guest-ului:
  06:00:58  RestartManager 10010 - nu poate reporni powershell.exe (restore-ul)
  06:01:04  SCM 7034 - OpenSSH SSH Server terminat neasteptat
  06:01:06  pveelite: client_loop: send disconnect: Broken pipe -> FAILED
  06:01:38  VM-ul se reboteaza singur

Fereastra testului (Sambata 06:00) era in afara Active Hours (08:00-17:00),
deci pentru Windows era fereastra de mentenanta valida - iar VM 109 fiind
pornit doar in timpul testului, aceea era singura fereastra posibila.
Agravant: sshd nu avea acsiuni de recovery (RESET_PERIOD 0), deci dupa ce a
murit a ramas mort si au esuat si colectarea logului si shutdown-ul gratios.

Masuri:
- NoAutoUpdate=1 + AUOptions=2 pe VM 109 (aplicat direct in registry)
- actiuni de recovery pentru sshd: restart la 5s/10s/30s, reset=86400
- guard "STEP 3b: Windows servicing" inainte de restore (check_servicing.ps1):
  asteapta idle 300s, consuma controlat un reboot in asteptare, altfel
  abandoneaza cu "ABORTED - Windows servicing" in loc de un "Restore failed"
  inselator. Fail-open daca checkul lipseste - nu are voie sa pice testul.
- fereastra lunara de patching (vm109-patch-window.sh + install_updates.ps1),
  prima duminica 03:00, cu re-armare NoAutoUpdate=1 indiferent de rezultat

Adaugat si .gitattributes: cu core.autocrlf=true scripturile .sh ajungeau in
working tree cu CRLF, iar ele se deployeaza prin scp direct pe Proxmox.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhQBTegE4PiMPPaapLHjkc
2026-08-08 16:56:24 +03:00

88 lines
2.7 KiB
PowerShell

# install_updates.ps1 — aplică update-urile Windows pe VM 109, controlat.
#
# Rulat exclusiv din vm109-patch-window.sh (fereastra lunară de patching).
# În restul timpului VM 109 are NoAutoUpdate=1, ca Windows Update să nu mai
# poată porni singur în timpul testului DR săptămânal (incident 2026-08-08).
#
# Folosește API-ul COM Microsoft.Update.Session — funcționează într-o sesiune
# SSH non-interactivă, spre deosebire de UsoClient care raportează asincron și
# nu întoarce niciun status utilizabil.
#
# Ieșire (linii parsabile din bash):
# FOUND=<n>
# INSTALLED=<n>
# FAILED=<n>
# REBOOT_REQUIRED=<true|false>
# RESULT=<OK|NO_UPDATES|ERROR>
$ErrorActionPreference = "Stop"
try {
$session = New-Object -ComObject Microsoft.Update.Session
$searcher = $session.CreateUpdateSearcher()
Write-Output "Searching for updates..."
$result = $searcher.Search("IsInstalled=0 AND IsHidden=0")
Write-Output ("FOUND=" + $result.Updates.Count)
if ($result.Updates.Count -eq 0) {
Write-Output "INSTALLED=0"
Write-Output "FAILED=0"
Write-Output "REBOOT_REQUIRED=false"
Write-Output "RESULT=NO_UPDATES"
exit 0
}
$toInstall = New-Object -ComObject Microsoft.Update.UpdateColl
foreach ($u in $result.Updates) {
if ($u.EulaAccepted -eq $false) { $u.AcceptEula() }
Write-Output (" + " + $u.Title)
$toInstall.Add($u) | Out-Null
}
Write-Output "Downloading..."
$downloader = $session.CreateUpdateDownloader()
$downloader.Updates = $toInstall
$downloader.Download() | Out-Null
$ready = New-Object -ComObject Microsoft.Update.UpdateColl
foreach ($u in $toInstall) {
if ($u.IsDownloaded) { $ready.Add($u) | Out-Null }
}
if ($ready.Count -eq 0) {
Write-Output "INSTALLED=0"
Write-Output "FAILED=0"
Write-Output "REBOOT_REQUIRED=false"
Write-Output "RESULT=ERROR"
exit 1
}
Write-Output "Installing..."
$installer = $session.CreateUpdateInstaller()
$installer.Updates = $ready
$installResult = $installer.Install()
# ResultCode: 2 = succeeded, 3 = succeeded with errors, restul = eșec.
$ok = 0
$ko = 0
for ($i = 0; $i -lt $ready.Count; $i++) {
$code = $installResult.GetUpdateResult($i).ResultCode
if ($code -eq 2 -or $code -eq 3) { $ok++ } else { $ko++ }
}
Write-Output ("INSTALLED=" + $ok)
Write-Output ("FAILED=" + $ko)
Write-Output ("REBOOT_REQUIRED=" + $installResult.RebootRequired.ToString().ToLower())
if ($ko -gt 0) { Write-Output "RESULT=ERROR"; exit 1 }
Write-Output "RESULT=OK"
exit 0
}
catch {
Write-Output ("ERROR: " + $_.Exception.Message)
Write-Output "RESULT=ERROR"
exit 1
}