前言

很多人第一次接触图吧工具箱,都是在装机、验机、二手电脑检测、硬盘健康检查这些场景里

CPU-Z 看处理器和主板,GPU-Z 看显卡,CrystalDiskInfo 看硬盘 SMART,AIDA64 / HWiNFO 看整机传感器和硬件信息

这些工具很好用,但它们有一个天然问题:更适合单机排查,不适合批量巡检

如果你手里只有一台电脑,打开图吧工具箱,一个个点工具完全没问题

但如果你是运维,要看 20 台、50 台、100 台 Windows 主机的基础健康状态,再靠人工打开工具就很容易变成体力活

所以这篇文章的思路不是“用脚本替代图吧工具箱”,而是把它们分工清楚

图吧工具箱负责单机深度检测

自动化脚本负责批量初筛

脚本先帮我们找出异常机器,比如磁盘空间不足、硬盘健康状态异常、内存偏低、CPU 长期高负载、系统运行时间异常等

然后再对异常主机使用图吧工具箱里的专业工具做二次确认

这才是更接近运维现场的玩法

一、为什么图吧工具箱适合和自动化结合

图吧工具箱的优势很明显

它集成了大量硬件检测、评分、压力测试、驱动辅助、系统工具

它适合本地打开,快速查看硬件细节

它对装机、验机、故障复现很友好

但运维场景里,我们还需要另外几件事

第一,批量获取多台机器状态

第二,把结果导出成 CSV 或 HTML 报告

第三,自动标记异常机器

第四,把异常结果沉淀下来,方便后续复盘

第五,减少重复点击和人工记录

所以这里的最佳实践是:

日常巡检靠脚本

异常复查靠图吧工具箱

报告归档靠 CSV / HTML

长期演进可以接入 Zabbix、Prometheus、Wazuh、ELK、Ansible、SaltStack 或企业 CMDB

二、整体方案设计

本文实现一个简单的 Windows 批量巡检脚本,核心流程如下

准备 hosts.txt 主机清单

使用 PowerShell Remoting 远程连接 Windows 主机

采集系统、CPU、内存、磁盘、物理硬盘健康状态、SMART 预测状态

根据阈值判断 OK、WARN、CRITICAL、OFFLINE

导出 CSV 报告

生成 HTML 巡检报告

对异常主机再使用图吧工具箱做单机复查

整体流程可以理解为:

hosts.txt
   |
   v
PowerShell 批量巡检脚本
   |
   +--> 系统信息
   +--> CPU 信息
   +--> 内存信息
   +--> 磁盘空间
   +--> 物理磁盘健康
   +--> SMART 预测状态
   |
   v
CSV / HTML 报告
   |
   v
异常主机使用图吧工具箱复查

三、适用场景

这个脚本适合下面这些场景

办公室 Windows 终端批量巡检

实验室电脑硬件状态检查

小型机房 Windows 主机日常检查

二手电脑批量验机前的快速初筛

企业 IT 资产盘点前的基础健康检查

家用 NAS / MINI PC / 虚拟化宿主机的健康巡检

它不适合替代专业监控平台

如果你已经有 Zabbix、Prometheus、夜莺、Wazuh、SCCM、Intune 这类平台,本文脚本更适合作为轻量补充工具

四、前置条件

本文使用 PowerShell Remoting,也就是 WinRM

被检测主机需要开启远程管理

在被检测 Windows 主机上执行:

Enable-PSRemoting -Force

如果是域环境,推荐使用域账号和 Kerberos 认证

如果是工作组环境,需要在检测端配置 TrustedHosts,例如:

Set-Item WSMan:\localhost\Client\TrustedHosts -Value "192.168.31.*" -Force
Restart-Service WinRM

如果只是实验环境,可以这样做

如果是生产环境,不建议直接使用通配符放开整个网段,应该按主机名或 IP 精确配置

五、准备主机清单

新建一个 hosts.txt

内容示例:

192.168.31.58
192.168.31.110
192.168.31.120
192.168.31.30

