Skip to main content

How to Inventory All Hardware Information in Batch Script

Performing a hardware inventory is essential for asset management, warranty tracking, and upgrade planning. Instead of opening the computer case or checking various Settings menus, a Batch script can use Windows Management Instrumentation (WMI) to extract deep hardware details. From the BIOS serial number and motherboard manufacturer to the specific model of every installed disk drive, this data can be captured automatically across hundreds of machines in minutes.

This guide will explain how to build a comprehensive hardware audit script using PowerShell's Get-CimInstance cmdlet.

Why PowerShell Instead of wmic

All hardware queries in this guide use PowerShell's Get-CimInstance rather than wmic for three reasons:

  1. wmic.exe is deprecated since Windows 10 21H1 and may be removed in future releases.
  2. wmic output contains invisible trailing \r characters that corrupt variables when captured by for /f, causing display corruption and comparison failures in Batch scripts.
  3. PowerShell handles 64-bit arithmetic natively, which is essential for converting memory and disk sizes from bytes to human-readable units. Batch's set /a overflows at approximately 2 GB.

Method 1: Comprehensive Hardware Report

This script generates a formatted text report covering the core hardware categories: identity, processor, memory, storage, network, and graphics.

@echo off
setlocal

set "Report=%~dp0HW_Inventory_%COMPUTERNAME%.txt"

echo [INFO] Collecting hardware inventory... Please wait.

:: Check for admin rights (some WMI classes require elevation)
net session >nul 2>&1
if errorlevel 1 (
echo [WARNING] Running without admin rights. Some fields may be incomplete. >&2
set "IsAdmin=No"
) else (
set "IsAdmin=Yes"
)

:: =============================================
:: Header
:: =============================================
(
echo ==================================================
echo HARDWARE INVENTORY REPORT
echo ==================================================
) > "%Report%"

for /f "usebackq delims=" %%t in (`powershell -NoProfile -Command "Get-Date -Format 'yyyy-MM-dd HH:mm:ss'"`) do echo Generated: %%t >> "%Report%"

echo Computer Name: %COMPUTERNAME% >> "%Report%"
echo Admin Rights: %IsAdmin% >> "%Report%"
echo. >> "%Report%"

:: =============================================
:: System Identity (BIOS and Motherboard)
:: =============================================
echo --- System Identity --- >> "%Report%"

for /f "usebackq delims=" %%a in (`powershell -NoProfile -Command "(Get-CimInstance Win32_BIOS).SerialNumber"`) do echo BIOS Serial: %%a >> "%Report%"
for /f "usebackq delims=" %%a in (`powershell -NoProfile -Command "(Get-CimInstance Win32_BaseBoard).Manufacturer"`) do echo Board Maker: %%a >> "%Report%"
for /f "usebackq delims=" %%a in (`powershell -NoProfile -Command "(Get-CimInstance Win32_BaseBoard).Product"`) do echo Board Model: %%a >> "%Report%"

:: FIX: Used -f operator and single quotes to bypass CMD parsing errors
for /f "usebackq delims=" %%a in (`powershell -NoProfile -Command "$cs = Get-CimInstance Win32_ComputerSystem; Write-Output ('{0} {1}' -f $cs.Manufacturer, $cs.Model)"`) do echo System Model: %%a >> "%Report%"

:: Detect virtual machines
for /f "usebackq delims=" %%a in (`powershell -NoProfile -Command "$model = (Get-CimInstance Win32_ComputerSystem).Model; $bios = (Get-CimInstance Win32_BIOS).Version; if ($model -match 'Virtual|VMware|VirtualBox|KVM|Xen|HVM' -or $bios -match 'VRTUAL|VBOX|Hyper-V') { Write-Output 'Yes (Detected)' } else { Write-Output 'No' }"`) do echo Virtual Machine: %%a >> "%Report%"

echo. >> "%Report%"

:: =============================================
:: Processor
:: =============================================
echo --- Processor --- >> "%Report%"

for /f "usebackq delims=" %%a in (`powershell -NoProfile -Command "(Get-CimInstance Win32_Processor).Name"`) do echo CPU Name: %%a >> "%Report%"

for /f "usebackq delims=" %%a in (`powershell -NoProfile -Command "$cpu = Get-CimInstance Win32_Processor; Write-Output ('{0} cores / {1} threads' -f $cpu.NumberOfCores, $cpu.NumberOfLogicalProcessors)"`) do echo Cores/Threads: %%a >> "%Report%"

echo. >> "%Report%"

:: =============================================
:: Memory (RAM)
:: =============================================
echo --- Memory --- >> "%Report%"

for /f "usebackq delims=" %%a in (`powershell -NoProfile -Command "$ram = (Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory; Write-Output ('{0} GB' -f [math]::Round($ram / 1GB, 1))"`) do echo Total RAM: %%a >> "%Report%"

