xetup/scripts/08-activation.ps1
X9 Dev d30767ef8b
Some checks failed
release / build-and-release (push) Failing after 32s
fix: comprehensive reliability and robustness improvements
Critical fixes:
- Fix resume mode: StepsByIDs returned Enabled=false, all resume steps
  would be SKIPPED (deployment could never resume after reboot)
- Add reboot loop protection: per-step retry counter (max 5) prevents
  infinite reboot cycles when a step always exits with code 9
- Block reboot when state.Save() fails in resumePhase (prevents state
  loss leading to full restart from scratch)
- Atomic state file write (write-to-tmp + rename) prevents JSON
  corruption on BSOD/power loss mid-write
- Script watchdog: kills scripts after 30 min of no output (resets on
  each line, so active long-running scripts are never killed)
- Fix copyFile: check Close() error explicitly instead of deferred
  close that silently drops flush errors (e.g. disk full)

High severity:
- Cleanup() now logs errors instead of silently ignoring them
- Email report: 3 retries with backoff + always saves C:\X9\report.html
- Winget parallel jobs: 10 min timeout, kill hung jobs
- UCPD stop verification: 2s wait + state check before PDF association
- Atera installer: /qn -> /qb so MFA window can appear
- GVLK activation: match by EditionID (registry, not localized) instead
  of fragile OS caption string matching

Medium severity:
- Default profile hive unload: retry loop (5 attempts, increasing delay)
- LayoutModification.xml: UTF-8 without BOM (PS 5.1 Set-Content adds BOM)
- Set-Reg SYSTEM task: try/finally ensures temp file + task cleanup
- Windows Update: @($available).Count for PS 5.1 single-result edge case
- config.json: add missing kmsServer field in activation section

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-28 11:49:43 +02:00

139 lines
7.8 KiB
PowerShell