也可以写主机名:

DESKTOP-01
DESKTOP-02
LAB-PC-01
LAB-PC-02

建议每行一台机器

以 # 开头的行会被脚本忽略

例如:

# 办公网终端
192.168.31.58
192.168.31.30

# 实验环境
192.168.31.110
192.168.31.120

六、批量巡检脚本

下面是完整脚本,保存为:

Invoke-HardwareHealthCheck.ps1

脚本功能:

批量连接 hosts.txt 中的 Windows 主机

采集 CPU、内存、磁盘、系统、BIOS、物理硬盘健康状态

判断磁盘空间是否低于阈值

判断是否存在 SMART 预测故障

判断物理硬盘状态是否异常

输出 CSV 和 HTML 报告

param(
    [string]$ComputerList = ".\hosts.txt",
    [string]$OutDir = ".\report",
    [int]$DiskWarnPercent = 20,
    [int]$DiskCriticalPercent = 10,
    [switch]$PromptCredential
)

$ErrorActionPreference = "Stop"

if (!(Test-Path $ComputerList)) {
    throw "主机清单不存在:$ComputerList"
}

if (!(Test-Path $OutDir)) {
    New-Item -ItemType Directory -Path $OutDir | Out-Null
}

$Credential = $null

if ($PromptCredential) {
    $Credential = Get-Credential
}

$Computers = Get-Content $ComputerList |
    ForEach-Object { $_.Trim() } |
    Where-Object { $_ -and !$_.StartsWith("#") }

$TimeStamp = Get-Date -Format "yyyyMMdd-HHmmss"
$CsvPath = Join-Path $OutDir "hardware-health-$TimeStamp.csv"
$HtmlPath = Join-Path $OutDir "hardware-health-$TimeStamp.html"

