Created
May 18, 2026 03:36
-
-
Save jborean93/24fa71278a8c4c365007735fe30605ae to your computer and use it in GitHub Desktop.
Get and parse SMBIOS data using PowerShell
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Copyright: (c) 2026, Jordan Borean (@jborean93) <jborean93@gmail.com> | |
| # MIT License (see LICENSE or https://opensource.org/licenses/MIT) | |
| function Get-RawSmbiosData { | |
| [OutputType([byte[]])] | |
| [CmdletBinding()] | |
| param () | |
| Add-Type -TypeDefinition @' | |
| using System; | |
| using System.ComponentModel; | |
| using System.Runtime.InteropServices; | |
| public static class FirmwareTables | |
| { | |
| [DllImport("kernel32.dll", SetLastError=true)] | |
| private static extern int GetSystemFirmwareTable( | |
| int FirmwareTableProviderSignature, | |
| int FirmwareTableID, | |
| IntPtr pFirmwareTableBuffer, | |
| int BufferSize); | |
| public static byte[] GetRsmbTable() | |
| { | |
| const int RSMB = 0x52534D42; // 'RSMB' in ASCII | |
| int size = GetSystemFirmwareTable(RSMB, 0, IntPtr.Zero, 0); | |
| if (size == 0) | |
| { | |
| throw new Win32Exception(); | |
| } | |
| IntPtr buffer = Marshal.AllocHGlobal(size); | |
| try | |
| { | |
| int result = GetSystemFirmwareTable(RSMB, 0, buffer, size); | |
| if (result == 0) | |
| { | |
| throw new Win32Exception(); | |
| } | |
| byte[] rawData = new byte[size]; | |
| Marshal.Copy(buffer, rawData, 0, size); | |
| return rawData; | |
| } | |
| finally | |
| { | |
| Marshal.FreeHGlobal(buffer); | |
| } | |
| } | |
| } | |
| '@ | |
| try { | |
| $rawData = [FirmwareTables]::GetRsmbTable() | |
| $callingMethod = $rawData[0] | |
| $smbiosMajorVersion = $rawData[1] | |
| $smbiosMinorVersion = $rawData[2] | |
| $dmiRevision = $rawData[3] | |
| $smbiosTableLength = [BitConverter]::ToInt32($rawData, 4) | |
| $data = [byte[]]::new($smbiosTableLength) | |
| if ($rawData.Length -lt (8 + $smbiosTableLength)) { | |
| throw "The raw data length is insufficient for the specified SMBIOS table length." | |
| } | |
| [Array]::Copy($rawData, 8, $data, 0, $smbiosTableLength) | |
| [PSCustomObject]@{ | |
| CallingMethod = $callingMethod | |
| SmbiosVersion = [Version]"$($smbiosMajorVersion).$smbiosMinorVersion" | |
| DmiRevision = $dmiRevision | |
| Length = $smbiosTableLength | |
| Data = $data | |
| } | |
| } | |
| catch { | |
| $PSCmdlet.ThrowTerminatingError($_) | |
| } | |
| } | |
| function Get-SmbiosEntry { | |
| [CmdletBinding()] | |
| param ( | |
| [Parameter(Mandatory, ValueFromPipelineByPropertyName)] | |
| [Alias("Data")] | |
| [byte[]] | |
| $InputObject, | |
| [Parameter(Mandatory, ValueFromPipelineByPropertyName)] | |
| [Alias("SmbiosVersion")] | |
| [Version] | |
| $Version | |
| ) | |
| $readString = { | |
| param ([string[]]$Table, [byte]$Index) | |
| if ($Index -le $Table.Length) { | |
| return $Table[$Index - 1] | |
| } | |
| else { | |
| return $null | |
| } | |
| } | |
| $typeUnpacker = @{ | |
| # BIOS Information (Type 0) | |
| 0 = { | |
| param ([IO.BinaryReader]$Reader, [string[]]$StringTable) | |
| $props = [Ordered]@{ | |
| Vendor = & $readString $StringTable $Reader.ReadByte() | |
| Version = & $readString $StringTable $Reader.ReadByte() | |
| BiosStartingAddressSegment = $Reader.ReadInt16() | |
| ReleaseDate = & $readString $StringTable $Reader.ReadByte() | |
| RomSize = $Reader.ReadByte() | |
| Characteristics = $Reader.ReadInt64() | |
| } | |
| [PSCustomObject]$props | |
| } | |
| # System Information (Type 1) | |
| 1 = { | |
| param ([IO.BinaryReader]$Reader, [string[]]$StringTable) | |
| $props = [Ordered]@{ | |
| Manufacturer = & $readString $StringTable $Reader.ReadByte() | |
| ProductName = & $readString $StringTable $Reader.ReadByte() | |
| Version = & $readString $StringTable $Reader.ReadByte() | |
| SerialNumber = & $readString $StringTable $Reader.ReadByte() | |
| } | |
| if ($Version -ge '2.1') { | |
| $props.Uuid = [Guid]::New($Reader.ReadBytes(16)) | |
| $props.WakeUpType = $Reader.ReadByte() | |
| } | |
| if ($Version -ge '2.4') { | |
| $props.SkuNumber = & $readString $StringTable $Reader.ReadByte() | |
| $props.Family = & $readString $StringTable $Reader.ReadByte() | |
| } | |
| [PSCustomObject]$props | |
| } | |
| # System Enclosure (Type 3) | |
| 3 = { | |
| param ([IO.BinaryReader]$Reader, [string[]]$StringTable) | |
| $props = [Ordered]@{ | |
| Manufacturer = & $readString $StringTable $Reader.ReadByte() | |
| ChassisType = $Reader.ReadByte() | |
| Version = & $readString $StringTable $Reader.ReadByte() | |
| SerialNumber = & $readString $StringTable $Reader.ReadByte() | |
| AssetTagNumber = & $readString $StringTable $Reader.ReadByte() | |
| } | |
| if ($Version -ge '2.1') { | |
| $props.BootupState = $Reader.ReadByte() | |
| $props.PowerSupplyState = $Reader.ReadByte() | |
| $props.ThermalState = $Reader.ReadByte() | |
| $props.SecurityStatus = $Reader.ReadByte() | |
| } | |
| if ($Version -ge '2.3') { | |
| $props.OemDefined = $Reader.ReadInt32() | |
| $props.Height = $Reader.ReadByte() | |
| $props.NumberOfPowerCords = $Reader.ReadByte() | |
| $props.ContainedElementCount = $Reader.ReadByte() | |
| $props.ContainedElementRecordLength = $Reader.ReadByte() | |
| $props.ContainedElements = @( | |
| for ($i = 0; $i -lt $props.ContainedElementCount; $i++) { | |
| [PSCustomObject]@{ | |
| Type = $Reader.ReadByte() | |
| Length = $Reader.ReadByte() | |
| Handle = $Reader.ReadInt16() | |
| } | |
| } | |
| ) | |
| } | |
| if ($Version -ge '2.7') { | |
| $props.SkuNumber = & $readString $StringTable $Reader.ReadByte() | |
| } | |
| [PSCustomObject]$props | |
| } | |
| # Processor Information (Type 4) | |
| 4 = { | |
| param ([IO.BinaryReader]$Reader, [string[]]$StringTable) | |
| $props = [Ordered]@{ | |
| SocketDesignation = & $readString $StringTable $Reader.ReadByte() | |
| ProcessorType = $Reader.ReadByte() | |
| ProcessorFamily = $Reader.ReadByte() | |
| Manufacturer = & $readString $StringTable $Reader.ReadByte() | |
| ProcessorId = $Reader.ReadInt64() | |
| ProcessorVersion = & $readString $StringTable $Reader.ReadByte() | |
| Voltage = $Reader.ReadByte() | |
| ExternalClock = $Reader.ReadInt16() | |
| MaxSpeed = $Reader.ReadInt16() | |
| CurrentSpeed = $Reader.ReadInt16() | |
| Status = $Reader.ReadByte() | |
| ProcessorUpgrade = $Reader.ReadByte() | |
| } | |
| if ($Version -ge '2.1') { | |
| $props.L1CacheHandle = $Reader.ReadInt16() | |
| $props.L2CacheHandle = $Reader.ReadInt16() | |
| $props.L3CacheHandle = $Reader.ReadInt16() | |
| } | |
| if ($Version -ge '2.3') { | |
| $props.SerialNumber = & $readString $StringTable $Reader.ReadByte() | |
| $props.AssetTag = & $readString $StringTable $Reader.ReadByte() | |
| $props.PartNumber = & $readString $StringTable $Reader.ReadByte() | |
| } | |
| if ($Version -ge '2.5') { | |
| $props.CoreCount = $Reader.ReadByte() | |
| $props.CoreEnabled = $Reader.ReadByte() | |
| $props.ThreadCount = $Reader.ReadByte() | |
| $props.ProcessorCharacteristics = $Reader.ReadInt16() | |
| } | |
| if ($Version -ge '2.6') { | |
| $props.ProcessorFamily2 = $Reader.ReadInt16() | |
| } | |
| if ($Version -ge '3.0') { | |
| $props.CoreCount2 = $Reader.ReadInt16() | |
| $props.CoreEnabled2 = $Reader.ReadInt16() | |
| $props.ThreadCount2 = $Reader.ReadInt16() | |
| } | |
| [PSCustomObject]$props | |
| } | |
| # End of Table (Type 127) | |
| 127 = { | |
| param ([IO.BinaryReader]$Reader, [string[]]$StringTable) | |
| [PSCustomObject]@{} | |
| } | |
| } | |
| Write-Verbose "Parsing SMBIOS data with version $Version" | |
| $current = $InputObject | |
| while ($current.Length) { | |
| try { | |
| if ($current.Length -lt 4) { | |
| $current = @() | |
| throw "Remaining data is too short to contain a valid SMBIOS entry header" | |
| } | |
| $headerType = $current[0] | |
| $headerLength = $current[1] | |
| $headerHandle = [BitConverter]::ToInt16($current, 2) | |
| $headerData = [byte[]]$current[4..($headerLength + 3)] | |
| Write-Verbose "Processing SMBIOS entry of type $headerType with handle $headerHandle and length $headerLength" | |
| # Extract the string table after the header data | |
| $i = $headerLength | |
| while (($i + 1) -le $current.Length) { | |
| if ($current[$i] -eq 0 -and $current[$i + 1] -eq 0) { | |
| break | |
| } | |
| $i++ | |
| } | |
| if ($i -gt $current.Length) { | |
| $rawHeaderStrings = $current[$headerLength..($current.Length - 1)] | |
| $current = @() | |
| } else { | |
| $rawHeaderStrings = $current[$headerLength..($i - 1)] | |
| $current = $current[($i + 2)..($current.Length - 1)] | |
| } | |
| $stringTable = [Text.Encoding]::UTF8.GetString($rawHeaderStrings) -split "`0" | |
| if ($typeUnpacker.ContainsKey([int]$headerType)) { | |
| $rawStream = [IO.MemoryStream]::new($headerData) | |
| $reader = [IO.BinaryReader]::new($rawStream) | |
| $entry = & $typeUnpacker[[int]$headerType] -Reader $reader -StringTable $stringTable | |
| $entry | Add-Member -MemberType NoteProperty -Name Type -Value $headerType -Force | |
| $entry | Add-Member -MemberType NoteProperty -Name Handle -Value $headerHandle -Force | |
| $entry | Add-Member -MemberType NoteProperty -Name RawData -Value ([Convert]::ToBase64String($headerData)) -Force | |
| $entry | Add-Member -MemberType NoteProperty -Name StringTable -Value $stringTable -Force | |
| $entry | |
| } else { | |
| [PSCustomObject]@{ | |
| Type = $headerType | |
| Handle = $headerHandle | |
| RawData = [Convert]::ToBase64String($headerData) | |
| StringTable = $stringTable | |
| } | |
| } | |
| if ($headerType -eq 127) { | |
| break | |
| } | |
| } | |
| catch { | |
| $PSCmdlet.WriteError($_) | |
| } | |
| } | |
| } | |
| Get-RawSmbiosData | Get-SmbiosEntry -Verbose | ConvertTo-Json |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment