AutopilotUtility.psm1
function ConvertFrom-AutopilotHash { [CmdletBinding()] param( [Parameter(Mandatory=$true, ParameterSetName="file")] [string] $CSVFile, [Parameter(Mandatory=$true, ParameterSetName="hash")] [string] $Hash ) Begin { if ($PSCmdlet.ParameterSetName -eq "file") { $csvContents = Import-Csv -Path $CSVFile $hashes = $csvContents.'Hardware Hash' } else { $hashes = $Hash } } Process { $hashes | % { $binary = [Convert]::FromBase64String($_) # Validate the header if ($binary[0] -ne 79 -or $binary[1] -ne 65) { Write-Error "Invalid hash" } # Validate the checksum # TODO # Process the values $totalLength = [System.BitConverter]::ToUInt16($binary, 2) $data = [ordered]@{} $currentOffset = 4 $diskSerialCount = 0 $diskInfoCount = 0 $displayResolutionCount = 0 $gpuCount = 0 $gpuNameCount = 0 $networkCount = 0 while ($currentOffset -lt $totalLength) { $type = $binary[$currentOffset] $length = [System.BitConverter]::ToUInt16($binary, $currentOffset + 2) switch ($type) { 1 { $offset = $currentOffset + 4 # Two version strings, 8 bytes each $versions = @() for ($i = 0; $i -le 1; $i++) { $minor = [System.BitConverter]::ToUInt16($binary, $offset) $major = [System.BitConverter]::ToUInt16($binary, $offset + 2) $rev = [System.BitConverter]::ToUInt16($binary, $offset + 4) $build = [System.BitConverter]::ToUInt16($binary, $offset + 6) $offset += 8 $versions += [Version]::new($major, $minor, $build, $rev) } $data["ToolBuild"] = $versions[0] $data["OSBuild"] = $versions[1] # Need to figure out the next five bytes # Last three bytes switch ($binary[$currentOffset + $length - 3]) { 2 { $data["OSType"] = "FullOS" } default { $data["OSType"] = $binary[$currentOffset + $length - 3] } } switch ($binary[$currentOffset + $length - 2]) { 0 { $data["OSCpuArchitecture"] = "X86" } 9 { $data["OSCpuArchitecture"] = "X64" } 12 { $data["OSCpuArchitecture"] = "ARM64" } default { $data["OSCpuArchitecture"] = $binary[$currentOffset + $length - 2] } } $data["ToolVersion"] = $binary[$currentOffset + $length - 1] } 2 { switch ($binary[$currentOffset + 4]) { 0 { $data["ProcessorArchitecture"] = "X86" } 9 { $data["ProcessorArchitecture"] = "X64" } 12 { $data["ProcessorArchitecture"] = "ARM64" } default { $data["ProcessorArchitecture"] = $binary[$currentOffset + 4] } } $data["ProcessorPackages"] = [System.BitConverter]::ToUInt16($binary, $currentOffset + 6) $data["ProcessorCores"] = [System.BitConverter]::ToUInt16($binary, $currentOffset + 8) $data["ProcessorThreads"] = [System.BitConverter]::ToUInt16($binary, $currentOffset + 10) if ($binary[$currentOffset + 12] -eq 1) { $data["ProcessorHyperthreading"] = $true } else { $data["ProcessorHyperthreading"] = $false } } 3 { $data["ProcessorManufacturer"] = [System.Text.Encoding]::UTF8.GetString($binary, $currentOffset + 4, $length - 4) } 4 { $data["ProcessorModel"] = [System.Text.Encoding]::UTF8.GetString($binary, $currentOffset + 4, $length - 4) } 5 { $data["TotalPhysicalRam"] = [System.BitConverter]::ToUInt16($binary, $currentOffset + 4) $data["SmbiosRamMaximumCapacity"] = [System.BitConverter]::ToUInt16($binary, $currentOffset + 12) $data["SmbiosRamSlots"] = [System.BitConverter]::ToUInt16($binary, $currentOffset + 20) $data["SmbiosRamArrayCount"] = [System.BitConverter]::ToUInt16($binary, $currentOffset + 22) switch ($binary[$currentOffset + 24]) { 3 { $data["SmbiosRamErrorCorrection"] = "None" } 4 { $data["SmbiosRamErrorCorrection"] = "Parity" } 5 { $data["SmbiosRamErrorCorrection"] = "Single-bit ECC" } 6 { $data["SmbiosRamErrorCorrection"] = "Multi-bit ECC" } 7 { $data["SmbiosRamErrorCorrection"] = "CRC" } default { $data["SmbiosRamErrorCorrection"] = $binary[$currentOffset + 24] } } } 6 { # 06 00 10 00 88 00 00 00 00 00 00 00 0A 00 FF 01 = From VM # 06 00 10 00 00 04 00 00 00 00 00 00 0B 00 00 01 = From P620 $diskInfoCount++ $data["Disk$diskInfoCount.DiskCapacity"] = [System.BitConverter]::ToUInt32($binary, $currentOffset + 4) # BusType from https://docs.microsoft.com/en-us/previous-versions/windows/desktop/stormgmt/msft-disk switch ($binary[$currentOffset + 12]) { 1 { $busType = "SCSI" } 2 { $busType = "ATAPI" } 3 { $busType = "ATA" } 8 { $busType = "RAID" } 10 { $busType = "SAS" } 11 { $busType = "SATA" } 17 { $busType = "NVMe" } default { $busType = $binary[$currentOffset + 12]} } $data["Disk$diskInfoCount.StorageBusType"] = $busType # Guessing on this one if ($busType -eq "NVMe") { $data["Disk$diskInfoCount.DiskType"] = "NVMe" } else { switch ($binary[$currentOffset + 14]) { 0 { $data["Disk$diskInfoCount.DiskType"] = "SSD" } 255 { $data["Disk$diskInfoCount.DiskType"] = "HDD" } } } } 7 { $diskSerialCount++ $data["Disk$diskSerialCount.DiskSerial"] = [System.Text.Encoding]::UTF8.GetString($binary, $currentOffset + 4, $length - 4) } 8 { # From VM: # 08 00 1E 00 00 00 00 00 00 15 5D E9 DB 33 0C 00 00 00 56 00 4D 00 42 00 55 00 53 00 00 00 # From P620: # 08 00 1A 00 0E 00 00 00 6C B3 11 40 77 8A 08 00 00 00 50 00 43 00 49 00 00 00 # 08 00 1A 00 0E 00 00 00 E0 4F 43 E6 0F 48 08 00 00 00 50 00 43 00 49 00 00 00 # 08 00 1A 00 0E 00 00 00 6C B3 11 40 77 8B 08 00 00 00 50 00 43 00 49 00 00 00 $networkCount++ switch ($binary[$currentOffset + 4]) { 1 { $medium = "Wireless Lan" } 9 { $medium = "Native 802.11" } 10 { $medium = "Bluetooth" } 14 { $medium = "Ethernet" } default { $medium = $binary[$currentOffset + 4] } } $data["Network$networkCount.PhysicalMedium"] = $medium $data["Network$networkCount.MacAddress"] = [System.BitConverter]::ToString($binary[($currentOffset + 8)..($currentOffset + 13)]) } 9 { $displayResolutionCount++ $data["Display$displayResolutionCount.SizePhysicalH"] = [System.BitConverter]::ToUInt16($binary, $currentOffset + 4) $data["Display$displayResolutionCount.SizePhysicalV"] = [System.BitConverter]::ToUInt16($binary, $currentOffset + 6) $data["Display$displayResolutionCount.ResolutionHorizontal"] = [System.BitConverter]::ToUInt16($binary, $currentOffset + 8) $data["Display$displayResolutionCount.ResolutionVertical"] = [System.BitConverter]::ToUInt16($binary, $currentOffset + 10) } 10 { # 0A 00 09 00 03 01 00 00 00 on VM $data["ChassisType"] = $binary[$currentOffset + 4] $data["PowerPlatformRole"] = [Microsoft.PowerShell.Commands.PowerPlatformRole]$binary[$currentOffset + 5] } 11 { $data["OfflineDeviceId"] = [Convert]::ToBase64String($binary[($currentOffset + 14)..($currentOffset + $length - 4)]) # Doing some guessing here: # 0B 00 2E 00 00 00 00 00 02 01 20 00 00 00 = UEFI_VARIABLE_TPM # 0B 00 2E 00 00 00 00 00 01 00 20 00 00 00 = TPM_EK switch ($binary[$currentOffset + 8]) { 1 { $data["OfficeDeviceIdType"] = "TPM_EK" } 2 { $data["OfficeDeviceIdType"] = "UEFI_VARIABLE_TPM" } default { $data["OfficeDeviceIdType"] = $binary[$currentOffset + 8] } } } 12 { $data["SmbiosUuid"] = [Guid]::new([byte[]]$binary[($currentOffset + 4)..($currentOffset + $length - 1)]) } 13 { $data["TpmVersion"] = [System.Text.Encoding]::UTF8.GetString($binary, $currentOffset + 4, $length - 4) } 14 { $data["SmbiosSerial"] = [System.Text.Encoding]::UTF8.GetString($binary, $currentOffset + 4, $length - 4) } 15 { $data["SmbiosFirmwareVendor"] = [System.Text.Encoding]::UTF8.GetString($binary, $currentOffset + 4, $length - 4) } 16 { $data["SmbiosSystemManufacturer"] = [System.Text.Encoding]::UTF8.GetString($binary, $currentOffset + 4, $length - 4) } 17 { $data["SmbiosProductName"] = [System.Text.Encoding]::UTF8.GetString($binary, $currentOffset + 4, $length - 4) } 18 { $data["SmbiosSKUNumber"] = [System.Text.Encoding]::UTF8.GetString($binary, $currentOffset + 4, $length - 4) } 19 { $data["SmbiosSystemFamily"] = [System.Text.Encoding]::UTF8.GetString($binary, $currentOffset + 4, $length - 4) } 20 { $data["SmbiosFirmwareVendor2"] = [System.Text.Encoding]::UTF8.GetString($binary, $currentOffset + 4, $length - 4) } 21 { $data["SmbiosBoardProduct"] = [System.Text.Encoding]::UTF8.GetString($binary, $currentOffset + 4, $length - 4) } 22 { $data["SmbiosBoardVersion"] = [System.Text.Encoding]::UTF8.GetString($binary, $currentOffset + 4, $length - 4) } 23 { $data["SmbiosSystemVersion"] = [System.Text.Encoding]::UTF8.GetString($binary, $currentOffset + 4, $length - 4) } 24 { $data["ProductKeyID"] = [System.Text.Encoding]::UTF8.GetString($binary, $currentOffset + 4, $length - 4) } 25 { $data["TpmEkPub"] = [Convert]::ToBase64String($binary[($currentOffset + 4)..($currentOffset + $length - 4)]) } 26 { $data["ProductKeyPkPn"] = [System.Text.Encoding]::UTF8.GetString($binary, $currentOffset + 4, $length - 4) } # 27 { # # No idea on this one, doesn't appear to have much value # # 1B 00 08 00 FF FF FF FF on P620 # # 1B 00 08 00 00 00 00 00 on VM # # Write-Host "@ $currentOffset : $type = $length" # } 28 { $data["DiskSSNKernel"] = [System.Text.Encoding]::UTF8.GetString($binary, $currentOffset + 4, $length - 4) } 29 { # 1D 00 16 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 01 on VM # 1D 00 16 00 00 E0 9B 7B 00 00 00 00 00 00 00 00 00 00 00 00 01 01 on P620 $gpuCount++ $data["Gpu$gpuCount.DedicatedVideoMemory"] = [System.BitConverter]::ToUInt32($binary, $currentOffset + 4) $data["Gpu$gpuCount.DedicatedSystemMemory"] = [System.BitConverter]::ToUInt32($binary, $currentOffset + 8) # Not sure on these flags, likely the last two bytes: # <p n="Gpu1.RemovalPolicy" v="NoRemoval" /> # <p n="Gpu1.InLocalMachineContainer" v="true" /> } 30 { $gpuNameCount++ $data["Gpu$gpuNameCount.VideoAdapter"] = [System.Text.Encoding]::UTF8.GetString($binary, $currentOffset + 4, $length - 4) } default { $hex = [System.BitConverter]::ToString($binary[($currentOffset)..($currentOffset + $length - 1)]) Write-Host "@ $currentOffset : $type ( $length ) $hex" } } $currentOffset += $length } [PSCustomObject] $data } } } function add-string($binary, $offset, $type, $value) { $binary[$offset] = $type $lengthBytes = [byte[]] [System.BitConverter]::GetBytes([uint16]$value.Length + 5) $binary[$offset + 2] = $lengthBytes[0] $binary[$offset + 3] = $lengthBytes[1] $stringBytes = [System.Text.Encoding]::UTF8.GetBytes($value) [System.Buffer]::BlockCopy($stringBytes, 0, $binary, $offset + 4, $value.Length) return $offset + $value.Length + 5 } function add-binary($binary, $offset, $type, $value) { $binary[$offset] = $type $lengthBytes = [byte[]] [System.BitConverter]::GetBytes([uint16]$value.Length + 4) $binary[$offset + 2] = $lengthBytes[0] $binary[$offset + 3] = $lengthBytes[1] [System.Buffer]::BlockCopy($value, 0, $binary, $offset + 4, $value.Length) return $offset + $value.Length + 4 } function add-version($binary, $offset) { # Set type 1 and length 28 (fixed) $binary[$offset] = 1 $binary[$offset + 2] = 28 $offset += 4 # Hard-code the version $major = [uint16]10 $minor = [uint16]0 $build = [uint16]22000 $rev = [uint16]800 # Convert it to bytes (minor, major, rev, build to get proper byte order) for ($i = 0; $i -le 1; $i++) { $partBytes = [byte[]] [System.BitConverter]::GetBytes([uint16]$minor) $binary[$offset] = $partBytes[0] $binary[$offset + 1] = $partBytes[1] $partBytes = [byte[]] [System.BitConverter]::GetBytes([uint16]$major) $binary[$offset + 2] = $partBytes[0] $binary[$offset + 3] = $partBytes[1] $partBytes = [byte[]] [System.BitConverter]::GetBytes([uint16]$rev) $binary[$offset + 4] = $partBytes[0] $binary[$offset + 5] = $partBytes[1] $partBytes = [byte[]] [System.BitConverter]::GetBytes([uint16]$build) $binary[$offset + 6] = $partBytes[0] $binary[$offset + 7] = $partBytes[1] $offset += 8 } # Skip unknown five bytes for now $offset += 5 # Add the architecture (hard-coded to x64 at this point) $binary[$offset] = 2 $binary[$offset + 1] = 9 $binary[$offset + 2] = 8 return $offset + 3 } function add-processor($binary, $offset) { $processorArchitecture = 9 $processorPackages = 1 $processorCores = 1 $processorThreads = 2 $processorHyperthreading = 1 # Set type 2 and length 16 (fixed) $binary[$offset] = 2 $binary[$offset + 2] = 16 $offset += 4 # Set the processor info $partBytes = [byte[]] [System.BitConverter]::GetBytes([uint16]$processorArchitecture) $binary[$offset] = $partBytes[0] $binary[$offset + 1] = $partBytes[1] $partBytes = [byte[]] [System.BitConverter]::GetBytes([uint16]$processorPackages) $binary[$offset + 2] = $partBytes[0] $binary[$offset + 3] = $partBytes[1] $partBytes = [byte[]] [System.BitConverter]::GetBytes([uint16]$processorCores) $binary[$offset + 4] = $partBytes[0] $binary[$offset + 5] = $partBytes[1] $partBytes = [byte[]] [System.BitConverter]::GetBytes([uint16]$processorThreads) $binary[$offset + 6] = $partBytes[0] $binary[$offset + 7] = $partBytes[1] $partBytes = [byte[]] [System.BitConverter]::GetBytes([uint16]$processorHyperthreading) $binary[$offset + 8] = $partBytes[0] $binary[$offset + 9] = $partBytes[1] return $offset + 12 } function New-AutopilotHash { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string] $Manufacturer, [Parameter(Mandatory=$true)] [string] $Model, [Parameter(Mandatory=$true)] [string] $Serial, [Parameter(Mandatory=$false)] [string] $UUID = "", [Parameter(Mandatory=$false)] [string] $SKUNumber = "", [Parameter(Mandatory=$false)] [string] $SystemFamily = "", [Parameter(Mandatory=$false)] [string] $TPMEkPub = "", [Parameter(Mandatory=$false)] [string] $TPMVersion = "", [Parameter(Mandatory=$false)] [string[]] $MacAddress = @(), [Parameter(Mandatory=$false)] [UInt32] $PhysicalMedium = 14, [Parameter(Mandatory=$false)] [string] $OutputFile = "" ) Process { # Initialize the byte array (3000 bytes become 4000 when base64-encoded) $binary = New-Object Byte[](3000) $binary[0] = 79 $binary[1] = 65 $currentOffset = 4 # Add the version info (type 1) $currentOffset = add-version -binary $binary -offset $currentOffset # Add the CPU info (type 2) $currentOffset = add-processor -binary $binary -offset $currentOffset # Add the manufacturer $currentOffset = add-string -binary $binary -offset $currentOffset -type 16 -value $Manufacturer # Add the model $currentOffset = add-string -binary $binary -offset $currentOffset -type 17 -value $Model # Add the serial $currentOffset = add-string -binary $binary -offset $currentOffset -type 14 -value $Serial # Add the UUID, if specified if ($UUID -ne "") { $guidBytes = ([System.Guid] $UUID).ToByteArray() $currentOffset = add-binary -binary $binary -offset $currentOffset -type 12 -value $guidBytes } # Add the TPMVersion, if specified if ($TPMVersion -ne "") { $currentOffset = add-string -binary $binary -offset $currentOffset -type 13 -value $TPMVersion } # Add the SKUNumber, if specified if ($SKUNumber -ne "") { $currentOffset = add-string -binary $binary -offset $currentOffset -type 18 -value $SKUNumber } # Add the SystemFamily, if specified if ($SystemFamily -ne "") { $currentOffset = add-string -binary $binary -offset $currentOffset -type 19 -value $SystemFamily } # Add the TPMEkPub, if specified if ($TPMEkPub -ne "") { $currentOffset = add-string -binary $binary -offset $currentOffset -type 25 -value $TPMEkPub } # Add any MAC addresses specified if ($MacAddress.Count -gt 0) { $hexAny = 8 $pcbBinary = 0 $pdwFlags = 0 $MacAddress | % { # Convert it to a byte array $macStripped = $_.Replace(":", "").Replace("-", "").Replace(" ", "") $macBytes = New-Object Byte[](6) [void][AutopilotUtility.Native]::CryptStringToBinary($macStripped, $macStripped.Length, $hexAny, $macBytes, [ref]$pcbBinary, 0, [ref]$pdwFlags) # Add the physical medium to the beginning $fullBytes = New-Object Byte[](10) $fullBytes[0] = $PhysicalMedium [System.Buffer]::BlockCopy($macBytes, 0, $fullBytes, 4, 6) # Add the entry $currentOffset = add-binary -binary $binary -offset $currentOffset -type 8 -value $fullBytes } } # Finalize with the length and checksum $lengthBytes = [byte[]] [System.BitConverter]::GetBytes([uint16]$currentOffset) $binary[2] = $lengthBytes[0] $binary[3] = $lengthBytes[1] $binary[$currentOffset] = 67 $binary[$currentOffset + 1] = 83 $binary[$currentOffset + 2] = 36 $binary[$currentOffset + 3] = 0 $hash = [System.Security.Cryptography.HashAlgorithm]::Create('sha256').ComputeHash($binary, 0, $currentOffset) [System.Buffer]::BlockCopy($hash, 0, $binary, $currentOffset + 4, $hash.Length) # Write out the hex buffer if ($DebugPreference -eq "Continue") { $binary | Format-Hex | Out-Host } # Build the output object $hash = [Convert]::ToBase64String($binary) $c = New-Object psobject -Property @{ "Device Serial Number" = $Serial "Windows Product ID" = "" "Hardware Hash" = $hash "Manufacturer name" = $Manufacturer "Device model" = $Model } # Write the output file if specified. Otherwise, just return the object. if ($OutputFile -ne "") { $c | Select "Device Serial Number", "Windows Product ID", "Hardware Hash" | ConvertTo-CSV -NoTypeInformation | % {$_ -replace '"',''} | Out-File $OutputFile } else { $c } } } Add-Type -MemberDefinition @" [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool CryptStringToBinary( [MarshalAs(UnmanagedType.LPWStr)] string pszString, uint cchString, uint dwFlags, byte[] pbBinary, ref uint pcbBinary, uint pdwSkip, ref uint pdwFlags); "@ -Namespace "AutopilotUtility" -Name "Native" |