85°F Knoxville
Mon–Fri 9–5 ETAnswered by a human
Automation · RESOLVED

Insurance Audit Hardware Inventory: 312 Assets in 47 Minutes

Cyber insurance required complete hardware inventory: serial numbers, specs, encryption status. Manual audit quoted at 2 weeks. Automated collection delivered same day.

Incident

Cyber insurance renewal audit required a complete hardware inventory: every endpoint's serial number, manufacturer, CPU, RAM, storage capacity, and BIOS version. The client had 312 Windows devices across 8 locations. Their existing "inventory" was a 3-year-old Excel spreadsheet sitting around 40% accurate — people had been added, equipment had been swapped, nothing had been maintained. The auditor gave 5 business days to produce accurate data or face policy non-renewal.

The manual approach

Traditional inventory methods are a slog:

  • physical inspection: walk to each machine and record specs
  • System Information: run msinfo32 on each device manually
  • time per machine: 5-10 minutes if it's accessible
  • remote workers: ship the laptop? schedule a screenshare?

At 7 minutes per machine (optimistic), 312 devices would require 36 hours of technician time — and that's assuming every machine was accessible. Remote workers, field staff, and machines actively in use would stretch that to weeks. The client did not have weeks.

What auditors actually need

The audit ask sounds daunting until you list it out. It's six data points per device:

  • serial numbers: tie the device to warranty and ownership records
  • manufacturer/model: identifies supported hardware
  • CPU specs: proves processing capability
  • RAM installed: verifies minimum requirements
  • storage capacity: confirms data handling ability
  • BIOS version: shows firmware patch status

All of this data already exists in Windows — in WMI/CIM — it just needs to be queried systematically. No physical access required, no user interaction needed, no logging in as the user.

Solution

Deploy via RMM to every endpoint in parallel. The script queries the relevant CIM classes for each hardware component, returns structured output captured in the RMM logs, and the aggregated result gets exported to CSV in an auditor-friendly format.

hardware_report.ps1View on GitHub →
$ErrorActionPreference = 'Stop'

<#
██╗     ██╗███╗   ███╗███████╗██╗  ██╗ █████╗ ██╗    ██╗██╗  ██╗
██║     ██║████╗ ████║██╔════╝██║  ██║██╔══██╗██║    ██║██║ ██╔╝
██║     ██║██╔████╔██║█████╗  ███████║███████║██║ █╗ ██║█████╔╝ 
██║     ██║██║╚██╔╝██║██╔══╝  ██╔══██║██╔══██║██║███╗██║██╔═██╗ 
███████╗██║██║ ╚═╝ ██║███████╗██║  ██║██║  ██║╚███╔███╔╝██║  ██╗
╚══════╝╚═╝╚═╝     ╚═╝╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝ ╚══╝╚══╝ ╚═╝  ╚═╝
================================================================================
 SCRIPT   : Hardware Report                                               v1.1.1
 AUTHOR   : Limehawk.io
 DATE      : January 2026
 USAGE    : .\hardware_report.ps1
================================================================================
 FILE     : hardware_report.ps1
 DESCRIPTION : Generates comprehensive hardware inventory report via CIM queries
--------------------------------------------------------------------------------
 README
