278 lines
12 KiB
PowerShell
278 lines
12 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
|
|
}
|
|
. ([scriptblock]::Create($LxsLibSource))
|
|
}
|
|
|
|
$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
|