PowerShell Get-ComputerSpecs: Your IT Swiss Army Knife

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).