Skip to main content
Glama
jackalterman

Windows Diagnostics MCP Server

by jackalterman
hardware_monitor.ps119.1 kB
<# .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)

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jackalterman/windows-diagnostic-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server