<#
.SYNOPSIS
Activates Windows using a product key from config or KMS GVLK fallback.
.DESCRIPTION
Checks if Windows is already activated (LicenseStatus = 1). If not, reads the
product key from config.json activation.productKey. If no key is present, selects
the appropriate GVLK for the detected Windows edition and activates via KMS.
Optionally configures a specific KMS server if activation.kmsServer is set.
.ITEMS
oa3-bios-uefi-klic-kontrola-embedded-ke: Checks for OA3 embedded product key in BIOS/UEFI firmware via SoftwareLicensingService.OA3xOriginalProductKey WMI query. If a key is found, it is installed via slmgr /ipk and activation is attempted. Most OEM machines (since Win8 OA3) have a digital entitlement key in firmware - this path handles them without requiring a key in config.json.
klic-z-config-json-activation-productkey: Reads activation.productKey from config.json. Installs via slmgr.vbs /ipk <key> and activates via slmgr.vbs /ato. Supports MAK (Multiple Activation Key) for volume licensing without KMS, and retail keys. Takes priority over GVLK fallback.
fallback-na-gvlk-kms-client-key-dle-edic: When no key is in config, detects Windows edition via EditionID registry value (HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\EditionID) and maps to Microsoft's published GVLK table. EditionID is not localized, unlike Win32_OperatingSystem.Caption. Professional: W269N-WFGWX-YVC9B-4J6C9-T83GX, Enterprise: NPPR9-FWDCX-D2C8J-H872K-2YT43, Core (Home): TX9XD-98N7V-6WMQ6-BX7FG-H8Q99.
volitelny-kms-server-activation-kmsserve: If activation.kmsServer is in config.json, runs slmgr.vbs /skms <server>:<port> before /ato. Used for clients with on-premises KMS infrastructure (common in larger organizations with volume licensing).
preskocit-pokud-jiz-aktivovano: Queries Win32_WindowsLicenseStatus or SoftwareLicensingProduct to check LicenseStatus. Value 1 = Licensed (fully activated). Script skips activation attempt and logs "Windows already activated" to avoid unnecessary slmgr calls.
typ-klice-mak-vs-kms-vs-retail: Key type selection depends on client's Microsoft licensing: MAK = volume license key activates online against Microsoft (limited activations), KMS = requires KMS server on network (VLSC subscription), Retail = individual license from Microsoft Store or OEM.
#>
param(
[string]$ConfigPath,
[string]$LogFile
)
. "$PSScriptRoot\common.ps1"
$Config = Load-Config $ConfigPath
# -----------------------------------------------------------------------
# KMS Generic Volume License Keys (GVLK)
# Source: https://docs.microsoft.com/en-us/windows-server/get-started/kms-client-activation-keys
# These are official Microsoft-published keys for use with KMS infrastructure.
# Replace with your MAK/retail key for standalone activation.
# -----------------------------------------------------------------------
$KmsKeys = @{
# EditionID -> GVLK (source: docs.microsoft.com/windows-server/get-started/kms-client-activation-keys)
# Same keys work for both Windows 10 and Windows 11
"Professional" = "W269N-WFGWX-YVC9B-4J6C9-T83GX"
"ProfessionalN" = "MH37W-N47XK-V7XM9-C7227-GCQG9"
"ProfessionalEducation" = "6TP4R-GNPTD-KYYHQ-7B7DP-J447Y"
"Education" = "NW6C2-QMPVW-D7KKK-3GKT6-VCFB2"
"Enterprise" = "NPPR9-FWDCX-D2C8J-H872K-2YT43"
"Core" = "TX9XD-98N7V-6WMQ6-BX7FG-H8Q99"
"ProfessionalWorkstation" = "NRG8B-VKK3Q-CXVCJ-9G2XF-6Q84J"
}
# -----------------------------------------------------------------------
# Check for OA3 embedded BIOS/UEFI product key
# Most OEM machines since Win8 OA3 have a product key embedded in firmware.
# -----------------------------------------------------------------------
Write-Log "Checking for OA3 embedded BIOS/UEFI product key" -Level INFO
$oa3Key = (Get-CimInstance -ClassName SoftwareLicensingService -ErrorAction SilentlyContinue).OA3xOriginalProductKey
if ($oa3Key -and $oa3Key.Trim() -ne '') {
$maskedKey = $oa3Key.Substring(0, [Math]::Min(5, $oa3Key.Length)) + "-XXXXX-XXXXX-XXXXX-XXXXX"
Write-Log " OA3 key found in firmware: $maskedKey" -Level OK
} else {
Write-Log " No OA3 key found in firmware" -Level INFO
$oa3Key = $null
}
# -----------------------------------------------------------------------
# Check current activation status
# -----------------------------------------------------------------------
Write-Log "Checking Windows activation status" -Level INFO
$licenseStatus = (Get-CimInstance SoftwareLicensingProduct -Filter "PartialProductKey IS NOT NULL AND Name LIKE 'Windows%'" -ErrorAction SilentlyContinue |
Select-Object -First 1).LicenseStatus
# LicenseStatus: 0=Unlicensed, 1=Licensed, 2=OOBGrace, 3=OOTGrace, 4=NonGenuineGrace, 5=Notification, 6=ExtendedGrace
if ($licenseStatus -eq 1) {
Write-Log " Windows is already activated - skipping" -Level OK
} else {
Write-Log " Activation status: $licenseStatus (not activated)" -Level WARN
# Detect Windows edition
$osCaption = (Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue).Caption
Write-Log " Detected OS: $osCaption" -Level INFO
# Key priority: config.json > OA3 firmware > GVLK
$customKey = $null
if ($Config -and $Config.activation -and $Config.activation.productKey) {
$customKey = $Config.activation.productKey
}
if ($customKey -and $customKey -ne "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX") {
# Use key from config (highest priority)
$keyToUse = $customKey
Write-Log " Using product key from config" -Level INFO
} elseif ($oa3Key) {
# Use OA3 key from firmware
$keyToUse = $oa3Key
Write-Log " Using OA3 key from firmware" -Level INFO
} else {
# Find matching GVLK key by EditionID (registry value, not localized)
$editionId = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name EditionID -ErrorAction SilentlyContinue).EditionID
Write-Log " EditionID: $editionId" -Level INFO
$keyToUse = $null
if ($editionId -and $KmsKeys.ContainsKey($editionId)) {
$keyToUse = $KmsKeys[$editionId]
Write-Log " Matched GVLK key for edition: $editionId" -Level INFO
} else {
Write-Log " No GVLK key for edition: $editionId" -Level WARN
}
}
if (-not $keyToUse) {
Write-Log " No matching key found for: $osCaption" -Level WARN
Write-Log " Skipping activation - set activation.productKey in config.json" -Level WARN
} else {
# Install key
Write-Log " Installing product key..." -Level INFO
$ipkResult = & cscript //nologo "$env:SystemRoot\System32\slmgr.vbs" /ipk $keyToUse 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Log " Key installed" -Level OK
} else {
Write-Log " Key install result: $ipkResult" -Level WARN
}
# Set KMS server if configured
if ($Config -and $Config.activation -and $Config.activation.kmsServer) {
$kmsServer = $Config.activation.kmsServer
Write-Log " Setting KMS server: $kmsServer" -Level INFO
& cscript //nologo "$env:SystemRoot\System32\slmgr.vbs" /skms $kmsServer 2>&1 | Out-Null
}
# Attempt activation
Write-Log " Attempting activation..." -Level INFO
$atoResult = & cscript //nologo "$env:SystemRoot\System32\slmgr.vbs" /ato 2>&1
$atoOutput = $atoResult -join " "
if ($atoOutput -match "successfully" -or $atoOutput -match "uspesn") {
Write-Log " Activation successful" -Level OK
} else {
Write-Log " Activation result: $atoOutput" -Level WARN
Write-Log " Activation may require a KMS server or valid MAK key" -Level WARN
}
}
}
Write-Log "Step 8 - Activation complete" -Level OK