echo Slot Details: >> "%Report%"
for /f "usebackq delims=" %%a in (`powershell -NoProfile -Command "Get-CimInstance Win32_PhysicalMemory | ForEach-Object { $capGB = [math]::Round($_.Capacity / 1GB, 1); Write-Output (' {0}: {1} GB @ {2} MHz' -f $_.DeviceLocator, $capGB, $_.Speed) }"`) do echo %%a >> "%Report%"

echo. >> "%Report%"

:: =============================================
:: Storage
:: =============================================
echo --- Storage Drives --- >> "%Report%"

for /f "usebackq delims=" %%a in (`powershell -NoProfile -Command "Get-CimInstance Win32_DiskDrive | ForEach-Object { $sizeGB = [math]::Round($_.Size / 1GB, 1); Write-Output (' {0} - {1} GB [{2}] Status: {3}' -f $_.Model, $sizeGB, $_.InterfaceType, $_.Status) }"`) do echo %%a >> "%Report%"

echo. >> "%Report%"

echo --- Logical Volumes --- >> "%Report%"

for /f "usebackq tokens=1-3" %%a in (`powershell -NoProfile -Command "Get-CimInstance Win32_LogicalDisk -Filter 'DriveType=3' | ForEach-Object { $totalGB = [math]::Round($_.Size / 1GB, 1); $freeGB = [math]::Round($_.FreeSpace / 1GB, 1); Write-Output ('{0} {1} {2}' -f $_.DeviceID, $totalGB, $freeGB) }"`) do echo Drive %%a Total: %%b GB Free: %%c GB >> "%Report%"

echo. >> "%Report%"

:: =============================================
:: Network Adapters
:: =============================================
echo --- Network Adapters --- >> "%Report%"

for /f "usebackq delims=" %%a in (`powershell -NoProfile -Command "Get-CimInstance Win32_NetworkAdapterConfiguration -Filter 'IPEnabled=True' | ForEach-Object { $ips = ($_.IPAddress -join ', '); $mac = $_.MACAddress; Write-Output (' {0}: {1} (MAC: {2})' -f $_.Description, $ips, $mac) }"`) do echo %%a >> "%Report%"

echo. >> "%Report%"

:: =============================================
:: Graphics
:: =============================================
echo --- Graphics --- >> "%Report%"

for /f "usebackq delims=" %%a in (`powershell -NoProfile -Command "Get-CimInstance Win32_VideoController | ForEach-Object { $vramMB = [math]::Round($_.AdapterRAM / 1MB); Write-Output (' {0} - Driver: {1} - VRAM: {2} MB' -f $_.Name, $_.DriverVersion, $vramMB) }"`) do echo %%a >> "%Report%"

echo. >> "%Report%"

:: =============================================
:: Footer
:: =============================================
echo ================================================== >> "%Report%"

echo [OK] Hardware inventory saved to: %Report%

endlocal
exit /b 0

What this report captures:

SectionWMI ClassKey Properties
System IdentityWin32_BIOS, Win32_BaseBoard, Win32_ComputerSystemSerial number, board manufacturer/model, system model
ProcessorWin32_ProcessorModel name, core count, thread count
MemoryWin32_ComputerSystem, Win32_PhysicalMemoryTotal RAM, per-slot capacity and speed
StorageWin32_DiskDrive, Win32_LogicalDiskDrive model, interface, capacity, health status, volume free space
NetworkWin32_NetworkAdapterConfigurationAdapter description, IP addresses, MAC address
GraphicsWin32_VideoControllerGPU name, driver version, video RAM

Method 2: CSV Export for Fleet Auditing

When inventorying dozens or hundreds of machines, you need one line per machine in a shared CSV, not individual text files. This script collects key hardware fields in a single PowerShell call and appends one CSV row.

@echo off
setlocal

:: If you are testing this locally and don't have a server, change this to a local path like "%~dp0hardware_inventory.csv"
set "CSVFile=\\Server\Share\hardware_inventory.csv"

:: Write header if the file does not exist
if not exist "%CSVFile%" (
echo "ComputerName","SerialNumber","Model","CPU","RAM_GB","DiskModel","DiskSizeGB","GPU","IP" > "%CSVFile%"
)

echo [INFO] Collecting hardware data for fleet inventory...

set "Success=0"

