SCRIPT LIBRARY · POWERSHELL
Upgrade a Windows Edition with a MAK Key from PowerShell
Install a product key, activate it and confirm the edition actually changed, without the key ever landing in a script file or a log.
- What it does
- Installs a Windows product key through the Software Licensing WMI classes, activates online, checks the license status and edition, and logs every step. With a MAK for a higher edition, it performs a key-based edition upgrade such as Education or Pro to Enterprise.
- Requires
- Windows PowerShell 5.1 or PowerShell 7+
- No modules
- Internet access to Microsoft's activation servers
- Permissions
- Local administrator. The script won't start without elevation.
- Runs on
- Windows 10/11
- Tested
- Parse-checked and dry-run with mocked cmdlets in PowerShell 7.4
Part 5 of the thread Packaging with ConfigMgr
Edition upgrades used to mean a reinstall. These days, going from Pro or Education to Enterprise is just a product key: install the right key, activate it, restart, and the machine comes back as Enterprise with every app and file still in place. It's one of the nicer things Windows does.
The catch is doing it well across a lot of machines. slmgr.vbs /ipk works, but it prints to a dialog or console text you have to scrape, and the original version of this script had the MAK sitting in a variable at the top of the file, headed for every distribution point it got copied to. Not great.
This version takes the key as a SecureString parameter, prompting if you leave it off, and never writes more than the last five characters anywhere. It talks to the same licensing WMI classes slmgr.vbs uses, checks the result properly, and only restarts if you ask it to.
<#
.SYNOPSIS
Installs a Windows product key (for example an Enterprise MAK) and activates it, logging each step.
.DESCRIPTION
Uses the Software Licensing WMI classes, the same ones slmgr.vbs calls, to install a product key,
activate Windows online, and confirm the result. If the key belongs to a different edition that
supports a key-based upgrade (Pro or Education to Enterprise, for instance), Windows switches
edition; a restart finishes the job. The key is taken as a SecureString and never written to the log.
Run it elevated. Supports -WhatIf.
.PARAMETER ProductKey
The product key, as a SecureString. Prompted for if you leave it off.
.PARAMETER ExpectedEdition
The EditionID you expect afterwards, for example Enterprise. Used for the check at the end.
.PARAMETER LogPath
Where to write the log. Default: C:\Windows\Temp\Update-WindowsEdition.log
.PARAMETER Restart
Restart the computer when everything succeeded. Off by default.
.EXAMPLE
.\Update-WindowsEdition.ps1 -ExpectedEdition Enterprise -WhatIf
.EXAMPLE
.\Update-WindowsEdition.ps1 -ProductKey (Read-Host -AsSecureString 'Product key') -ExpectedEdition Enterprise -Restart
#>
#Requires -RunAsAdministrator
[CmdletBinding(SupportsShouldProcess)]
param(
[securestring]$ProductKey,
[ValidateNotNullOrEmpty()][string]$ExpectedEdition = 'Enterprise',
[string]$LogPath = (Join-Path $env:SystemRoot 'Temp\Update-WindowsEdition.log'),
[switch]$Restart
)
$ErrorActionPreference = 'Stop'
$windowsAppId = '55c92734-d682-4d71-983e-d6ec3f16059f' # ApplicationID for Windows itself in SoftwareLicensingProduct
function Write-Log {
param([string]$Message)
$line = '{0:yyyy-MM-dd HH:mm:ss} {1}' -f (Get-Date), $Message
Write-Verbose $line
Add-Content -Path $LogPath -Value $line -WhatIf:$false
}
function Get-WindowsLicense {
Get-CimInstance -ClassName SoftwareLicensingProduct -Filter "ApplicationID='$windowsAppId' AND PartialProductKey IS NOT NULL" |
Select-Object -First 1
}
$editionKey = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
$before = (Get-ItemProperty -Path $editionKey).EditionID
Write-Log "Starting. Current edition: $before"
if (-not $ProductKey) { $ProductKey = Read-Host -AsSecureString 'Product key' }
$plainKey = [System.Net.NetworkCredential]::new('', $ProductKey).Password.Trim().ToUpper()
if ($plainKey -notmatch '^([A-Z0-9]{5}-){4}[A-Z0-9]{5}$') { throw 'That doesn''t look like a product key (XXXXX-XXXXX-XXXXX-XXXXX-XXXXX).' }
$lastFive = $plainKey.Substring(24)
$result = [ordered]@{ ComputerName = $env:COMPUTERNAME; EditionBefore = $before; EditionAfter = $null; KeyEndsWith = $lastFive; Activated = $false; RestartNeeded = $false }
if (-not $PSCmdlet.ShouldProcess($env:COMPUTERNAME, "Install product key ending $lastFive and activate")) {
return [pscustomobject]$result
}
try {
$service = Get-CimInstance -ClassName SoftwareLicensingService
Write-Log "Installing product key ending $lastFive"
Invoke-CimMethod -InputObject $service -MethodName InstallProductKey -Arguments @{ ProductKey = $plainKey } | Out-Null
Invoke-CimMethod -InputObject $service -MethodName RefreshLicenseStatus | Out-Null
$license = Get-WindowsLicense
Write-Log "Activating $($license.Name)"
Invoke-CimMethod -InputObject $license -MethodName Activate | Out-Null
$license = Get-WindowsLicense
$result.Activated = ($license.LicenseStatus -eq 1)
Write-Log "License status: $($license.LicenseStatus) (1 = licensed). Channel: $($license.ProductKeyChannel)"
}
catch {
Write-Log "FAILED: $($_.Exception.Message)"
throw
}
finally {
$plainKey = $null
}
$after = (Get-ItemProperty -Path $editionKey).EditionID
$result.EditionAfter = $after
$result.RestartNeeded = ($after -ne $before)
Write-Log "Edition now reports: $after"
if ($after -ne $ExpectedEdition) {
Write-Warning "Edition is '$after', expected '$ExpectedEdition'. Some changes only show after a restart."
$result.RestartNeeded = $true
}
[pscustomobject]$result
if ($Restart -and $result.Activated -and $PSCmdlet.ShouldProcess($env:COMPUTERNAME, 'Restart')) {
Write-Log 'Restarting'
Restart-Computer -Force
}
Parameters
| Parameter | Type | Default | What it's for |
|---|---|---|---|
-ProductKey | securestring | — | The product key. If you don't pass it, you're prompted for it, and it's never echoed to the screen. |
-ExpectedEdition | string | Enterprise | The EditionID you expect when it's done, like Enterprise or Professional. Used for the final check. |
-LogPath | string | C:\Windows\Temp\Update-WindowsEdition.log | Where the step-by-step log goes. The key itself is never logged, only its last five characters. |
-Restart | switch | — | Restart when activation succeeds. Leave it off to let your deployment tool or the user handle the restart. |
Run it
Check what would happen, and see the current edition in the output.
.\Update-WindowsEdition.ps1 -WhatIfRun it interactively. You'll be prompted for the key.
.\Update-WindowsEdition.ps1 -ExpectedEdition EnterpriseRead the key from a vault instead of typing it (this example uses the SecretManagement module).
.\Update-WindowsEdition.ps1 -ProductKey (Get-Secret -Name WindowsEnterpriseMAK) -RestartWhat you'll see
ComputerName : PC-0142
EditionBefore : Education
EditionAfter : Enterprise
KeyEndsWith : 7QXB4
Activated : True
RestartNeeded : True
How it works
- Record where you started. It reads
EditionIDfromHKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersionand writes it to the log. - Get the key safely. The key comes in as a SecureString, from the parameter or a
Read-Host -AsSecureStringprompt. It's turned into plain text only for the licensing call, checked against the usual 5x5 format, and cleared in afinallyblock. The log only ever sees the last five characters. - Install and activate.
InstallProductKeyon theSoftwareLicensingServiceclass installs the key (that's exactly whatslmgr /ipkcalls),RefreshLicenseStatusmakes Windows re-read its licensing state, andActivateon the WindowsSoftwareLicensingProductinstance activates online. - Check, don't assume. It reads the license back and treats
LicenseStatus1 as activated, then compares the edition with-ExpectedEdition. Any failure is logged with its message and rethrown, so your deployment tool sees a real failure instead of a cheerful exit code 0. - Restart only if told to. With
-Restartand a successful activation, it restarts. Otherwise it reportsRestartNeededand leaves the timing to you.
Take it further
- Check a fleet first.
Get-CimInstance SoftwareLicensingProduct -Filter "ApplicationID='55c92734-d682-4d71-983e-d6ec3f16059f' AND PartialProductKey IS NOT NULL" -CimSession $sessionsshows the current license channel and status across many machines before you touch anything. - Offline machines. For networks that can't reach Microsoft's activation servers, the Volume Activation Management Tool (VAMT) can do proxy activation for MAKs.
- Look at subscription activation. If your licensing includes Windows Enterprise E3 or E5, Entra-joined Pro devices can step up to Enterprise when the licensed user signs in, and there's no key to protect at all.
Things that'll trip you up
- Keep the key out of your packages. A MAK in a script, a batch file or a package command line ends up in plain text on every distribution point and in every log that records command lines. Pull it from a secrets vault at run time, or better, skip keys entirely with KMS, Active Directory-based activation or subscription activation if your licensing covers it.
- Not every edition change is an upgrade. Moving up (Pro or Education to Enterprise, for instance) works with a key. Moving down, or sideways into some editions, doesn't and usually means a reinstall. Check Microsoft's Windows edition upgrade guidance for the exact paths before you plan a rollout.
- changepk.exe is the other route. Microsoft documents changepk.exe /ProductKey as the command-line way to do a key-based edition upgrade, and slmgr /ipk (which is what this script does under the hood) works for most of the common paths. If the key installs but the edition won't budge, test changepk on a lab machine before assuming the key is wrong.
- MAKs have a count. Each activation uses one from the key's allowance, and reimaging a machine uses another. Error 0xC004C008 means the key has run out. 0xC004F050 means the key isn't valid for this product at all.
- The edition may not update until after a restart. The script reports what the registry says right away. If EditionAfter still shows the old edition but Activated is True, restart and check again before you call it a failure.