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:
wmic.exeis deprecated since Windows 10 21H1 and may be removed in future releases.wmicoutput contains invisible trailing\rcharacters that corrupt variables when captured byfor /f, causing display corruption and comparison failures in Batch scripts.- PowerShell handles 64-bit arithmetic natively, which is essential for converting memory and disk sizes from bytes to human-readable units. Batch's
set /aoverflows 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:
| Section | WMI Class | Key Properties |
|---|---|---|
| System Identity | Win32_BIOS, Win32_BaseBoard, Win32_ComputerSystem | Serial number, board manufacturer/model, system model |
| Processor | Win32_Processor | Model name, core count, thread count |
| Memory | Win32_ComputerSystem, Win32_PhysicalMemory | Total RAM, per-slot capacity and speed |
| Storage | Win32_DiskDrive, Win32_LogicalDisk | Drive model, interface, capacity, health status, volume free space |
| Network | Win32_NetworkAdapterConfiguration | Adapter description, IP addresses, MAC address |
| Graphics | Win32_VideoController | GPU 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.