$Results = foreach ($Computer in $Computers) {
    Write-Host "正在检测 $Computer ..." -ForegroundColor Cyan

    try {
        $InvokeParams = @{
            ComputerName = $Computer
            ErrorAction  = "Stop"
        }

        if ($Credential) {
            $InvokeParams.Credential = $Credential
        }

        Invoke-Command @InvokeParams -ArgumentList $DiskWarnPercent, $DiskCriticalPercent -ScriptBlock {
            param(
                [int]$DiskWarnPercent,
                [int]$DiskCriticalPercent
            )

            $Issues = New-Object System.Collections.Generic.List[string]

            $OS = Get-CimInstance Win32_OperatingSystem
            $CS = Get-CimInstance Win32_ComputerSystem
            $BIOS = Get-CimInstance Win32_BIOS
            $CPU = Get-CimInstance Win32_Processor

            $IPv4 = Get-CimInstance Win32_NetworkAdapterConfiguration -Filter "IPEnabled=True" |
                ForEach-Object {
                    $_.IPAddress | Where-Object { $_ -match "^\d{1,3}(\.\d{1,3}){3}$" }
                } |
                Select-Object -Unique

            $TotalMemGB = [math]::Round($OS.TotalVisibleMemorySize / 1MB, 2)
            $FreeMemGB = [math]::Round($OS.FreePhysicalMemory / 1MB, 2)
            $FreeMemPercent = if ($TotalMemGB -gt 0) {
                [math]::Round(($FreeMemGB / $TotalMemGB) * 100, 2)
            } else {
                0
            }

            if ($FreeMemPercent -lt 10) {
                $Issues.Add("内存可用率低于 10%")
            } elseif ($FreeMemPercent -lt 20) {
                $Issues.Add("内存可用率低于 20%")
            }

            $CpuLoad = ($CPU | Measure-Object -Property LoadPercentage -Average).Average
            $CpuLoad = [math]::Round($CpuLoad, 2)

            if ($CpuLoad -ge 90) {
                $Issues.Add("CPU 当前负载高于 90%")
            } elseif ($CpuLoad -ge 80) {
                $Issues.Add("CPU 当前负载高于 80%")
            }

            $LogicalDisks = Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" |
                ForEach-Object {
                    $SizeGB = [math]::Round($_.Size / 1GB, 2)
                    $FreeGB = [math]::Round($_.FreeSpace / 1GB, 2)
                    $FreePercent = if ($_.Size -gt 0) {
                        [math]::Round(($_.FreeSpace / $_.Size) * 100, 2)
                    } else {
                        0
                    }

                    if ($FreePercent -lt $DiskCriticalPercent) {
                        $Issues.Add("$($_.DeviceID) 盘空间严重不足,剩余 $FreePercent%")
                    } elseif ($FreePercent -lt $DiskWarnPercent) {
                        $Issues.Add("$($_.DeviceID) 盘空间偏低,剩余 $FreePercent%")
                    }

                    [PSCustomObject]@{
                        Drive       = $_.DeviceID
                        SizeGB      = $SizeGB
                        FreeGB      = $FreeGB
                        FreePercent = $FreePercent
                    }
                }

            $DiskSummary = ($LogicalDisks | ForEach-Object {
                "$($_.Drive) $($_.FreeGB)GB/$($_.SizeGB)GB free $($_.FreePercent)%"
            }) -join "; "

            $PhysicalDiskSummary = "N/A"

            try {
                if (Get-Command Get-PhysicalDisk -ErrorAction SilentlyContinue) {
                    $PhysicalDisks = Get-PhysicalDisk | ForEach-Object {
                        if ($_.HealthStatus -ne "Healthy") {
                            $Issues.Add("物理磁盘健康状态异常:$($_.FriendlyName) $($_.HealthStatus)")
                        }

                        "$($_.FriendlyName) [$($_.MediaType)] $($_.HealthStatus)"
                    }

                    if ($PhysicalDisks) {
                        $PhysicalDiskSummary = $PhysicalDisks -join "; "
                    }
                }
            } catch {
                $PhysicalDiskSummary = "Get-PhysicalDisk 获取失败:$($_.Exception.Message)"
            }

            $SmartPredictFailure = "Unknown"

            try {
                $SmartStatus = Get-CimInstance -Namespace root\wmi -ClassName MSStorageDriver_FailurePredictStatus -ErrorAction Stop

                $Failed = $SmartStatus | Where-Object { $_.PredictFailure -eq $true }

                if ($Failed) {
                    $SmartPredictFailure = "True"
                    $Issues.Add("SMART 预测存在潜在故障")
                } else {
                    $SmartPredictFailure = "False"
                }
            } catch {
                $SmartPredictFailure = "Unsupported or PermissionDenied"
            }

            $LastBoot = $OS.LastBootUpTime
            $UptimeDays = [math]::Round(((Get-Date) - $LastBoot).TotalDays, 2)

            if ($UptimeDays -gt 180) {
                $Issues.Add("系统运行时间超过 180 天,建议评估补丁和重启窗口")
            }

            $Status = "OK"

            if ($Issues.Count -gt 0) {
                $CriticalKeywords = @("严重不足", "SMART", "异常", "高于 90%")

                $IsCritical = $false

                foreach ($Keyword in $CriticalKeywords) {
                    if (($Issues -join " | ") -match $Keyword) {
                        $IsCritical = $true
                        break
                    }
                }

                if ($IsCritical) {
                    $Status = "CRITICAL"
                } else {
                    $Status = "WARN"
                }
            }

            [PSCustomObject]@{
                Target              = $env:COMPUTERNAME
                HostName            = $env:COMPUTERNAME
                IPv4                = ($IPv4 -join ", ")
                Manufacturer        = $CS.Manufacturer
                Model               = $CS.Model
                SerialNumber        = $BIOS.SerialNumber
                OS                  = $OS.Caption
                OSVersion           = $OS.Version
                LastBootTime        = $LastBoot
                UptimeDays          = $UptimeDays
                CPU                 = (($CPU | Select-Object -ExpandProperty Name -Unique) -join "; ")
                CpuLoadPercent      = $CpuLoad
                TotalMemoryGB       = $TotalMemGB
                FreeMemoryGB        = $FreeMemGB
                FreeMemoryPercent   = $FreeMemPercent
                DiskSummary         = $DiskSummary
                PhysicalDiskSummary = $PhysicalDiskSummary
                SmartPredictFailure = $SmartPredictFailure
                Status              = $Status
                Issues              = if ($Issues.Count -gt 0) { $Issues -join " | " } else { "None" }
                CheckTime           = Get-Date
            }
        }
    } catch {
        [PSCustomObject]@{
            Target              = $Computer
            HostName            = $Computer
            IPv4                = ""
            Manufacturer        = ""
            Model               = ""
            SerialNumber        = ""
            OS                  = ""
            OSVersion           = ""
            LastBootTime        = ""
            UptimeDays          = ""
            CPU                 = ""
            CpuLoadPercent      = ""
            TotalMemoryGB       = ""
            FreeMemoryGB        = ""
            FreeMemoryPercent   = ""
            DiskSummary         = ""
            PhysicalDiskSummary = ""
            SmartPredictFailure = ""
            Status              = "OFFLINE"
            Issues              = $_.Exception.Message
            CheckTime           = Get-Date
        }
    }
}

