Auto-activate a (non-domain joined) Windows OS using Powershell and a domain KMS host

The Story

Since Windows Server 2012  there have been a number of windows roles that can be used on a non-domain joined (standalone) server.  Roles like DHCP and WDS no longer requiere a domain which give us an opportunity to create a standalone deployment server on a virtual machine which we can take with us to deploy Windows servers in our clients datacenters. I’m building a server like that right now. However, since the server is not joined to a domain it won’t be able to find the KMS (Key Management System) host on the network to activate the Windows Server operating system. And so, a new idea for a powershell script was born!

The Script

Here’s what I want my script to do:

  • Check for elevation (which we’ll need for activation)
  • Check current license status and prompt if already activated
  • Look for a KMS host on a specific domain (parameter)
  • If necessary uninstall the current license
  • Select and install the appropriate KMS client key for the detected OS
  • Configure KMS service to always connect to the domain for activation
  • Activate Windows
  • Have Debug, Force and WhatIf parameters
  • Work on all OS’s that are KMS capable (including client OS’s like Windows 8)

Here’s a quick glance at the script. I’ll highlight a few things I think you’ll find interesting but, as always, the complete script can be downloaded at the end of the article.

Firstly I put in a function called IsAdministrator which will return true if the current powershell session is elevated:

function IsAdministrator {
    $Identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
    $Principal = New-Object System.Security.Principal.WindowsPrincipal($Identity)
    $Principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
}

For the current license status I adopted a function written by Bryan Lipscy called Get-ActivationStatus. To check that out click here.

If the OS is already activated (maybe with a MSDN or trial key?) we need to ask the user if he wants to proceed, unless the Force or WhatIf parameter was used. Here’s what I used:

IF (($Licensed.status -eq 'Licensed') -and (!$Force) -and (!$WhatIf)) {
    Write-Debug 'Windows is already activated.'
    $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes",""
    $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No",""
    $choices = [System.Management.Automation.Host.ChoiceDescription[]]($yes,$no)
    $caption = "Warning!"
    $message = "Windows is already activated! Proceed anyway?"
    $result = $Host.UI.PromptForChoice($caption,$message,$choices,0)
    if($result -eq 1) {
        Write-Host 'The script was cancelled.'
        exit
    }
    else {
        Write-Debug 'Windows will be deactivated.'
        $Deactivate = 'yes'
    }
}

We’ll get to the deactivate variable later. So how do we check if there’s a KMS host on a domain? We use good-old-fashioned nslookup! The output of the command get’s redirected in a variable using code 2>&1 and using a regular expression we get the name of the KMS host (if one is found):

$findkms = nslookup -type=srv _vlmcs._tcp.$domain 2>&1
[regex]$regex = '.+?\.' + ($Domain -replace '\.','\.')
$kmshost = $regex.Matches($findkms) | ForEach-Object {$_.value} | select -Last 1

IF ($kmshost -match "_vlmcs\._tcp") {
    throw 'No KMS host has been found. Please check the domain name.'
}
else {
    $kmshost = $kmshost -replace ' '
    Write-Debug "KMS Host found: $kmshost"
}

As you can see I’m using my domain parameter/variable to create a regular expression which will select all server FQDN’s using the domain we entered as a suffix. If no KMS host is found the outcome will be a message stating that NSlookup can’t find _vlmcs._tcp.domain so we can utilize this as well.

In order to select the correct KMS client key we first need to know the version of the OS we’re on. For this I like to use:

$OSversion = (Get-WmiObject -class Win32_OperatingSystem).Caption

Now we know the OS version we can use a switch to create a variable $key with the corresponding KMS client key:

