Updating An Existing Get-ProductKey Function

A couple of days ago I received my daily PowerTips email from PowerShell.com talking about how to get the Windows Product key via PowerShell from your local system.

The code is simple and to the point to look for and translate the data into a readable product key:

function Get-ProductKey {    
    $map="BCDFGHJKMPQRTVWXY2346789" 
    $value = (get-itemproperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").digitalproductid[0x34..0x42]  
    $ProductKey = ""  
    for ($i = 24; $i -ge 0; $i--) { 
      $r = 0 
      for ($j = 14; $j -ge 0; $j--) { 
        $r = ($r * 256) -bxor $value[$j] 
        $value[$j] = [math]::Floor([double]($r/24)) 
        $r = $r % 24 
      } 
      $ProductKey = $map[$r] + $ProductKey 
      if (($i % 5) -eq 0 -and $i -ne 0) { 
        $ProductKey = "-" + $ProductKey 
      } 
    } 
    $ProductKey
} 

However, being that I had a little free time and really like this code, I wanted to enhance it to make it more robust by adding a few extra things here and there. By doing this, you can see just how easy it is to take some existing code and modify it to meet your own specific requirements.

The list of items I wanted to add to the existing code are:

  • Run against multiple systems.
  • Allow this function to query remote systems; not just locally
  • Output an object instead of just text
  • In the object, show the following properties
    • Computername, ProductKey,OSDescription,OSVersion
  • Allow to query against 64bit systems
  • Error handling
  • Comment Based Help

First off, lets add some comment based help to this code:

function Get-ProductKey {
     <#   
    .SYNOPSIS   
        Retrieves the product key and OS information from a local or remote system/s.
         
    .DESCRIPTION   
        Retrieves the product key and OS information from a local or remote system/s. Queries of 64bit OS from a 32bit OS will result in 
        inaccurate data being returned for the Product Key. You must query a 64bit OS from a system running a 64bit OS.
        
    .PARAMETER Computername
        Name of the local or remote system/s.
         
    .NOTES   
        Author: Boe Prox
        Version: 1.1       
            -Update of function from http://powershell.com/cs/blogs/tips/archive/2012/04/30/getting-windows-product-key.aspx
            -Added capability to query more than one system
            -Supports remote system query
            -Supports querying 64bit OSes
            -Shows OS description and Version in output object
            -Error Handling
     
    .EXAMPLE 
     Get-ProductKey -Computername Server1
     
    OSDescription                                           Computername OSVersion ProductKey                   
    -------------                                           ------------ --------- ----------                   
    Microsoft(R) Windows(R) Server 2003, Enterprise Edition Server1       5.2.3790  bcdfg-hjklm-pqrtt-vwxyy-12345     
         
        Description 
        ----------- 
        Retrieves the product key information from 'Server1'
    #>  

I also add some parameters to the existing code with a default parameter value for the Computername to point to the local machine. I also add some aliases to support the ValueFromPipeLineByPropertyName which allows extra pipeline support. You will also notice that I set the type for Computername to [string[]] which means that it will take a collection of strings for that parameter.

    [cmdletbinding()]
    Param (
        [parameter(ValueFromPipeLine=$True,ValueFromPipeLineByPropertyName=$True)]
        [Alias("CN","__Server","IPAddress","Server")]
        [string[]]$Computername = $Env:Computername
    )

I start off with a quick check to see if the system is reachable on the network and then perform a WMI query for some pieces of information regarding the Caption (Operating System name), Version and also the Architecture which will tell me if the OS is 64bit or 32bit which will be vital in determining what to do next for the product key.

    Process {
        ForEach ($Computer in $Computername) {
            Write-Verbose ("{0}: Checking network availability" -f $Computer)
            If (Test-Connection -ComputerName $Computer -Count 1 -Quiet) {
                Try {
                    Write-Verbose ("{0}: Retrieving WMI OS information" -f $Computer)
                    $OS = Get-WmiObject -ComputerName $Computer Win32_OperatingSystem -ErrorAction Stop                
                } Catch {
                    $OS = New-Object PSObject -Property @{
                        Caption = $_.Exception.Message
                        Version = $_.Exception.Message
                    }
                }

The rest of the code deals with the actual operations to query the specified system’s registry key based on whether it is a 64bit system or 32bit. This is important because if the system is 64bit, the DigitalProductId4 key value must be used instead of DigitalProductId, otherwise you will not receive the accurate data for the product key. The other part of the code performs the translation of the value of the registry key to find the product key and also using a .Net class ([Microsoft.Win32.RegistryKey]) which will allow me to perform a remote query of the registry.

                Try {
                    Write-Verbose ("{0}: Attempting remote registry access" -f $Computer)
                    $remoteReg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine,$Computer)
                    If ($OS.OSArchitecture -eq '64-bit') {
                        $value = $remoteReg.OpenSubKey("SOFTWARE\Microsoft\Windows NT\CurrentVersion").GetValue('DigitalProductId4')[0x34..0x42]
                    } Else {                        
                        $value = $remoteReg.OpenSubKey("SOFTWARE\Microsoft\Windows NT\CurrentVersion").GetValue('DigitalProductId')[0x34..0x42]
                    }
                    $ProductKey = ""  
                    Write-Verbose ("{0}: Translating data into product key" -f $Computer)
                    for ($i = 24; $i -ge 0; $i--) { 
                      $r = 0 
                      for ($j = 14; $j -ge 0; $j--) { 
                        $r = ($r * 256) -bxor $value[$j] 
                        $value[$j] = [math]::Floor([double]($r/24)) 
                        $r = $r % 24 
                      } 
                      $ProductKey = $map[$r] + $ProductKey 
                      if (($i % 5) -eq 0 -and $i -ne 0) { 
                        $ProductKey = "-" + $ProductKey 
                      } 
                    }
                } Catch {
                    $ProductKey = $_.Exception.Message
                } 

 

Lastly we need to make this into an object because this is PowerShell, after all. I use the New-Object cmdlet and supply a hash table of data that is saved to a variable. After the object is created, I opt to create a custom type name for this object to make it more unique which is then displayed to the user running the code.

                $object = New-Object PSObject -Property @{
                    Computername = $Computer
                    ProductKey = $ProductKey
                    OSDescription = $os.Caption
                    OSVersion = $os.Version
                } 
                $object.pstypenames.insert(0,'ProductKey.Info')
                $object
            } Else {
                $object = New-Object PSObject -Property @{
                    Computername = $Computer
                    ProductKey = 'Unreachable'
                    OSDescription = 'Unreachable'
                    OSVersion = 'Unreachable'
                }  
                $object.pstypenames.insert(0,'ProductKey.Info')
                $object                           
            }
        }
    }
}

