kms-website: consequences-gated edition switch (Windows changepk + Office ODT)

When the installed product can't KMS-activate as-is, offer to fix it after
showing the consequences (default No; non-interactive needs explicit env consent):
- Windows non-VL edition (Home/retail) -> changepk.exe /ProductKey <target GVLK>
  (default Pro, $env:KMS_EDITION override). Warns: reboot required, one-way,
  re-run after reboot to activate.
- No VL Office/Project/Visio installed -> slim ODT setup.exe /configure to the
  target VL product (default ProPlus/ProjectPro/VisioPro 2024,
  $env:KMS_OFFICE_PRODUCT override). Warns: ~3 GB download, closes Office apps.
setup-kms.ps1 stays minimal: a non-VL edition is pointed at the bootstrap
one-liner (which can upgrade) rather than duplicating changepk.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Viktor Barzin 2026-06-01 14:35:56 +00:00
parent cf5f91c303
commit e38f34f146
2 changed files with 101 additions and 3 deletions

View file

@ -63,6 +63,19 @@ function Choice([string]$key, [string]$prompt, [bool]$default) {
return Ask $prompt $default
}
# Show the consequences of a destructive/heavy action and get consent (default No).
# Interactive -> prompt. Non-interactive (KMS_AUTO set or no console) -> proceed
# ONLY if an explicit env override gave consent ($envConsent), else skip.
function Approve([string]$consequences, [bool]$envConsent) {
Write-Host ""
Write-Host $consequences -ForegroundColor Yellow
$interactive = ($auto.Count -eq 0) -and [Environment]::UserInteractive
if ($interactive) { return (Ask "Proceed?" $false) }
if ($envConsent) { Write-Host " (non-interactive; proceeding on explicit env override)"; return $true }
Warn "Skipped (non-interactive and no explicit env override to consent)."
return $false
}
Step "What would you like to do?"
$doWin = Choice 'win' "Activate this Windows installation against KMS?" $true
$doOfficeAct = Choice 'office' "Activate an already-installed Office (Pro Plus 2024 / 2021 / 2019 / 2016)?" $false
@ -112,6 +125,35 @@ function Resolve-WindowsGvlk {
return $null
}
# The current edition can't KMS-activate (Home/retail). Offer an in-place edition
# UPGRADE to a Volume License edition (default Pro; $env:KMS_EDITION overrides).
function Upgrade-WindowsEdition {
$cv = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -ErrorAction SilentlyContinue
$cur = if ($cv) { $cv.EditionID } else { 'this edition' }
$keys = Get-Keys; if (-not $keys) { Bad "No key list available - cannot pick a target edition."; return }
$targetName = if ($env:KMS_EDITION) { $env:KMS_EDITION } else { 'Pro' }
$t = $keys.windows | Where-Object { $_.edition -eq $targetName -or $_.editionid -eq $targetName } | Select-Object -First 1
if (-not $t) { Bad "Target edition '$targetName' is not in the published list. Set `$env:KMS_EDITION to a listed edition (Pro, Enterprise, Education, ...)."; return }
$text = @"
This Windows edition ($cur) cannot be activated by KMS.
It can be UPGRADED in place to a Volume License edition:
$cur -> $($t.edition) (GVLK $($t.gvlk))
Consequences:
* Runs changepk.exe /ProductKey <GVLK> - an in-place edition UPGRADE
* REQUIRES A REBOOT to complete; after reboot, re-run this one-liner to activate
* One-way: you cannot downgrade back to $cur without a full reinstall
* Only works along Microsoft's supported upgrade paths (e.g. Home -> Pro -> Enterprise)
"@
if (-not (Approve $text ([bool]$env:KMS_EDITION))) { Warn "Edition upgrade skipped; $cur cannot KMS-activate as-is."; return }
$changepk = "$env:WINDIR\System32\changepk.exe"
if (-not (Test-Path $changepk)) { Bad "changepk.exe not found on this OS - cannot upgrade edition automatically."; return }
Step "Upgrading $cur -> $($t.edition) (changepk.exe /ProductKey ...)"
& $changepk /ProductKey $($t.gvlk)
OK "Edition upgrade started. REBOOT, then re-run this one-liner to activate $($t.edition)."
}
function Activate-Windows {
Step "Windows activation"
$slmgr = "$env:WINDIR\System32\slmgr.vbs"
@ -122,7 +164,7 @@ function Activate-Windows {
if ($null -eq $lic) {
Step "No Volume License key - fetching the GVLK for this edition"
$gvlk = Resolve-WindowsGvlk
if (-not $gvlk) { Bad "Couldn't auto-select a GVLK. Pick one from https://kms.viktorbarzin.me/#windows and run: slmgr /ipk <GVLK>"; return }
if (-not $gvlk) { Upgrade-WindowsEdition; return }
Write-Host " installing GVLK $gvlk"
& cscript //Nologo $slmgr /ipk $gvlk | Out-Host
if ($LASTEXITCODE -ne 0) { Bad "slmgr /ipk failed"; return }
@ -159,6 +201,36 @@ function Get-OfficeReleaseIds {
return @()
}
# Reinstall Office as a Volume License product via the Office Deployment Tool.
# Heavy (multi-GB download, closes Office). Only invoked after explicit consent.
$script:ODT_URL = 'https://download.microsoft.com/download/2/7/A/27AF1BE6-DD20-4CB4-B154-EBAB8A7D4A7E/officedeploymenttool_19127-20198.exe'
function Reinstall-OfficeVL([string]$product) {
$tmp = Join-Path $env:TEMP "kms-odt-$(Get-Random)"
New-Item -ItemType Directory -Force -Path $tmp | Out-Null
$odt = Join-Path $tmp 'odt.exe'
Step "Downloading the Office Deployment Tool"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
try { Invoke-WebRequest -UseBasicParsing -Uri $script:ODT_URL -OutFile $odt } catch { Bad "ODT download failed: $($_.Exception.Message)"; return $false }
Start-Process -FilePath $odt -ArgumentList "/extract:`"$tmp`"", '/quiet' -Wait
$setup = Join-Path $tmp 'setup.exe'
if (-not (Test-Path $setup)) { Bad "ODT extraction failed (no setup.exe)."; return $false }
$channel = if ($product -match '2021') { 'PerpetualVL2021' } elseif ($product -match '2019') { 'PerpetualVL2019' } else { 'PerpetualVL2024' }
$cfg = Join-Path $tmp 'config.xml'
@"
<Configuration>
<Add OfficeClientEdition="64" Channel="$channel">
<Product ID="$product"><Language ID="MatchOS" /></Product>
</Add>
<Display Level="None" AcceptEULA="TRUE" />
<Property Name="FORCEAPPSHUTDOWN" Value="TRUE" />
</Configuration>
"@ | Set-Content -Path $cfg -Encoding UTF8
Step "Running setup.exe /configure (multi-GB download + reinstall; several minutes)"
Start-Process -FilePath $setup -ArgumentList '/configure', "`"$cfg`"" -Wait
Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue
return $true
}
# Per-product license check. ospp /dstatus lists EVERY installed product, so a
# blanket '---LICENSED---' match treats Office-licensed as Project-licensed too.
# Walk the LICENSE NAME / STATUS blocks and check only THIS family.
@ -194,6 +266,30 @@ function Activate-Ospp([string]$label) {
($label -eq 'Visio' -and $_ -match 'Visio') -or
($label -eq 'Office' -and $_ -notmatch 'Project|Visio')
}
# No Volume License product of this family installed (e.g. retail Office).
# Offer a VL reinstall via the ODT (default product per family; override
# with $env:KMS_OFFICE_PRODUCT). Heavy + closes apps, so consent-gated.
if (-not $rels) {
$target = if ($env:KMS_OFFICE_PRODUCT) { $env:KMS_OFFICE_PRODUCT }
elseif ($label -eq 'Project') { 'ProjectPro2024Volume' }
elseif ($label -eq 'Visio') { 'VisioPro2024Volume' }
else { 'ProPlus2024Volume' }
$text = @"
No Volume License $label is installed, so KMS cannot activate it.
The Office Deployment Tool can reinstall it as Volume License:
-> $target
Consequences:
* Downloads ~3 GB and runs setup.exe /configure
* CLOSES all running Office apps (Word/Excel/Outlook/...)
* Several minutes; replaces the current $label install
"@
if (-not (Approve $text ([bool]$env:KMS_OFFICE_PRODUCT))) { Warn "$label reinstall skipped; no VL $label to activate."; return }
if (-not (Reinstall-OfficeVL $target)) { return }
$ospp = Find-Ospp; if (-not $ospp) { Bad "ospp.vbs not found after reinstall."; return }
$rels = @($target)
}
$keys = Get-Keys
foreach ($rel in $rels) {
$k = if ($keys) { ($keys.office | Where-Object { $_.product -eq $rel } | Select-Object -First 1).gvlk } else { $null }

View file

@ -91,8 +91,10 @@ if ($null -eq $lic) {
Step "No Volume License key installed - detecting edition and fetching its GVLK"
$gvlk = Resolve-WindowsGvlk
if (-not $gvlk) {
Bad "Could not auto-select a GVLK for this edition."
Write-Host " Pick one from https://kms.viktorbarzin.me/#windows and run: slmgr /ipk <GVLK>"
Bad "This edition has no KMS GVLK - Home/retail can't KMS-activate as-is."
Write-Host " To UPGRADE it in place to a Volume License edition (e.g. Home -> Pro) and"
Write-Host " activate, run the interactive bootstrap (it shows the consequences first):"
Write-Host " iwr -UseBasicParsing https://kms.viktorbarzin.me/scripts/kms-bootstrap.ps1 | iex"
return
}
Step "Installing GVLK $gvlk"