Share via


changepk.exe from startup script not working to upgrade from Pro to Enterprise

Question

Friday, December 30, 2016 5:03 AM

We recently purchased licenses to upgrade all our Windows 10 Pro computers to Windows 10 Enterprise, and we have updated our KMS server with the new host key. Research shows that the simplest way to upgrade the clients is to run:

changepk.exe /ProductKey NPPR9-FWDCX-D2C8J-H872K-2YT43

from an elevated command prompt on the desktops.  I've tested it, and it works.  So simple, just run this command remotely on each PC.  However, it displays a modal dialog and automatically reboots.  This is a problem.  So I got the idea to run the command in a Startup Script in GPO.  This way we can just ask users to reboot before they go home at night.  No data loss and fewer user complaints; winner-winner!

But, it's not working.  The changepk.exe process runs for awhile but quits (exitcode=0) without initiating the upgrade.  Through some effort, I was able to launch an interactive/administrative (nt authority\system) command prompt as a startup script.  When I run changepk (no parameters) in this manner, it prompts for the product key, which I enter.  It churns for a couple minutes checking the key, and eventually responds with a message stating that the key "didn't work", error code 0x80040154.

This doesn't make sense "Class not registered".  If I continue to sign in, and run the command, it works.  So, whatever class it wants is registered.

Any ideas?  What can I do to make this work?

-Tony

All replies (8)

Thursday, March 16, 2017 10:29 PM âś…Answered

... I was hoping to use Changepk.exe and suppress a reboot for reasons that you stated. But others have stated switches aren't supported either...

Where ever you go, there you are.

I read somewhere that running changepk.exe on v1607 doesn't force a reboot.  It certainly does reboot when run on v1511.  I don't know if that helps you.  I wouldn't hold my breath for v1703 "fixing" this problem.  You must run changepk.exe as an administrative user; it will not work when run as system.

Here's my workaround...

In MDT, create three actions that do the following:

  1. Specify a system startup script for myscript.ps1, set Run startup scripts asynchronously = Disabled, set Specify maximum wait time for Group Policy scripts = 0
  2. Reboot
  3. Undo changes made in step 1.

When the computer reboots during step 2, the script performs the edition change.

