Created
January 6, 2026 23:20
-
-
Save disjustin/9d15d03449df5055aaa2f2b692ef4ac9 to your computer and use it in GitHub Desktop.
Python script to convert dmidecode output to json
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
| """ | |
| This Python script defines a tool to extract and parse specific hardware information from a system's DMI (Desktop Management Interface) / SMBIOS data using the dmidecode command-line utility (commonly available on Linux systems). | |
| Tested on Rocky Linux 9.5 using dmidecode version 3.6 (dmidecode-3.6-1.el9.x86_64) | |
| Original version 1/6/2025 Justin Wong | |
| """ | |
| import subprocess | |
| import json | |
| from datetime import datetime | |
| import re | |
| class DmidecodeParser: | |
| def __init__(self, types=None): | |
| """ | |
| Initialize the DmidecodeParser. | |
| :param types: Optional list of DMI types to parse. If None, all types (0-255) are included. | |
| """ | |
| if types is None: | |
| self.types = list(range(0, 256)) | |
| else: | |
| self.types = types | |
| def parse(self, dmidecode_output): | |
| """ | |
| Parse the output from dmidecode into a structured dictionary. | |
| :param dmidecode_output: The raw string output from the dmidecode command. | |
| :return: A dictionary organized by DMI type, handle, with details including handle, type, size, title, and data. | |
| """ | |
| result = {} | |
| lines = dmidecode_output.splitlines() | |
| i = 0 | |
| while i < len(lines): | |
| line = lines[i].strip() | |
| if line.startswith("Handle"): | |
| parts = re.split(r',\s*', line) | |
| handle = parts[0].split()[1] | |
| type_str = parts[1].split()[2] | |
| type_num = int(type_str) | |
| size_str = parts[2].split()[0] | |
| dmi_size = int(size_str) | |
| if type_num not in self.types: | |
| # Skip to next handle | |
| i += 1 | |
| while i < len(lines) and not lines[i].strip().startswith("Handle"): | |
| i += 1 | |
| continue | |
| if type_num not in result: | |
| result[type_num] = {} | |
| current = { | |
| 'dmi_handle': handle, | |
| 'dmi_type': type_num, | |
| 'dmi_size': dmi_size, | |
| 'dmi_title' : '', | |
| 'data': {} | |
| } | |
| result[type_num][handle] = current | |
| i += 1 | |
| # Title | |
| if i < len(lines) and not lines[i].strip().startswith("Handle"): | |
| current['dmi_title'] = lines[i].strip() | |
| i += 1 | |
| current_key = None | |
| sub_key = None | |
| in_hex = False | |
| in_strings = False | |
| while i < len(lines) and not lines[i].strip().startswith("Handle"): | |
| # remove trailing whitespace | |
| line = lines[i].rstrip() | |
| # skip empty lines | |
| if not line.strip(): | |
| i += 1 | |
| continue | |
| indent = len(line) - len(line.lstrip('\t')) | |
| content = line.strip() | |
| # print(f"{indent} - {content}") | |
| if in_hex: | |
| if indent == 1: | |
| current['data']['Header and Data'].append(content) | |
| else: | |
| in_hex = False | |
| if in_strings: | |
| if indent == 1 and content: | |
| current['data']['Strings'].append(content) | |
| else: | |
| in_strings = False | |
| if in_hex or in_strings: | |
| i += 1 | |
| continue | |
| # line handler based on indentation level | |
| if indent == 1: | |
| if ':' in content: | |
| key, value = [x.strip() for x in content.split(':', 1)] | |
| if value: | |
| current['data'][key] = value | |
| else: | |
| current['data'][key] = [] | |
| current_key = key | |
| sub_key = None | |
| elif content == 'Header and Data:': | |
| current['data']['Header and Data'] = [] | |
| in_hex = True | |
| current_key = 'Header and Data' | |
| elif content == 'Strings:': | |
| current['data']['Strings'] = [] | |
| in_strings = True | |
| current_key = 'Strings' | |
| else: | |
| # Treat as key without value, start list | |
| current['data'][content] = [] | |
| current_key = content | |
| elif indent == 2: | |
| if current_key and isinstance(current['data'].get(current_key), list): | |
| current['data'][current_key].append(content) | |
| elif current_key: | |
| if ':' in content: | |
| sub_key, sub_value = [x.strip() for x in content.split(':', 1)] | |
| if 'subfields' not in current['data'][current_key]: | |
| current['data'][current_key] = {'subfields': {}} | |
| current['data'][current_key]['subfields'][sub_key] = sub_value if sub_value else [] | |
| sub_key = sub_key | |
| else: | |
| current['data'][current_key] = content | |
| else: | |
| pass | |
| elif indent == 3: | |
| if current_key and isinstance(current['data'].get(current_key), list): | |
| current['data'][current_key].append(content) | |
| i += 1 | |
| else: | |
| i += 1 | |
| return result | |
| def to_json(self, data): | |
| """ | |
| Convert the parsed data to a JSON string, sorted by type number. | |
| :param data: The parsed data dictionary from the parse method. | |
| :return: A JSON string representation of the data. | |
| """ | |
| sorted_data = {k: data[k] for k in sorted(data.keys())} | |
| return json.dumps(sorted_data, indent=4) | |
| def main(): | |
| try: | |
| output = subprocess.check_output(["dmidecode"]).decode("utf-8") | |
| except subprocess.CalledProcessError: | |
| print("Error running dmidecode. Ensure it's installed and run with sufficient privileges.") | |
| return | |
| # types_list = [14, 39, 40, 42, 43, 44, 45] + list(range(128, 256)) | |
| types_list = list(range(0, 256)) | |
| parser = DmidecodeParser(types=types_list) | |
| data = parser.parse(output) | |
| json_data = parser.to_json(data) | |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| try: | |
| # if type 1 parsed, use system serial number | |
| systemsn = data[1]["0x0001"]["data"]["Serial Number"] | |
| filename = f"dmidecode_{systemsn}_{timestamp}.json" | |
| except: | |
| # if type 1 not parsed, use timestamp | |
| filename = f"dmidecode_{timestamp}.json" | |
| with open(filename, "w") as f: | |
| f.write(json_data) | |
| print(f"Exported to {filename}") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment