Commit graph

23 commits

Author SHA1 Message Date
Viktor Barzin
a794d1acde kms-website: detect + remove pre-existing MSI Office before C2R VL install (real SXSMSI cause)
Live debug on a user laptop (direct SSH, verbose MSI log) found the true SXSMSI/1603
root cause: a pre-existing 32-bit MSI Office (Office Standard 2010) blocks the 64-bit
C2R VL install. C2R log: SXSMSIValidator "32bit MSI Installation found and trying to
install 64bit C2R". MSI Office cannot coexist with C2R and never appears in C2R
ProductReleaseIds (the only place the script looked) - so it was invisible. This is
why the VM 300 pilot (no old MSI Office) succeeded and DISM/SFC didn't help.

- Get-MsiOffice: detect main MSI Office suites via ARP keys OfficeNN.<RELEASE>
  (Office14.STANDARDR etc.) across both registry hives, with the Office Setup
  Controller path per version.
- Remove-MsiOffice: silent uninstall via the setup controller + Display=none config
  (Office 2010/2013/2016).
- Install-LatestOfficeVL now removes BOTH blockers before the VL install: retail/M365
  C2R AND any MSI Office; shows them in the consent prompt; reboot+re-run after.
- Get-OfficeState telemetry now includes msiOffice=[...].
2026-06-02 20:47:22 +00:00
Viktor Barzin
6f74565356 kms-website: escalate persistent SXSMSI to in-place-repair guidance (no loop)
Pilot on PVE VM 300 established strong counterfactuals: the IDENTICAL script +
the user's EXACT journey both succeed on a healthy Win10 -
  CF1: clean (Remove-All + reboot) -> VL install -> office/ok
  CF2: retail O365HomePremRetail -> script targeted-remove -> reboot -> VL install
       -> office/ok
So a persistent [Failing PreReq=SXSMSI]/1603 with all script-checkable causes
clean (msiserver healthy, EventLog up, no DisableMSI, no stale MSI, disk OK, no
pending reboot) is machine-specific Windows servicing/Installer corruption below
DISM/SFC - not the script, ODT, retail->VL transition, or KMS.

Cover the case without looping:
- Repair-OfficePrereq now persists a marker (HKLM\SOFTWARE\kms-bootstrap
  DeepRepairDone).
- On a 1603 install failure: first time -> offer the deep repair; if the deep
  repair already ran and it STILL fails -> Show-InPlaceRepairHint (the only
  reliable fix: in-place Windows repair-install, keeps files+apps) + emit
  'sxsmsi-unrecoverable' telemetry.
2026-06-02 00:07:09 +00:00
Viktor Barzin
b4927236cd kms-website: add consent-gated deep repair for wedged SXSMSI/1603 Office install
When the Office VL install fails with setup.exe 1603 (C2R 'SXSMSI' prereq) AND no
reboot is pending AND the common causes are clean (verified via telemetry:
msiserver healthy, EventLog running, no DisableMSI policy, no stale InProgress MSI,
disk OK) AND a manual DISM/SFC + reboot did not help, the install subsystem itself
is wedged. New Repair-OfficePrereq (consent-gated; $env:KMS_DEEP_REPAIR=1 to
auto-consent) goes one level past DISM without uninstalling anything: re-registers
the Windows Installer engine (msiexec /unregister + /regserver) and resets the
servicing/update caches (SoftwareDistribution + catroot2), then prompts a restart
and re-run. Offered automatically from Reinstall-OfficeVL on a 1603 with no pending
reboot. ODT exit code now exposed via $script:OdtExitCode.
2026-06-01 22:16:58 +00:00
Viktor Barzin
dfc83fbb0a kms-website: capture Event Log service + DisableMSI policy for SXSMSI 1603
Telemetry ruled out the obvious SXSMSI causes (msiserver Manual/normal, no
pending reboot, 37 GB free, no stale InProgress MSI), yet 1603 persists on a
clean machine. Add the last two web-documented script-detectable causes to the
state snapshot: Windows Event Log service status, TrustedInstaller start-type,
and the DisableMSI group policy. Also auto-start EventLog if it's not running
(Office C2R install depends on it). If all clean, the remaining cause is
servicing-stack corruption -> DISM /RestoreHealth + sfc.
2026-06-01 21:51:53 +00:00
Viktor Barzin
651154218f kms-website: diagnose SXSMSI 1603 (Windows Installer state) + fix false reboot msg
Post-restart telemetry: officePFRO cleared (real restart worked) but install still
fails [Failing PreReq=SXSMSI]/1603 with NO pending reboot - so 1603 is not a reboot
issue here.
- Stop claiming "reboot needed" on every 1603; only when a reboot is actually
  pending (Test-OfficeRebootPending = CBS/WU or an Office file-rename).