--------------------------------------------------------------------------------
 PURPOSE
 Generates a comprehensive hardware inventory report for the local machine.
 Collects system information, CPU, memory, storage, GPU, network adapters,
 and BIOS details. Designed for unattended execution in RMM environments
 to gather standardized hardware data for asset management and documentation.

 DATA SOURCES & PRIORITY
 1) Hardcoded values (defined within the script body)
 2) WMI/CIM queries (Win32_ComputerSystem, Win32_Processor, etc.)
 3) Error

 REQUIRED INPUTS
 - IncludeStorage      : $true
   (Whether to include storage device information in the report.)
 - IncludeGpu          : $true
   (Whether to include GPU information in the report.)
 - IncludeNetwork      : $true
   (Whether to include network adapter information in the report.)
 - IncludeBios         : $true
   (Whether to include BIOS information in the report.)
 - IncludeMemoryModules: $true
   (Whether to include individual RAM module details.)

 SETTINGS
 - Uses CIM/WMI for hardware data collection (Windows native).
 - All output formatted as Key : Value pairs for RMM parsing.
 - No external dependencies or modules required.
 - Sizes reported in GB with 2 decimal precision.

 BEHAVIOR
 - Script collects hardware information from local system via CIM queries.
 - All sections are optional and can be disabled via hardcoded inputs.
 - Missing or unavailable hardware components are reported as "N/A".
 - Output is structured for easy capture by RMM agent stdout collection.

 PREREQUISITES
 - PowerShell 5.1 or later.
 - Windows operating system with CIM/WMI support.
 - No special permissions required (runs in user context).

 SECURITY NOTES
 - No secrets are printed to the console.
 - No sensitive system information exposed beyond standard hardware inventory.
 - All data collected is read-only from system WMI providers.

 ENDPOINTS
 - N/A (local system queries only)

 EXIT CODES
 - 0 success
 - 1 failure

 EXAMPLE RUN

 [INFO] INPUT VALIDATION
 ==============================================================
 IncludeStorage       : True
 IncludeGpu           : True
 IncludeNetwork       : True
 IncludeBios          : True
 IncludeMemoryModules : True

 [INFO] SYSTEM INFORMATION
 ==============================================================
 Computer Name        : DESKTOP-ABC123
 Manufacturer         : Dell Inc.
 Model                : OptiPlex 7090
 Serial Number        : 1A2B3C4D
 Total Memory         : 32.00 GB
 Number of Processors : 1

 [INFO] CPU INFORMATION
 ==============================================================
 Name                 : Intel(R) Core(TM) i7-10700 CPU @ 2.90GHz
 Cores                : 8
 Logical Processors   : 16
 Max Clock Speed      : 2904 MHz

 [INFO] STORAGE DEVICES
 ==============================================================
 Drive C (Windows)    : 476.46 GB Total | 123.45 GB Free | 353.01 GB Used
 Drive D (Data)       : 931.51 GB Total | 456.78 GB Free | 474.73 GB Used

 [INFO] FINAL STATUS
 ==============================================================
 Hardware report generated successfully.

 [OK] SCRIPT COMPLETED
 ==============================================================
--------------------------------------------------------------------------------
 CHANGELOG
--------------------------------------------------------------------------------
 2026-01-19 v1.1.1 Updated to two-line ASCII console output style
 2025-12-23 v1.1.0 Updated to Limehawk Script Framework
 2025-10-31 v1.0.0 Initial release
================================================================================
#>

Set-StrictMode -Version Latest

# ==== STATE (NO ARRAYS/LISTS) ====
$errorOccurred = $false
$errorText     = ""

# ==== HARDCODED INPUTS (MANDATORY) ====
$IncludeStorage       = $true
$IncludeGpu           = $true
$IncludeNetwork       = $true
$IncludeBios          = $true
$IncludeMemoryModules = $true

# ==== VALIDATION ====
# Boolean inputs don't require validation, but we verify they're boolean type
if ($IncludeStorage -isnot [bool]) {
    $errorOccurred = $true
    if ($errorText.Length -gt 0) { $errorText += "`n" }
    $errorText += "- IncludeStorage must be a boolean value."
}
if ($IncludeGpu -isnot [bool]) {
    $errorOccurred = $true
    if ($errorText.Length -gt 0) { $errorText += "`n" }
    $errorText += "- IncludeGpu must be a boolean value."
}
if ($IncludeNetwork -isnot [bool]) {
    $errorOccurred = $true
    if ($errorText.Length -gt 0) { $errorText += "`n" }
    $errorText += "- IncludeNetwork must be a boolean value."
}
if ($IncludeBios -isnot [bool]) {
    $errorOccurred = $true
    if ($errorText.Length -gt 0) { $errorText += "`n" }
    $errorText += "- IncludeBios must be a boolean value."
}
if ($IncludeMemoryModules -isnot [bool]) {
    $errorOccurred = $true
    if ($errorText.Length -gt 0) { $errorText += "`n" }
    $errorText += "- IncludeMemoryModules must be a boolean value."
}

