diff --git a/View-WindowsIdentifiers.ps1 b/View-WindowsIdentifiers.ps1 new file mode 100644 index 0000000..c8017b9 --- /dev/null +++ b/View-WindowsIdentifiers.ps1 @@ -0,0 +1,346 @@ +#Requires -Version 5.1 +<# + View-WindowsIdentifiers.ps1 + + Purpose : READ-ONLY privacy audit. Enumerates the unique identifiers that + Windows and the hardware expose about this machine and user - + the values that can be used to fingerprint or correlate the + device. Nothing is changed; this only reports. + + Shows : Machine identifiers (MachineGuid, Product ID / "PUID", + install ID, SQM/telemetry client IDs), Windows activation / + licensing (edition, status, partial + full product key, + OEM firmware key), advertising / user identifiers (Advertising + ID, user SID, MSA), and hardware identifiers (SMBIOS UUID, BIOS + / baseboard / disk serials, MAC addresses, TPM, CPU). + + Privacy : Output contains SENSITIVE data. By default, sensitive values are + MASKED so the report is safe to share/screenshot. Use -Reveal to + print full values (e.g. to back up your own product key). + + Notes : - Windows PowerShell 5.1 compatible. ASCII-only source. + - Read-only: makes NO changes to the system. + - Some values (OEM product key, certain WMI/TPM data) require + Administrator; without it they show "(needs admin)". + + Usage : + .\View-WindowsIdentifiers.ps1 # masked report + .\View-WindowsIdentifiers.ps1 -Reveal # full values (sensitive!) + .\View-WindowsIdentifiers.ps1 -Csv .\ids.csv # export (respects masking) + .\View-WindowsIdentifiers.ps1 -Reveal -Csv .\ids.csv + .\View-WindowsIdentifiers.ps1 -Json .\ids.json + + DISCLAIMER: USE AT YOUR OWN RISK. Provided as-is, no warranty. This tool + only reports identifiers already present on YOUR machine. Handle the + output (especially with -Reveal) as confidential. +#> + +[CmdletBinding()] +param( + [switch]$Reveal, + [string]$Csv, + [string]$Json +) + +Set-StrictMode -Version 2.0 +$ErrorActionPreference = 'Stop' + +# --------------------------------------------------------------------------- +# Elevation +# --------------------------------------------------------------------------- +function Test-IsAdmin { + $id = [Security.Principal.WindowsIdentity]::GetCurrent() + $pr = New-Object Security.Principal.WindowsPrincipal($id) + return $pr.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +} +$Script:IsAdmin = Test-IsAdmin + +# --------------------------------------------------------------------------- +# Safe getters +# --------------------------------------------------------------------------- +function Get-RegVal { + param([string]$Path, [string]$Name) + try { + if (-not (Test-Path -LiteralPath $Path)) { return $null } + $p = Get-ItemProperty -LiteralPath $Path -Name $Name -ErrorAction Stop + return $p.$Name + } catch { return $null } +} + +function Get-Cim { + param([string]$Class, [string]$Namespace = 'root\cimv2', [string]$Filter = $null) + try { + if ($Filter) { return Get-CimInstance -ClassName $Class -Namespace $Namespace -Filter $Filter -ErrorAction Stop } + return Get-CimInstance -ClassName $Class -Namespace $Namespace -ErrorAction Stop + } catch { return $null } +} + +# --------------------------------------------------------------------------- +# Product key decoder (classic DigitalProductId base-24 decode) +# Decodes the product key stored in the registry. Legitimate: reads YOUR +# machine's own installed key so you can record it before a reinstall. +# --------------------------------------------------------------------------- +function ConvertFrom-DigitalProductId { + param([byte[]]$Id) + if ($null -eq $Id -or $Id.Count -lt 67) { return $null } + try { + $chars = 'BCDFGHJKMPQRTVWXY2346789' + $offset = 52 + $isWin8 = [int](([math]::Floor($Id[66] / 6)) -band 1) + $Id[66] = [byte](($Id[66] -band 0xF7) -bor (($isWin8 -band 2) * 4)) + $key = '' + for ($i = 24; $i -ge 0; $i--) { + $cur = 0 + for ($j = 14; $j -ge 0; $j--) { + $cur = ($cur * 256) + [int]$Id[$offset + $j] + $Id[$offset + $j] = [byte][math]::Floor($cur / 24) + $cur = $cur % 24 + } + $key = $chars[$cur] + $key + } + if ($isWin8 -eq 1) { + $last = $key.Substring(1, 1) + $key = $key.Remove(1, 1) + $insAt = [int]$last + $key = $key.Insert($insAt, 'N') + } + # group into 5x5 + $groups = @() + for ($g = 0; $g -lt 25; $g += 5) { $groups += $key.Substring($g, 5) } + return ($groups -join '-') + } catch { return $null } +} + +# --------------------------------------------------------------------------- +# Masking +# --------------------------------------------------------------------------- +function Format-Masked { + param($Value, [switch]$Sensitive) + if ($null -eq $Value -or "$Value" -eq '') { return '(not set)' } + $s = "$Value" + # Never mask status placeholders like (needs admin) / (not set) / (unavailable) + if ($s -match '^\(.*\)$') { return $s } + if (-not $Sensitive -or $Reveal) { return $s } + + # Product-key style AAAAA-BBBBB-... : keep first and last group + if ($s -match '^[A-Z0-9]{5}(-[A-Z0-9]{5}){4}$') { + $parts = $s.Split('-') + return ('{0}-XXXXX-XXXXX-XXXXX-{1}' -f $parts[0], $parts[4]) + } + # GUID-ish / long strings: show first 6 chars + $keep = [math]::Min(6, $s.Length) + return ($s.Substring(0, $keep) + ('*' * [math]::Max(0, ($s.Length - $keep)))) +} + +# --------------------------------------------------------------------------- +# Identifier collection +# --------------------------------------------------------------------------- +function New-Id { + param([string]$Category, [string]$Name, $Value, [switch]$Sensitive, [string]$Source = '') + [pscustomobject]@{ + Category = $Category + Name = $Name + Raw = $Value + Sensitive = [bool]$Sensitive + Source = $Source + } +} + +function Get-Identifiers { + $ids = New-Object System.Collections.ArrayList + $cvNt = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' + $crypto = 'HKLM:\SOFTWARE\Microsoft\Cryptography' + $sqm = 'HKLM:\SOFTWARE\Microsoft\SQMClient' + $adv = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\AdvertisingInfo' + + $os = Get-Cim 'Win32_OperatingSystem' + $cs = Get-Cim 'Win32_ComputerSystem' + $csp = Get-Cim 'Win32_ComputerSystemProduct' + $bios = Get-Cim 'Win32_BIOS' + $base = Get-Cim 'Win32_BaseBoard' + $cpu = Get-Cim 'Win32_Processor' + + # ---- Machine ---- + [void]$ids.Add( (New-Id 'Machine' 'Computer name' $env:COMPUTERNAME -Source 'env') ) + [void]$ids.Add( (New-Id 'Machine' 'MachineGuid' (Get-RegVal $crypto 'MachineGuid') -Sensitive -Source 'Cryptography\MachineGuid') ) + [void]$ids.Add( (New-Id 'Machine' 'Product ID (PUID)' (Get-RegVal $cvNt 'ProductId') -Sensitive -Source 'CurrentVersion\ProductId') ) + [void]$ids.Add( (New-Id 'Machine' 'Build GUID' (Get-RegVal $cvNt 'BuildGUID') -Source 'CurrentVersion\BuildGUID') ) + [void]$ids.Add( (New-Id 'Machine' 'Install date' $(if ($os) { $os.InstallDate } else { $null }) -Source 'Win32_OperatingSystem') ) + if ($csp) { + [void]$ids.Add( (New-Id 'Machine' 'SMBIOS UUID' $csp.UUID -Sensitive -Source 'Win32_ComputerSystemProduct') ) + [void]$ids.Add( (New-Id 'Machine' 'System SKU/IdentifyingNumber' $csp.IdentifyingNumber -Sensitive -Source 'Win32_ComputerSystemProduct') ) + } + + # ---- Telemetry client IDs ---- + [void]$ids.Add( (New-Id 'Telemetry' 'SQM Machine ID' (Get-RegVal $sqm 'MachineId') -Sensitive -Source 'SQMClient\MachineId') ) + [void]$ids.Add( (New-Id 'Telemetry' 'SQM User ID' (Get-RegVal "$sqm\Windows" 'UserId') -Sensitive -Source 'SQMClient\Windows\UserId') ) + # Diagnostics / Universal Telemetry Client ID (if present) + $utcClientId = Get-RegVal 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Diagnostics\DiagTrack\SettingsRequests' 'ClientId' + [void]$ids.Add( (New-Id 'Telemetry' 'DiagTrack Client ID' $utcClientId -Sensitive -Source 'DiagTrack\SettingsRequests\ClientId') ) + + # ---- Activation / licensing ---- + if ($os) { + [void]$ids.Add( (New-Id 'Activation' 'Windows edition' $os.Caption -Source 'Win32_OperatingSystem') ) + } + $partial = $null; $licStatus = $null; $licDesc = $null; $chan = $null + $lic = Get-Cim 'SoftwareLicensingProduct' -Filter "ApplicationID='55c92734-d682-4d71-983e-d6ec3f16059f' AND PartialProductKey IS NOT NULL" + if ($lic) { + $lic = @($lic)[0] + $partial = $lic.PartialProductKey + $chan = $lic.ProductKeyChannel + $map = @{ 0='Unlicensed'; 1='Licensed'; 2='OOB Grace'; 3='OOT Grace'; 4='Non-Genuine Grace'; 5='Notification'; 6='Extended Grace' } + $licStatus = if ($map.ContainsKey([int]$lic.LicenseStatus)) { $map[[int]$lic.LicenseStatus] } else { $lic.LicenseStatus } + $licDesc = $lic.Description + } + [void]$ids.Add( (New-Id 'Activation' 'License status' $licStatus -Source 'SoftwareLicensingProduct') ) + [void]$ids.Add( (New-Id 'Activation' 'License channel' $chan -Source 'SoftwareLicensingProduct') ) + [void]$ids.Add( (New-Id 'Activation' 'License description' $licDesc -Source 'SoftwareLicensingProduct') ) + [void]$ids.Add( (New-Id 'Activation' 'Partial product key' $partial -Sensitive -Source 'SoftwareLicensingProduct') ) + + # OEM firmware key (needs admin) + $oemKey = $null + if ($Script:IsAdmin) { + $sls = Get-Cim 'SoftwareLicensingService' + if ($sls) { $oemKey = $sls.OA3xOriginalProductKey } + if ([string]::IsNullOrEmpty($oemKey)) { $oemKey = '(none in firmware)' } + } else { + $oemKey = '(needs admin)' + } + [void]$ids.Add( (New-Id 'Activation' 'OEM firmware key (OA3)' $oemKey -Sensitive -Source 'SoftwareLicensingService.OA3xOriginalProductKey') ) + + # Decoded installed product key from DigitalProductId + $decoded = $null + $dpi = Get-RegVal $cvNt 'DigitalProductId' + if ($dpi) { + try { $decoded = ConvertFrom-DigitalProductId -Id ([byte[]]$dpi) } catch { $decoded = $null } + } + if ([string]::IsNullOrEmpty($decoded)) { + $decoded = '(unavailable)' + } else { + # Volume (MAK/KMS) channels do not store a recoverable key: decode is garbage + # (typically a single repeated character). Detect and report honestly. + $clean = $decoded -replace '-', '' + $distinct = @($clean.ToCharArray() | Select-Object -Unique).Count + if ($distinct -le 3) { $decoded = '(unavailable - volume/MAK license)' } + } + [void]$ids.Add( (New-Id 'Activation' 'Installed product key (decoded)' $decoded -Sensitive -Source 'CurrentVersion\DigitalProductId') ) + + # ---- Advertising / user ---- + [void]$ids.Add( (New-Id 'User' 'User name' ('{0}\{1}' -f $env:USERDOMAIN, $env:USERNAME) -Source 'env') ) + try { + $sid = ([Security.Principal.WindowsIdentity]::GetCurrent()).User.Value + [void]$ids.Add( (New-Id 'User' 'User SID' $sid -Sensitive -Source 'WindowsIdentity') ) + } catch { } + $advId = Get-RegVal $adv 'Id' + [void]$ids.Add( (New-Id 'User' 'Advertising ID' $advId -Sensitive -Source 'AdvertisingInfo\Id') ) + $advOn = Get-RegVal $adv 'Enabled' + [void]$ids.Add( (New-Id 'User' 'Advertising ID enabled' $advOn -Source 'AdvertisingInfo\Enabled') ) + + # ---- Hardware ---- + if ($bios) { + [void]$ids.Add( (New-Id 'Hardware' 'BIOS serial number' $bios.SerialNumber -Sensitive -Source 'Win32_BIOS') ) + } + if ($base) { + [void]$ids.Add( (New-Id 'Hardware' 'Baseboard serial' $base.SerialNumber -Sensitive -Source 'Win32_BaseBoard') ) + } + if ($cpu) { + $cpu1 = @($cpu)[0] + [void]$ids.Add( (New-Id 'Hardware' 'CPU ProcessorId' $cpu1.ProcessorId -Sensitive -Source 'Win32_Processor') ) + } + # Disk serials + $disks = Get-Cim 'Win32_DiskDrive' + if ($disks) { + $n = 0 + foreach ($d in @($disks)) { + $n++ + $ser = if ($d.SerialNumber) { ($d.SerialNumber).Trim() } else { '(none)' } + [void]$ids.Add( (New-Id 'Hardware' ("Disk {0} serial" -f $n) $ser -Sensitive -Source 'Win32_DiskDrive') ) + } + } + # MAC addresses (physical adapters with a MAC) + $nics = Get-Cim 'Win32_NetworkAdapter' -Filter 'PhysicalAdapter=TRUE AND MACAddress IS NOT NULL' + if ($nics) { + foreach ($nic in @($nics)) { + [void]$ids.Add( (New-Id 'Network' ("MAC - {0}" -f $nic.NetConnectionID) $nic.MACAddress -Sensitive -Source 'Win32_NetworkAdapter') ) + } + } + # TPM (needs admin; separate namespace) + $tpm = Get-Cim 'Win32_Tpm' -Namespace 'root\cimv2\Security\MicrosoftTpm' + if ($tpm) { + $tpm1 = @($tpm)[0] + [void]$ids.Add( (New-Id 'Hardware' 'TPM present' $tpm1.IsEnabled_InitialValue -Source 'Win32_Tpm') ) + [void]$ids.Add( (New-Id 'Hardware' 'TPM manufacturer ID' $tpm1.ManufacturerId -Source 'Win32_Tpm') ) + } elseif (-not $Script:IsAdmin) { + [void]$ids.Add( (New-Id 'Hardware' 'TPM info' '(needs admin)' -Source 'Win32_Tpm') ) + } + + return $ids +} + +# --------------------------------------------------------------------------- +# Display +# --------------------------------------------------------------------------- +function Show-Report { + param($Ids) + + Write-Host '' + Write-Host '==================================================================' -ForegroundColor Cyan + Write-Host ' Windows Identifier Privacy Audit (READ-ONLY)' -ForegroundColor Cyan + Write-Host (' Host: {0} Admin: {1} {2}' -f $env:COMPUTERNAME, $Script:IsAdmin, (Get-Date)) -ForegroundColor DarkCyan + if ($Reveal) { + Write-Host ' MODE: REVEAL - full sensitive values shown. Handle as confidential.' -ForegroundColor Red + } else { + Write-Host ' MODE: MASKED - sensitive values redacted. Use -Reveal for full values.' -ForegroundColor Magenta + } + Write-Host '==================================================================' -ForegroundColor Cyan + + $categories = @('Machine','Activation','Telemetry','User','Hardware','Network') + foreach ($cat in $categories) { + $group = @($Ids | Where-Object { $_.Category -eq $cat }) + if ($group.Count -eq 0) { continue } + Write-Host '' + Write-Host (" [{0}]" -f $cat) -ForegroundColor White + foreach ($id in $group) { + $disp = Format-Masked -Value $id.Raw -Sensitive:$id.Sensitive + $color = if ($id.Sensitive) { 'Yellow' } else { 'Gray' } + $tag = if ($id.Sensitive) { '!' } else { ' ' } + Write-Host (' {0} {1,-32}' -f $tag, $id.Name) -NoNewline + Write-Host $disp -ForegroundColor $color + } + } + Write-Host '' + Write-Host ' ! = sensitive identifier (masked unless -Reveal).' -ForegroundColor DarkYellow + if (-not $Script:IsAdmin) { + Write-Host ' Some values need Administrator (OEM key, TPM) - shown as (needs admin).' -ForegroundColor DarkYellow + } + Write-Host '' +} + +function Export-Rows { + param($Ids) + foreach ($id in $Ids) { + [pscustomobject]@{ + Category = $id.Category + Name = $id.Name + Value = (Format-Masked -Value $id.Raw -Sensitive:$id.Sensitive) + Sensitive = $id.Sensitive + Source = $id.Source + } + } +} + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- +$ids = Get-Identifiers + +Show-Report -Ids $ids + +if ($Csv) { + Export-Rows -Ids $ids | Export-Csv -Path $Csv -NoTypeInformation -Encoding ASCII + Write-Host ("CSV written to {0}{1}" -f $Csv, $(if (-not $Reveal) { ' (masked)' } else { ' (REVEALED - confidential)' })) -ForegroundColor Green +} +if ($Json) { + Export-Rows -Ids $ids | ConvertTo-Json -Depth 3 | Out-File -FilePath $Json -Encoding ASCII + Write-Host ("JSON written to {0}{1}" -f $Json, $(if (-not $Reveal) { ' (masked)' } else { ' (REVEALED - confidential)' })) -ForegroundColor Green +}