- Fast-Startup-aware restart hint (use Restart, NOT Shut down) when reboot IS
  pending; gate before the ~3 GB download (this is the user-visible restart notice).
- Capture Windows Installer state in diagnostics (msiserver status/starttype,
  Installer\InProgress, free disk GB) to pinpoint the SXSMSI prereq failure.
- Defensively re-enable msiserver if Disabled (a common SXSMSI cause).
- Get-OdtLogTail also matches prereq/sxsmsi lines.
2026-06-01 21:44:26 +00:00
Viktor Barzin
e037caf710 kms-website: richer anonymous install diagnostics + scrub hostname from telemetry
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.
2026-06-01 21:32:37 +00:00
Viktor Barzin
07f88b8f1f kms-website: reboot-aware ODT failure handling (exit 1603) + richer C2R log capture
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.
2026-06-01 20:52:19 +00:00
Viktor Barzin
3d31a39099 kms-website: verify ODT install completion + capture ODT log; reboot-aware after uninstall
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).
2026-06-01 20:21:58 +00:00
Viktor Barzin
2027d8bcc0 kms-website: uninstall incompatible (retail/M365) Office before VL install
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>
2026-06-01 20:00:54 +00:00
Viktor Barzin
7cfcd73d83 kms-website: anonymous client diagnostics (Send-Diag -> /diag) + FAQ disclosure
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>
2026-06-01 19:46:49 +00:00
Viktor Barzin
b51dc9894c kms-website: self-host the ODT bootstrapper (fix 404 on Office reinstall)
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>
2026-06-01 19:37:10 +00:00
Viktor Barzin
06009dae30 kms-website: fix consent prompt suppressed by KMS_AUTO
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>
2026-06-01 19:23:51 +00:00
Viktor Barzin
a0c100ab08 kms-website: install the LATEST VL Office, and offer it when none is installed
- 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>
2026-06-01 19:02:08 +00:00
Viktor Barzin
e38f34f146 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>
2026-06-01 14:35:56 +00:00
Viktor Barzin
cf5f91c303 kms-website: per-product Office license check (multi-product idempotency)
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>
2026-06-01 10:24:34 +00:00
Viktor Barzin
01803ab812 kms-website: ASCII-only comment (em-dash -> colon)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 10:18:43 +00:00
Viktor Barzin
f1440dfcf4 kms-website: fix Office auto-key label filter ($_ shadowing in switch)
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>
2026-06-01 10:18:24 +00:00
Viktor Barzin
9059dbc85b kms-website: auto-fetch + auto-install GVLKs (no manual key lookup)
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>
2026-06-01 10:12:03 +00:00
Viktor Barzin
c27077549c kms-website: ASCII-only script output (em-dash -> hyphen)
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>
2026-06-01 09:02:19 +00:00
Viktor Barzin
d11dc8c0ce kms-website: make activation scripts idempotent + harden Office detection
- 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>
2026-06-01 08:53:20 +00:00
Viktor Barzin
efa1353e6b kms-website: activate against vlmcs.viktorbarzin.me, drop ODT-install + deep-legacy GVLKs
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>
2026-06-01 08:05:31 +00:00
Viktor Barzin
8bcb64bf99 kms-website: 3-up KMS_AUTO quick-start cards + save-as downloads + clean script comments
- 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
2026-05-09 22:12:21 +00:00
Viktor Barzin
5da130be93 kms-website: public scripts + sanitized copy + slack notifier
- static/scripts/{setup-kms.ps1,kms-bootstrap.ps1}: public, internet-friendly
- Drop \\nas.viktorbarzin.lan\\Emo shared\\ refs (internal SMB share, leaks personal name)
- Reframe LAN section as optional auto-discovery for self-hosters
- Add privacy + legality FAQs
- Quick Start uses one-liner: iwr | iex against /scripts/kms-bootstrap.ps1
- bootstrapURL now points at site-relative /scripts/

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 17:54:38 +00:00