Users can't always paste logs, so capture install-relevant system state in
telemetry instead. New Get-OfficeState (ProductReleaseIds, Office root-folder
count, reboot-signal breakdown cbs/wu/officePFRO, ClickToRun service, ospp
presence) is sent (1) BEFORE the install (ships before the ~3 GB download, so a
failure is debuggable even if aborted) and (2) on ODT failure alongside a
tightened ODT error tail. Stop dumping the verbose C2R log to the screen.
Scrub COMPUTERNAME/USERNAME from all telemetry (a C2R log filename leaked the
machine name) and raise the detail cap 600->1800.
setup.exe exit 1603 right after removing the bundled consumer Office almost
always means the old install is pending a reboot. The script handed users a bare
1603; now it explains it. Changes:
- Test-RebootRequiredHard (CBS/WU only) GATES Reinstall-OfficeVL before the ~3 GB
download, so a pending reboot stops early with reboot+re-run guidance instead
of failing with 1603.
- Invoke-Odt failure path detects pending-reboot / 1603 and tells the user to
reboot + re-run; ships reboot status in telemetry.
- Get-OdtLogTail also searches %TEMP% (where the C2R client logs the real error)
and prefers error-bearing lines, so a failure no longer reports an empty log.
Invoke-Odt returned $true unconditionally after setup.exe, so a failed or
not-yet-finished Click-to-Run install surfaced only as a bare "ospp.vbs not
found after install". Root-cause fixes:
- <Logging> in the config XML -> a capture dir, read back on failure so the
real setup.exe exit code / error is reported (and sent as telemetry).
- setup.exe run with -PassThru; non-zero (not 0/3010) exit -> fail + log tail.
- Wait-OfficeInstalled polls on-disk state (ospp.vbs + ProductReleaseIds)
instead of trusting setup.exe's early return under Display Level=None.
- After removing incompatible consumer Office (e.g. O365HomePremRetail), a
pending reboot now stops the run with reboot+re-run guidance rather than
half-completing the VL install in the same session (idempotent on re-run).
When the Office install path runs and a non-VL Click-to-Run Office is present
(ProductReleaseIds not ending in 'Volume' = retail/M365), it can't coexist with
a VL install of the same suite. Now: detect it, show it in the consent prompt,
ODT /configure <Remove> only those products (VL products of other families
preserved), then proceed with the VL install. Refactored the ODT run into a
shared Invoke-Odt used by both install (<Add>) and uninstall (<Remove>).
Telemetry on the uninstall step.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fire-and-forget telemetry so script failures are captured server-side (Loki via
the kms-diag collector). kms-bootstrap.ps1 + setup-kms.ps1 POST a small anonymous
JSON event at each outcome (action, ok/fail, error text + exit codes, EditionID/
build/locale, detected Office products; no hostname/user/keys). 3s timeout,
errors swallowed -- never affects activation. $env:KMS_NO_TELEMETRY=1 opts out;
$env:KMS_DIAG_URL overrides. Version baked at build via Dockerfile sed
(__KMS_VERSION__ -> SCRIPT_VERSION build-arg). FAQ updated to disclose it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Microsoft's download.microsoft.com ODT URL is build-numbered and rotates every
release; the hardcoded officedeploymenttool_19127-20198.exe now 404s, so the
"install latest VL Office" path failed right after the consent prompt. Serve a
known-good ODT bootstrapper from our own /scripts/odt-setup.exe (carved out of
Anubis already) and point Reinstall-OfficeVL at it. $env:KMS_ODT_URL overrides.
The bootstrapper self-updates the Office payload, so it rarely needs refreshing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The activate-windows/office wrappers set $env:KMS_AUTO to pre-select the
product, but Approve() treated any KMS_AUTO as "non-interactive" and skipped the
consequences prompt -- so on a machine needing the ODT/edition install it printed
the consequences then exited without asking. Gate the prompt on a real console
([Environment]::UserInteractive + not IsInputRedirected, guarded) instead of
KMS_AUTO. KMS_AUTO now only selects WHICH products to activate.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Get-LatestOfficeProduct picks the newest ProPlus/ProjectPro/VisioPro VL SKU
from keys.json by year (data-driven: add a future LTSC to products.yaml and
the installer follows; no hardcoded 2024). $env:KMS_OFFICE_PRODUCT still wins.
- Activate-Ospp now offers the ODT install in BOTH cases: no Office found at all
(previously it just skipped with "not found"), and a non-VL/retail/M365 Office
installed. ODT channel comes from the chosen product's keys.json entry.
Note: KMS can't activate Microsoft 365/retail, so "latest" = latest LTSC VL.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
ospp /dstatus lists every installed product, so the blanket '---LICENSED---'
match treated an Office-licensed machine as Project/Visio-licensed too, causing
KMS_AUTO=office,project to skip Project. Add Test-OsppLicensed that parses the
per-SKU LICENSE NAME/STATUS blocks and checks only the requested family.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Inside `switch ($label) {...}` the automatic $_ is the switch input (the label),
not the Where-Object pipeline item, so the per-product filter always matched and
KMS_AUTO=office would also install Project/Visio keys. Replace with explicit
label/$_ comparisons.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Scripts now detect the running edition and fetch the matching GVLK from a
published key list instead of requiring the user to copy one from the table.
- data/products.yaml: add editionid to every Windows/Server entry, plus build
numbers where an EditionID spans releases (LTSC, Server). Azure Edition left
unmapped on purpose (collides with Datacenter; KMS may fail there anyway).
- /keys.json: Hugo KEYS output format renders products.yaml as JSON
(single source of truth). layouts/index.keys.json.
- setup-kms.ps1: when no VL key is installed, read registry EditionID
(+build/ProductType for server) -> fetch /keys.json -> slmgr /ipk the match
-> activate. Only acts when not already licensed (never clobbers retail).
- kms-bootstrap.ps1: same for Windows; for Office/Project/Visio, read
Click-to-Run ProductReleaseIds -> ospp /inpkey the matching GVLK -> /act.
- $env:KMS_KEYS_URL overrides the key-list URL.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Em-dashes in the new idempotency status messages render as "?" garbage on
non-UTF-8 Windows consoles (cp437/850). Replace with ASCII hyphens.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Replace locale-dependent "License Status: Licensed" regex with a
locale-independent WMI probe (SoftwareLicensingProduct.LicenseStatus==1).
Fixes false "not licensed yet" reports on non-English Windows and on re-runs.
- Idempotent: always pin the KMS host, but skip /ato (Windows) and /act
(Office) when already licensed — report days remaining instead of
re-contacting the public KMS server.
- Find-Ospp now also checks Click-to-Run \root\Office16\ (+ \root\Office15\)
layouts, not just the MSI Office16 path.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The page advertised kms.viktorbarzin.me:1688 as the KMS host, but that name
is the website (Traefik) — internally it resolves to 10.0.20.203 which has no
:1688 listener, so LAN clients failed with "KMS server cannot be reached".
Split the concern: siteHost (kms.viktorbarzin.me) serves the page + /scripts
downloads; kmsHost is now the dedicated A-only vlmcs.viktorbarzin.me endpoint
that resolves to the vlmcsd MetalLB IP (10.0.20.202) on the LAN (Technitium)
and to the public IP over the internet (Cloudflare -> pfSense WAN NAT :1688).
Moderate cleanup:
- remove the Office-install-via-ODT path from kms-bootstrap.ps1 (activation
only now; manual ODT install docs stay on the page)
- collapse Windows 8.1/8/7/Vista + Server 2012/2008 GVLK tables into a legacy
note (those keys still activate; just no longer tabled)
- drop the unused kmsHostLan param
Pairs with the infra /scripts Anubis carve-out that makes `iwr | iex` work.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Replace single interactive one-liner with three side-by-side cards
(Windows / Office+Project+Visio / Both) using $env:KMS_AUTO=...; the
contract is already supported by kms-bootstrap.ps1
- Make Downloads links use the 'download' attribute so browsers prompt
save-as instead of rendering .ps1 as text
- Strip operator-side framing: kms-bootstrap.ps1 no longer says
"this activation has been logged" and both scripts now point Source
at the public mirror instead of forgejo.viktorbarzin.me