switch -Regex ($OSversion) {
    'Windows 8.1 Professional N'             {$key = 'HMCNV-VVBFX-7HMBH-CTY9B-B4FXY';break}
    'Windows 8.1 Professional'               {$key = 'GCRJD-8NW9H-F2CDX-CCM8D-9D6T9';break}
    'Windows 8.1 Enterprise N'               {$key = 'TT4HM-HN7YT-62K67-RGRQJ-JFFXW';break}
    'Windows 8.1 Enterprise'                 {$key = 'MHF9N-XY6XB-WVXMC-BTDCT-MKKG7';break}
    'Windows Server 2012 R2 Standard'        {$key = 'D2N9P-3P6X9-2R39C-7RTCD-MDVJX';break}
    'Windows Server 2012 R2 Datacenter'      {$key = 'W3GGN-FT8W3-Y4M27-J84CP-Q3VJ9';break}
etc...
}

At first I thought about creating a function that could read the technet article containing all the KMS client keys but not all of the OS names will correspond with the value of $OSversion. Maybe I’ll think of something in the future and I’ll update the script then. I haven’t had the chance to test this on every OS so feel free to alter/add the information if you get an error that no KMS client key was found. If that’s the case the detected OS will be shown in the console, so you’ll know what to alter/add. If you do make a alteration I’d appreciate it if you’d leave a comment so I can update my script.

To deactivate Windows current activation I used the slmgr.vbs script present in the system32 folder. I searched for a Powershell equivalent but failed to find one. But hey, it works! For activating windows we need to alter the KMS service which we’ll objectify and throw into a variable:

$KMSservice = Get-WMIObject -query "select * from SoftwareLicensingService"

Now we can do our little song and dance with it:

Write-Debug 'Activating Windows.'
$null = $KMSservice.InstallProductKey($key)
IF (($KMSservice | gm -Name SetKeyManagementServiceLookupDomain) -ne $null) {
    $null = $KMSservice.SetKeyManagementServiceLookupDomain($Domain)
}
else {
    $null = $KMSservice.SetKeyManagementServiceMachine($kmshost)
}
$null = $KMSservice.RefreshLicenseStatus()

You can see I first install the KMS client key. Then I check if there’s a method  called SetKeyManagementServiceLookupDomain available for the KMSservice and if so, set the lookup domain for the service. This way if your KMS host service is migrated to another host the standalone server will still be able to find the new KMS host for license renewal. There’s no need to enter the KMS host we found earlier using nslookup into the service because once the service knows in what domain to look it will quickly detect the host itself. This method is not available in Server 2008 R2 and earlier so we’ll have to input the KMS host we found and activate the OS that way. If the KMS host changes we’ll have to run the script again.

Download

You can get the whole script here. Either click to view or right-click Save as to download.

Enjoy!!

Later, folks!

Edit: I’ve made some changes to this script. It now has a function that will retreive the current KMS client keys from the Microsoft website. Also it has improved WhatIf support.

Advertisements

About MicaH

I'm a Technical Specialist at PepperByte BV (the Netherlands).
This entry was posted in Powershell and tagged , , , , , , , , , , , , , . Bookmark the permalink.

6 Responses to Auto-activate a (non-domain joined) Windows OS using Powershell and a domain KMS host

  1. Vincent Doan says:

    I’m facing this error

    C:\KMS-Activate.ps1 : Windows activation failed.
    At line:1 char:1
    + .\KMS-Activate.ps1
    + ~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,KMS-Activate.ps1

    • MicaH says:

      What happens if you run slmgr /ato in an elevated command prompt? Also check if the script has determined the correct KMS client key. What OS version is the client running?

  2. Hello,

    I am actually trying to activate devices with the OEM key after deployment, is there a way to do it using a similar process outlined above?

    • MicaH says:

      Sure. You can use the SoftwareLicensingService wmi object for OEM as well:

      $Key = ''
      $KMSservice = Get-WMIObject -query "select * from SoftwareLicensingService"
      $KMSservice.InstallProductKey($key)
      $KMSservice.RefreshLicenseStatus()

      You can then use the Get-ActivationStatus function to check if it was successful.

  3. Adam says:

    How would I keep the window open on screen to show the result. It seems to keep closing after the script runs.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s