if ($errorOccurred) {
    Write-Host ""
    Write-Host "[ERROR] INPUT VALIDATION FAILED"
    Write-Host "=============================================================="
    Write-Host $errorText

    Write-Host ""
    Write-Host "[INFO] RESULT"
    Write-Host "=============================================================="
    Write-Host "Status : Failure"

    Write-Host ""
    Write-Host "[INFO] FINAL STATUS"
    Write-Host "=============================================================="
    Write-Host "Script cannot proceed due to invalid hardcoded inputs."

    Write-Host ""
    Write-Host "[ERROR] SCRIPT FAILED"
    Write-Host "=============================================================="
    exit 1
}

# ==== RUNTIME OUTPUT ====
Write-Host ""
Write-Host "[INFO] INPUT VALIDATION"
Write-Host "=============================================================="
Write-Host "IncludeStorage       : $IncludeStorage"
Write-Host "IncludeGpu           : $IncludeGpu"
Write-Host "IncludeNetwork       : $IncludeNetwork"
Write-Host "IncludeBios          : $IncludeBios"
Write-Host "IncludeMemoryModules : $IncludeMemoryModules"

# Helper function to format bytes to GB
function Format-BytesToGB {
    param([long]$Bytes)
    if ($Bytes -eq 0) { return "0.00" }
    return [math]::Round($Bytes / 1GB, 2)
}

# Helper function to safely get property
function Get-SafeProperty {
    param($Object, $PropertyName, $DefaultValue = "N/A")
    if ($Object.PSObject.Properties.Name -contains $PropertyName) {
        $value = $Object.$PropertyName
        if ($null -eq $value -or [string]::IsNullOrWhiteSpace($value)) {
            return $DefaultValue
        }
        return $value
    }
    return $DefaultValue
}

try {
    # ==== SYSTEM INFORMATION ====
    Write-Host ""
    Write-Host "[INFO] SYSTEM INFORMATION"
    Write-Host "=============================================================="

    $sysInfo = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop

    Write-Host "Computer Name        : $env:COMPUTERNAME"
    Write-Host "Manufacturer         : $(Get-SafeProperty $sysInfo 'Manufacturer')"
    Write-Host "Model                : $(Get-SafeProperty $sysInfo 'Model')"

    # Get serial number from BIOS
    $biosSerial = Get-CimInstance -ClassName Win32_BIOS -ErrorAction Stop
    Write-Host "Serial Number        : $(Get-SafeProperty $biosSerial 'SerialNumber')"

    $totalMemGB = Format-BytesToGB (Get-SafeProperty $sysInfo 'TotalPhysicalMemory' 0)
    Write-Host "Total Memory         : $totalMemGB GB"
    Write-Host "Number of Processors : $(Get-SafeProperty $sysInfo 'NumberOfProcessors' 0)"

    # ==== CPU INFORMATION ====
    Write-Host ""
    Write-Host "[INFO] CPU INFORMATION"
    Write-Host "=============================================================="

    $cpuInfo = Get-CimInstance -ClassName Win32_Processor -ErrorAction Stop | Select-Object -First 1

    Write-Host "Name                 : $(Get-SafeProperty $cpuInfo 'Name')"
    Write-Host "Cores                : $(Get-SafeProperty $cpuInfo 'NumberOfCores' 0)"
    Write-Host "Logical Processors   : $(Get-SafeProperty $cpuInfo 'NumberOfLogicalProcessors' 0)"
    Write-Host "Max Clock Speed      : $(Get-SafeProperty $cpuInfo 'MaxClockSpeed' 0) MHz"

    # ==== MEMORY MODULES ====
    if ($IncludeMemoryModules) {
        Write-Host ""
        Write-Host "[INFO] MEMORY MODULES"
        Write-Host "=============================================================="

        $ramModules = Get-CimInstance -ClassName Win32_PhysicalMemory -ErrorAction Stop

        if ($ramModules) {
            $moduleCount = 0
            $totalRam = 0

            foreach ($ram in $ramModules) {
                $moduleCount++
                $capacity = Get-SafeProperty $ram 'Capacity' 0
                $totalRam += $capacity
                $capacityGB = Format-BytesToGB $capacity
                $speed = Get-SafeProperty $ram 'Speed' 0
                $manufacturer = Get-SafeProperty $ram 'Manufacturer' 'Unknown'

                Write-Host "Module $moduleCount          : $manufacturer - $capacityGB GB @ $speed MHz"
            }

            $totalRamGB = Format-BytesToGB $totalRam
            Write-Host "Total Installed      : $totalRamGB GB"
        } else {
            Write-Host "No RAM module details available."
        }
    }

    # ==== STORAGE DEVICES ====
    if ($IncludeStorage) {
        Write-Host ""
        Write-Host "[INFO] STORAGE DEVICES"
        Write-Host "=============================================================="

        $disks = Get-CimInstance -ClassName Win32_LogicalDisk -ErrorAction Stop | Where-Object { $_.DriveType -eq 3 }

        if ($disks) {
            foreach ($disk in $disks) {
                $deviceId = Get-SafeProperty $disk 'DeviceID' 'Unknown'
                $volumeName = Get-SafeProperty $disk 'VolumeName' 'No Label'
                $size = Get-SafeProperty $disk 'Size' 0
                $freeSpace = Get-SafeProperty $disk 'FreeSpace' 0

                $totalGB = Format-BytesToGB $size
                $freeGB = Format-BytesToGB $freeSpace
                $usedGB = [math]::Round($totalGB - $freeGB, 2)

                Write-Host "Drive $deviceId ($volumeName) : $totalGB GB Total | $freeGB GB Free | $usedGB GB Used"
            }
        } else {
            Write-Host "No storage devices found."
        }
    }

    # ==== GPU INFORMATION ====
    if ($IncludeGpu) {
        Write-Host ""
        Write-Host "[INFO] GPU INFORMATION"
        Write-Host "=============================================================="

        $gpus = Get-CimInstance -ClassName Win32_VideoController -ErrorAction Stop

        if ($gpus) {
            $gpuCount = 0
            foreach ($gpu in $gpus) {
                $gpuCount++
                $name = Get-SafeProperty $gpu 'Name'
                $adapterRam = Get-SafeProperty $gpu 'AdapterRAM' 0
                $driverVersion = Get-SafeProperty $gpu 'DriverVersion'

                $gpuRamGB = if ($adapterRam -gt 0) { Format-BytesToGB $adapterRam } else { "N/A" }

                Write-Host "GPU $gpuCount Name          : $name"
                Write-Host "GPU $gpuCount RAM           : $gpuRamGB GB"
                Write-Host "GPU $gpuCount Driver        : $driverVersion"

                if ($gpuCount -lt @($gpus).Count) {
                    Write-Host ""
                }
            }
        } else {
            Write-Host "No GPU devices detected."
        }
    }

    # ==== NETWORK ADAPTERS ====
    if ($IncludeNetwork) {
        Write-Host ""
        Write-Host "[INFO] NETWORK ADAPTERS"
        Write-Host "=============================================================="

        $netAdapters = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -ErrorAction Stop | Where-Object { $_.IPEnabled -eq $true }

        if ($netAdapters) {
            $adapterCount = 0
            foreach ($adapter in $netAdapters) {
                $adapterCount++
                $description = Get-SafeProperty $adapter 'Description'
                $ipAddresses = Get-SafeProperty $adapter 'IPAddress' @()

                $ips = if ($ipAddresses -is [Array]) { $ipAddresses -join ", " } else { $ipAddresses }
                if ([string]::IsNullOrWhiteSpace($ips)) { $ips = "N/A" }

                Write-Host "Adapter $adapterCount         : $description"
                Write-Host "IP Addresses         : $ips"

                if ($adapterCount -lt @($netAdapters).Count) {
                    Write-Host ""
                }
            }
        } else {
            Write-Host "No active network adapters found."
        }
    }

    # ==== BIOS INFORMATION ====
    if ($IncludeBios) {
        Write-Host ""
        Write-Host "[INFO] BIOS INFORMATION"
        Write-Host "=============================================================="

        $biosInfo = Get-CimInstance -ClassName Win32_BIOS -ErrorAction Stop

        Write-Host "Manufacturer         : $(Get-SafeProperty $biosInfo 'Manufacturer')"
        Write-Host "Version              : $(Get-SafeProperty $biosInfo 'SMBIOSBIOSVersion')"

        $releaseDate = Get-SafeProperty $biosInfo 'ReleaseDate' $null
        if ($releaseDate -and $releaseDate -is [DateTime]) {
            Write-Host "Release Date         : $($releaseDate.ToString('yyyy-MM-dd'))"
        } else {
            Write-Host "Release Date         : N/A"
        }
    }

} catch {
    $errorOccurred = $true
    if ($_.Exception.Message.Length -gt 0) { $errorText = $_.Exception.Message }
    else { $errorText = $_.ToString() }
}