:: 1. Used usebackq and backticks (`)
:: 2. Condensed to a single line to avoid CMD line-break parsing errors
:: 3. Replaced \" with single quotes (') inside the PowerShell command
:: 4. Used the PowerShell format operator (-f) to safely construct the output string with pipes (|)
for /f "usebackq tokens=1-8 delims=|" %%a in (`powershell -NoProfile -Command "$bios = Get-CimInstance Win32_BIOS; $cs = Get-CimInstance Win32_ComputerSystem; $cpu = Get-CimInstance Win32_Processor; $disk = Get-CimInstance Win32_DiskDrive | Select-Object -First 1; $gpu = Get-CimInstance Win32_VideoController | Select-Object -First 1; $net = Get-CimInstance Win32_NetworkAdapterConfiguration -Filter 'IPEnabled=True' | Select-Object -First 1; $ram = [math]::Round($cs.TotalPhysicalMemory / 1GB, 1); $diskGB = if ($disk.Size) { [math]::Round($disk.Size / 1GB, 1) } else { 'N/A' }; $ip = if ($net.IPAddress) { $net.IPAddress[0] } else { 'N/A' }; $gpuName = if ($gpu) { $gpu.Name } else { 'N/A' }; $model = '{0} {1}' -f $cs.Manufacturer, $cs.Model; '{0}|{1}|{2}|{3}|{4}|{5}|{6}|{7}' -f $bios.SerialNumber, $model, $cpu.Name, $ram, $disk.Model, $diskGB, $gpuName, $ip"`) do (

echo "%COMPUTERNAME%","%%a","%%b","%%c","%%d","%%e","%%f","%%g","%%h" >> "%CSVFile%"
set "Success=1"
)

:: Check our custom Success variable instead of errorlevel
if "%Success%"=="0" (
echo [ERROR] Failed to collect hardware data. >&2
endlocal
exit /b 1
)

echo [OK] Data appended to: %CSVFile%

endlocal
exit /b 0

Deployment:

Run this script on each machine via Group Policy logon script, SCCM, PDQ Deploy, or a similar deployment tool. Each machine appends one row to the shared CSV, building a complete fleet inventory automatically.

Why a single PowerShell call:

Method 1 makes multiple powershell -Command calls for readability. Each call takes 1–3 seconds for process startup. For fleet auditing, where the script runs on every machine, combining all queries into a single call minimizes total execution time.

How to Avoid Common Errors

Wrong Way: Using wmic Output Directly in Variables

:: BROKEN: wmic output contains invisible \r characters
for /f "tokens=2 delims==" %%a in ('wmic bios get serialnumber /value') do set "Serial=%%a"
echo Serial: %Serial%

The variable Serial contains ABC123\r (with an invisible carriage return), which causes display corruption and breaks string comparisons.

Correct Way: Use PowerShell's Get-CimInstance, which returns clean typed data.

Wrong Way: Using Batch Math for Size Conversions

:: BROKEN: overflows for any disk or RAM value above ~2 GB
set /a "SizeGB=%SizeBytes% / 1073741824"

Batch set /a is limited to 32-bit signed integers (max ~2.1 billion). Any modern disk or RAM size in bytes exceeds this limit.

Correct Way: Use PowerShell's [math]::Round($bytes / 1GB, 1) for accurate 64-bit division.

Problem: Virtual Machines Return Generic Serial Numbers

In virtual environments (VMware, Hyper-V, VirtualBox), the BIOS serial number is often a generic string like VMware-56 4d... or 0 rather than a meaningful asset tag.

Solution: Detect virtual machines and flag them in the report. The Method 1 script includes VM detection by checking the system model and BIOS version for virtualization keywords.

Problem: Blank Fields Without Admin Rights

Some WMI classes, particularly Win32_BIOS serial number, Win32_PhysicalMemory slot details, and certain Win32_DiskDrive properties, return incomplete or blank data when queried by a standard user.

Solution: Run the inventory script with administrator privileges. The Method 1 script includes an admin-rights check and notes the result in the report header, so the reader knows whether missing fields are due to access restrictions.

Wrong Way: Using dxdiag for Scripted Inventory

dxdiag /t output.txt generates a comprehensive dump, but the output is a large, unstructured text file that is extremely difficult to parse for specific fields like serial numbers or RAM speeds.

Correct Way: Use targeted Get-CimInstance queries that return exactly the properties you need in a predictable format.

Best Practices and Rules

1. Use CSV for Multi-Machine Inventories

For auditing more than a handful of machines, output CSV (Method 2) rather than individual text reports. A single CSV with one row per machine is vastly easier to sort, filter, and import into asset management databases.

2. Run as Administrator

Always run hardware inventory scripts with elevated privileges to ensure complete data. Document the admin requirement in the script's header comments and include a runtime check.

3. Include VM Detection

Modern IT environments contain a mix of physical and virtual machines. Always detect and flag VMs so that physical asset counts are not inflated by virtual instances.

4. Locale-Independent Timestamps

Use PowerShell's Get-Date -Format for timestamps rather than %date% and %time%, which vary by regional settings and produce inconsistent filenames and report headers across machines.

5. Protect Sensitive Data

Hardware reports contain serial numbers, MAC addresses, and IP addresses. Handle them according to your organization's data classification policy. Do not store them on public shares or transmit them over unencrypted channels.

Conclusions

Building a hardware inventory script transforms the way you manage IT assets. By automating the collection of BIOS, motherboard, CPU, memory, disk, network, and GPU data through PowerShell and WMI, you create a reliable source of truth for your infrastructure. Whether generating detailed single-machine reports or fleet-wide CSV inventories, this professional, automated approach ensures you always have the data you need for audits, warranty tracking, and capacity planning, without ever needing to physically touch the machine.