$Results | Export-Csv -Path $CsvPath -NoTypeInformation -Encoding UTF8

$Css = @"
<style>
body {
    font-family: Consolas, Microsoft YaHei, Arial;
    margin: 24px;
}
table {
    border-collapse: collapse;
    width: 100%;
    font-size: 13px;
}
th, td {
    border: 1px solid #ddd;
    padding: 8px;
}
th {
    background-color: #f2f2f2;
}
.OK {
    background-color: #e8f5e9;
}
.WARN {
    background-color: #fff8e1;
}
.CRITICAL {
    background-color: #ffebee;
}
.OFFLINE {
    background-color: #eeeeee;
}
</style>
"@

$Rows = foreach ($Item in $Results) {
    "<tr class='$($Item.Status)'>
        <td>$($Item.Target)</td>
        <td>$($Item.IPv4)</td>
        <td>$($Item.Manufacturer)</td>
        <td>$($Item.Model)</td>
        <td>$($Item.OS)</td>
        <td>$($Item.CpuLoadPercent)</td>
        <td>$($Item.FreeMemoryPercent)</td>
        <td>$($Item.DiskSummary)</td>
        <td>$($Item.PhysicalDiskSummary)</td>
        <td>$($Item.SmartPredictFailure)</td>
        <td>$($Item.Status)</td>
        <td>$($Item.Issues)</td>
    </tr>"
}

$Html = @"
<html>
<head>
<meta charset="utf-8">
<title>硬件健康巡检报告</title>
$Css
</head>
<body>
<h1>硬件健康巡检报告</h1>
<p>生成时间:$(Get-Date)</p>
<table>
<thead>
<tr>
<th>主机</th>
<th>IPv4</th>
<th>厂商</th>
<th>型号</th>
<th>系统</th>
<th>CPU负载%</th>
<th>内存可用%</th>
<th>磁盘空间</th>
<th>物理磁盘</th>
<th>SMART预测</th>
<th>状态</th>
<th>问题</th>
</tr>
</thead>
<tbody>
$($Rows -join "`n")
</tbody>
</table>
</body>
</html>
"@

$Html | Out-File -Path $HtmlPath -Encoding UTF8

Write-Host "巡检完成" -ForegroundColor Green
Write-Host "CSV 报告:$CsvPath"
Write-Host "HTML 报告:$HtmlPath"

七、运行脚本

普通运行:

.\Invoke-HardwareHealthCheck.ps1 -ComputerList .\hosts.txt

需要输入账号密码时:

.\Invoke-HardwareHealthCheck.ps1 -ComputerList .\hosts.txt -PromptCredential

修改磁盘空间阈值:

