Public/System/Get-WindowsKey.ps1

function Get-WindowsKey {
    <#
    .SYNOPSIS
        Retrieves the Windows product key and OS information from local or remote computers.
 
    .DESCRIPTION
        The Get-WindowsKey function retrieves the Windows product key by decoding the DigitalProductId
        from the registry. It also collects additional operating system information such as OS version,
        build number, architecture, and registration details.
         
        This function can target local or remote computers and returns comprehensive information
        about each system's Windows installation.
 
    .PARAMETER ComputerName
        Specifies the target computers to query. Default is the local computer.
        Can accept multiple computer names or IP addresses.
 
    .PARAMETER Credential
        Specifies the credential object to use for authentication when connecting to remote computers.
        Not required for local computer access or when using current credentials.
 
    .EXAMPLE
        Get-WindowsKey
         
        Retrieves the Windows product key and OS information from the local computer.
 
    .EXAMPLE
        Get-WindowsKey -ComputerName "Server01", "Server02"
         
        Retrieves the Windows product key and OS information from two remote servers.
 
    .EXAMPLE
        Get-WindowsKey -ComputerName "Server01" -Credential (Get-Credential)
         
        Prompts for credentials and uses them to retrieve the Windows product key from a remote server.
 
    .EXAMPLE
        "Server01", "Server02" | Get-WindowsKey | Export-Csv -Path "C:\WindowsKeys.csv" -NoTypeInformation
         
        Retrieves Windows product keys from multiple servers and exports the results to a CSV file.
 
    .NOTES
        Original concept by Jakob Bindslet (jakob@bindslet.dk)
        Enhanced with modern PowerShell techniques and improved error handling
        this makes the function faster and more robust.
         
        The digital product key decoding algorithm works on Windows XP through Windows 11.
        Remote access requires appropriate permissions and network connectivity.
    #>

    
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)]
        [Alias("Computer", "ComputerName", "HostName")]
        [string[]]$Targets = ".",
        
        [Parameter()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential
    )

    Begin {
        Write-Verbose "Starting Get-WindowsKey function"
        $hklm = 2147483650  # HKEY_LOCAL_MACHINE
        $regPath = "Software\Microsoft\Windows NT\CurrentVersion"
        $regValue = "DigitalProductId"
        
        # Character set for the Windows product key
        $charsArray = "B","C","D","F","G","H","J","K","M","P","Q","R","T","V","W","X","Y","2","3","4","6","7","8","9"
    }
    
    Process {
        Foreach ($target in $Targets) {
            Write-Verbose "Processing computer: $target"
            try {
                # Create hashtable for WMI parameters
                $wmiParams = @{
                    ComputerName = $target
                    ErrorAction = 'Stop'
                }
                
                # Add credentials if specified
                if ($Credential) {
                    $wmiParams.Add('Credential', $Credential)
                }
                
                # Check if target is local machine or remote
                if ($target -eq "." -or $target -eq "localhost" -or $target -eq $env:COMPUTERNAME) {
                    # Access registry directly on local machine for better performance
                    Write-Verbose "Accessing local registry"
                    try {
                        $regKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryView]::Default)
                        $regSubKey = $regKey.OpenSubKey($regPath)
                        if ($regSubKey) {
                            $digitalProductId = $regSubKey.GetValue($regValue)
                            if ($digitalProductId) {
                                $binArray = $digitalProductId[52..66]
                            } else {
                                Write-Warning "DigitalProductId value not found in registry on $target"
                                continue
                            }
                        } else {
                            Write-Warning "Windows NT registry key not found on $target"
                            continue
                        }
                    } catch {
                        Write-Warning "Error accessing local registry on $target`: $_"
                        continue
                    }
                } else {
                    # Use WMI for remote registry access
                    Write-Verbose "Accessing remote registry via WMI"
                    try {
                        $wmi = Get-WmiObject -Class "StdRegProv" -Namespace "root\default" @wmiParams
                        $data = $wmi.GetBinaryValue($hklm, $regPath, $regValue)
                        
                        if (-not $data -or -not $data.uValue) {
                            Write-Warning "Failed to retrieve product key data from $target"
                            continue
                        }
                        
                        $binArray = ($data.uValue)[52..66]
                    } catch {
                        Write-Warning "Error connecting to $target`: $_"
                        continue
                    }
                }
                
                # Decode the product key
                $productKey = $null
                
                try {
                    # Decrypt base24 encoded binary data
                    For ($i = 24; $i -ge 0; $i--) {
                        $k = 0
                        For ($j = 14; $j -ge 0; $j--) {
                            $k = $k * 256 -bxor $binArray[$j]
                            $binArray[$j] = [math]::Truncate($k / 24)
                            $k = $k % 24
                        }
                        $productKey = $charsArray[$k] + $productKey
                        If (($i % 5 -eq 0) -and ($i -ne 0)) {
                            $productKey = "-" + $productKey
                        }
                    }
                } catch {
                    Write-Warning "Error decoding product key on $target`: $_"
                    $productKey = "DECODE-ERROR"
                }
                
                # Get OS Details
                try {
                    # Get OS info using CIM on newer systems or WMI as fallback
                    $osInfoParams = $wmiParams.Clone()
                    $win32os = Get-CimInstance -ClassName Win32_OperatingSystem @osInfoParams -ErrorAction Stop
                } catch {
                    try {
                        $win32os = Get-WmiObject -Class Win32_OperatingSystem @wmiParams
                    } catch {
                        Write-Warning "Error retrieving OS information from $target`: $_"
                        # Create minimal object if OS info collection fails
                        $win32os = [PSCustomObject]@{
                            Caption = "Unknown"
                            CSDVersion = "Unknown"
                            OSArchitecture = "Unknown"
                            BuildNumber = "Unknown"
                            RegisteredUser = "Unknown"
                            SerialNumber = "Unknown"
                        }
                    }
                }
                
                # Create and return the output object with OS and product key information
                [PSCustomObject]@{
                    Computer = $target
                    Caption = $win32os.Caption
                    Version = $win32os.Version
                    CSDVersion = $win32os.CSDVersion
                    OSArchitecture = $win32os.OSArchitecture
                    BuildNumber = $win32os.BuildNumber
                    RegisteredTo = $win32os.RegisteredUser
                    ProductID = $win32os.SerialNumber
                    ProductKey = $productKey
                    InstallDate = if ($win32os.InstallDate) { 
                        [System.Management.ManagementDateTimeConverter]::ToDateTime($win32os.InstallDate)
                    } else { $null }
                }
            } catch {
                Write-Error "An unexpected error occurred while processing $target`: $_"
            }
        }
    }
    
    End {
        Write-Verbose "Get-WindowsKey function completed"
    }
}