My script (I cut and pasted the pieces from a bigger script to post here, so it may not work exactly as listed, but it shouldn't take much effort, if any, to get it working):

 

Set-StrictMode -Version 2.0

    function Get-OSAbbreviation {
        param (
            [switch] $AppendEdition,
            [string] $Computername=$env:COMPUTERNAME
        )

        $os_info = Get-WmiObject -ComputerName $Computername -Class Win32_OperatingSystem
        $os_info.Version -match "^([0-9]+)\.([0-9]+)" | Out-Null
        $os_abbrev = "v" + $Matches[1] +$Matches[2]

        $os_arch = @(Get-WmiObject -ComputerName $Computername -Class Win32_Processor)[0]
        $os_abbrev += switch ($os_arch.Architecture) {
            0 {"x86"}
            1 {"MIPS"}
            2 {"Alpha"}
            3 {"PowerPC"}
            5 {"ARM"}
            6 {"ia64"}
            9 {"x64"}
            default {"Unknown"}
        }

        if ($AppendEdition) {
            $os_abbrev += if ($os_info.ProductType -eq 1) { "Wks" } else { "Svr" }
            $os_abbrev += switch ($os_info.OperatingSystemSKU) {
                1 {"Ult"}
                2 {"HomeBasic"}
                3 {"HomePremium"}
                4 {"Ent"}
                6 {"Bus"}
                7 {"Std"}
                8 {"DC"}
                9 {"SBS"}
                10 {"Ent"}
                11 {"Starter"}
                12 {"DCCore"}
                13 {"StdCore"}
                14 {"EntCore"}
                17 {"Web"}
                19 {"Home"}
                20 {"StorExp"}
                21 {"StorStd"}
                22 {"StorWkg"}
                23 {"StorEnt"}
                24 {"SBE"}
                25 {"SBEPrem"}
                27 {"EntN"}
                28 {"UltN"}
                29 {"WebCore"}
                36 {"StdV"}
                37 {"DCV"}
                38 {"EntV"}
                39 {"DCCoreV"}
                40 {"StdCoreV"}
                41 {"EntCoreV"}
                42 {"HypV"}
                43 {"StorExpCore"}
                44 {"StorStdCore"}
                45 {"StorWkgCore"}
                46 {"StoreEntCore"}
                48 {"Pro"}
                50 {"Ess"}
                63 {"SBEPremCore"}
                64 {"Compute"}
                97 {"RT"}
                101 {"Home"}
                103 {"ProWMC"}
                104 {"Mobile"}
                123 {"IOT"}
                143 {"DCNano"}
                144 {"StdNano"}
                147 {"DCCore"}
                148 {"StdCore"}
                default {$_}
            }
        }

        return $os_abbrev
    }


    function GeneratePassword([int] $length=15)
    {
        $pass = "Ab1-"
        for (; $length -gt 0; $length--) {
            $rnd = Get-Random -Minimum 48 -Maximum 110
            if ($rnd -gt 57) { $rnd+=7 }
            if ($rnd -gt 90) { $rnd+=6 }
            $pass += [System.Convert]::ToChar($rnd)
        }
        return $pass
    }

###################################################################################################################################

$taskname = "EntUpgrade"
$account = "EntUpgradeTemp"


if ((Get-OSAbbreviation -AppendEdition) -notmatch ".*wksent") {
    $password = GeneratePassword 100
            
    Write-Output "Creating temporary local admin account..."
    net user /ADD $account $password /y
    net localgroup Administrators $account /ADD

    Write-Output "Creating scheduled task..."
    $action = New-ScheduledTaskAction -Execute "changepk.exe" -Argument "/ProductKey NPPR9-FWDCX-D2C8J-H872K-2YT43"
    Register-ScheduledTask -TaskName $taskname -Action $action -User $account -Password $password -RunLevel Highest

    Write-Output "Starting scheduled task..."
    Start-ScheduledTask -TaskName $taskname
    while ((Get-ScheduledTask -TaskName $taskname).State -eq "Running") {
        Start-Sleep -Seconds 30
        Write-Output " Waiting for scheduled task to end."
    }

    (Get-Date).DateTime
    Write-Output "Waiting indefinately for the upgrade to reboot this computer."
    while ($true) { Start-Sleep -Seconds 60 }
} else {
    Write-Output "Deleting scheduled task..."
    Unregister-ScheduledTask -TaskName $taskname -Confirm:$false

    Write-Output "Deleting temporary account user profile..."
    $profpath = Join-Path (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\').ProfilesDirectory $account
    (Get-WmiObject -Class Win32_UserProfile | Where-Object { $_.LocalPath -eq $profpath }).Delete()

    Write-Output "Deleting temporary local admin account..."
    net user /DELETE $account

    Write-Output "Replacing RunOnce regkey entry to make non-blocking..."
    Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce\" -Name "UpgradeResultsUI.exe" -Type ExpandString -Value "cmd.exe /c start %SystemRoot%\System32\UpgradeResultsUI.exe" -Force
}

Hope this helps others.

-Tony


Friday, December 30, 2016 6:43 AM

More testing using the system-level command prompt launched during the startup script...

I thought, "maybe it won't work as SYSTEM", so I tried to use runas.exe to start another cmd.exe as a local or domain account.  Get an error every time.

Also, psexec.exe causes the same error.

"The application was unable to start correctly (0xc0000142)"

-Tony


Monday, January 2, 2017 12:19 AM

Hi Tony,

It seems that this command can only work if there is an user logging on this system.

Have you tried to deploy a task scheduler to run the script?

We can configure it just Run One time and Check on these options:

Deploy task scheduler via GPO:

https://technet.microsoft.com/en-us/library/cc725745%28v=ws.11%29.aspx?f=255&MSPPError=-2147217396

Please remember to mark the replies as answers if they help.
If you have feedback for TechNet Subscriber Support, contact tnmff@microsoft.com.


Tuesday, January 3, 2017 10:02 PM

Hi Tony,

It seems that this command can only work if there is an user logging on this system.

Have you tried to deploy a task scheduler to run the script?

I have tried task scheduler, and it does work. I originally tried a "at startup" trigger.  But it is not blocking and allows the user to logon and begin working.  After 5-10 minutes their PC spontaneously reboots.

This is the exact reason why I cannot use "run only when user is logged on".  Automatically rebooting hundreds of users PC in the middle of their work is unacceptable. People have been fired for less.

Regardless of the trigger, changepk.exe will reboot the computer without warning. This is a problem, and I can't use this approach.

Any other ideas?

Can we create a trouble ticket so that changepk is modified to not require a user logged on?

-Tony


Wednesday, March 8, 2017 11:15 PM

I would LOVE to hear more about this tool. I'm interested in running something like this in MDT in the /online OS to upgrade machines coming from Dell and simply using the OEM Win10 Pro.

Where ever you go, there you are.


Tuesday, March 14, 2017 6:23 PM

I would LOVE to hear more about this tool. I'm interested in running something like this in MDT in the /online OS to upgrade machines coming from Dell and simply using the OEM Win10 Pro.

Where ever you go, there you are.

What do you mean?  changepk.exe is a built-in Windows utility.  Using MDT, you can make an action that simply runs the command and waits indefinitely.  changepk.exe will automatically reboot, so you'll have to work around the "unexpected" reboot within MDT.

I got around my problem with startup scripts and some PowerShell code.  Any non-Enterprise edition computer on the domain is automatically upgraded to Enterprise during startup.  The user must wait for the update to finish before they can sign on.

For new deployments, I simply deploy Enterprise from scratch; we don't re-provision Dell's installation.

-Tony


Thursday, March 16, 2017 4:01 PM

We have a TS in MDT wrapped in an EXE that calls LiteTouch.VBS with switches. It's launched in the full OS from a machine straight from Dell or a Surface tablet. We call it an "overlay" but it basically runs a StateRestore and allows our techs to select applications to install and our Line of Business apps, then the machine joins AD. It's a quick way of deploying without a wipe/load. Since OEM's can't ship an Enterprise version of the OS, we want to flip it during the TS, in the online OS.

So we looked at using a PPGK from ICD (that's not really working), and as Johan tweeted back to me, all the automation is in PE not the full OS. So I was hoping to use Changepk.exe and suppress a reboot for reasons that you stated. But others have stated switches aren't supported either.

I understand that there should be PS CMDlets in the Creators Update, so hopefully this will be solved!

Where ever you go, there you are.


Monday, April 10, 2017 10:57 PM

Thanks for that. I ended up using a new script I found from Johan's site. We threw PSExec and the PS script into PS-ADT to make a nice little exe. With some basic WMI filtering the TS now upgrades from Pro to Ent in the online OS!

$namespaceName = "root\cimv2\mdm\dmmap"
$className = "MDM_WindowsLicensing"
$methodName = "UpgradeEditionWithProductKeyMethod"
# Public KMS Client Key for Windows 10 Enterprise
$ProductKey = "NPPR9-FWDCX-D2C8J-H872K-2YT43"
 
$session = New-CimSession
 
$params = New-Object Microsoft.Management.Infrastructure.CimMethodParametersCollection
$param = [Microsoft.Management.Infrastructure.CimMethodParameter]::Create("param", $ProductKey, "String", "In")
$params.Add($param)
 
try
{
    $instance = Get-CimInstance -Namespace $namespaceName -ClassName $className -Filter "ParentID='./Vendor/MSFT' and InstanceID='WindowsLicensing'"
    $session.InvokeMethod($namespaceName, $instance, $methodName, $params)
}
catch [Exception]
{
    write-host $_ | out-string
}
Exit

Where ever you go, there you are.