if ($errorOccurred) {
    Write-Host ""
    Write-Host "[ERROR] OPERATION FAILED"
    Write-Host "=============================================================="
    Write-Host "Failed to collect hardware information:"
    Write-Host $errorText

    Write-Host ""
    Write-Host "[INFO] RESULT"
    Write-Host "=============================================================="
    Write-Host "Status : Failure"
}

Write-Host ""
Write-Host "[INFO] FINAL STATUS"
Write-Host "=============================================================="
if ($errorOccurred) {
    Write-Host "Hardware report generation failed. See error details above."
} else {
    Write-Host "Hardware report generated successfully."
}

Write-Host ""
if ($errorOccurred) {
    Write-Host "[ERROR] SCRIPT FAILED"
} else {
    Write-Host "[OK] SCRIPT COMPLETED"
}
Write-Host "=============================================================="

if ($errorOccurred) {
    exit 1
} else {
    exit 0
}

Cim classes used

Windows Management Instrumentation has covered this ground since XP. The classes we hit:

  • Win32_ComputerSystem: hostname, manufacturer, model, RAM
  • Win32_Processor: CPU name, cores, clock speed
  • Win32_PhysicalMemory: individual DIMM details
  • Win32_LogicalDisk: storage capacity and free space
  • Win32_VideoController: GPU info and driver version
  • Win32_NetworkAdapter: NIC details and IP addresses
  • Win32_BIOS: serial, version, release date

No third-party tools required. The script runs in user context for the basic inventory data (no admin needed) and produces structured output that's easy to parse and reformat.

Outcome

Script deployed at 9 AM. By 10 AM we had a complete hardware inventory for every managed device. Exported to CSV, reformatted into the template the auditor wanted, delivered the same day. The auditor commented that it was the most complete inventory they'd seen from a company that size — most of their reviews involve a lot of "we'll get back to you on that one." Policy renewed on time, no premium increase.

endpoints inventoried: 312 of 312 collection time: 47 minutes (parallel) data accuracy: 100% (live system data) audit result: passed, policy renewed

The hardware report now runs monthly as a scheduled job. Instead of scrambling at renewal time next year, the client will have 12 months of timestamped inventory snapshots — continuous compliance instead of annual panic.

Key takeaways