In IT we often need to quickly get high-level information about our systems. That’s why I developed this PowerShell script, it grabs comprehensive hardware and software inventory from Windows machines, locally or remotely, in seconds.
Script Capabilities
This function delivers a treasure trove of system details in a single PSCustomObject:
Hardware Specs: CPU (cores/logical processors), motherboard details, RAM (manufacturer, capacity, DDR type/speed), disks (model, SSD/HDD/NVMe, size/free space), GPUs.
System Essentials: Manufacturer/model/serial, BIOS version, firmware mode (UEFI/Legacy), OS build/install date, last boot time, IP/MAC addresses.
Security Posture: TPM presence/version, Secure Boot status, BitLocker on system drive, Windows Defender (real-time protection), Firewall profiles, TLS support.
Operational Status: Power plan, Windows Update (pending/reboot flags).
The PowerShell Script
<#
.SYNOPSIS
Get-ComputerSpecs - Comprehensive Windows system inventory collection
.DESCRIPTION
Collects detailed hardware, software, and security information from local
or remote Windows systems using CIM instances. Outputs structured PSCustomObject
with CPU, RAM, disks (SSD/HDD/NVMe detection), motherboard, OS details,
security status (TPM, BitLocker, Defender), and operational info.
.PARAMETER ComputerName
Array of computer names to inventory. Defaults to local machine ($env:COMPUTERNAME).
.EXAMPLE
# Local inventory
Get-ComputerSpecs
.EXAMPLE
# Multiple remote systems
Get-ComputerSpecs -ComputerName "PC01", "PC02", "SERVER01"
.EXAMPLE
# Export PC specs to CSV
Get-ComputerSpecs -ComputerName (Get-Content computers.txt) |
Export-Csv "Inventory-$(Get-Date -f yyyyMMdd).csv" -NoTypeInformation
.NOTES
Author: Daniel Pineault, CARDA Consultants Inc.
Version: 1.000
Date: 2025-11-16
Components: Win32_CIM classes, Get-PhysicalDisk, BitLocker module
#>
function Get-ComputerSpecs {
[CmdletBinding()]
param (
[string[]]$ComputerName = $env:COMPUTERNAME
)
foreach ($Computer in $ComputerName) {
$ScriptBlock = {
# ============================================================
# Memory type mapping (SMBIOS)
# ============================================================
$MemoryTypeMap = @{
20 = "DDR"
21 = "DDR2"
22 = "DDR2 FB-DIMM"
24 = "DDR3"
26 = "DDR4"
34 = "DDR5"
}
# ============================================================
# Core system information
# ============================================================
$ComputerSystem = Get-CimInstance Win32_ComputerSystem
$BIOS = Get-CimInstance Win32_BIOS
$RAM = Get-CimInstance Win32_PhysicalMemory
$CPU = Get-CimInstance Win32_Processor
$BaseBoard = Get-CimInstance Win32_BaseBoard
$OS = Get-CimInstance Win32_OperatingSystem
$TPM = Get-CimInstance -Namespace root\cimv2\security\microsofttpm `
-Class Win32_Tpm -ErrorAction SilentlyContinue
$Chassis = Get-CimInstance Win32_SystemEnclosure -ErrorAction SilentlyContinue
# $Battery = Get-CimInstance Win32_Battery -ErrorAction SilentlyContinue
# $PortableBattery = Get-CimInstance Win32_PortableBattery -ErrorAction SilentlyContinue
# ============================================================
# Motherboard / Baseboard
# ============================================================
$Motherboard_Display = @(
$BaseBoard.Manufacturer
$BaseBoard.Product
$BaseBoard.Version
) -join "`n"
# ============================================================
# CPU display
# ============================================================
$CoreCount = ($CPU | Measure-Object NumberOfCores -Sum).Sum
$LogicalCount = ($CPU | Measure-Object NumberOfLogicalProcessors -Sum).Sum
$CoreLabel = if ($CoreCount -eq 1) {
"1 core"
} else {
"$CoreCount cores"
}
$LogicalLabel = if ($LogicalCount -eq 1) {
"1 logical processor"
} else {
"$LogicalCount logical processors"
}
$CPU_Display = @(
$CPU.Name
"$CoreLabel, $LogicalLabel"
) -join "`n"
# ============================================================
# Disk information (type, size, free space)
# ============================================================
$DiskDrives = Get-CimInstance Win32_DiskDrive
$PhysicalDisks = Get-PhysicalDisk -ErrorAction SilentlyContinue
$Partitions = Get-CimInstance Win32_DiskPartition
$LogicalDisks = Get-CimInstance Win32_LogicalDisk
$Disk_Display = foreach ($Disk in $DiskDrives) {
$DiskPartitions = $Partitions |
Where-Object { $_.DiskIndex -eq $Disk.Index }
$Volumes = foreach ($Part in $DiskPartitions) {
Get-CimAssociatedInstance -InputObject $Part `
-ResultClassName Win32_LogicalDisk
}
$FreeGB = if ($Volumes) {
[math]::Round(
($Volumes | Measure-Object FreeSpace -Sum).Sum / 1GB,
0
)
}
else { 0 }
$PD = $PhysicalDisks |
Where-Object { $_.FriendlyName -eq $Disk.Model } |
Select-Object -First 1
$MediaType = if ($PD) { $PD.MediaType } else { "Unknown" }
$BusType = if ($PD) { $PD.BusType } else { "Unknown" }
$TypeLabel = switch ($MediaType) {
"SSD" {
if ($BusType -eq "NVMe") { "SSD (NVMe)" }
elseif ($BusType -eq "SATA") { "SSD (SATA)" }
else { "SSD ($BusType)" }
}
"HDD" { "HDD" }
default { "Unknown" }
}
"$($Disk.Model) $TypeLabel $([math]::Round($Disk.Size / 1GB, 0))GB ($FreeGB GB Free)"
}
# ============================================================
# Drive breakdown (capacity, used %, free %)
# ============================================================
$DriveBreakdown = Get-CimInstance Win32_LogicalDisk |
Where-Object DriveType -eq 3 | # Local disks only
ForEach-Object {
$TotalGB = [math]::Round($_.Size / 1GB, 0)
$FreeGB = [math]::Round($_.FreeSpace / 1GB, 0)
$UsedGB = $TotalGB - $FreeGB
$FreePct = if ($_.Size -gt 0) {
[math]::Round(($_.FreeSpace / $_.Size) * 100, 0)
} else { 0 }
$UsedPct = 100 - $FreePct
"{0} ({1}) {2}GB total | {3}GB used ({4}%) | {5}GB free ({6}%)" -f `
$_.DeviceID,
$_.VolumeName,
$TotalGB,
$UsedGB,
$UsedPct,
$FreeGB,
$FreePct
}
# ============================================================
# GPU(s)
# ============================================================
$GPU_Display = (Get-CimInstance Win32_VideoController).Name
# ============================================================
# RAM display
# ============================================================
$RAM_Display = $RAM | ForEach-Object {
"$($_.Manufacturer) $([math]::Round($_.Capacity / 1GB, 0))GB " +
"$($MemoryTypeMap[$_.SMBIOSMemoryType]) $($_.Speed)MHz"
}
# ============================================================
# Firmware mode
# ============================================================
$Firmware = try {
Confirm-SecureBootUEFI | Out-Null
"UEFI"
} catch {
"Legacy"
}
# ============================================================
# TPM version
# ============================================================
$TPM_Version = if ($TPM.SpecVersion) {
($TPM.SpecVersion -split ',')[0]
} else {
"Not Present"
}
# ============================================================
# TLS support
# ============================================================
$TLS = @()
if ([Net.ServicePointManager]::SecurityProtocol -band [Net.SecurityProtocolType]::Tls12) { $TLS += "TLS 1.2" }
if ([Net.ServicePointManager]::SecurityProtocol -band 12288) { $TLS += "TLS 1.3" }
$TLS_Display = if ($TLS) { $TLS -join ", " } else { "Unknown" }
# ============================================================
# MAC address (active adapters)
# ============================================================
$MAC = Get-NetAdapter |
Where-Object Status -eq "Up" |
Select-Object -ExpandProperty MacAddress
# ============================================================
# OS install date (ISO)
# ============================================================
$OS_InstallDate = $null
if ($OS.InstallDate -and $OS.InstallDate -match '^\d{14}\.') {
$OS_InstallDate = [Management.ManagementDateTimeConverter]::ToDateTime($OS.InstallDate)
}
elseif (Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion') {
$regDate = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion').InstallDate
if ($regDate) {
$OS_InstallDate = (Get-Date '1970-01-01').AddSeconds($regDate)
}
}
if (-not $OS_InstallDate) {
$OS_InstallDate = "Unknown"
}
elseif ($OS_InstallDate -is [datetime]) {
$OS_InstallDate = $OS_InstallDate.ToString('yyyy-MM-dd HH:mm:ss')
}
# ============================================================
# Last OS boot time
# ============================================================
$LastBootTime = "Unknown"
# --- Method 1: Win32_OperatingSystem
try {
$BootRaw = (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
if ($BootRaw) {
$LastBootTime = [Management.ManagementDateTimeConverter]::ToDateTime(
$BootRaw
).ToString('yyyy-MM-dd HH:mm:ss')
}
}
catch {}
# --- Method 2: Event Log fallback
if ($LastBootTime -eq "Unknown") {
try {
$BootEvent = Get-WinEvent `
-FilterHashtable @{
LogName = 'System'
Id = 6005 # Event log service started (boot)
} `
-MaxEvents 1
if ($BootEvent.TimeCreated) {
$LastBootTime = $BootEvent.TimeCreated.ToString(
'yyyy-MM-dd HH:mm:ss'
)
}
}
catch {}
}
# ============================================================
# Power plan
# ============================================================
$PowerPlan = try {
$pp = Get-CimInstance -Namespace root\cimv2\power `
-ClassName Win32_PowerPlan `
-Filter "IsActive = TRUE"
"$($pp.ElementName) ($($pp.InstanceID -replace '.*\{|\}'))"
} catch {
"Unknown"
}
# ============================================================
# Windows Defender
# ============================================================
$DefenderStatus = try {
$MP = Get-CimInstance -Namespace root/Microsoft/Windows/Defender `
-ClassName MSFT_MpComputerStatus
if ($MP.AntivirusEnabled -and $MP.RealTimeProtectionEnabled) {
"Enabled (Real-Time Protection On)"
}
elseif ($MP.AntivirusEnabled) {
"Enabled (Real-Time Protection Off)"
}
else {
"Disabled"
}
} catch {
"Not Available"
}
# ============================================================
# Windows Firewall
# ============================================================
$FirewallStatus = try {
(Get-NetFirewallProfile | ForEach-Object {
"$($_.Name): $(if ($_.Enabled) { 'On' } else { 'Off' })"
}) -join "`n"
} catch {
"Unknown"
}
# ============================================================
# Windows Update status (accurate on Win10/11)
# ============================================================
$WindowsUpdate = try {
$WUService = Get-CimInstance Win32_Service `
-Filter "Name='wuauserv'"
$PendingUpdates = Test-Path `
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\PackagesPending'
$RebootRequired = Test-Path `
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired'
"StartupType=$($WUService.StartMode); State=$($WUService.State); PendingUpdates=$(
if ($PendingUpdates) { 'Yes' } else { 'No' }
); RebootRequired=$(
if ($RebootRequired) { 'Yes' } else { 'No' }
)"
}
catch {
"Unknown"
}
# ============================================================
# Device type detection
# ============================================================
$DeviceType = switch ($ComputerSystem.PCSystemType) {
1 { "Desktop" }
2 { "Mobile/Laptop" }
3 { "Workstation" }
4 { "Server" }
5 { "SOHO Server" }
6 { "Appliance" }
7 { "Performance Server" }
8 { "Maximum Performance Server" }
default {
# Fallback using chassis type
if ($Chassis.ChassisTypes -match '8|9|10|14') {
"Laptop"
}
elseif ($Chassis.ChassisTypes -match '3|4|5|6|7|15|16') {
"Desktop"
}
else {
"Unknown"
}
}
}
# Detect virtual machines
if ($ComputerSystem.Model -match 'Virtual|VMware|KVM|Hyper-V|VirtualBox') {
$DeviceType = "Virtual Machine"
}
# ============================================================
# Battery information (mobile devices)
# ============================================================
$BatteryStatus = "Not Present"
$BatteryCharge = "N/A"
$BatteryHealth = "N/A"
if ($DeviceType -in @("Mobile", "Laptop", "Tablet")) {
$Battery = Get-CimInstance Win32_Battery -ErrorAction SilentlyContinue
$PortableBattery = Get-CimInstance Win32_PortableBattery -ErrorAction SilentlyContinue
if ($Battery) {
$BatteryStatus = switch ($Battery.BatteryStatus) {
1 { "Discharging" }
2 { "AC Connected" }
3 { "Fully Charged" }
4 { "Low" }
5 { "Critical" }
default { "Unknown" }
}
if ($Battery.EstimatedChargeRemaining -ne $null) {
$BatteryCharge = "$($Battery.EstimatedChargeRemaining)%"
}
}
# Battery health calculation
if ($PortableBattery.DesignCapacity -and $PortableBattery.FullChargeCapacity) {
$BatteryHealth = "{0}%" -f (
[math]::Round(
($PortableBattery.FullChargeCapacity /
$PortableBattery.DesignCapacity) * 100,
0
)
)
}
}
# ============================================================
# Output
# ============================================================
[PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
Manufacturer = $ComputerSystem.Manufacturer
Model = $ComputerSystem.Model
SerialNumber = $BIOS.SerialNumber
BIOS_Version = $BIOS.SMBIOSBIOSVersion
Firmware = $Firmware
IPAddress = (Get-NetIPAddress -AddressFamily IPv4 |
Where-Object IPAddress -ne "127.0.0.1" |
Select-Object -ExpandProperty IPAddress -First 1)
MAC_Address = $MAC -join "`n"
Motherboard = $Motherboard_Display
CPU = $CPU_Display
RAM = $RAM_Display -join "`n"
Disks = $Disk_Display -join "`n"
DriveUsage = $DriveBreakdown -join "`n"
GPUs = $GPU_Display -join "`n"
DeviceType = $DeviceType
BatteryStatus = $BatteryStatus
BatteryCharge = $BatteryCharge
BatteryHealth = $BatteryHealth
Windows = $OS.Caption
Build = $OS.BuildNumber
InstallDate = $OS_InstallDate
LastBootTime = $LastBootTime
DefenderStatus = $DefenderStatus
FirewallStatus = $FirewallStatus
WindowsUpdate = $WindowsUpdate
PowerPlan = $PowerPlan
TPM_Present = $TPM.IsEnabled_InitialValue
TPM_Version = $TPM_Version
SecureBoot = [bool](Confirm-SecureBootUEFI -ErrorAction SilentlyContinue)
BitLocker = (Get-BitLockerVolume -MountPoint $env:SystemDrive `
-ErrorAction SilentlyContinue).VolumeStatus
TLS_Supported = $TLS_Display
}
}
if ($Computer -eq $env:COMPUTERNAME -or $Computer -eq 'localhost') {
& $ScriptBlock
}
else {
Invoke-Command -ComputerName $Computer -ScriptBlock $ScriptBlock
}
}
}
How to Use It
Single local machine (default):
Get-ComputerSpecs
Multiple remote systems:
Get-ComputerSpecs -ComputerName "PC01", "PC02", "SERVER01"
Output
It will output something along the lines of:
ComputerName : MSI
Manufacturer : Micro-Star International Co., Ltd.
Model : GP73 Leopard 8RE
SerialNumber : 9S717C513079ZIC000003
BIOS_Version : E17C5IMS.10D
Firmware : UEFI
IPAddress : 192.168.56.1
MAC_Address :
0A-00-17-00-00-15
00-50-53-C0-00-01
00-50-53-C0-00-06
0A-00-17-00-00-0C
18-1D-DA-31-69-F2
Motherboard : Micro-Star International Co., Ltd.
MS-17C5
REV:1.0
CPU : Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
6 cores, 12 logical processors
RAM : Samsung 16GB 2667MHz
Disks : Samsung SSD 870 EVO 2TB SSD (SATA) 1863GB (771 GB Free)
WDC PC SN720 SDAPNTW-512G SSD (NVMe) 477GB (67 GB Free)
DriveUsage : C: (Windows) 476GB total | 409GB used (86%) | 67GB free (14%)
D: (Data) 1863GB total | 1092GB used (59%) | 771GB free (41%)
M: () 400GB total | 359GB used (90%) | 41GB free (10%)
GPUs : NVIDIA GeForce GTX 1060
Intel(R) UHD Graphics 630
DeviceType : Mobile/Laptop
BatteryStatus : Not Present
BatteryCharge : N/A
BatteryHealth : N/A
Windows : Microsoft Windows 11 Home
Build : 26200
InstallDate : 2025-08-04 09:26:46
LastBootTime : 2026-02-11 14:03:55
DefenderStatus : Enabled (Real-Time Protection On)
FirewallStatus : Domain: On
Private: On
Public: On
WindowsUpdate : StartupType=Manual; State=Stopped; PendingUpdates=No; RebootRequired=No
PowerPlan : Ultimate Performance (fc1fae7a-891c-47b3-9930-ba61535019eb)
TPM_Present : True
TPM_Version : 2.0
SecureBoot : True
BitLocker : FullyDecrypted
TLS_Supported : TLS 1.2
It’s nice to be able to quickly pull this up, even remotely or run against a pool of computers with a single command to get up to speed, without needing to go through 5-15 different Windows dialogs.
Works locally via direct execution, remotely via Invoke-Command (PSRemoting required).