Files
lxs/windows/tools/system-info.ps1
T

281 lines
13 KiB
PowerShell

<#
LXS - System Infos (Windows)
Description: Essential system monitoring and diagnostic views.
Read-only: nothing here changes the machine.
Repo: https://git.hyko.cx/hykocx/lxs
#>
# Load LXS common library (colors, UI helpers, spinner, loggers, guards).
# Prefers the sibling ..\lib\common.ps1 (repo checkout or installed layout) and
# only hits the network when this script is run standalone.
$LxsRawBase = if ($env:LXS_RAW_BASE) { $env:LXS_RAW_BASE } else { 'https://git.hyko.cx/hykocx/lxs/raw/branch/main' }
if (-not $env:LXS_RAW_PLATFORM_BASE) { $env:LXS_RAW_PLATFORM_BASE = "$LxsRawBase/windows" }
$LxsLibPath = if ($PSScriptRoot) { Join-Path $PSScriptRoot '..\lib\common.ps1' } else { $null }
if ($LxsLibPath -and (Test-Path $LxsLibPath)) {
. $LxsLibPath
} else {
try {
$LxsLibSource = Invoke-RestMethod -Uri "$env:LXS_RAW_PLATFORM_BASE/lib/common.ps1" -UseBasicParsing -ErrorAction Stop
} catch {
Write-Error 'Failed to fetch lib/common.ps1'
exit 1
}
# The library ships with a UTF-8 BOM (Windows PowerShell 5.1 needs it to
# read the file as UTF-8); over HTTP that BOM arrives as a leading U+FEFF
# character, which the parser will not accept.
. ([scriptblock]::Create(($LxsLibSource -replace '^\uFEFF', '')))
}
$env:LXS_LOG_FILE = Join-Path (Get-LxsTempDir) 'lxs_system_infos.log'
function Write-LxsField {
param([string]$Label, $Value)
Write-Host "$($script:Cyan)$($script:Bold)${Label}:$($script:NC)"
foreach ($line in @($Value)) { Write-Host " $line" }
Write-Host ''
}
function Show-LxsSystemInformation {
Clear-Host
Show-LxsBoxTop -Title 'SYSTEM INFORMATION'
Write-Host ''
try {
$os = Get-CimInstance Win32_OperatingSystem
$cs = Get-CimInstance Win32_ComputerSystem
$cpu = @(Get-CimInstance Win32_Processor)
$bios = Get-CimInstance Win32_BIOS
Write-LxsField 'Operating System' "$($os.Caption) $($os.OSArchitecture)"
Write-LxsField 'Version / Build' "$($os.Version) (build $($os.BuildNumber))"
Write-LxsField 'Installed On' $os.InstallDate
$span = (Get-Date) - $os.LastBootUpTime
Write-LxsField 'System Uptime' ("{0} days, {1} hours, {2} minutes" -f $span.Days, $span.Hours, $span.Minutes)
Write-LxsField 'Hostname' $env:COMPUTERNAME
Write-LxsField 'Manufacturer' "$($cs.Manufacturer) $($cs.Model)"
Write-LxsField 'BIOS' "$($bios.Manufacturer) $($bios.SMBIOSBIOSVersion)"
Write-LxsField 'CPU' ($cpu | ForEach-Object { "$($_.Name.Trim()) ($($_.NumberOfCores)C/$($_.NumberOfLogicalProcessors)T)" })
Write-LxsField 'Memory' ("{0} GB total" -f [math]::Round($cs.TotalPhysicalMemory / 1GB, 1))
Write-LxsField 'PowerShell' $PSVersionTable.PSVersion.ToString()
} catch {
Write-LxsErr "Could not read system information: $($_.Exception.Message)"
}
try {
$gpu = @(Get-CimInstance Win32_VideoController -ErrorAction Stop |
ForEach-Object { "$($_.Name) (driver $($_.DriverVersion))" })
if ($gpu.Count -gt 0) { Write-LxsField 'GPU' $gpu }
} catch {
# Headless or restricted WMI - not worth failing the whole view.
}
# Activation status. LicenseStatus 1 = licensed.
try {
$lic = @(Get-CimInstance SoftwareLicensingProduct -ErrorAction Stop |
Where-Object { $_.PartialProductKey -and $_.Name -like 'Windows*' } | Select-Object -First 1)
if ($lic.Count -gt 0) {
$state = switch ([int]$lic[0].LicenseStatus) {
0 { 'Unlicensed' }
1 { 'Licensed' }
2 { 'Out-of-box grace period' }
3 { 'Out-of-tolerance grace period' }
4 { 'Non-genuine grace period' }
5 { 'Notification (not activated)' }
6 { 'Extended grace' }
default { 'Unknown' }
}
Write-LxsField 'Activation' "$state - $($lic[0].Name)"
}
} catch {
# SoftwareLicensingProduct is unavailable on some SKUs.
}
}
function Show-LxsDiskSpace {
Clear-Host
Show-LxsBoxTop -Title 'DISK SPACE'
Write-Host ''
Get-CimInstance Win32_LogicalDisk -Filter 'DriveType=3' | ForEach-Object {
$totalGB = [math]::Round($_.Size / 1GB, 1)
$freeGB = [math]::Round($_.FreeSpace / 1GB, 1)
$usedGB = [math]::Round(($_.Size - $_.FreeSpace) / 1GB, 1)
$pct = if ($_.Size -gt 0) { [math]::Round(100 * ($_.Size - $_.FreeSpace) / $_.Size) } else { 0 }
$bar = ('#' * [math]::Round($pct / 5)).PadRight(20, '.')
$color = if ($pct -ge 90) { $script:Red } else { $script:Cyan }
Write-Host (" $($script:White)$($script:Bold){0,-4}$($script:NC) {1}[{2}]$($script:NC) {3,6} GB used / {4,6} GB free / {5,6} GB total ({6}%)" -f `
$_.DeviceID, $color, $bar, $usedGB, $freeGB, $totalGB, $pct)
}
Write-Host ''
}
function Show-LxsMemoryUsage {
Clear-Host
Show-LxsBoxTop -Title 'MEMORY USAGE'
Write-Host ''
$os = Get-CimInstance Win32_OperatingSystem
$totalGB = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2)
$freeGB = [math]::Round($os.FreePhysicalMemory / 1MB, 2)
$usedGB = [math]::Round($totalGB - $freeGB, 2)
$pct = if ($totalGB -gt 0) { [math]::Round(100 * $usedGB / $totalGB) } else { 0 }
Write-LxsField 'Physical Memory' "$usedGB GB used / $freeGB GB free / $totalGB GB total ($pct%)"
$pageTotal = [math]::Round($os.SizeStoredInPagingFiles / 1MB, 2)
$pageFree = [math]::Round($os.FreeSpaceInPagingFiles / 1MB, 2)
Write-LxsField 'Paging File' "$([math]::Round($pageTotal - $pageFree, 2)) GB used / $pageTotal GB total"
Write-Host "$($script:Cyan)$($script:Bold)Top 10 processes by memory:$($script:NC)"
Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First 10 |
Format-Table -AutoSize @{ Name = 'PID'; Expression = { $_.Id } },
@{ Name = 'Name'; Expression = { $_.ProcessName } },
@{ Name = 'Memory (MB)'; Expression = { [math]::Round($_.WorkingSet64 / 1MB, 1) } } |
Out-String | Write-Host
}
function Show-LxsCpuLoad {
Clear-Host
Show-LxsBoxTop -Title 'CPU LOAD'
Write-Host ''
try {
$load = (Get-CimInstance Win32_Processor | Measure-Object -Property LoadPercentage -Average).Average
Write-LxsField 'Current Load' "$load%"
} catch {
Write-LxsWarn 'Could not read the CPU load counter.'
}
Write-LxsField 'Logical Processors' $env:NUMBER_OF_PROCESSORS
Write-Host "$($script:Cyan)$($script:Bold)Top 10 processes by CPU time:$($script:NC)"
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10 |
Format-Table -AutoSize @{ Name = 'PID'; Expression = { $_.Id } },
@{ Name = 'Name'; Expression = { $_.ProcessName } },
@{ Name = 'CPU (s)'; Expression = { if ($_.CPU) { [math]::Round($_.CPU, 1) } else { 0 } } } |
Out-String | Write-Host
}
function Show-LxsNetwork {
Clear-Host
Show-LxsBoxTop -Title 'NETWORK'
Write-Host ''
try {
Get-NetIPConfiguration -ErrorAction Stop | ForEach-Object {
Write-Host "$($script:Cyan)$($script:Bold)$($_.InterfaceAlias)$($script:NC) $($script:Gray)($($_.InterfaceDescription))$($script:NC)"
Write-Host " IPv4 : $($_.IPv4Address.IPAddress -join ', ')"
Write-Host " Gateway : $($_.IPv4DefaultGateway.NextHop -join ', ')"
Write-Host " DNS : $($_.DNSServer.ServerAddresses -join ', ')"
Write-Host ''
}
} catch {
Write-LxsWarn 'Get-NetIPConfiguration unavailable; falling back to ipconfig.'
ipconfig /all | Out-String | Write-Host
}
Write-LxsField 'Public IP' (Get-LxsPublicIP)
}
function Show-LxsEventLogs {
Clear-Host
Show-LxsBoxTop -Title 'SYSTEM LOGS' -Right 'LAST 50'
Write-Host ''
foreach ($logName in @('System', 'Application')) {
Write-Host "$($script:Cyan)$($script:Bold)$logName - errors and warnings$($script:NC)"
try {
$events = Get-WinEvent -FilterHashtable @{ LogName = $logName; Level = 1, 2, 3 } -MaxEvents 25 -ErrorAction Stop
$events | Format-Table -AutoSize -Wrap `
@{ Name = 'Time'; Expression = { $_.TimeCreated.ToString('MM-dd HH:mm') } },
@{ Name = 'Level'; Expression = { $_.LevelDisplayName } },
@{ Name = 'Source'; Expression = { $_.ProviderName } },
@{ Name = 'Message'; Expression = { ($_.Message -split "`n")[0] } } |
Out-String -Width 160 | Write-Host
} catch {
Write-LxsWarn "No matching entries in $logName (or access denied)."
}
Write-Host ''
}
}
function Show-LxsTopProcesses {
Clear-Host
Show-LxsBoxTop -Title 'TOP PROCESSES'
Write-Host ''
Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First 20 |
Format-Table -AutoSize @{ Name = 'PID'; Expression = { $_.Id } },
@{ Name = 'Name'; Expression = { $_.ProcessName } },
@{ Name = 'Memory (MB)'; Expression = { [math]::Round($_.WorkingSet64 / 1MB, 1) } },
@{ Name = 'CPU (s)'; Expression = { if ($_.CPU) { [math]::Round($_.CPU, 1) } else { 0 } } },
@{ Name = 'Threads'; Expression = { $_.Threads.Count } } |
Out-String | Write-Host
}
function Show-LxsDiskHealth {
Clear-Host
Show-LxsBoxTop -Title 'DISK HEALTH' -Right 'SMART'
Write-Host ''
try {
$disks = @(Get-PhysicalDisk -ErrorAction Stop)
} catch {
Write-LxsErr 'Get-PhysicalDisk is unavailable on this system.'
return
}
foreach ($disk in $disks) {
$sizeGB = [math]::Round($disk.Size / 1GB, 1)
$healthColor = if ($disk.HealthStatus -eq 'Healthy') { $script:Cyan } else { $script:Red }
Write-Host "$($script:White)$($script:Bold)$($disk.FriendlyName)$($script:NC) $($script:Gray)// $($disk.MediaType), $sizeGB GB$($script:NC)"
Write-Host " Health : $healthColor$($disk.HealthStatus)$($script:NC) (operational: $($disk.OperationalStatus -join ', '))"
# Reliability counters need admin; report the gap instead of failing.
try {
$rc = $disk | Get-StorageReliabilityCounter -ErrorAction Stop
if ($null -ne $rc.Temperature) { Write-Host " Temp : $($rc.Temperature) C" }
if ($null -ne $rc.Wear) { Write-Host " Wear : $($rc.Wear)%" }
if ($null -ne $rc.PowerOnHours) { Write-Host " Power on : $($rc.PowerOnHours) h" }
if ($null -ne $rc.ReadErrorsTotal) { Write-Host " Rd errors: $($rc.ReadErrorsTotal)" }
if ($null -ne $rc.WriteErrorsTotal) { Write-Host " Wr errors: $($rc.WriteErrorsTotal)" }
} catch {
if (-not (Test-LxsAdmin)) {
Write-Host "$($script:Gray) (run as administrator for SMART counters)$($script:NC)"
} else {
Write-Host "$($script:Gray) (this device exposes no reliability counters)$($script:NC)"
}
}
Write-Host ''
}
}
function Show-LxsSystemInfoMenu {
while ($true) {
Clear-Host
Show-LxsBoxTop -Title 'SYSTEM INFOS'
Write-Host ''
Show-LxsMenuItem '1' 'View system information'
Show-LxsMenuItem '2' 'Check disk space'
Show-LxsMenuItem '3' 'Check memory usage'
Show-LxsMenuItem '4' 'Check CPU load'
Show-LxsMenuItem '5' 'Check network'
Show-LxsMenuItem '6' 'View system logs (errors and warnings)'
Show-LxsMenuItem '7' 'Show top resource-consuming processes'
Show-LxsMenuItem '8' 'Check disk health (SMART)'
Show-LxsMenuItem '0' 'Back' '' -Exit
Write-Host ''
Show-LxsBoxBottom
Write-Host ''
$choice = Read-LxsChoice
Write-Host ''
switch ($choice) {
'1' { Show-LxsSystemInformation; Read-LxsEnter }
'2' { Show-LxsDiskSpace; Read-LxsEnter }
'3' { Show-LxsMemoryUsage; Read-LxsEnter }
'4' { Show-LxsCpuLoad; Read-LxsEnter }
'5' { Show-LxsNetwork; Read-LxsEnter }
'6' { Show-LxsEventLogs; Read-LxsEnter }
'7' { Show-LxsTopProcesses; Read-LxsEnter }
'8' { Show-LxsDiskHealth; Read-LxsEnter }
'0' { return }
default { Write-LxsErr 'Invalid protocol. Select 0-8.'; Start-Sleep -Seconds 1 }
}
}
}
if (-not (Assert-LxsWindows)) { exit 1 }
Show-LxsSystemInfoMenu
exit 75