Wes Ellis./ a personal notebook
Technology. Stories. Side projects.
A few things worth writing down.
← Back to Script Library

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.

AT A GLANCEUpdate-WindowsEdition.ps1
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

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.

Update-WindowsEdition.ps1Download
<#
.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

ParameterTypeDefaultWhat it's for
-ProductKeysecurestring—The product key. If you don't pass it, you're prompted for it, and it's never echoed to the screen.
-ExpectedEditionstringEnterpriseThe EditionID you expect when it's done, like Enterprise or Professional. Used for the final check.
-LogPathstringC:\Windows\Temp\Update-WindowsEdition.logWhere the step-by-step log goes. The key itself is never logged, only its last five characters.
-Restartswitch—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 -WhatIf

Run it interactively. You'll be prompted for the key.

.\Update-WindowsEdition.ps1 -ExpectedEdition Enterprise

Read the key from a vault instead of typing it (this example uses the SecretManagement module).

.\Update-WindowsEdition.ps1 -ProductKey (Get-Secret -Name WindowsEnterpriseMAK) -Restart

What you'll see

Example outputvalues are illustrative
ComputerName  : PC-0142
EditionBefore : Education
EditionAfter  : Enterprise
KeyEndsWith   : 7QXB4
Activated     : True
RestartNeeded : True

How it works

  1. Record where you started. It reads EditionID from HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion and writes it to the log.
  2. Get the key safely. The key comes in as a SecureString, from the parameter or a Read-Host -AsSecureString prompt. It's turned into plain text only for the licensing call, checked against the usual 5x5 format, and cleared in a finally block. The log only ever sees the last five characters.
  3. Install and activate. InstallProductKey on the SoftwareLicensingService class installs the key (that's exactly what slmgr /ipk calls), RefreshLicenseStatus makes Windows re-read its licensing state, and Activate on the Windows SoftwareLicensingProduct instance activates online.
  4. Check, don't assume. It reads the license back and treats LicenseStatus 1 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.
  5. Restart only if told to. With -Restart and a successful activation, it restarts. Otherwise it reports RestartNeeded and 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 $sessions shows 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.