PS_HWinfo.psm1
|
# PS_HWinfo PowerShell Module # Provides a hardware info dashboard similar to HWinfo64 function Show-HWTerminalDashboard { param([Parameter(Mandatory)][psobject]$Snapshot) $cpu = $Snapshot.CPU $mb = $Snapshot.Motherboard $gp = @($Snapshot.GPU) $mem = $Snapshot.Memory $monitors = @($Snapshot.Monitors) Write-Host ("=" * 78) Write-Host ("PowerShell Utility Inspired by HWinfo64 — Report: {0} " -f $Snapshot.Timestamp.ToString("yyyy-MM-dd HH:mm:ss")) -ForegroundColor Cyan Write-Host "Written by: Jared Heinrichs (https://jaredheinrichs.substack.com)" -ForegroundColor DarkGray Write-Host ("=" * 78) # BIOS section first (left), Motherboard section right $biosLines = @() $biosLines += '[BIOS]' try { $biosObj = Get-WmiObject win32_bios | Select-Object -First 1 if ($biosObj) { $biosLines += "SMBIOSBIOSVersion: $($biosObj.SMBIOSBIOSVersion)" $biosLines += "Manufacturer: $($biosObj.Manufacturer)" $biosLines += "Name: $($biosObj.Name)" $biosLines += "SerialNumber: $($biosObj.SerialNumber)" $biosLines += "Version: $($biosObj.Version)" } else { $biosLines += "BIOS Info: n/a" } } catch { $biosLines += "BIOS Info: n/a" } $mbLines = @() $mbLines += '[Motherboard]' $mbLines += "{0} {1} (S/N: {2})" -f $mb.Manufacturer, $mb.Product, ($mb.SerialNumber ?? 'n/a') # Chassis type mapping $chassisTypeMap = @{ 1 = 'Other'; 2 = 'Unknown'; 3 = 'Desktop'; 4 = 'Low Profile Desktop'; 5 = 'Pizza Box'; 6 = 'Mini Tower'; 7 = 'Tower'; 8 = 'Portable'; 9 = 'Laptop'; 10 = 'Notebook'; 11 = 'Hand Held'; 12 = 'Docking Station'; 13 = 'All in One'; 14 = 'Sub Notebook'; 15 = 'Space-saving'; 16 = 'Lunch Box'; 17 = 'Main System Chassis'; 18 = 'Expansion Chassis'; 19 = 'SubChassis'; 20 = 'Bus Expansion Chassis'; 21 = 'Peripheral Chassis'; 22 = 'Storage Chassis'; 23 = 'Rack Mount Chassis'; 24 = 'Sealed-case PC'; 25 = 'Multi-system chassis'; 26 = 'Compact PCI'; 27 = 'Advanced TCA'; 28 = 'Blade'; 29 = 'Blade Enclosure'; 30 = 'Tablet'; 31 = 'Convertible'; 32 = 'Detachable'; 33 = 'IoT Gateway'; 34 = 'Embedded PC'; 35 = 'Mini PC'; 36 = 'Stick PC' } $chassisStr = 'n/a' if ($mb.ChassisType) { $types = $mb.ChassisType -split ',' | ForEach-Object { $_.Trim() } $chassisStr = ($types | ForEach-Object { $name = $chassisTypeMap[[int]$_] ?? 'Unknown' "$name (Code=$_)" }) -join ', ' } $mbLines += "Chassis: {0}" -f $chassisStr $mbLines += "Fans Detected: {0}" -f $mb.FansDetected # Print BIOS and Motherboard sections side by side Write-Host "" $maxBIOS = $biosLines.Count $maxMB = $mbLines.Count $maxLines = [Math]::Max($maxBIOS, $maxMB) $biosColWidth = ($biosLines | ForEach-Object { $_.Length } | Measure-Object -Maximum).Maximum if ($biosColWidth -lt 24) { $biosColWidth = 24 } for ($i = 0; $i -lt $maxLines; $i++) { $biosCol = if ($i -lt $biosLines.Count) { $biosLines[$i] } else { '' } $mbCol = if ($i -lt $mbLines.Count) { $mbLines[$i] } else { '' } $biosColPadded = $biosCol.PadRight($biosColWidth) if ($i -eq 0) { Write-Host ("{0} {1}" -f $biosColPadded, $mbCol) -ForegroundColor Yellow } elseif ($i -eq 1) { Write-Host ("{0} {1}" -f $biosColPadded, $mbCol) } else { Write-Host ("{0} {1}" -f $biosColPadded, $mbCol) } } if ($mb.FanInfo) { $mb.FanInfo | ForEach-Object { $name = $_.Name $ds = $_.DesiredSpeed $vs = $_.VariableSpeed if (($name -and $name -ne 'Cooling Device') -or ($ds -and $ds -ne 'n/a') -or ($vs -and $vs -ne 'n/a')) { " - {0} DesiredSpeed: {1} Variable: {2}" -f ($name ?? 'n/a'), ($ds ?? 'n/a'), ($vs ?? 'n/a') | Write-Host } } } if ($mb.StorageTemps) { Write-Host "Storage Temps:" -ForegroundColor DarkYellow $mb.StorageTemps | ForEach-Object { " - {0}: {1} °C" -f $_.Drive, $_.TempC | Write-Host } } # CPU section Write-Host "" Write-Host "[CPU]" -ForegroundColor Yellow $cpuTable = [PSCustomObject]@{ Sockets = $cpu.Sockets Cores = $cpu.Cores Logical = $cpu.LogicalProcessors 'Base(MHz)' = $cpu.BaseClockMHz 'Current(MHz)' = $cpu.CurrentClockMHz 'Load(%)' = $cpu.LoadPercent 'L3 Cache' = $cpu.L3Cache } $cpu.Name | Write-Host $cpuTable | Format-Table -AutoSize if ($mb.FanInfo) { $mb.FanInfo | ForEach-Object { $name = $_.Name $ds = $_.DesiredSpeed $vs = $_.VariableSpeed if (($name -and $name -ne 'Cooling Device') -or ($ds -and $ds -ne 'n/a') -or ($vs -and $vs -ne 'n/a')) { " - {0} DesiredSpeed: {1} Variable: {2}" -f ($name ?? 'n/a'), ($ds ?? 'n/a'), ($vs ?? 'n/a') | Write-Host } } } if ($mb.StorageTemps) { Write-Host "Storage Temps:" -ForegroundColor DarkYellow $mb.StorageTemps | ForEach-Object { " - {0}: {1} °C" -f $_.Drive, $_.TempC | Write-Host } } # GPU(s) Write-Host "`n[GPU]" -ForegroundColor Yellow if ($gp.Count -eq 0 -or ($gp.Count -eq 1 -and -not $gp[0].Name)) { Write-Host "(No GPU info found)" } else { $gpuTable = foreach ($g in $gp) { if ($g.Name) { [PSCustomObject]@{ Name = $g.Name VRAM = $g.AdapterRAM Driver = $g.DriverVersion AdapterCompatibility = $g.AdapterCompatibility } } } $gpuTable | Format-Table -AutoSize } # Monitors Write-Host "[Monitors]" -ForegroundColor Yellow if ($monitors.Count -eq 0 -or ($monitors.Count -eq 1 -and -not $monitors[0].DeviceName)) { Write-Host "(No monitor info found)" } else { $monitorTable = foreach ($m in $monitors) { [PSCustomObject]@{ Device = ($m.DeviceName -replace '^[.\\]+', '') Primary = if ($m.Primary) { 'Yes' } else { '' } Resolution = $m.Resolution Serial = $m.SerialNumber Mfg = $m.Manufacturer Model = $m.Model } } $monitorTable | Format-Table -AutoSize } # Memory Write-Host "[Memory]" -ForegroundColor Yellow if ($mem.Modules -and $mem.Modules.Count -gt 0) { $mem.Modules | Format-Table Manufacturer, BankLabel, PartNumber, Speed, CapacityGB -AutoSize $summary = "Total: {0} | Used: {1} | Free: {2} | Modules Total: {3} GB" -f ` ($mem.Total ?? 'n/a'), ($mem.Used ?? 'n/a'), ($mem.Free ?? 'n/a'), ($mem.TotalModuleGB ?? 'n/a') Write-Host ("-" * 78) Write-Host $summary } else { $memObj = [PSCustomObject]@{ Total = $mem.Total Used = $mem.Used Free = $mem.Free PhysicalRAM = $mem.PhysicalRAM } $memObj | Format-Table -AutoSize } Write-Host ("-" * 78) # Hard Drives Section Write-Host Write-Host "[Hard Drives]" -ForegroundColor Yellow try { $drives = Get-PhysicalDisk | Select-Object DeviceId, Model, MediaType, BusType, @{Name="SizeGB"; Expression={ [int]($_.Size/1Gb) }} if ($drives -and $drives.Count -gt 0) { $drives | Sort-Object DeviceId | Format-Table DeviceId, Model, MediaType, BusType, SizeGB -AutoSize } else { Write-Host "(No physical disks found)" } } catch { Write-Host "(Error retrieving physical disk info)" } Write-Host ("-" * 78) Write-Host "Press [Enter] to exit" -ForegroundColor Cyan Read-Host | Out-Null } function Get-HWTerminalSnapshot { [CmdletBinding()] param() try { $cpu = Get-CpuInfo } catch { $cpu = $null } try { $mb = Get-MotherboardInfo } catch { $mb = $null } try { $gpuInfo = Get-GpuInfo $gpu = $gpuInfo.GPUs $monitors = $gpuInfo.Monitors } catch { $gpu = @(); $monitors = @() } try { $mem = Try-GetCim -Class Win32_OperatingSystem $phys = Try-GetCim -Class Win32_ComputerSystem $memModules = Get-CimInstance Win32_PhysicalMemory $memObj = $null $modules = @() $totalModuleGB = 0 if ($memModules) { foreach ($mod in $memModules) { $gb = [math]::Round($mod.Capacity / 1GB, 2) $totalModuleGB += $gb $modules += [pscustomobject]@{ Manufacturer = $mod.Manufacturer BankLabel = $mod.BankLabel PartNumber = $mod.PartNumber Speed = $mod.Speed CapacityGB = $gb } } } if ($mem) { $total = $mem.TotalVisibleMemorySize * 1KB $free = $mem.FreePhysicalMemory * 1KB $memObj = [pscustomobject]@{ Section = "Memory" Total = Format-Bytes $total Free = Format-Bytes $free Used = Format-Bytes ($total - $free) PhysicalRAM = if ($phys.TotalPhysicalMemory) { Format-Bytes $phys.TotalPhysicalMemory } else { $null } Modules = $modules TotalModuleGB = $totalModuleGB } } } catch { $memObj = $null } [pscustomobject]@{ Timestamp = (Get-Date) User = $env:USERNAME CPU = $cpu Motherboard = $mb GPU = $gpu Monitors = $monitors Memory = $memObj } } function Try-GetCim { param( [Parameter(Mandatory)][string]$Class, [string]$Namespace = 'root\cimv2', [hashtable]$PropertyMap = @{} ) try { $o = Get-CimInstance -ClassName $Class -Namespace $Namespace -ErrorAction Stop if ($PropertyMap.Count -gt 0 -and $o) { $sel = [ordered]@{} foreach ($k in $PropertyMap.Keys) { $sel[$k] = $o.$($PropertyMap[$k]) } return [pscustomobject]$sel } return $o } catch { Write-Verbose "CIM $Class failed: $($_.Exception.Message)" return $null } } function Try-GetCounter { param([Parameter(Mandatory)][string]$Path) try { (Get-Counter -Counter $Path -ErrorAction Stop).CounterSamples | Select-Object CookedValue } catch { Write-Verbose "Counter $Path failed: $($_.Exception.Message)" $null } } function Format-Bytes { param([double]$Bytes) $units = "B", "KB", "MB", "GB", "TB" $i = 0 while ($Bytes -ge 1024 -and $i -lt $units.Length - 1) { $Bytes /= 1024; $i++ } "{0:N2} {1}" -f $Bytes, $units[$i] } function Get-CpuInfo { $cpu = Try-GetCim -Class Win32_Processor if (-not $cpu) { return [pscustomobject]@{ Section = "CPU"; Error = "Win32_Processor unavailable" } } $load = Try-GetCounter '\Processor(_Total)\% Processor Time' $mhz = Try-GetCounter '\Processor Information(_Total)\Processor Frequency' $tz = Try-GetCim -Class MSAcpi_ThermalZoneTemperature -Namespace root\WMI $pkgTemp = $null if ($tz) { foreach ($t in $tz) { if ($t.CurrentTemperature -gt 0) { $pkgTemp = [math]::Round(($t.CurrentTemperature / 10 - 273.15), 1) break } } } $logical = ($cpu | Measure-Object -Property NumberOfLogicalProcessors -Sum).Sum $cores = ($cpu | Measure-Object -Property NumberOfCores -Sum).Sum $baseMHz = ($cpu | Select-Object -ExpandProperty MaxClockSpeed | Measure-Object -Average).Average $voltage = $cpu.CurrentVoltage if ($voltage -is [int] -and $voltage -gt 0) { $voltage = "$voltage (tenths of volt encoded)" } else { $voltage = $null } [pscustomobject]@{ Section = "CPU" Name = $cpu.Name Manufacturer = $cpu.Manufacturer Sockets = ($cpu | Measure-Object).Count Cores = $cores LogicalProcessors = $logical BaseClockMHz = [math]::Round($baseMHz, 0) CurrentClockMHz = if ($mhz) { [math]::Round($mhz.CookedValue, 0) } else { $null } PackageTemperatureC = $pkgTemp Voltage = $voltage LoadPercent = if ($load) { [math]::Round($load.CookedValue, 1) } else { $null } L3Cache = ($cpu.L3CacheSize) ? ("{0} KB" -f $cpu.L3CacheSize) : $null Caption = $cpu.Caption } } function Get-MotherboardInfo { $base = Try-GetCim -Class Win32_BaseBoard $bios = Try-GetCim -Class Win32_BIOS $chassis = Try-GetCim -Class Win32_SystemEnclosure $fans = Try-GetCim -Class Win32_Fan $smartTemps = @() try { $nvme = Get-CimInstance -Namespace root\wmi -Class MSFT_NvmeDriveInformation -ErrorAction Stop foreach ($n in $nvme) { if ($n.Temperature) { $smartTemps += [pscustomobject]@{ Drive = $n.ModelNumber; TempC = $n.Temperature } } } } catch {} [pscustomobject]@{ Section = "Motherboard" Manufacturer = $base.Manufacturer Product = $base.Product SerialNumber = $base.SerialNumber BIOSVersion = (@($bios.SMBIOSBIOSVersion) + @($bios.BIOSVersion) | Where-Object { $_ } | ForEach-Object { $_ -is [array] ? ($_.Trim() -join '; ') : $_ } | Where-Object { $_ }) -join " | " BIOSReleaseDate = if ($bios.ReleaseDate -is [datetime]) { $bios.ReleaseDate } elseif ($bios.ReleaseDate) { [datetime]::ParseExact(($bios.ReleaseDate -as [string]).Split('.')[0], 'yyyyMMddHHmmss', $null) } else { $null } ChassisType = ($chassis.ChassisTypes -join ',') FansDetected = if ($fans) { ($fans | Measure-Object).Count } else { 0 } FanInfo = if ($fans) { $fans | Select-Object Name, DesiredSpeed, VariableSpeed } else { $null } StorageTemps = if ($smartTemps.Count) { $smartTemps } else { $null } } } function Get-GpuInfo { $gpus = Try-GetCim -Class Win32_VideoController $list = @() if ($gpus) { foreach ($gpu in $gpus) { $list += [pscustomobject]@{ Name = $gpu.Name AdapterRAM = if ($gpu.AdapterRAM) { Format-Bytes $gpu.AdapterRAM } else { $null } DriverVersion = $gpu.DriverVersion DriverDate = $gpu.DriverDate CurrentResolution = if ($gpu.CurrentHorizontalResolution -and $gpu.CurrentVerticalResolution) { "{0}x{1}" -f $gpu.CurrentHorizontalResolution, $gpu.CurrentVerticalResolution } else { $null } CurrentRefreshHz = $gpu.CurrentRefreshRate VideoProcessor = $gpu.VideoProcessor Status = $gpu.Status DeviceID = $gpu.DeviceID PNPDeviceID = $gpu.PNPDeviceID AdapterCompatibility = $gpu.AdapterCompatibility } } } $monitors = @() try { Add-Type -AssemblyName System.Windows.Forms $screens = [System.Windows.Forms.Screen]::AllScreens $wmiMonitors = Get-CimInstance -ClassName Win32_DesktopMonitor $pnpMonitors = Get-CimInstance -ClassName Win32_PnPEntity | Where-Object { $_.PNPClass -eq 'Monitor' } $wmiMonitorIDs = Get-CimInstance -Namespace root\wmi -ClassName WmiMonitorID function Decode-WmiString($arr) { if (-not $arr) { return $null } -join ($arr | Where-Object { $_ -ne 0 } | ForEach-Object { [char]$_ }) } $monitorIdList = @() foreach ($id in $wmiMonitorIDs) { $monitorIdList += [pscustomobject]@{ Manufacturer = Decode-WmiString $id.ManufacturerName Model = Decode-WmiString $id.UserFriendlyName SerialNumber = Decode-WmiString $id.SerialNumberID InstanceName = $id.InstanceName } } $i = 0 foreach ($s in $screens) { $devName = $s.DeviceName $monId = $null if ($monitorIdList.Count -gt $i) { $monId = $monitorIdList[$i] } $wmi = $wmiMonitors | Where-Object { $_.PNPDeviceID -and $devName -replace '^.*?([A-Z0-9]+)$', '$1' -like $_.PNPDeviceID -replace '\\', '' } if (-not $wmi) { $wmi = $null } $pnp = $pnpMonitors | Where-Object { $_.Name -and $devName -replace '^.*?([A-Z0-9]+)$', '$1' -like $_.Name } if (-not $pnp) { $pnp = $null } $monitors += [pscustomobject]@{ DeviceName = $s.DeviceName Primary = $s.Primary Bounds = "{0}x{1}+{2}+{3}" -f $s.Bounds.Width, $s.Bounds.Height, $s.Bounds.X, $s.Bounds.Y Resolution = "{0}x{1}" -f $s.Bounds.Width, $s.Bounds.Height WorkingArea = "{0}x{1}+{2}+{3}" -f $s.WorkingArea.Width, $s.WorkingArea.Height, $s.WorkingArea.X, $s.WorkingArea.Y SerialNumber = $monId.SerialNumber ?? ($wmi.SerialNumber | Select-Object -First 1) Manufacturer = $monId.Manufacturer ?? ($wmi.MonitorManufacturer | Select-Object -First 1) Model = $monId.Model ?? ($wmi.Name | Select-Object -First 1) PnPName = $pnp.Name | Select-Object -First 1 } $i++ } } catch { $monitors = $null } return @{ GPUs = $list; Monitors = $monitors } } function Start-PSHWinfo { [CmdletBinding()] param() Clear-Host Write-Host "Starting PowerShell Utility that was inspired by HWinfo64..." -ForegroundColor Cyan $shot = $null try { $shot = Get-HWTerminalSnapshot } catch { $shot = $null } if (-not $shot -or -not $shot.CPU) { Write-Host "Error: Unable to retrieve hardware information. Please ensure you have the necessary permissions and try again." -ForegroundColor Red return } Clear-Host Show-HWTerminalDashboard -Snapshot $shot } Export-ModuleMember -Function Start-PSHWinfo |