<#
.SYNOPSIS
Comprehensive Hardware Monitoring Script for Windows Systems
.DESCRIPTION
Monitors CPU/GPU temperatures, fan speeds, SMART drive data, and memory health
.NOTES
Requires Administrator privileges for full functionality
Some features may require additional WMI providers or specific hardware support
#>
param(
[switch]$checkTemperatures,
[switch]$checkFanSpeeds,
[switch]$checkSmartStatus,
[switch]$checkMemoryHealth,
[switch]$checkDiskUsage,
[switch]$scanLargeFiles
)
# Default all switches to true if not provided
$checkTemperatures = if ($PSBoundParameters.ContainsKey("checkTemperatures")) { $checkTemperatures } else { $true }
$checkFanSpeeds = if ($PSBoundParameters.ContainsKey("checkFanSpeeds")) { $checkFanSpeeds } else { $true }
$checkSmartStatus = if ($PSBoundParameters.ContainsKey("checkSmartStatus")) { $checkSmartStatus } else { $true }
$checkMemoryHealth = if ($PSBoundParameters.ContainsKey("checkMemoryHealth")) { $checkMemoryHealth } else { $true }
$checkDiskUsage = if ($PSBoundParameters.ContainsKey("checkDiskUsage")) { $checkDiskUsage } else { $false }
$scanLargeFiles = if ($PSBoundParameters.ContainsKey("scanLargeFiles")) { $scanLargeFiles } else { $false }
# Initialize results object with the expected structure
$Output = @{
Temperatures = @()
FanSpeeds = @()
SMARTStatus = @()
MemoryHealth = @{
Status = "Unknown"
Errors = @()
}
DiskUsage = @()
LargeFiles = @()
LargeFolders = @()
Errors = @()
}
Add-Type -AssemblyName System.Web
#region Temperature Monitoring
if ($checkTemperatures) {
try {
# CPU Temperature (WMI - may not work on all systems)
$cpuTemp = Get-WmiObject -Namespace "root\wmi" -Class "MSAcpi_ThermalZoneTemperature" -ErrorAction SilentlyContinue
if ($cpuTemp) {
foreach ($temp in $cpuTemp) {
$celsius = [math]::Round(($temp.CurrentTemperature / 10) - 273.15, 1)
$Output.Temperatures += @{
Sensor = "CPU_Zone_$($temp.InstanceName)"
TemperatureC = $celsius
Status = if ($celsius -gt 80) { "Critical" } elseif ($celsius -gt 70) { "Warning" } else { "Normal" }
}
}
}
# Alternative CPU temp via OpenHardwareMonitor WMI (if installed)
$ohmTemp = Get-WmiObject -Namespace "root\OpenHardwareMonitor" -Class "Sensor" -ErrorAction SilentlyContinue |
Where-Object { $_.SensorType -eq "Temperature" }
if ($ohmTemp) {
foreach ($sensor in $ohmTemp) {
$Output.Temperatures += @{
Sensor = $sensor.Name
TemperatureC = [math]::Round($sensor.Value, 1)
Status = if ($sensor.Value -gt 80) { "Critical" } elseif ($sensor.Value -gt 70) { "Warning" } else { "Normal" }
}
}
}
# GPU Temperature via WMI (NVIDIA/AMD specific)
$gpuTemp = Get-WmiObject -Namespace "root\cimv2" -Class "Win32_TemperatureProbe" -ErrorAction SilentlyContinue
if ($gpuTemp) {
foreach ($probe in $gpuTemp) {
if ($probe.CurrentReading) {
$celsius = [math]::Round(($probe.CurrentReading / 10), 1)
$Output.Temperatures += @{
Sensor = "GPU_$($probe.DeviceID)"
TemperatureC = $celsius
Status = if ($celsius -gt 85) { "Critical" } elseif ($celsius -gt 75) { "Warning" } else { "Normal" }
}
}
}
}
if ($Output.Temperatures.Count -eq 0) {
$Output.Errors += "No temperature sensors accessible via standard WMI queries"
}
}
catch {
$Output.Errors += "Temperature monitoring error: $($_.Exception.Message)"
}
}
#endregion
#region Fan Speed Monitoring
if ($checkFanSpeeds) {
try {
# Fan speeds via WMI
$fans = Get-WmiObject -Namespace "root\cimv2" -Class "Win32_Fan" -ErrorAction SilentlyContinue
if ($fans) {
foreach ($fan in $fans) {
$Output.FanSpeeds += @{
Fan = $fan.DeviceID
SpeedRPM = [int]$fan.DesiredSpeed
}
}
}
# Alternative via OpenHardwareMonitor
$ohmFans = Get-WmiObject -Namespace "root\OpenHardwareMonitor" -Class "Sensor" -ErrorAction SilentlyContinue |
Where-Object { $_.SensorType -eq "Fan" }
if ($ohmFans) {
foreach ($fan in $ohmFans) {
$Output.FanSpeeds += @{
Fan = $fan.Name
SpeedRPM = [math]::Round($fan.Value, 0)
}
}
}
if ($Output.FanSpeeds.Count -eq 0) {
$Output.Errors += "No fan sensors accessible"
}
}
catch {
$Output.Errors += "Fan monitoring error: $($_.Exception.Message)"
}
}
#endregion
#region SMART Drive Data
if ($checkSmartStatus) {
try {
$drives = Get-WmiObject -Class "Win32_DiskDrive" -ErrorAction SilentlyContinue
if ($drives) {
foreach ($drive in $drives) {
$smartInfo = @{
Disk = $drive.Model
Status = "Unknown"
DeviceID = $drive.DeviceID
SerialNumber = $drive.SerialNumber
FirmwareVersion = $drive.FirmwareRevision
Manufacturer = $drive.Manufacturer
MediaType = $drive.MediaType
BusType = $drive.BusType
Attributes = @{
Size = [math]::Round($drive.Size / 1GB, 2)
Interface = $drive.InterfaceType
Partitions = $drive.Partitions
SectorsPerTrack = $drive.SectorsPerTrack
TracksPerCylinder = $drive.TracksPerCylinder
TotalCylinders = $drive.TotalCylinders
TotalHeads = $drive.TotalHeads
TotalSectors = $drive.TotalSectors
BytesPerSector = $drive.BytesPerSector
Capabilities = $drive.Capabilities
CapabilityDescriptions = $drive.CapabilityDescriptions
CompressionMethod = $drive.CompressionMethod
ConfigManagerErrorCode = $drive.ConfigManagerErrorCode
ConfigManagerUserConfig = $drive.ConfigManagerUserConfig
DefaultBlockSize = $drive.DefaultBlockSize
Index = $drive.Index
InstallDate = $drive.InstallDate
LastErrorCode = $drive.LastErrorCode
MaxBlockSize = $drive.MaxBlockSize
MinBlockSize = $drive.MinBlockSize
NeedsCleaning = $drive.NeedsCleaning
NumberOfMediaSupported = $drive.NumberOfMediaSupported
PNPDeviceID = $drive.PNPDeviceID
PowerManagementCapabilities = $drive.PowerManagementCapabilities
PowerManagementSupported = $drive.PowerManagementSupported
SCSIBus = $drive.SCSIBus
SCSILogicalUnit = $drive.SCSILogicalUnit
SCSIPort = $drive.SCSIPort
SCSITargetId = $drive.SCSITargetId
SCSITerminated = $drive.SCSITerminated
Signature = $drive.Signature
Status = $drive.Status
StatusInfo = $drive.StatusInfo
SystemName = $drive.SystemName
TimeOfLastReset = $drive.TimeOfLastReset
}
}
# Get SMART data
$smartData = Get-WmiObject -Namespace "root\wmi" -Class "MSStorageDriver_FailurePredictStatus" -ErrorAction SilentlyContinue |
Where-Object { $_.InstanceName -like "*$($drive.Index)*" }
if ($smartData) {
$smartInfo.Status = if ($smartData.PredictFailure) { "Warning" } else { "Healthy" }
}
# Get additional SMART attributes if available
$smartAttributes = Get-WmiObject -Namespace "root\wmi" -Class "MSStorageDriver_FailurePredictData" -ErrorAction SilentlyContinue |
Where-Object { $_.InstanceName -like "*$($drive.Index)*" }
if ($smartAttributes) {
$smartInfo.SMARTAttributes = @{
VendorSpecific = $smartAttributes.VendorSpecific
VendorSpecificLength = $smartAttributes.VendorSpecificLength
}
}
# Get temperature if available
$tempData = Get-WmiObject -Namespace "root\wmi" -Class "MSStorageDriver_FailurePredictThresholds" -ErrorAction SilentlyContinue |
Where-Object { $_.InstanceName -like "*$($drive.Index)*" }
if ($tempData) {
$smartInfo.TemperatureThresholds = @{
VendorSpecific = $tempData.VendorSpecific
VendorSpecificLength = $tempData.VendorSpecificLength
}
}
$Output.SMARTStatus += $smartInfo
}
}
if ($Output.SMARTStatus.Count -eq 0) {
$Output.Errors += "No drives found or SMART data not accessible"
}
}
catch {
$Output.Errors += "Drive health monitoring error: $($_.Exception.Message)"
}
}
#endregion
#region Memory Health
if ($checkMemoryHealth) {
try {
# Physical memory information
$memory = Get-WmiObject -Class "Win32_PhysicalMemory" -ErrorAction SilentlyContinue
if ($memory) {
$totalMemory = ($memory | Measure-Object -Property Capacity -Sum).Sum / 1GB
$Output.MemoryHealth.TotalMemoryGB = [math]::Round($totalMemory, 1)
# Detailed RAM module information
$Output.MemoryHealth.RAMModules = @()
foreach ($module in $memory) {
$moduleInfo = @{
CapacityGB = [math]::Round($module.Capacity / 1GB, 2)
Speed = $module.Speed
Manufacturer = $module.Manufacturer
PartNumber = $module.PartNumber
SerialNumber = $module.SerialNumber
FormFactor = $module.FormFactor
MemoryType = $module.MemoryType
DeviceLocator = $module.DeviceLocator
BankLabel = $module.BankLabel
ConfiguredClockSpeed = $module.ConfiguredClockSpeed
ConfiguredVoltage = $module.ConfiguredVoltage
}
$Output.MemoryHealth.RAMModules += $moduleInfo
}
# Get current memory usage using Win32_OperatingSystem
$os = Get-WmiObject -Class "Win32_OperatingSystem" -ErrorAction SilentlyContinue
if ($os) {
$totalPhysicalMemory = $os.TotalVisibleMemorySize * 1024 # Convert KB to bytes
$freePhysicalMemory = $os.FreePhysicalMemory * 1024 # Convert KB to bytes
$usedMemory = $totalPhysicalMemory - $freePhysicalMemory
$usedMemoryPercent = [math]::Round(($usedMemory / $totalPhysicalMemory) * 100, 1)
$Output.MemoryHealth.UsagePercent = $usedMemoryPercent
$Output.MemoryHealth.UsedMemoryGB = [math]::Round($usedMemory / 1GB, 1)
$Output.MemoryHealth.FreeMemoryGB = [math]::Round($freePhysicalMemory / 1GB, 1)
# Set memory health status based on usage
$Output.MemoryHealth.Status = if ($usedMemoryPercent -gt 90) { "Critical" }
elseif ($usedMemoryPercent -gt 80) { "Warning" }
else { "Normal" }
}
# Check for memory errors in event log
$memoryErrors = Get-WinEvent -FilterHashtable @{LogName="System"; ID=41,1001; StartTime=(Get-Date).AddDays(-7)} -ErrorAction SilentlyContinue |
Where-Object { $_.LevelDisplayName -eq "Critical" -and $_.Message -like "*memory*" }
if ($memoryErrors) {
$Output.MemoryHealth.Errors = $memoryErrors | Select-Object -First 5 | ForEach-Object {
"$($_.TimeCreated.ToString("yyyy-MM-dd HH:mm:ss")): $($_.Message)"
}
}
}
else {
$Output.MemoryHealth.Status = "Unknown"
$Output.Errors += "Memory information not accessible"
}
}
catch {
$Output.MemoryHealth.Status = "Error"
$Output.Errors += "Memory monitoring error: $($_.Exception.Message)"
}
}
#endregion
#region Disk Usage Analysis
if ($checkDiskUsage) {
try {
Write-Host "Scanning disk usage..." -ForegroundColor Yellow
# Get all logical drives
$drives = Get-WmiObject -Class "Win32_LogicalDisk" -ErrorAction SilentlyContinue
if ($drives) {
foreach ($drive in $drives) {
if ($drive.DriveType -eq 3) { # Fixed drives only
$totalSize = $drive.Size
$freeSpace = $drive.FreeSpace
$usedSpace = $totalSize - $freeSpace
$usagePercent = if ($totalSize -gt 0) { [math]::Round(($usedSpace / $totalSize) * 100, 1) } else { 0 }
$diskInfo = @{
Drive = $drive.DeviceID
Label = $drive.VolumeName
FileSystem = $drive.FileSystem
TotalSizeGB = [math]::Round($totalSize / 1GB, 2)
UsedSizeGB = [math]::Round($usedSpace / 1GB, 2)
FreeSizeGB = [math]::Round($freeSpace / 1GB, 2)
UsagePercent = $usagePercent
Status = if ($usagePercent -gt 90) { "Critical" } elseif ($usagePercent -gt 80) { "Warning" } else { "Normal" }
}
$Output.DiskUsage += $diskInfo
}
}
}
if ($Output.DiskUsage.Count -eq 0) {
$Output.Errors += "No disk usage data available"
}
}
catch {
$Output.Errors += "Disk usage scanning error: $($_.Exception.Message)"
}
}
#endregion
#region Large Files and Folders Scanning
if ($scanLargeFiles) {
try {
Write-Host "Scanning for large files and folders..." -ForegroundColor Yellow
# Get all logical drives
$drives = Get-WmiObject -Class "Win32_LogicalDisk" -ErrorAction SilentlyContinue
if ($drives) {
foreach ($drive in $drives) {
if ($drive.DriveType -eq 3) { # Fixed drives only
$driveLetter = $drive.DeviceID.TrimEnd('\')
# Scan for large files (>100MB)
try {
$largeFiles = Get-ChildItem -Path $driveLetter -Recurse -File -ErrorAction SilentlyContinue |
Where-Object { $_.Length -gt 100MB } |
Sort-Object Length -Descending |
Select-Object -First 20 |
ForEach-Object {
@{
Path = $_.FullName
SizeMB = [math]::Round($_.Length / 1MB, 2)
SizeGB = [math]::Round($_.Length / 1GB, 2)
LastModified = $_.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss")
Extension = $_.Extension
}
}
$Output.LargeFiles += $largeFiles
}
catch {
$Output.Errors += "Error scanning large files on $driveLetter : $($_.Exception.Message)"
}
# Scan for large folders (>1GB)
try {
$largeFolders = Get-ChildItem -Path $driveLetter -Directory -ErrorAction SilentlyContinue |
ForEach-Object {
try {
$size = (Get-ChildItem -Path $_.FullName -Recurse -File -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum).Sum
if ($size -gt 1GB) {
@{
Path = $_.FullName
SizeMB = [math]::Round($size / 1MB, 2)
SizeGB = [math]::Round($size / 1GB, 2)
LastModified = $_.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss")
ItemCount = (Get-ChildItem -Path $_.FullName -Recurse -File -ErrorAction SilentlyContinue | Measure-Object).Count
}
}
}
catch {
# Skip folders that can't be accessed
}
} |
Sort-Object SizeGB -Descending |
Select-Object -First 15
$Output.LargeFolders += $largeFolders
}
catch {
$Output.Errors += "Error scanning large folders on $driveLetter : $($_.Exception.Message)"
}
}
}
}
if ($Output.LargeFiles.Count -eq 0 -and $Output.LargeFolders.Count -eq 0) {
$Output.Errors += "No large files or folders found"
}
}
catch {
$Output.Errors += "Large files/folders scanning error: $($_.Exception.Message)"
}
}
#endregion
# Convert to JSON and output
Write-Output ($Output | ConvertTo-Json -Depth 10 -Compress)