PowerShell System Info Report: Build a Hardware Inventory

PowerShell System Info Report: Build a Hardware Inventory

PowerShell Tips Editor 4 min read
PowerShell System Info Report: Build a Hardware Inventory

Auditors want a spreadsheet with OS versions, CPU models, RAM, and disk capacity for every machine in the environment — and they want it by Monday. Clicking through Computer Properties on 200 workstations is not an option. PowerShell system information collection via Get-CimInstance lets you build a complete hardware and OS inventory automatically, export it to CSV, and run it against as many machines as you have in minutes.

Collect OS Version and Install Date

Start with Win32_OperatingSystem, which gives you the OS caption (friendly name), version number, architecture, and original install date. These are the fields auditors and asset management systems most commonly require.

$os = Get-CimInstance -ClassName Win32_OperatingSystem
[PSCustomObject]@{
    OSName       = $os.Caption
    Version      = $os.Version
    Architecture = $os.OSArchitecture
    InstallDate  = $os.InstallDate
    LastBoot     = $os.LastBootUpTime
}
OSName       : Microsoft Windows 11 Pro
Version      : 10.0.22631
Architecture : 64-bit
InstallDate  : 1/10/2025 8:32:00 AM
LastBoot     : 5/3/2026 7:15:00 AM

CPU Model and Core Count

Win32_Processor returns one object per physical CPU socket. In multi-socket servers, there will be multiple rows. Use Select-Object -First 1 for workstation-style inventory where you expect one CPU, or aggregate with Measure-Object for servers.

$cpu = Get-CimInstance -ClassName Win32_Processor | Select-Object -First 1
[PSCustomObject]@{
    CPUName     = $cpu.Name.Trim()
    Cores       = $cpu.NumberOfCores
    LogicalCPUs = $cpu.NumberOfLogicalProcessors
    SpeedMHz    = $cpu.MaxClockSpeed
}

Total and Available RAM

Physical RAM details come from Win32_PhysicalMemory (individual DIMMs) or the total and available figures from Win32_OperatingSystem. Both values are in bytes — divide by 1 GB for human-readable output.

$os = Get-CimInstance -ClassName Win32_OperatingSystem
$totalRAM = [math]::Round($os.TotalVisibleMemorySize / 1MB, 1)
$freeRAM  = [math]::Round($os.FreePhysicalMemory / 1MB, 1)

Write-Output "Total RAM: $totalRAM GB — Free: $freeRAM GB"

Note: TotalVisibleMemorySize and FreePhysicalMemory are returned in kilobytes, not bytes, so divide by 1MB to convert to GB.

Disk Drives and Free Space

Query Win32_LogicalDisk with a filter for local fixed disks (DriveType 3). For multi-drive machines, join all drives into a single summary string so the inventory fits on one row per computer in the CSV.

$disks = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType = 3"
$diskSummary = $disks | ForEach-Object {
    "$($_.DeviceID) $([math]::Round($_.FreeSpace/1GB,1))GB free / $([math]::Round($_.Size/1GB,1))GB"
}
$diskInfo = $diskSummary -join " | "
Write-Output $diskInfo

Last Boot Time

Last boot time is critical for patch compliance reporting — a machine that has not rebooted in 90 days probably has not applied updates. Calculate uptime in days for a more useful metric.

$os       = Get-CimInstance -ClassName Win32_OperatingSystem
$lastBoot = $os.LastBootUpTime
$upDays   = [math]::Round(((Get-Date) - $lastBoot).TotalDays, 1)

Write-Output "Last boot: $lastBoot — Uptime: $upDays days"

Export Multi-Computer Report to CSV

Combine all the queries above into a function that accepts a computer name and returns a single inventory object. Then call it across your entire machine list and export to CSV.

function Get-SystemInventory {
    param([string]$ComputerName = $env:COMPUTERNAME)

    $os    = Get-CimInstance -ComputerName $ComputerName -ClassName Win32_OperatingSystem
    $cpu   = Get-CimInstance -ComputerName $ComputerName -ClassName Win32_Processor | Select-Object -First 1
    $bios  = Get-CimInstance -ComputerName $ComputerName -ClassName Win32_BIOS
    $disks = Get-CimInstance -ComputerName $ComputerName -ClassName Win32_LogicalDisk -Filter "DriveType=3"

    [PSCustomObject]@{
        Computer    = $ComputerName
        OS          = $os.Caption
        OSVersion   = $os.Version
        CPU         = $cpu.Name.Trim()
        Cores       = $cpu.NumberOfCores
        TotalRAMGB  = [math]::Round($os.TotalVisibleMemorySize / 1MB, 1)
        FreeRAMGB   = [math]::Round($os.FreePhysicalMemory / 1MB, 1)
        Serial      = $bios.SerialNumber
        Disks       = ($disks | ForEach-Object { "$($_.DeviceID):$([math]::Round($_.FreeSpace/1GB,1))GB/$([math]::Round($_.Size/1GB,1))GB" }) -join "; "
        LastBoot    = $os.LastBootUpTime
        UptimeDays  = [math]::Round(((Get-Date) - $os.LastBootUpTime).TotalDays, 1)
    }
}

$computers = Get-Content -Path "C:\Logs\computers.txt"
$inventory = foreach ($pc in $computers) {
    try {
        Get-SystemInventory -ComputerName $pc
    } catch {
        Write-Warning "Failed: $pc — $_"
    }
}

$inventory | Export-Csv -Path "C:\Logs\inventory-$(Get-Date -Format yyyyMMdd).csv" -NoTypeInformation
Write-Output "Inventory complete. $($inventory.Count) machines reported."

Common Errors and Fixes

  • Memory values in bytes need conversion: Win32_PhysicalMemory.Capacity is in bytes. Win32_OperatingSystem.TotalVisibleMemorySize and FreePhysicalMemory are in kilobytes. Always check the WMI class documentation or test a known machine to confirm units before putting raw values in a report.
  • Remote queries need WinRM enabled: If remote collection fails, confirm WinRM is running on the target with Test-WSMan -ComputerName server01. On machines where WinRM cannot be enabled, use New-CimSession -SessionOption (New-CimSessionOption -Protocol Dcom) to fall back to DCOM transport.

Related Cmdlets / See Also

Wrapping Up

The Get-SystemInventory function above is production-ready as-is — run it against your full computer list and you’ll have an auditable CSV in minutes. As a next step, schedule it as a weekly task and store results with a datestamp so you can track hardware changes and flag machines that haven’t rebooted in over 30 days.

Send-Item -To