.\Invoke-HardwareHealthCheck.ps1 -ComputerList .\hosts.txt -DiskWarnPercent 25 -DiskCriticalPercent 10

执行完成后,会在 report 目录下生成两个文件

hardware-health-时间戳.csv
hardware-health-时间戳.html

CSV 适合后续导入 Excel、Power BI、CMDB 或资产系统

HTML 适合直接发给同事看

八、报告字段说明

报告里比较关键的字段有这些

Target:检测目标

HostName:远程主机名

IPv4:主机 IP 地址

Manufacturer:设备厂商

Model:设备型号

SerialNumber:序列号

OS:操作系统版本

UptimeDays:系统运行天数

CPU:CPU 型号

CpuLoadPercent:当前 CPU 负载

TotalMemoryGB:总内存

FreeMemoryGB:可用内存

FreeMemoryPercent:可用内存百分比

DiskSummary:逻辑盘空间摘要

PhysicalDiskSummary:物理磁盘健康状态

SmartPredictFailure:SMART 是否预测故障

Status:整体状态

Issues:异常原因

九、状态判断逻辑

脚本把主机分为四种状态

OK:未发现明显异常

WARN:存在轻微风险,例如磁盘剩余空间偏低、内存可用率偏低

CRITICAL:存在高风险,例如 SMART 预测故障、物理磁盘状态异常、磁盘空间严重不足

OFFLINE:无法连接主机,可能是关机、网络不通、WinRM 未启用、账号权限不足、防火墙拦截

这里的状态判断不是绝对结论,而是运维初筛结果

比如 CPU 当前 90% 不代表机器一定有故障,可能只是用户正在运行大型软件

但它足够告诉我们:这台机器值得进一步看一眼

十、和图吧工具箱如何配合

脚本跑完之后,我们重点关注 WARN 和 CRITICAL 主机

对于磁盘异常主机,可以用图吧工具箱里的 CrystalDiskInfo、HD Tune、DiskGenius 等工具进一步确认

重点看以下指标

硬盘通电时间

通电次数

重映射扇区计数

当前待映射扇区计数

不可校正扇区计数

SSD 健康度

NVMe 温度

对于 CPU 或内存异常主机,可以使用 AIDA64、HWiNFO、CPU-Z、MemTest 相关工具继续分析

重点看以下信息

CPU 型号是否和资产登记一致

内存容量是否被正确识别

内存频率是否异常

温度是否过高

是否存在降频

是否存在硬件配置被更换

对于显卡异常主机,可以使用 GPU-Z、FurMark、3DMark 相关工具复查

重点看以下信息

显卡型号

显存容量

驱动版本

核心温度

显存温度

PCIe 通道状态

满载是否掉驱动

这就是“自动化 + 图吧工具箱”的组合价值

脚本负责告诉你先看谁

图吧工具箱负责告诉你具体坏在哪里

十一、本地快速启动图吧工具箱常用工具

图吧工具箱内部工具路径可能会随着版本变化,所以不建议在脚本里写死路径

可以写一个简单启动器,通过文件名模糊搜索常用工具

保存为:

Start-TuBaTools.ps1
param(
    [string]$ToolBoxRoot = "D:\Tools\图吧工具箱"
)

if (!(Test-Path $ToolBoxRoot)) {
    throw "图吧工具箱目录不存在:$ToolBoxRoot"
}

$ToolPatterns = @(
    "CrystalDiskInfo*.exe",
    "DiskInfo*.exe",
    "HWiNFO*.exe",
    "CPU-Z*.exe",
    "GPU-Z*.exe",
    "AIDA64*.exe",
    "HD Tune*.exe",
    "DiskGenius*.exe"
)

foreach ($Pattern in $ToolPatterns) {
    $Tool = Get-ChildItem -Path $ToolBoxRoot -Recurse -File -Include $Pattern -ErrorAction SilentlyContinue |
        Select-Object -First 1

    if ($Tool) {
        Write-Host "启动 $($Tool.Name)" -ForegroundColor Green
        Start-Process $Tool.FullName
    } else {
        Write-Host "未找到 $Pattern" -ForegroundColor Yellow
    }
}

使用方法:

.\Start-TuBaTools.ps1 -ToolBoxRoot "D:\Tools\图吧工具箱"

这个脚本适合在异常主机本地执行

比如远程桌面登录到一台异常电脑后,一键打开常用检测工具

它不是远程批量压测脚本,也不建议在用户正在工作的电脑上直接批量打开压力测试工具

运维做自动化,边界感很重要

十二、生产环境注意事项

第一,不要在办公时间批量执行压力测试

像 FurMark、AIDA64 Stability Test、MemTest 这类工具可能明显影响用户使用,甚至触发温度、功耗、风扇噪音问题

第二,SMART 读取结果并不等于最终诊断

不同硬盘、不同控制器、不同驱动对 SMART 支持程度不一样

脚本发现异常后,应该用 CrystalDiskInfo、厂商工具或服务器带外管理继续确认

第三,WinRM 权限要控制好

不要为了方便就在生产环境里随便放开 TrustedHosts

域环境优先使用域账号、最小权限、专用巡检账号

第四,报告要归档

一次巡检只能看到当前状态

真正有价值的是趋势

比如某台机器 C 盘从 30% 剩余变成 18%,再变成 8%,这个过程比单次告警更值得关注

第五,脚本阈值要按场景调整

办公电脑、渲染工作站、虚拟化宿主机、数据库服务器,对 CPU、内存、磁盘空间的阈值要求不一样

不要拿一套阈值硬套所有机器

十三、可以继续扩展的方向

这个脚本只是一个基础版本,还可以继续扩展

加入钉钉、企业微信、飞书机器人告警

加入 SQLite 或 MySQL,保存历史巡检数据

接入资产系统,自动匹配使用人和部门

增加 Windows Update 补丁状态检查

增加 Defender 状态检查

增加 BitLocker 状态检查

增加本地管理员组成员检查

增加 RDP、SMB、WinRM 服务状态检查

增加显卡驱动版本采集

增加温度采集,但这通常需要第三方工具或硬件 SDK 支持

如果规模继续扩大,可以把这个思路迁移到 Ansible、SaltStack、SCCM、Intune、Zabbix Agent、Wazuh Agent 这类体系里

脚本解决的是“从 0 到 1 能跑起来”的问题

平台解决的是“从 1 到 1000 稳定运营”的问题

十四、多厂商环境下的类比思路

这个方法不只适用于 Windows 电脑

运维里的很多巡检,本质都是同一套逻辑

先自动化采集基础状态

再筛选异常对象

最后使用专业工具深挖问题

比如服务器环境里,可以用 Dell iDRAC、HPE iLO、Lenovo XClarity、IPMI、Redfish 获取硬件健康状态

网络设备环境里,可以用 SNMP、SSH、NETCONF、RESTCONF 获取接口状态、光模块信息、CPU、内存、温度、电源、风扇状态

华为、H3C、Cisco、Juniper、Arista、MikroTik 的命令不同,但自动化思路类似

先采集

再结构化

再判断

再告警

再复查

图吧工具箱在 PC 运维里的定位,有点像网络工程师手里的 show 命令合集

单机排障很好用

但批量巡检还需要脚本和平台来补齐

十五、总结

图吧工具箱适合做单机硬件深度检测

PowerShell 脚本适合做 Windows 主机批量初筛

两者结合起来,可以把“人工点工具”的流程变成“脚本先筛异常,工具再做复查”的流程

这对小型运维团队、实验室、办公终端管理、二手电脑验机都很实用

运维自动化不是一上来就搞大平台

很多时候,先写一个能跑的脚本,把重复劳动砍掉一半,就已经很有价值

本文脚本可以作为一个起点

后续可以继续接入告警、数据库、资产系统和监控平台

最终形成一套属于自己的轻量级硬件巡检体系

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