Code in Action

Ok, lets run this against a local and remote system and see it in action.

Get-ProductKey -Computername Boe-PC,DC1

image

Whoops! Forgot to power on the server… Lets try this one more time!

image

Ok, that looks better. As you can see, not only is the product key displayed, but the computername as well as the Operating System and version are also displayed as well. This allows for better use of the code to see not only what the product key is, but what it also applies to as well.

Download This Function

Script Repository

Hope you enjoy my updated take on this already great function! This also shows how you can take some existing code and modify it to your own requirements to accomplish a goal.

This entry was posted in powershell, scripts and tagged , , . Bookmark the permalink.

57 Responses to Updating An Existing Get-ProductKey Function

  1. Hi, Boe!
    Great script, thnx, but we have some issues with it

    30-40 2012R2 servers
    You script gives us exact one ProductKey for all servers.
    But! If we run slmgr /dlv on servers – this command gives us very different Partial Product Keys
    And if we run some 3rd-party software to get these full keys – they are different than in step 2
    How can it be?? Two different objects called “Product Key” ??? How we can resolve this issue??

  2. Yos says:

    This script doesn’t work

    • Yos says:

      edwinjor is the man. Had to Google Translate his message though 🙂

      Open Windows PowerShell as Administrator.
      Copy and paste the Script into the console.
      Then press the enter key repeatedly until PS C: …
      Now type Get-ProductKey and press enter.

      Note:

      Otherwise the script executes: Set-ExecutionPolicy Unrestricted
      To export the result use: Get-ProductKey | Out-File c: \ productkey.txt

  3. edwinjor says:

    Otra manera de ejecutarlo es:

    Abre Windows PowerShell como Administrador.
    Copia y pega el Script en la consola.
    Luego presiona varias veces la tecla enter hasta que muestre PS C:...
    Ahora escribe Get-ProductKey y presiona enter.

    Nota:

    Sino ejecuta el script utiliza: Set-ExecutionPolicy Unrestricted
    Para exportar el resultado utiliza: Get-ProductKey | Out-File c:\productkey.txt

  4. TJ says:

    The way this script is currently written it will not work on anything newer than Windows 7 and or 64bit. You can correct the script by replacing the try catch block that is actually doing the decoding with the following:

    Try {
                Write-Verbose ("{0}: Attempting remote registry access" -f $Computer)
                $remoteReg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine,$Computer)
                $value = $remoteReg.OpenSubKey("SOFTWARE\Microsoft\Windows NT\CurrentVersion").GetValue('DigitalProductId')[0x34..0x42]
                $isWin8OrNewer = [math]::Floor(($value[14] / 6)) -band 1
                $value[14] = ($value[14] -band 0xF7) -bor (($isWin8OrNewer -band 2) * 4)
                $ProductKey = ""  
                Write-Verbose ("{0}: Translating data into product key" -f $Computer)
                for ($i = 24; $i -ge 0; $i--) { 
                  $r = 0 
                  for ($j = 14; $j -ge 0; $j--) { 
                    $r = ($r * 256) -bxor $value[$j] 
                    $value[$j] = [math]::Floor([double]($r/24)) 
                    $r = $r % 24 
                  }
                  $ProductKey = $map[$r] + $ProductKey 
                }
            } Catch {
                $ProductKey = $_.Exception.Message
            }
    
            if ($isWin8OrNewer){
                $ProductKey = $ProductKey.Remove(0, 1)
                $ProductKey = $ProductKey.Insert($r, 'N')
            }
    
            #insert dashes to make key more readable
            for($i = 5; $i -lt 29; $i = $i + 6){
                $ProductKey = $ProductKey.Insert($i, '-')
            }
    

    I have tested this on Windows Server 2012 R2 (and was actually able to install Windows Server 2012 R2 as a VM with the key that was returned), and I have tested this on Windows 10 which will return VK7JG-NPHTM-C97JM-9MPGT-3V66T which is a generic key that anybody who has Windows 10 will get when running a key finder on their machine, see http://www.maximumpc.com/microsoft-changes-rules-for-windows-10-activation-for-the-better/ for more information on that.

    • D says:

      Thank you for providing the working try catch block. I attempted several of the other scripts I could find and I ended up with as many different and incorrect product keys. I was seriously considering a product key extractor third party tool until I modified the script with your try catch block. I have successfully tested the new script on two different systems Windows 8.1 Pro 64-bit and Windows 8.1 32-bit. Inside a VM I was able validate the modified script is getting the same results as iSunshare Product Key Finder.

  5. techsavvy88 says:

    What’s going on here again?
    Write-Verbose (“{0}: Translating data into product key” -f $Computer)
    for ($i = 24; $i -ge 0; $i–) {
    $r = 0
    for ($j = 14; $j -ge 0; $j–) {
    $r = ($r * 256) -bxor $value[$j]
    $value[$j] = [math]::Floor(double)
    $r = $r % 24
    }
    $ProductKey = $map[$r] + $ProductKey
    if (($i % 5) -eq 0 -and $i -ne 0) {
    $ProductKey = “-” + $ProductKey
    }
    }

  6. Mike T says:

    Hi,

    We have a server in the cloud that is giving us a licensing error. When I run your script it returns the following. Does this mean I do not have a Productkey installed?

    PS C:> Get-ProductKey -Computername Boe-PC,DC1

    OSDescription Computername OSVersion ProductKey
    ————- ———— ——— ———-
    Unreachable Boe-PC Unreachable Unreachable
    Unreachable DC1 Unreachable Unreachable

    I got the script to run by using the following in Powershell.

    Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass

  7. Renato Souza says:

    I can not run, error occurs:CategoryInfo : SecurityError: (:) [], ParentContainsErrorRecordException

  8. Rich Lampe says:

    Hi, my problem is I don’t get the full product key. I get X-Y-Z… where X, Y and Z are a 5 digit set of alphanumerics

  9. Pingback: How to find the Product Key on any version of Windows - fixedByVonnie

  10. Dominik says:

    Bei mir wurde ebenfalls immer der gleiche Code angeziegt, dank deiner Arbeit habe musste ich aber nicht viel Zeit investieren, Danke!
    CODE:
    function Get-Productkey ($ComputerName) {
    invoke-command -computername $ComputerName -scriptblock {
    $map=”BCDFGHJKMPQRTVWXY2346789″
    $value = (get-itemproperty “HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion”).digitalproductid[0x34..0x42]
    $ProductKey = “”
    for ($i = 24; $i -ge 0; $i–) {
    $r = 0
    for ($j = 14; $j -ge 0; $j–) {
    $r = ($r * 256) -bxor $value[$j]
    $value[$j] = [math]::Floor(double)
    $r = $r % 24
    }
    $ProductKey = $map[$r] + $ProductKey
    if (($i % 5) -eq 0 -and $i -ne 0) {
    $ProductKey = “-” + $ProductKey
    }
    }
    $ProductKey

    }
    }

  11. Pingback: PowerShell获取Windows系统序列号 - PowerShell 中文博客

  12. Erdemturk says:

    first script gives me different key
    your script gives me something totally different.

    I run ‘produkey’ software and it’s gives me something else.

    I’m totally confuse. 🙂

  13. e arch says:

    Your script turns up the SAME product key for multiple machines that I run it against. What is up with that brother!

  14. Robert says:

    I don’t understand what you mean by “Dot Sourcing” normally with these scripts I download them run them and then get my report. But with the product key script it just doesn’t return any data or reports when I run it.

  15. Where did you get this?:
    ” This is important because if the system is 64bit, the DigitalProductId4 key value must be used instead of DigitalProductId, otherwise you will not receive the accurate data for the product key.”
    Based on my tests this is not correct.
    Using DigitalProductId4 always derive an icorrect Product Key

  16. WhoKnows says:

    Go to PowerShell, checkyour ExecPolicy setting with: #Get-ExecutionPolicy
    If result is Restricted set to RemoteSigned with: #Set-ExecutionPolicy RemoteSigned
    re-check your Policy …

    then Load PS-Script with: #. .\Get-ProductKey.ps1
    there is a [:space:] between the dots ._.\Get-ProductKey.ps1
    i think that space is all poeples problem who are new to PS

    then run function of that script with: Get-ProductKey

  17. denvergoals says:

    I know this is a pretty old post, but I’m confused. I enter a product key to activate windows, and it works. Then I run this script, and it returns a different value for the product key. What’s going on here?

  18. Dave says:

    Hi, i tryed your code and it worked great. I am kind of new to Powershell and I have a question? How can i get this script to check if the productkey is active and return a TRUE/FALSE response?

    Thanks.

  19. Jim Thomas says:

    When i run your script, the first one, i get no output. I am only trying to retrieve info off the local computer. I have tried . .\get-productkey, . .\get-productkey mycomputername, . .\get-productkey mycomputername, . .\get-productkey -computername mycomputername. I have also tried it without the dot sourcing. Please Advise.

    • Boe Prox says:

      You’re close. First dot source the script and then run the function.
      . ./Get-ProductKey.ps1
      Get-ProductKey

      • tonny says:

        Boe Prox, I’ve already come to see the Client UI but after I put in username and serve IP It can’t connect to the server, then the server-side PowerShell had the remind tips said BOB has disconnected from the server

  20. Ennis Sarac says:

    Same here, when I run the script, I get no output at all (no errors either).

    • Ennis Sarac says:

      OK, dot sourcing the script resolved this problem. You basically have to put a dot and then a space before the name of the script before you can run it successfully (. Get-ProductKey.ps1 and then run Get-ProductKey)

      Now I am getting the wrong key though. I queried 3 different systems and they all came up with the same product key. Two of the systems were Windows 2008 R2 servers and one is a Windows 7 box.

  21. crimiusxiii says:

    I’m having some issues getting results from this. I have it running seemingly fine, and am passing it a list of computers from a file. It gets parsed ok, and since some of the names aren’t on the network currently, they come up ‘unreachable’ , which is to be expected. However, none of the other names that should be returning results actually are returning a key.

    I get “Exception Callin…” in the Product Key column, and that’s it. Seems like it’s catching and truncating the exception to fit in my PS window, even when I output to a file via “| Out-File keys.txt”.

    This is on remote machines. If I run it with just the local name it seems to get a key, however that is truncated as well.

    Being rather new to the PS world, I’m not really sure how to prevent such truncating, nor what the exception might be likely to say. Any advice on this, or the Exception Calling problem?

    • crimiusxiii says:

      Executing it bit by bit on a few different names yields the ‘Exception Callin..’ is likely ‘Exception calling “OpenRemoteBaseKey” with “2” argument(s): “The network path was not found”, which is a bit more descriptive of an error.

  22. crimiusxiii says:

    Having some issues running this. I have it running just fine, and am passing it a list of computers from a file. It gets parsed ok, and since some of the names aren’t on the network currently, they come up ‘unreachable’ , which is to be expected. However, none of the other names that should be returning results actually are returning a key.

    I get “Exception Callin…” in the Product Key column, and that’s it. Seems like it’s catching and truncating the exception to fit in my PS window, which even when I output to a file via “| Out-File keys.txt”.

    This is on remote machines. If I run it with just the local name it seems to get a key, however that is truncated as well.

    Being rather new to the PS world, I’m not really sure how to prevent such truncating. Any advice on this, or the Exception Calling problem?

  23. Matt Warner says:

    Hi Boe, thank you for all your hard work. I got this to work today in a domain environment but I am not sure why the Product Key is actually different than the license key on the box. Now, a product key finder will return the key on the box versus the Product Key that Get-ProductKey finds. Maybe I am missing something simple here, but are they supposed to be different?

  24. MarcelS says:

    Works fine:

    function Get-ProductKey {
    script code
    }
    Get-ProductKey
    or
    Get-ProductKey -Computer

  25. msuk says:

    how can i provide a list of computers from a “computers.txt: and export the result to “result.txt”

  26. Justin says:

    I’m not sure where I’m going wrong. I run:

    .\get-productkey
    get-productkey -computername (servername)

    Result:

    + FullyQualifiedErrorId : CommandNotFoundException

    I run:
    .\Get-ProductKeydownloaded.ps1 Get-productkey -computername (servername)
    Result: Nothing

    Even if I output it to a txt file:
    .\Get-ProductKeydownloaded.ps1 Get-productkey -computername (servername) >> C:\Tmp\test.txt

    The result is an empty txt file.

    • Boe Prox says:

      Running this as a script will not work. You have to dot source the function into the session.
      . .\Get-ProductKey.ps1

      Then run the function to get it to work.

      • Fabrício Paim says:

        Boa Noite!
        Executei o script de forma correta, o resultado não está correto.
        A Chave Serial que retorna não é a mesma utilizada para instalar o windows, tentei em diversas plataformas. Foi realizada alguma correção? Grato, Fabrício Paim

  27. Raghav Singh says:

    When I am executing i am getting below error
    File E:\Get-ProductKey.ps1 cannot be loaded. The file E:\Get-ProductKey.ps1 is not digitally signed. The script will no
    t execute on the system. Please see “get-help about_signing” for more details..
    At line:1 char:22
    + ..\Get-ProductKey.ps1 <<<<
    + CategoryInfo : NotSpecified: (:) [], PSSecurityException
    + FullyQualifiedErrorId : RuntimeException

    • Boe Prox says:

      You will need to unblock the file,either via the properties of the script or using Unblock-File in PowerShell V3.

      • Fabrício Paim says:

        Good night!
        I ran the script correctly, the result is not correct.
        Serial Key that returns is not the same one used to install the windows, tried on several platforms. A correction was made? Grateful, Fabricio Paim

  28. chungboon says:

    I run the script on window 7 but no result after running the script ( no error as well)

  29. Stephen says:

    Why doesnt the script work for me? I set set-exec pol unrestricted run the ps1 file and get the warning to allow Run once and returns nothing…..

  30. John says:

    When I run this on some PC’s I get this “Exception calling “OpenRemoteBaseKey” with “2” argument(s): “The network path was not found….” where the product key is supposed to be. Any Ideas?

  31. John says:

    When I run the script it asks me if I want to run. Then I run once and it doesn’t output anything. Just puts me right back in the directory I started.

  32. Aram says:

    Hi, I have run your script on windows 7 64bit as “PS C:\Scripting> .\Get-ProductKey.ps1” and I get no report back. Tried it with -Computername… and still got no results. I have also tried it on other windows 7 32bit comps and did not receive any results.

    • Boe Prox says:

      This is actually a function that resides in the script, so you need to dot-source the script before using the function.

      . .\Get-ProductKeys.ps1
      Get-ProductKeys

  33. Zul says:

    Hi, I tried executing this on several systems which consists of combinations of 32 & 64bits systems. On the output, on the license key column show “Cannot index into a null array.”

    • Boe Prox says:

      Hmmm… When running against 64 bit systems, you need to make sure that you are running it from a 64 bit system. Also, while I don’t think it should matter, are you also running it from an Administrator prompt (run as administrator)?

Leave a reply to tonny Cancel reply