Created
September 10, 2026 22:01
-
-
Save elico/f92e4e653de3971f7ed2619f23cbcdef to your computer and use it in GitHub Desktop.
win-kms-activation.ps1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ============================================================ | |
| # KMS Activation Utility -- Enhanced Edition | |
| # Author : ngtech1ltd@gmail.com | |
| # Version: 2.3 | |
| # ============================================================ | |
| # --- Self-Elevation: relaunch with the SAME binary that started us --- | |
| $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole( | |
| [Security.Principal.WindowsBuiltInRole]::Administrator) | |
| if (-not $isAdmin) { | |
| Write-Host "Elevating permissions to Administrator..." -ForegroundColor Yellow | |
| try { | |
| $scriptPath = $MyInvocation.MyCommand.Definition | |
| if (-not $scriptPath) { $scriptPath = $PSCommandPath } | |
| $currentExe = [System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName | |
| Start-Process -FilePath $currentExe ` | |
| -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`"" ` | |
| -Verb RunAs | |
| exit | |
| } catch { | |
| Write-Host "Elevation request failed or was cancelled." -ForegroundColor Red | |
| Pause | |
| exit | |
| } | |
| } | |
| # --- Load WinForms --- | |
| Add-Type -AssemblyName System.Windows.Forms | |
| Add-Type -AssemblyName System.Drawing | |
| [System.Windows.Forms.Application]::EnableVisualStyles() | |
| # ============================================================ | |
| # GVLK Table (official Microsoft KMS client setup keys) | |
| # ============================================================ | |
| $GVLKTable = [ordered]@{ | |
| "Windows 10/11 Pro" = "W269N-WFGWX-YVC9B-4J6C9-T83GX" | |
| "Windows 10/11 Pro Workstation" = "NRG8B-VKK3Q-CXVCJ-9G2XF-6Q84J" | |
| "Windows 10/11 Enterprise" = "NPPR9-FWDCX-D2C8J-H872K-2YT43" | |
| "Windows 10/11 Education" = "NW6C2-QMPVW-D7KKK-3GKT6-VCFB2" | |
| "Windows Server 2025 Standard" = "TVRH6-WHNXV-R9WG3-9XRFY-MY832" | |
| "Windows Server 2025 Datacenter" = "D764K-2NDRG-47T6Q-P8T8W-YP6DF" | |
| "Windows Server 2025 Azure Edition" = "XGN3F-F394H-FD2MY-PP6FD-8MCRC" | |
| "Windows Server 2022 Standard" = "VDYBN-27WPP-V4HQT-9VMD4-VMK7H" | |
| "Windows Server 2022 Datacenter" = "WX4NM-KYWYW-QJJR4-VW3GC-62PVU" | |
| "Windows Server 2019 Standard" = "N69G4-B89J2-4G8F4-WWYCC-J464C" | |
| "Windows Server 2019 Datacenter" = "WMDGN-G9PQG-XVVXX-R3X43-FCF23" | |
| "Windows Server 2016 Standard" = "WC2BQ-8NRM3-FDDYY-2BFGV-KHKQY" | |
| "Windows Server 2016 Datacenter" = "CB7KF-BWN84-R7R2Y-793K2-8XDDG" | |
| } | |
| # Which editions are valid per OS family (used to filter the dropdown) | |
| $EditionsByFamily = @{ | |
| "Desktop" = @( | |
| "Windows 10/11 Pro" | |
| "Windows 10/11 Pro Workstation" | |
| "Windows 10/11 Enterprise" | |
| "Windows 10/11 Education" | |
| ) | |
| "Server2025" = @( | |
| "Windows Server 2025 Standard" | |
| "Windows Server 2025 Datacenter" | |
| "Windows Server 2025 Azure Edition" | |
| ) | |
| "Server2022" = @( | |
| "Windows Server 2022 Standard" | |
| "Windows Server 2022 Datacenter" | |
| ) | |
| "Server2019" = @( | |
| "Windows Server 2019 Standard" | |
| "Windows Server 2019 Datacenter" | |
| ) | |
| "Server2016" = @( | |
| "Windows Server 2016 Standard" | |
| "Windows Server 2016 Datacenter" | |
| ) | |
| } | |
| # ============================================================ | |
| # OS Detection | |
| # ============================================================ | |
| function Get-OSInfo { | |
| $os = Get-CimInstance Win32_OperatingSystem | |
| $caption = $os.Caption | |
| $reg = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -ErrorAction SilentlyContinue | |
| $editionId = if ($reg -and $reg.EditionID) { $reg.EditionID } else { "" } | |
| $productName = if ($reg -and $reg.ProductName) { $reg.ProductName } else { $caption } | |
| # Detect evaluation: OS caption/product name says "Evaluation", | |
| # or the registry EditionID ends with "Eval" (e.g. ServerStandardEval) | |
| $isEval = ($caption -match "Evaluation") -or | |
| ($productName -match "Evaluation") -or | |
| ($editionId -match "Eval$") | |
| # Determine server version from caption or productName | |
| $serverVersion = $null | |
| foreach ($ver in @("2025","2022","2019","2016")) { | |
| if ($caption -match "Server $ver" -or $productName -match "Server $ver") { | |
| $serverVersion = $ver | |
| break | |
| } | |
| } | |
| $family = "Unknown" | |
| if ($serverVersion) { | |
| $family = "Server$serverVersion" | |
| } elseif ($caption -match "Windows 10|Windows 11" -or $productName -match "Windows 10|Windows 11") { | |
| $family = "Desktop" | |
| } | |
| return @{ | |
| Caption = $caption | |
| ProductName = $productName | |
| EditionId = $editionId | |
| Family = $family | |
| ServerVersion = $serverVersion | |
| IsEvaluation = $isEval | |
| } | |
| } | |
| # ============================================================ | |
| # Edition picker dialog (Standard vs Datacenter) | |
| # ============================================================ | |
| function Show-EditionPicker { | |
| param([string]$ServerVersion) | |
| $pf = New-Object System.Windows.Forms.Form | |
| $pf.Text = "Choose Target Edition" | |
| $pf.Size = New-Object System.Drawing.Size(330, 190) | |
| $pf.StartPosition = "CenterParent" | |
| $pf.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog | |
| $pf.MaximizeBox = $false | |
| $pf.MinimizeBox = $false | |
| $pf.Font = New-Object System.Drawing.Font("Segoe UI", 9) | |
| # Apply current theme colours | |
| $T = Get-Theme | |
| $pf.BackColor = $T.FormBack | |
| $pf.ForeColor = $T.LabelFore | |
| $lbl = New-Object System.Windows.Forms.Label | |
| $lbl.Location = New-Object System.Drawing.Point(12, 14) | |
| $lbl.Size = New-Object System.Drawing.Size(296, 36) | |
| $lbl.Text = "Upgrading Windows Server $ServerVersion Evaluation.`nSelect the target edition:" | |
| $lbl.BackColor = [System.Drawing.Color]::FromArgb(0,0,0,0) | |
| $pf.Controls.Add($lbl) | |
| $rbStd = New-Object System.Windows.Forms.RadioButton | |
| $rbStd.Location = New-Object System.Drawing.Point(20, 58) | |
| $rbStd.Size = New-Object System.Drawing.Size(280, 22) | |
| $rbStd.Text = "Standard" | |
| $rbStd.Checked = $true | |
| $rbStd.BackColor = [System.Drawing.Color]::FromArgb(0,0,0,0) | |
| $rbStd.ForeColor = $T.LabelFore | |
| $pf.Controls.Add($rbStd) | |
| $rbDC = New-Object System.Windows.Forms.RadioButton | |
| $rbDC.Location = New-Object System.Drawing.Point(20, 84) | |
| $rbDC.Size = New-Object System.Drawing.Size(280, 22) | |
| $rbDC.Text = "Datacenter" | |
| $rbDC.BackColor = [System.Drawing.Color]::FromArgb(0,0,0,0) | |
| $rbDC.ForeColor = $T.LabelFore | |
| $pf.Controls.Add($rbDC) | |
| $btnOK = New-Object System.Windows.Forms.Button | |
| $btnOK.Location = New-Object System.Drawing.Point(150, 118) | |
| $btnOK.Size = New-Object System.Drawing.Size(72, 26) | |
| $btnOK.Text = "OK" | |
| $btnOK.DialogResult = [System.Windows.Forms.DialogResult]::OK | |
| $btnOK.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat | |
| $btnOK.BackColor = $T.BtnBack | |
| $btnOK.ForeColor = $T.BtnFore | |
| $btnOK.FlatAppearance.BorderSize = 0 | |
| $pf.Controls.Add($btnOK) | |
| $pf.AcceptButton = $btnOK | |
| $btnCancel = New-Object System.Windows.Forms.Button | |
| $btnCancel.Location = New-Object System.Drawing.Point(232, 118) | |
| $btnCancel.Size = New-Object System.Drawing.Size(72, 26) | |
| $btnCancel.Text = "Cancel" | |
| $btnCancel.DialogResult = [System.Windows.Forms.DialogResult]::Cancel | |
| $btnCancel.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat | |
| $btnCancel.BackColor = $T.DangerBack | |
| $btnCancel.ForeColor = $T.BtnFore | |
| $btnCancel.FlatAppearance.BorderSize = 0 | |
| $pf.Controls.Add($btnCancel) | |
| $pf.CancelButton = $btnCancel | |
| $res = $pf.ShowDialog() | |
| $pf.Dispose() | |
| if ($res -eq [System.Windows.Forms.DialogResult]::OK) { | |
| if ($rbStd.Checked) { return "Standard" } else { return "Datacenter" } | |
| } | |
| return $null | |
| } | |
| # ============================================================ | |
| # Theme | |
| # ============================================================ | |
| $script:IsDark = $true | |
| function Get-Theme { | |
| if ($script:IsDark) { | |
| return @{ | |
| FormBack = [System.Drawing.Color]::FromArgb(30, 30, 30) | |
| GroupBack = [System.Drawing.Color]::FromArgb(45, 45, 48) | |
| CtrlBack = [System.Drawing.Color]::FromArgb(60, 60, 63) | |
| CtrlFore = [System.Drawing.Color]::FromArgb(220, 220, 220) | |
| LabelFore = [System.Drawing.Color]::FromArgb(200, 200, 200) | |
| BtnBack = [System.Drawing.Color]::FromArgb(0, 120, 212) | |
| BtnFore = [System.Drawing.Color]::White | |
| DangerBack = [System.Drawing.Color]::FromArgb(180, 40, 40) | |
| LogBack = [System.Drawing.Color]::FromArgb(20, 20, 20) | |
| LogFore = [System.Drawing.Color]::FromArgb(0, 210, 110) | |
| SBarBack = [System.Drawing.Color]::FromArgb(0, 100, 180) | |
| SBarFore = [System.Drawing.Color]::White | |
| } | |
| } else { | |
| return @{ | |
| FormBack = [System.Drawing.Color]::FromArgb(245, 245, 245) | |
| GroupBack = [System.Drawing.Color]::White | |
| CtrlBack = [System.Drawing.Color]::White | |
| CtrlFore = [System.Drawing.Color]::FromArgb(30, 30, 30) | |
| LabelFore = [System.Drawing.Color]::FromArgb(50, 50, 50) | |
| BtnBack = [System.Drawing.Color]::FromArgb(0, 120, 212) | |
| BtnFore = [System.Drawing.Color]::White | |
| DangerBack = [System.Drawing.Color]::FromArgb(196, 43, 28) | |
| LogBack = [System.Drawing.Color]::FromArgb(250, 250, 250) | |
| LogFore = [System.Drawing.Color]::FromArgb(0, 100, 0) | |
| SBarBack = [System.Drawing.Color]::FromArgb(0, 122, 204) | |
| SBarFore = [System.Drawing.Color]::White | |
| } | |
| } | |
| } | |
| function Apply-Theme { | |
| param([System.Windows.Forms.Control]$Root) | |
| $T = Get-Theme | |
| $queue = New-Object System.Collections.Generic.Queue[System.Windows.Forms.Control] | |
| $queue.Enqueue($Root) | |
| while ($queue.Count -gt 0) { | |
| $c = $queue.Dequeue() | |
| $tn = $c.GetType().Name | |
| switch ($tn) { | |
| "Form" { $c.BackColor = $T.FormBack ; $c.ForeColor = $T.LabelFore } | |
| "GroupBox" { $c.BackColor = $T.GroupBack ; $c.ForeColor = $T.LabelFore } | |
| "Label" { if ($c.Name -ne "lblLicenseWarn") { $c.BackColor = [System.Drawing.Color]::FromArgb(0,0,0,0) ; $c.ForeColor = $T.LabelFore } } | |
| "TextBox" { $c.BackColor = $T.CtrlBack ; $c.ForeColor = $T.CtrlFore } | |
| "ComboBox" { $c.BackColor = $T.CtrlBack ; $c.ForeColor = $T.CtrlFore } | |
| "RadioButton" { $c.BackColor = [System.Drawing.Color]::FromArgb(0,0,0,0) ; $c.ForeColor = $T.LabelFore } | |
| "CheckBox" { $c.BackColor = [System.Drawing.Color]::FromArgb(0,0,0,0) ; $c.ForeColor = $T.LabelFore } | |
| "ProgressBar" { $c.BackColor = $T.CtrlBack } | |
| "Button" { | |
| $c.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat | |
| $c.FlatAppearance.BorderSize = 0 | |
| $c.BackColor = if ($c.Tag -eq "danger") { $T.DangerBack } else { $T.BtnBack } | |
| $c.ForeColor = $T.BtnFore | |
| } | |
| } | |
| if ($c.Name -eq "txtLog") { $c.BackColor = $T.LogBack ; $c.ForeColor = $T.LogFore } | |
| foreach ($child in $c.Controls) { $queue.Enqueue($child) } | |
| } | |
| $script:statusStrip.BackColor = $T.SBarBack | |
| $script:statusStrip.ForeColor = $T.SBarFore | |
| $script:statusLabel.ForeColor = $T.SBarFore | |
| $script:statusLabel.BackColor = $T.SBarBack | |
| } | |
| # ============================================================ | |
| # Helpers | |
| # ============================================================ | |
| function Test-GVLKFormat { | |
| param([string]$Key) | |
| return $Key -match '^[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}$' | |
| } | |
| function Get-CurrentKMSSettings { | |
| try { | |
| $sls = Get-CimInstance -ClassName SoftwareLicensingService | |
| $server = $sls.KeyManagementServiceMachine | |
| $port = $sls.KeyManagementServicePort | |
| if ([string]::IsNullOrWhiteSpace($server)) { $server = "(not set)" } | |
| if (-not $port -or $port -eq 0) { $port = 1688 } | |
| return @{ Server = $server; Port = $port } | |
| } catch { | |
| return @{ Server = "Error reading"; Port = 1688 } | |
| } | |
| } | |
| function Write-Log { | |
| param([string]$Message) | |
| $ts = Get-Date -Format "HH:mm:ss" | |
| $script:txtLog.AppendText("[$ts] $Message`r`n") | |
| $script:txtLog.ScrollToCaret() | |
| } | |
| function Refresh-KMSLabel { | |
| $k = Get-CurrentKMSSettings | |
| $script:lblCurrentKMS.Text = "Currently registered KMS: $($k.Server) Port: $($k.Port)" | |
| } | |
| function Refresh-LicenseWarning { | |
| try { | |
| $wAppId = "55c92734-d682-4d71-983e-d6ec3f16059f" | |
| $lic = Get-CimInstance -ClassName SoftwareLicensingProduct | | |
| Where-Object { $_.ApplicationId -eq $wAppId -and $_.PartialProductKey } | |
| $script:lblLicenseWarn.Visible = ($null -ne $lic -and $lic.LicenseStatus -eq 1) | |
| } catch { | |
| $script:lblLicenseWarn.Visible = $false | |
| } | |
| } | |
| function Set-Busy { | |
| param([bool]$Busy, [string]$Status = "") | |
| $script:progressBar.Visible = $Busy | |
| $script:btnActivate.Enabled = -not $Busy | |
| $script:btnStatus.Enabled = -not $Busy | |
| $script:btnClearKMS.Enabled = -not $Busy | |
| $script:btnTestPort.Enabled = -not $Busy | |
| # Upgrade eval button only re-enabled if this is actually an eval build | |
| if ($script:OSInfo -and $script:OSInfo.IsEvaluation -and $script:OSInfo.ServerVersion) { | |
| $script:btnUpgradeEval.Enabled = -not $Busy | |
| } | |
| $script:form.Cursor = if ($Busy) { [System.Windows.Forms.Cursors]::WaitCursor } ` | |
| else { [System.Windows.Forms.Cursors]::Default } | |
| if ($Status) { $script:statusLabel.Text = $Status } | |
| $script:form.Refresh() | |
| } | |
| # ============================================================ | |
| # Background initialisation (runs after window is visible) | |
| # ============================================================ | |
| function Start-BackgroundInit { | |
| # Determinate progress bar -- 3 tasks | |
| $script:progressBar.Style = [System.Windows.Forms.ProgressBarStyle]::Blocks | |
| $script:progressBar.Minimum = 0 | |
| $script:progressBar.Maximum = 3 | |
| $script:progressBar.Value = 0 | |
| $script:progressBar.Visible = $true | |
| $script:statusLabel.Text = "Loading system information (0/3)..." | |
| $script:form.Cursor = [System.Windows.Forms.Cursors]::WaitCursor | |
| $script:initOSdone = $false | |
| $script:initKMSdone = $false | |
| $script:initLICdone = $false | |
| # Runspace pool: all 3 tasks run in parallel | |
| $script:rsPool = [RunspaceFactory]::CreateRunspacePool(1, 3) | |
| $script:rsPool.Open() | |
| # --- Task 1: OS Detection --- | |
| $script:psOS = [PowerShell]::Create() | |
| $script:psOS.RunspacePool = $script:rsPool | |
| [void]$script:psOS.AddScript({ | |
| $os = Get-CimInstance Win32_OperatingSystem | |
| $caption = $os.Caption | |
| $reg = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -ErrorAction SilentlyContinue | |
| $edId = if ($reg -and $reg.EditionID) { $reg.EditionID } else { "" } | |
| $prodNm = if ($reg -and $reg.ProductName) { $reg.ProductName } else { $caption } | |
| $isEval = ($caption -match "Evaluation") -or ($prodNm -match "Evaluation") -or ($edId -match "Eval$") | |
| $sver = $null | |
| foreach ($v in @("2025","2022","2019","2016")) { | |
| if ($caption -match "Server $v" -or $prodNm -match "Server $v") { $sver = $v; break } | |
| } | |
| $fam = "Unknown" | |
| if ($sver) { $fam = "Server$sver" } | |
| elseif ($caption -match "Windows 10|Windows 11" -or $prodNm -match "Windows 10|Windows 11") { $fam = "Desktop" } | |
| [PSCustomObject]@{ | |
| Caption = $caption | |
| ProductName = $prodNm | |
| EditionId = $edId | |
| Family = $fam | |
| ServerVersion = $sver | |
| IsEvaluation = $isEval | |
| } | |
| }) | |
| $script:handleOS = $script:psOS.BeginInvoke() | |
| # --- Task 2: KMS Settings --- | |
| $script:psKMS = [PowerShell]::Create() | |
| $script:psKMS.RunspacePool = $script:rsPool | |
| [void]$script:psKMS.AddScript({ | |
| try { | |
| $sls = Get-CimInstance -ClassName SoftwareLicensingService | |
| $srv = $sls.KeyManagementServiceMachine | |
| $port = $sls.KeyManagementServicePort | |
| if ([string]::IsNullOrWhiteSpace($srv)) { $srv = "(not set)" } | |
| if (-not $port -or $port -eq 0) { $port = 1688 } | |
| [PSCustomObject]@{ Server = $srv; Port = $port; Ok = $true } | |
| } catch { | |
| [PSCustomObject]@{ Server = "Error reading"; Port = 1688; Ok = $false } | |
| } | |
| }) | |
| $script:handleKMS = $script:psKMS.BeginInvoke() | |
| # --- Task 3: License Status --- | |
| $script:psLIC = [PowerShell]::Create() | |
| $script:psLIC.RunspacePool = $script:rsPool | |
| [void]$script:psLIC.AddScript({ | |
| try { | |
| $wId = "55c92734-d682-4d71-983e-d6ec3f16059f" | |
| $lic = Get-CimInstance -ClassName SoftwareLicensingProduct | | |
| Where-Object { $_.ApplicationId -eq $wId -and $_.PartialProductKey } | |
| if ($lic) { | |
| [PSCustomObject]@{ Found=$true; LicenseStatus=($lic.LicenseStatus) | |
| Name=($lic.Name); PartialKey=($lic.PartialProductKey) } | |
| } else { | |
| [PSCustomObject]@{ Found=$false; LicenseStatus=-1; Name=""; PartialKey="" } | |
| } | |
| } catch { | |
| [PSCustomObject]@{ Found=$false; LicenseStatus=-1; Name=""; PartialKey="" } | |
| } | |
| }) | |
| $script:handleLIC = $script:psLIC.BeginInvoke() | |
| # --- Polling timer: runs on the UI thread every 200 ms --- | |
| $script:pollTimer = New-Object System.Windows.Forms.Timer | |
| $script:pollTimer.Interval = 200 | |
| $script:pollTimer.Add_Tick({ | |
| $done = 0 | |
| if ($script:initOSdone) { $done++ } | |
| if ($script:initKMSdone) { $done++ } | |
| if ($script:initLICdone) { $done++ } | |
| # Task 1 complete? | |
| if (-not $script:initOSdone -and $script:handleOS.IsCompleted) { | |
| $script:initOSdone = $true | |
| try { | |
| $r = $script:psOS.EndInvoke($script:handleOS)[0] | |
| # Store OSInfo as a plain hashtable for compatibility with other handlers | |
| $script:OSInfo = @{ | |
| Caption = $r.Caption | |
| ProductName = $r.ProductName | |
| EditionId = $r.EditionId | |
| Family = $r.Family | |
| ServerVersion = $r.ServerVersion | |
| IsEvaluation = $r.IsEvaluation | |
| } | |
| $osLabel = "Detected OS: $($r.Caption)" | |
| if ($r.IsEvaluation) { $osLabel += " [EVALUATION]" } | |
| $script:lblDetectedOS.Text = $osLabel | |
| # Filter dropdown to only editions valid for this OS | |
| $script:cmbKeys.Items.Clear() | |
| $allowed = $EditionsByFamily[$r.Family] | |
| if (-not $allowed) { $allowed = @($GVLKTable.Keys) } | |
| foreach ($ed in $GVLKTable.Keys) { | |
| if ($allowed -contains $ed) { [void]$script:cmbKeys.Items.Add($ed) } | |
| } | |
| if ($script:cmbKeys.Items.Count -gt 0) { | |
| $script:cmbKeys.SelectedIndex = 0 | |
| foreach ($ed in $script:cmbKeys.Items) { | |
| $words = ($ed -split '[\s/]+') | Where-Object { $_.Length -gt 2 } | |
| $mtch = ($words | Where-Object { $r.Caption -match [regex]::Escape($_) }).Count | |
| if ($mtch -ge ($words.Count - 1)) { $script:cmbKeys.SelectedItem = $ed; break } | |
| } | |
| } | |
| if ($r.IsEvaluation -and $r.ServerVersion) { | |
| $script:btnUpgradeEval.Visible = $true | |
| $script:btnUpgradeEval.Text = "Upgrade Eval -> WS$($r.ServerVersion) Std/DC" | |
| } | |
| Write-Log "OS : $($r.Caption)" | |
| Write-Log "Family : $($r.Family) | Eval: $($r.IsEvaluation)" | |
| Write-Log "Edition : $($r.EditionId)" | |
| } catch { Write-Log "OS detection error: $_" } | |
| $done++ | |
| $script:progressBar.Value = $done | |
| $script:statusLabel.Text = "Loading system information ($done/3)..." | |
| } | |
| # Task 2 complete? | |
| if (-not $script:initKMSdone -and $script:handleKMS.IsCompleted) { | |
| $script:initKMSdone = $true | |
| try { | |
| $r = $script:psKMS.EndInvoke($script:handleKMS)[0] | |
| $script:lblCurrentKMS.Text = "Currently registered KMS: $($r.Server) Port: $($r.Port)" | |
| Write-Log "KMS : $($r.Server) Port: $($r.Port)" | |
| } catch { | |
| $script:lblCurrentKMS.Text = "Currently registered KMS: Error reading" | |
| Write-Log "KMS read error: $_" | |
| } | |
| $done++ | |
| $script:progressBar.Value = $done | |
| $script:statusLabel.Text = "Loading system information ($done/3)..." | |
| } | |
| # Task 3 complete? | |
| if (-not $script:initLICdone -and $script:handleLIC.IsCompleted) { | |
| $script:initLICdone = $true | |
| try { | |
| $r = $script:psLIC.EndInvoke($script:handleLIC)[0] | |
| $script:lblLicenseWarn.Visible = ($r.Found -and $r.LicenseStatus -eq 1) | |
| if ($r.Found) { | |
| $stxt = switch ($r.LicenseStatus) { | |
| 0 {"Unlicensed"} 1 {"Licensed (fully activated)"} 2 {"OOB Grace"} | |
| 3 {"OOT Grace"} 4 {"Non-Genuine Grace"} 5 {"Notification"} | |
| 6 {"Extended Grace"} default {"Unknown ($($r.LicenseStatus))"} | |
| } | |
| Write-Log "License : $stxt | Key: $($r.PartialKey)" | |
| } else { | |
| Write-Log "License : No active Windows license product found" | |
| } | |
| } catch { Write-Log "License check error: $_" } | |
| $done++ | |
| $script:progressBar.Value = $done | |
| $script:statusLabel.Text = "Loading system information ($done/3)..." | |
| } | |
| # All 3 done? | |
| if ($script:initOSdone -and $script:initKMSdone -and $script:initLICdone) { | |
| $script:pollTimer.Stop() | |
| $script:pollTimer.Dispose() | |
| # Reset progress bar back to marquee for operation use | |
| $script:progressBar.Visible = $false | |
| $script:progressBar.Style = [System.Windows.Forms.ProgressBarStyle]::Marquee | |
| # Enable action buttons | |
| $script:btnActivate.Enabled = $true | |
| $script:btnStatus.Enabled = $true | |
| $script:btnClearKMS.Enabled = $true | |
| $script:btnTestPort.Enabled = $true | |
| if ($script:OSInfo -and $script:OSInfo.IsEvaluation -and $script:OSInfo.ServerVersion) { | |
| $script:btnUpgradeEval.Enabled = $true | |
| } | |
| $script:form.Cursor = [System.Windows.Forms.Cursors]::Default | |
| $script:statusLabel.Text = "Ready" | |
| Write-Log "=== System information loaded. Ready. ===" | |
| # Clean up runspaces | |
| try { $script:psOS.Dispose() } catch {} | |
| try { $script:psKMS.Dispose() } catch {} | |
| try { $script:psLIC.Dispose() } catch {} | |
| try { $script:rsPool.Close(); $script:rsPool.Dispose() } catch {} | |
| } | |
| }) | |
| $script:pollTimer.Start() | |
| } | |
| # ============================================================ | |
| # FORM | |
| # ============================================================ | |
| $script:form = New-Object System.Windows.Forms.Form | |
| $script:form.Text = "KMS Activation Utility v2.3 (Administrator)" | |
| $script:form.Size = New-Object System.Drawing.Size(590, 700) | |
| $script:form.MinimumSize = New-Object System.Drawing.Size(530, 640) | |
| $script:form.StartPosition = "CenterScreen" | |
| $script:form.Font = New-Object System.Drawing.Font("Segoe UI", 9) | |
| # StatusStrip | |
| $script:statusStrip = New-Object System.Windows.Forms.StatusStrip | |
| $script:statusLabel = New-Object System.Windows.Forms.ToolStripStatusLabel | |
| $script:statusLabel.Text = "Ready" | |
| $script:statusLabel.Spring = $true | |
| $script:statusLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft | |
| $script:statusStrip.Items.Add($script:statusLabel) | Out-Null | |
| $script:form.Controls.Add($script:statusStrip) | |
| # ---- GROUP: System Information --------------------------------- | |
| $grpOS = New-Object System.Windows.Forms.GroupBox | |
| $grpOS.Text = "System Information" | |
| $grpOS.Location = New-Object System.Drawing.Point(10, 8) | |
| $grpOS.Size = New-Object System.Drawing.Size(560, 48) | |
| $grpOS.Anchor = "Top,Left,Right" | |
| $script:form.Controls.Add($grpOS) | |
| $script:lblDetectedOS = New-Object System.Windows.Forms.Label | |
| $script:lblDetectedOS.Location = New-Object System.Drawing.Point(8, 18) | |
| $script:lblDetectedOS.Size = New-Object System.Drawing.Size(544, 20) | |
| $script:lblDetectedOS.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold) | |
| $grpOS.Controls.Add($script:lblDetectedOS) | |
| # ---- LICENSE WARNING BANNER (hidden until fully-licensed detected) ---- | |
| $script:lblLicenseWarn = New-Object System.Windows.Forms.Label | |
| $script:lblLicenseWarn.Location = New-Object System.Drawing.Point(10, 60) | |
| $script:lblLicenseWarn.Size = New-Object System.Drawing.Size(560, 40) | |
| $script:lblLicenseWarn.Text = " WARNING: This machine is FULLY ACTIVATED. A new product key will revoke the current" + | |
| " license immediately and start a 30-day grace / reactivation period." | |
| $script:lblLicenseWarn.BackColor = [System.Drawing.Color]::FromArgb(255, 185, 0) | |
| $script:lblLicenseWarn.ForeColor = [System.Drawing.Color]::FromArgb(30, 30, 30) | |
| $script:lblLicenseWarn.Font = New-Object System.Drawing.Font("Segoe UI", 8.5, [System.Drawing.FontStyle]::Bold) | |
| $script:lblLicenseWarn.Anchor = "Top,Left,Right" | |
| $script:lblLicenseWarn.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft | |
| $script:lblLicenseWarn.Visible = $false | |
| $script:lblLicenseWarn.Name = "lblLicenseWarn" | |
| $script:form.Controls.Add($script:lblLicenseWarn) | |
| # ---- GROUP: KMS Server ----------------------------------------- | |
| $grpServer = New-Object System.Windows.Forms.GroupBox | |
| $grpServer.Text = "KMS Server" | |
| $grpServer.Location = New-Object System.Drawing.Point(10, 102) | |
| $grpServer.Size = New-Object System.Drawing.Size(560, 78) | |
| $grpServer.Anchor = "Top,Left,Right" | |
| $script:form.Controls.Add($grpServer) | |
| $lblServer = New-Object System.Windows.Forms.Label | |
| $lblServer.Location = New-Object System.Drawing.Point(8, 22) | |
| $lblServer.Size = New-Object System.Drawing.Size(110, 20) | |
| $lblServer.Text = "KMS Server Host:" | |
| $grpServer.Controls.Add($lblServer) | |
| $script:txtServer = New-Object System.Windows.Forms.TextBox | |
| $script:txtServer.Location = New-Object System.Drawing.Point(122, 20) | |
| $script:txtServer.Size = New-Object System.Drawing.Size(200, 22) | |
| $script:txtServer.Text = "kms.nbk.ngtech.co.il" | |
| $script:txtServer.Anchor = "Top,Left,Right" | |
| $grpServer.Controls.Add($script:txtServer) | |
| $lblPort = New-Object System.Windows.Forms.Label | |
| $lblPort.Location = New-Object System.Drawing.Point(330, 22) | |
| $lblPort.Size = New-Object System.Drawing.Size(32, 20) | |
| $lblPort.Text = "Port:" | |
| $grpServer.Controls.Add($lblPort) | |
| $script:txtPort = New-Object System.Windows.Forms.TextBox | |
| $script:txtPort.Location = New-Object System.Drawing.Point(362, 20) | |
| $script:txtPort.Size = New-Object System.Drawing.Size(52, 22) | |
| $script:txtPort.Text = "1688" | |
| $grpServer.Controls.Add($script:txtPort) | |
| $script:btnTestPort = New-Object System.Windows.Forms.Button | |
| $script:btnTestPort.Location = New-Object System.Drawing.Point(422, 18) | |
| $script:btnTestPort.Size = New-Object System.Drawing.Size(128, 26) | |
| $script:btnTestPort.Text = "Test Connection" | |
| $script:btnTestPort.Anchor = "Top,Right" | |
| $grpServer.Controls.Add($script:btnTestPort) | |
| $script:lblCurrentKMS = New-Object System.Windows.Forms.Label | |
| $script:lblCurrentKMS.Location = New-Object System.Drawing.Point(8, 50) | |
| $script:lblCurrentKMS.Size = New-Object System.Drawing.Size(544, 18) | |
| $script:lblCurrentKMS.Font = New-Object System.Drawing.Font("Segoe UI", 8, [System.Drawing.FontStyle]::Italic) | |
| $script:lblCurrentKMS.Text = "Currently registered KMS: loading..." | |
| $grpServer.Controls.Add($script:lblCurrentKMS) | |
| # ---- GROUP: Product Key ---------------------------------------- | |
| $grpKey = New-Object System.Windows.Forms.GroupBox | |
| $grpKey.Text = "Product Key" | |
| $grpKey.Location = New-Object System.Drawing.Point(10, 186) | |
| $grpKey.Size = New-Object System.Drawing.Size(560, 80) | |
| $grpKey.Anchor = "Top,Left,Right" | |
| $script:form.Controls.Add($grpKey) | |
| $lblEdition = New-Object System.Windows.Forms.Label | |
| $lblEdition.Location = New-Object System.Drawing.Point(8, 22) | |
| $lblEdition.Size = New-Object System.Drawing.Size(110, 20) | |
| $lblEdition.Text = "Select Edition:" | |
| $grpKey.Controls.Add($lblEdition) | |
| $script:cmbKeys = New-Object System.Windows.Forms.ComboBox | |
| $script:cmbKeys.Location = New-Object System.Drawing.Point(122, 20) | |
| $script:cmbKeys.Size = New-Object System.Drawing.Size(430, 22) | |
| $script:cmbKeys.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList | |
| $script:cmbKeys.Anchor = "Top,Left,Right" | |
| # Dropdown is populated after OS detection in Form Load | |
| $grpKey.Controls.Add($script:cmbKeys) | |
| $lblKey = New-Object System.Windows.Forms.Label | |
| $lblKey.Location = New-Object System.Drawing.Point(8, 50) | |
| $lblKey.Size = New-Object System.Drawing.Size(110, 20) | |
| $lblKey.Text = "GVLK Key Value:" | |
| $grpKey.Controls.Add($lblKey) | |
| $script:txtKey = New-Object System.Windows.Forms.TextBox | |
| $script:txtKey.Location = New-Object System.Drawing.Point(122, 48) | |
| $script:txtKey.Size = New-Object System.Drawing.Size(430, 22) | |
| $script:txtKey.CharacterCasing = [System.Windows.Forms.CharacterCasing]::Upper | |
| $script:txtKey.Anchor = "Top,Left,Right" | |
| $grpKey.Controls.Add($script:txtKey) | |
| $script:KeyFromDropdown = "" | |
| $script:KeyManuallyEdited = $false | |
| $script:cmbKeys.Add_SelectedIndexChanged({ | |
| $sel = $script:cmbKeys.SelectedItem.ToString() | |
| if ($GVLKTable.Contains($sel)) { | |
| $script:KeyFromDropdown = $GVLKTable[$sel] | |
| $script:txtKey.Text = $script:KeyFromDropdown | |
| $script:KeyManuallyEdited = $false | |
| } | |
| }) | |
| $script:txtKey.Add_TextChanged({ | |
| $script:KeyManuallyEdited = ($script:txtKey.Text -ne $script:KeyFromDropdown -and $script:KeyFromDropdown -ne "") | |
| }) | |
| # ---- GROUP: Activity Log --------------------------------------- | |
| $grpLog = New-Object System.Windows.Forms.GroupBox | |
| $grpLog.Text = "Activity Log" | |
| $grpLog.Location = New-Object System.Drawing.Point(10, 272) | |
| $grpLog.Size = New-Object System.Drawing.Size(560, 210) | |
| $grpLog.Anchor = "Top,Bottom,Left,Right" | |
| $script:form.Controls.Add($grpLog) | |
| $script:txtLog = New-Object System.Windows.Forms.TextBox | |
| $script:txtLog.Name = "txtLog" | |
| $script:txtLog.Location = New-Object System.Drawing.Point(8, 18) | |
| $script:txtLog.Size = New-Object System.Drawing.Size(544, 155) | |
| $script:txtLog.Multiline = $true | |
| $script:txtLog.ScrollBars = "Vertical" | |
| $script:txtLog.ReadOnly = $true | |
| $script:txtLog.Font = New-Object System.Drawing.Font("Consolas", 8.5) | |
| $script:txtLog.Anchor = "Top,Bottom,Left,Right" | |
| $grpLog.Controls.Add($script:txtLog) | |
| $script:btnCopyLog = New-Object System.Windows.Forms.Button | |
| $script:btnCopyLog.Location = New-Object System.Drawing.Point(8, 180) | |
| $script:btnCopyLog.Size = New-Object System.Drawing.Size(120, 24) | |
| $script:btnCopyLog.Text = "Copy Log" | |
| $script:btnCopyLog.Anchor = "Bottom,Left" | |
| $grpLog.Controls.Add($script:btnCopyLog) | |
| $script:btnSaveLog = New-Object System.Windows.Forms.Button | |
| $script:btnSaveLog.Location = New-Object System.Drawing.Point(136, 180) | |
| $script:btnSaveLog.Size = New-Object System.Drawing.Size(130, 24) | |
| $script:btnSaveLog.Text = "Save Log to File" | |
| $script:btnSaveLog.Anchor = "Bottom,Left" | |
| $grpLog.Controls.Add($script:btnSaveLog) | |
| $script:btnClearLog = New-Object System.Windows.Forms.Button | |
| $script:btnClearLog.Location = New-Object System.Drawing.Point(274, 180) | |
| $script:btnClearLog.Size = New-Object System.Drawing.Size(100, 24) | |
| $script:btnClearLog.Text = "Clear Log" | |
| $script:btnClearLog.Tag = "danger" | |
| $script:btnClearLog.Anchor = "Bottom,Left" | |
| $grpLog.Controls.Add($script:btnClearLog) | |
| # ---- GROUP: Actions -------------------------------------------- | |
| $grpActions = New-Object System.Windows.Forms.GroupBox | |
| $grpActions.Text = "Actions" | |
| $grpActions.Location = New-Object System.Drawing.Point(10, 488) | |
| $grpActions.Size = New-Object System.Drawing.Size(560, 152) | |
| $grpActions.Anchor = "Bottom,Left,Right" | |
| $script:form.Controls.Add($grpActions) | |
| # Row 1: main action buttons | |
| $script:btnActivate = New-Object System.Windows.Forms.Button | |
| $script:btnActivate.Location = New-Object System.Drawing.Point(8, 22) | |
| $script:btnActivate.Size = New-Object System.Drawing.Size(170, 30) | |
| $script:btnActivate.Text = "Apply Key && Activate" | |
| $grpActions.Controls.Add($script:btnActivate) | |
| $script:btnStatus = New-Object System.Windows.Forms.Button | |
| $script:btnStatus.Location = New-Object System.Drawing.Point(186, 22) | |
| $script:btnStatus.Size = New-Object System.Drawing.Size(170, 30) | |
| $script:btnStatus.Text = "Check License Status" | |
| $grpActions.Controls.Add($script:btnStatus) | |
| $script:btnClearKMS = New-Object System.Windows.Forms.Button | |
| $script:btnClearKMS.Location = New-Object System.Drawing.Point(364, 22) | |
| $script:btnClearKMS.Size = New-Object System.Drawing.Size(188, 30) | |
| $script:btnClearKMS.Text = "Clear KMS Settings" | |
| $script:btnClearKMS.Tag = "danger" | |
| $script:btnClearKMS.Anchor = "Top,Right" | |
| $grpActions.Controls.Add($script:btnClearKMS) | |
| # Row 2: evaluation upgrade (shown only for server eval builds) + theme toggle | |
| $script:btnUpgradeEval = New-Object System.Windows.Forms.Button | |
| $script:btnUpgradeEval.Location = New-Object System.Drawing.Point(8, 60) | |
| $script:btnUpgradeEval.Size = New-Object System.Drawing.Size(240, 30) | |
| $script:btnUpgradeEval.Text = "Upgrade Evaluation Edition" | |
| $script:btnUpgradeEval.Tag = "danger" | |
| $script:btnUpgradeEval.Visible = $false # hidden until OS detected as Server Eval | |
| $grpActions.Controls.Add($script:btnUpgradeEval) | |
| $script:btnTheme = New-Object System.Windows.Forms.Button | |
| $script:btnTheme.Location = New-Object System.Drawing.Point(464, 60) | |
| $script:btnTheme.Size = New-Object System.Drawing.Size(88, 30) | |
| $script:btnTheme.Text = "Light Mode" | |
| $script:btnTheme.Anchor = "Top,Right" | |
| $grpActions.Controls.Add($script:btnTheme) | |
| # Row 3: progress bar | |
| $script:progressBar = New-Object System.Windows.Forms.ProgressBar | |
| $script:progressBar.Location = New-Object System.Drawing.Point(8, 100) | |
| $script:progressBar.Size = New-Object System.Drawing.Size(544, 18) | |
| $script:progressBar.Style = [System.Windows.Forms.ProgressBarStyle]::Marquee | |
| $script:progressBar.Visible = $false | |
| $script:progressBar.Anchor = "Bottom,Left,Right" | |
| $grpActions.Controls.Add($script:progressBar) | |
| # ============================================================ | |
| # EVENTS | |
| # ============================================================ | |
| # -- Test Connection -- | |
| $script:btnTestPort.Add_Click({ | |
| $srv = $script:txtServer.Text.Trim() | |
| $port = $script:txtPort.Text.Trim() | |
| if ([string]::IsNullOrWhiteSpace($srv)) { Write-Log "Error: KMS Server Host cannot be empty."; return } | |
| if (-not [int]::TryParse($port, [ref]$null)) { Write-Log "Error: Port must be a numeric value."; return } | |
| Write-Log "Testing TCP connection to ${srv}:${port}..." | |
| Set-Busy $true "Testing connection..." | |
| try { | |
| $r = Test-NetConnection -ComputerName $srv -Port ([int]$port) -WarningAction SilentlyContinue | |
| if ($r.TcpTestSucceeded) { | |
| Write-Log "SUCCESS: $srv : $port is reachable." | |
| $script:statusLabel.Text = "Connection OK" | |
| } else { | |
| Write-Log "FAILED: Cannot reach $srv on port $port." | |
| $script:statusLabel.Text = "Connection FAILED" | |
| } | |
| } catch { | |
| Write-Log "Error: $_" | |
| $script:statusLabel.Text = "Connection error" | |
| } finally { | |
| Set-Busy $false | |
| } | |
| }) | |
| # -- Apply Key & Activate (with retry) -- | |
| $script:btnActivate.Add_Click({ | |
| $key = $script:txtKey.Text.Trim().ToUpper() | |
| $srv = $script:txtServer.Text.Trim() | |
| $portTx = $script:txtPort.Text.Trim() | |
| $maxRetry = 3 | |
| if (-not (Test-GVLKFormat -Key $key)) { | |
| [System.Windows.Forms.MessageBox]::Show( | |
| "Invalid GVLK key format.`nExpected: XXXXX-XXXXX-XXXXX-XXXXX-XXXXX", | |
| "Invalid Key", "OK", "Warning") | Out-Null | |
| return | |
| } | |
| if ($script:KeyManuallyEdited) { | |
| $ans = [System.Windows.Forms.MessageBox]::Show( | |
| "You manually changed the key.`nDropdown key : $script:KeyFromDropdown`nYou entered : $key`n`nProceed with the manual key?", | |
| "Manual Key Override", "YesNo", "Warning") | |
| if ($ans -ne "Yes") { return } | |
| } | |
| if (-not [int]::TryParse($portTx, [ref]$null)) { Write-Log "Error: Port must be numeric."; return } | |
| $portInt = [int]$portTx | |
| # --- Fully-licensed warning --- | |
| try { | |
| $wAppId = "55c92734-d682-4d71-983e-d6ec3f16059f" | |
| $curLic = Get-CimInstance -ClassName SoftwareLicensingProduct | | |
| Where-Object { $_.ApplicationId -eq $wAppId -and $_.PartialProductKey } | |
| if ($curLic -and $curLic.LicenseStatus -eq 1) { | |
| $warnText = "WARNING: This machine is already FULLY ACTIVATED." + [Environment]::NewLine + [Environment]::NewLine + | |
| "Replacing the product key is usually unnecessary and carries real risks:" + [Environment]::NewLine + [Environment]::NewLine + | |
| " - Your current activation will be REVOKED immediately." + [Environment]::NewLine + | |
| " - The system enters a grace / reactivation period (typically 30 days)." + [Environment]::NewLine + | |
| " - KMS must be reached again within that window, or Windows will show" + [Environment]::NewLine + | |
| " activation warnings and may restrict certain personalisation features." + [Environment]::NewLine + | |
| " - If the new key does not match your actual Volume License agreement," + [Environment]::NewLine + | |
| " you could be out of compliance with Microsoft licensing terms." + [Environment]::NewLine + [Environment]::NewLine + | |
| "Only proceed if you are intentionally moving this machine to a different" + [Environment]::NewLine + | |
| "edition or KMS infrastructure and understand the consequences." + [Environment]::NewLine + [Environment]::NewLine + | |
| "Proceed and REPLACE the current activation?" | |
| $ans2 = [System.Windows.Forms.MessageBox]::Show( | |
| $warnText, | |
| "Machine Already Fully Activated -- Are You Sure?", | |
| [System.Windows.Forms.MessageBoxButtons]::YesNo, | |
| [System.Windows.Forms.MessageBoxIcon]::Warning, | |
| [System.Windows.Forms.MessageBoxDefaultButton]::Button2) | |
| if ($ans2 -ne [System.Windows.Forms.DialogResult]::Yes) { | |
| Write-Log "Activation cancelled -- machine is already fully licensed." | |
| return | |
| } | |
| Write-Log "WARNING: User chose to replace activation on a fully-licensed machine." | |
| } | |
| } catch { | |
| # If license query fails, do not block activation | |
| } | |
| Set-Busy $true "Activating..." | |
| $attempt = 0 ; $ok = $false | |
| while ($attempt -lt $maxRetry -and -not $ok) { | |
| $attempt++ | |
| Write-Log "--- Attempt $attempt / $maxRetry ---" | |
| try { | |
| Write-Log "Installing product key..." | |
| $sls = Get-CimInstance -ClassName SoftwareLicensingService | |
| Invoke-CimMethod -InputObject $sls -MethodName "InstallProductKey" ` | |
| -Arguments @{ ProductKey = $key } | Out-Null | |
| Write-Log "Setting KMS server: $srv" | |
| Invoke-CimMethod -InputObject $sls -MethodName "SetKeyManagementServiceMachine" ` | |
| -Arguments @{ KeyManagementServiceMachine = $srv } | Out-Null | |
| Write-Log "Setting KMS port: $portInt" | |
| Invoke-CimMethod -InputObject $sls -MethodName "SetKeyManagementServicePort" ` | |
| -Arguments @{ PortNumber = [uint32]$portInt } | Out-Null | |
| Invoke-CimMethod -InputObject $sls -MethodName "RefreshLicenseStatus" | Out-Null | |
| Write-Log "Requesting activation..." | |
| $wAppId = "55c92734-d682-4d71-983e-d6ec3f16059f" | |
| $lic = Get-CimInstance -ClassName SoftwareLicensingProduct | | |
| Where-Object { $_.ApplicationId -eq $wAppId -and $_.PartialProductKey } | |
| if (-not $lic) { | |
| Write-Log "WARNING: No product with partial key found after install." | |
| } else { | |
| $res = Invoke-CimMethod -InputObject $lic -MethodName "Activate" | |
| if ($res.ReturnValue -eq 0) { $ok = $true ; Write-Log "Activation succeeded." } | |
| else { Write-Log "Activation returned code: $($res.ReturnValue)" } | |
| } | |
| } catch { | |
| Write-Log "Error: $_" | |
| if ($attempt -lt $maxRetry) { Write-Log "Retrying in 3 s..." ; Start-Sleep -Seconds 3 } | |
| } | |
| } | |
| if ($ok) { $script:statusLabel.Text = "Activation successful" ; Write-Log "=== Done after $attempt attempt(s). ===" } | |
| else { $script:statusLabel.Text = "Activation failed" ; Write-Log "=== Failed after $maxRetry attempt(s). ===" } | |
| Refresh-KMSLabel | |
| Refresh-LicenseWarning | |
| Set-Busy $false | |
| }) | |
| # -- Check License Status -- | |
| $script:btnStatus.Add_Click({ | |
| Set-Busy $true "Reading license status..." | |
| try { | |
| $wAppId = "55c92734-d682-4d71-983e-d6ec3f16059f" | |
| $lic = Get-CimInstance -ClassName SoftwareLicensingProduct | | |
| Where-Object { $_.ApplicationId -eq $wAppId -and $_.PartialProductKey } | |
| if ($lic) { | |
| $statusTxt = switch ($lic.LicenseStatus) { | |
| 0 { "Unlicensed (0)" } 1 { "Licensed (1)" } 2 { "OOB Grace (2)" } | |
| 3 { "OOT Grace (3)" } 4 { "Non-Genuine Grace (4)" } 5 { "Notification (5)" } | |
| 6 { "Extended Grace (6)" } default { "Unknown ($($lic.LicenseStatus))" } | |
| } | |
| Write-Log "--- License Status Report ---" | |
| Write-Log "Status : $statusTxt" | |
| Write-Log "Product : $($lic.Name)" | |
| Write-Log "Partial Key : $($lic.PartialProductKey)" | |
| if ($lic.GracePeriodRemaining -gt 0) { | |
| $gd = [math]::Floor($lic.GracePeriodRemaining / 1440) | |
| $gh = [math]::Floor(($lic.GracePeriodRemaining % 1440) / 60) | |
| $gm = $lic.GracePeriodRemaining % 60 | |
| Write-Log "Grace Left : ${gd}d ${gh}h ${gm}m" | |
| } else { | |
| Write-Log "Grace Left : N/A (fully licensed)" | |
| } | |
| if ($lic.KeyManagementServiceMachine) { | |
| Write-Log "KMS Server : $($lic.KeyManagementServiceMachine)" | |
| Write-Log "KMS Port : $($lic.KeyManagementServicePort)" | |
| } | |
| if ($lic.KeyManagementServiceCurrentCount) { | |
| Write-Log "KMS Count : $($lic.KeyManagementServiceCurrentCount)" | |
| } | |
| if ($lic.KeyManagementServiceLicensedRequests -gt 0) { | |
| Write-Log "KMS Licensed : $($lic.KeyManagementServiceLicensedRequests) requests" | |
| } | |
| $script:statusLabel.Text = "License: $statusTxt" | |
| } else { | |
| Write-Log "No active license product found." | |
| $script:statusLabel.Text = "No license found" | |
| } | |
| } catch { | |
| Write-Log "Error: $_" | |
| $script:statusLabel.Text = "Error reading license" | |
| } finally { | |
| Refresh-LicenseWarning | |
| Set-Busy $false | |
| } | |
| }) | |
| # -- Clear KMS Settings -- | |
| $script:btnClearKMS.Add_Click({ | |
| $ans = [System.Windows.Forms.MessageBox]::Show( | |
| "This will clear the registered KMS server and port.`nAre you sure?", | |
| "Clear KMS Settings", "YesNo", "Warning") | |
| if ($ans -ne "Yes") { return } | |
| Set-Busy $true "Clearing KMS settings..." | |
| try { | |
| $sls = Get-CimInstance -ClassName SoftwareLicensingService | |
| Invoke-CimMethod -InputObject $sls -MethodName "SetKeyManagementServiceMachine" ` | |
| -Arguments @{ KeyManagementServiceMachine = "" } | Out-Null | |
| Invoke-CimMethod -InputObject $sls -MethodName "SetKeyManagementServicePort" ` | |
| -Arguments @{ PortNumber = [uint32]0 } | Out-Null | |
| Write-Log "KMS settings cleared." | |
| $script:statusLabel.Text = "KMS settings cleared" | |
| Refresh-KMSLabel | |
| } catch { | |
| Write-Log "Error: $_" | |
| $script:statusLabel.Text = "Error" | |
| } finally { | |
| Set-Busy $false | |
| } | |
| }) | |
| # -- Upgrade Evaluation Edition (DISM) -- | |
| $script:btnUpgradeEval.Add_Click({ | |
| $osInfo = $script:OSInfo | |
| # Guard: must be a Server Evaluation | |
| if (-not $osInfo -or -not $osInfo.IsEvaluation -or -not $osInfo.ServerVersion) { | |
| [System.Windows.Forms.MessageBox]::Show( | |
| "This function is only available on Windows Server Evaluation builds.", | |
| "Not Applicable", "OK", "Information") | Out-Null | |
| return | |
| } | |
| $ver = $osInfo.ServerVersion | |
| # Show Standard / Datacenter picker | |
| $tier = Show-EditionPicker -ServerVersion $ver | |
| if (-not $tier) { return } # user cancelled | |
| # Look up the GVLK for this server version + tier | |
| $editionName = "Windows Server $ver $tier" | |
| if (-not $GVLKTable.Contains($editionName)) { | |
| [System.Windows.Forms.MessageBox]::Show( | |
| "No GVLK key found for: $editionName", | |
| "Missing Key", "OK", "Warning") | Out-Null | |
| return | |
| } | |
| $key = $GVLKTable[$editionName] | |
| $dismEdition = if ($tier -eq "Standard") { "ServerStandard" } else { "ServerDatacenter" } | |
| $ans = [System.Windows.Forms.MessageBox]::Show( | |
| "Ready to upgrade:`n`n Source : Windows Server $ver Evaluation`n Target : $tier ($dismEdition)`n GVLK : $key`n`nA reboot will likely be required. Proceed?", | |
| "Confirm Evaluation Upgrade", "YesNo", "Warning") | |
| if ($ans -ne "Yes") { return } | |
| Set-Busy $true "Running DISM upgrade..." | |
| Write-Log "--- Evaluation Edition Upgrade ---" | |
| Write-Log "Source : Windows Server $ver Evaluation" | |
| Write-Log "Target : $dismEdition ($tier)" | |
| Write-Log "Key : $key" | |
| try { | |
| $outFile = Join-Path $env:TEMP "kms_dism_out.txt" | |
| $errFile = Join-Path $env:TEMP "kms_dism_err.txt" | |
| $dArgs = "/online /Set-Edition:$dismEdition /ProductKey:$key /AcceptEula /NoRestart" | |
| $proc = Start-Process -FilePath "DISM.exe" -ArgumentList $dArgs ` | |
| -Wait -PassThru -NoNewWindow ` | |
| -RedirectStandardOutput $outFile -RedirectStandardError $errFile | |
| if (Test-Path $outFile) { $o = Get-Content $outFile -Raw ; if ($o.Trim()) { Write-Log "DISM: $o" } } | |
| if (Test-Path $errFile) { $e = Get-Content $errFile -Raw ; if ($e.Trim()) { Write-Log "DISM ERR: $e" } } | |
| if ($proc.ExitCode -eq 0) { | |
| Write-Log "DISM completed successfully (exit 0)." | |
| $script:statusLabel.Text = "Upgrade complete" | |
| [System.Windows.Forms.MessageBox]::Show( | |
| "Edition upgrade completed.`nA reboot may be needed to finalise.", | |
| "Done", "OK", "Information") | Out-Null | |
| } elseif ($proc.ExitCode -eq 3010) { | |
| Write-Log "DISM completed -- reboot required (exit 3010)." | |
| $rb = [System.Windows.Forms.MessageBox]::Show( | |
| "Upgrade complete. A reboot is required.`nReboot now?", | |
| "Reboot Required", "YesNo", "Question") | |
| if ($rb -eq "Yes") { Restart-Computer -Force } | |
| } else { | |
| Write-Log "DISM failed with exit code $($proc.ExitCode)." | |
| $script:statusLabel.Text = "DISM failed (code $($proc.ExitCode))" | |
| } | |
| } catch { | |
| Write-Log "Error running DISM: $_" | |
| $script:statusLabel.Text = "DISM error" | |
| } finally { | |
| Set-Busy $false | |
| } | |
| }) | |
| # -- Copy Log -- | |
| $script:btnCopyLog.Add_Click({ | |
| if ([string]::IsNullOrWhiteSpace($script:txtLog.Text)) { $script:statusLabel.Text = "Log is empty." ; return } | |
| [System.Windows.Forms.Clipboard]::SetText($script:txtLog.Text) | |
| $script:statusLabel.Text = "Log copied to clipboard." | |
| }) | |
| # -- Save Log -- | |
| $script:btnSaveLog.Add_Click({ | |
| if ([string]::IsNullOrWhiteSpace($script:txtLog.Text)) { $script:statusLabel.Text = "Log is empty." ; return } | |
| $dlg = New-Object System.Windows.Forms.SaveFileDialog | |
| $dlg.Filter = "Text Files (*.txt)|*.txt|All Files (*.*)|*.*" | |
| $dlg.FileName = "KMS-Log-$(Get-Date -Format 'yyyyMMdd-HHmmss').txt" | |
| $dlg.Title = "Save Activity Log" | |
| if ($dlg.ShowDialog() -eq "OK") { | |
| try { | |
| $script:txtLog.Text | Out-File -FilePath $dlg.FileName -Encoding UTF8 | |
| Write-Log "Log saved: $($dlg.FileName)" | |
| $script:statusLabel.Text = "Log saved." | |
| } catch { Write-Log "Save error: $_" } | |
| } | |
| }) | |
| # -- Clear Log -- | |
| $script:btnClearLog.Add_Click({ | |
| $script:txtLog.Clear() | |
| $script:statusLabel.Text = "Log cleared." | |
| }) | |
| # -- Theme Toggle -- | |
| $script:btnTheme.Add_Click({ | |
| $script:IsDark = -not $script:IsDark | |
| $script:btnTheme.Text = if ($script:IsDark) { "Light Mode" } else { "Dark Mode" } | |
| Apply-Theme -Root $script:form | |
| }) | |
| # ============================================================ | |
| # FORM LOAD -- pure UI setup, no CIM/registry calls here | |
| # ============================================================ | |
| $script:form.Add_Load({ | |
| # Placeholders while background tasks run | |
| $script:lblDetectedOS.Text = "Detecting operating system..." | |
| $script:lblCurrentKMS.Text = "Currently registered KMS: loading..." | |
| $script:btnUpgradeEval.Visible = $false | |
| # Pre-populate dropdown with all editions; background task will re-filter once OS is known | |
| $script:cmbKeys.Items.Clear() | |
| foreach ($ed in $GVLKTable.Keys) { [void]$script:cmbKeys.Items.Add($ed) } | |
| if ($script:cmbKeys.Items.Count -gt 0) { $script:cmbKeys.SelectedIndex = 0 } | |
| # Disable action buttons until background init completes | |
| $script:btnActivate.Enabled = $false | |
| $script:btnStatus.Enabled = $false | |
| $script:btnClearKMS.Enabled = $false | |
| $script:btnTestPort.Enabled = $false | |
| $script:btnUpgradeEval.Enabled = $false | |
| Apply-Theme -Root $script:form | |
| Write-Log "KMS Activation Utility v2.3 starting..." | |
| }) | |
| # ============================================================ | |
| # FORM SHOWN -- window is visible; kick off background tasks | |
| # ============================================================ | |
| $script:form.Add_Shown({ | |
| Start-BackgroundInit | |
| }) | |
| # ============================================================ | |
| # LAUNCH | |
| # ============================================================ | |
| [void]$script:form.ShowDialog() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment