iT邦幫忙

2026 iThome 鐵人賽

DAY 7
0
IT Operation

系統工程師的 30 天自動化維運實戰:PowerShell × AD × Windows Server系列 第 7

Day 7|Windows Server 健康檢查:CPU、Memory、Disk 一次抓出來

  • 分享至 

  • xImage
  •  

前面幾天,我們已經把 PowerShell 的基礎慢慢串起來了。

從:

變數

Array

foreach

if

try / catch

CSV

Log

到 Day 6,我們甚至已經可以對多台 Server 做連線檢查,並把錯誤寫進報表。

但前面有一件事情其實一直沒有真的做到。

我們一直說:

Server Check

但其實目前真正檢查的只有:

ICMP Ping

也就是:

這台主機有沒有回應?

這跟:

這台 Server 現在是不是健康?

其實是兩件完全不同的事情。

所以 Day 7 開始,我們正式進入 Windows Server 維運。

今天的目標是取得:

CPU
Memory
Disk

最後做出一份像這樣的健康檢查結果:

Computer : SERVER01

CPU : 22 %
Memory : 68 %
Disk C: : 71 %
Disk D: : 45 %

Status : Healthy
Server 可以 Ping,不代表它是健康的

假設:

Test-Connection SERVER01 -Count 1 -Quiet

回傳:

True

只能證明:

SERVER01 目前有回 ICMP。

但這台 Server 可能同時是:

CPU 99%
Memory 97%
Disk C: 99%

甚至某個重要 Service 已經停止。

所以:

Ping Success

只能代表:

網路層面有回應。

不能直接等同:

Server Healthy

因此真正做巡檢時,我們通常需要同時看多個指標。

今天第一次使用 Get-CimInstance

今天會開始使用一個 Windows 系統管理非常實用的 Cmdlet:

Get-CimInstance

它可以取得 Windows 系統裡許多資訊。

例如:

Get-CimInstance Win32_OperatingSystem

可能會得到:

SystemDirectory : C:\Windows\system32
Organization :
BuildNumber : 26100
RegisteredUser : Windows User
SerialNumber : ...
Version : 10.0.26100

再例如:

Get-CimInstance Win32_Processor

可以取得 CPU 資訊。

還有:

Get-CimInstance Win32_LogicalDisk

取得磁碟資料。

可以先把它理解成:

PowerShell 向 Windows 詢問系統目前的狀態。

第一個檢查:CPU

先輸入:

Get-CimInstance Win32_Processor

通常會看到:

Caption
DeviceID
Manufacturer
MaxClockSpeed
Name
SocketDesignation

但是我們現在想知道的是:

CPU 使用率。

可以使用:

Get-CimInstance Win32_Processor |
Select-Object Name, LoadPercentage

例如:

Name LoadPercentage


Intel(R) Xeon(R) CPU E5-2680 v4 23

也就是:

CPU Load = 23%

如果只想拿數字:

$CPU = Get-CimInstance Win32_Processor |
Measure-Object -Property LoadPercentage -Average

然後:

$CPU.Average

可能得到:

23

我們也可以直接:

$CPUUsage = (
Get-CimInstance Win32_Processor |
Measure-Object -Property LoadPercentage -Average
).Average

現在:

$CPUUsage

就是目前取得的 CPU Load。

CPU 數字要怎麼判斷?

先假設我們自己定一個簡單門檻:

0~79% → Normal
80~89% → Warning
90%以上 → Critical

PowerShell 可以寫:

if ($CPUUsage -ge 90) {

$CPUStatus = "Critical"

}
elseif ($CPUUsage -ge 80) {

$CPUStatus = "Warning"

}
else {

$CPUStatus = "Normal"

}

這裡第一次出現:

elseif

意思就是:

如果前面的條件不符合,再檢查下一個條件。

CPU 80% 就一定有問題嗎?

也不是。

例如 Server 剛好正在:

跑 Backup
掃描防毒
處理大量資料
跑批次工作
Windows Update

CPU 瞬間升到 90%,不一定代表異常。

所以這裡的 Threshold 只是:

用來快速找出值得注意的 Server。

真正判斷異常通常還要考慮:

持續多久?
平常使用率多少?
當時跑什麼 Process?
是否影響 Service?

這跟前面 Service 巡檢的觀念一樣:

Automation 幫我們縮小範圍,不是直接替工程師下結論。

第二個檢查:Memory

Windows 記憶體資訊可以從:

Get-CimInstance Win32_OperatingSystem

取得。

先看:

Get-CimInstance Win32_OperatingSystem |
Select-Object TotalVisibleMemorySize, FreePhysicalMemory

可能得到:

TotalVisibleMemorySize FreePhysicalMemory


          16712124            5231000

這裡的單位通常是:

KB

直接看其實不太直覺。

我們真正想要的是:

總 Memory
使用 Memory
Memory 使用率
計算 Memory 使用率

先取得資料:

$OS = Get-CimInstance Win32_OperatingSystem

總記憶體:

$TotalMemory = $OS.TotalVisibleMemorySize

可用記憶體:

$FreeMemory = $OS.FreePhysicalMemory

已使用:

$UsedMemory = $TotalMemory - $FreeMemory

最後計算百分比:

$MemoryUsage = math::Round(
($UsedMemory / $TotalMemory) * 100,
2
)

例如結果:

68.42

代表:

Memory Usage = 68.42%
math::Round 是什麼?

這裡看到:

它是拿來四捨五入。

假設:

68.42873491

我們不需要報表顯示那麼多小數。

所以:

math::Round(68.42873491, 2)

結果:

68.43

維運報表通常會好看很多。

順便把 Memory 轉成 GB

總記憶體:

$TotalMemoryGB = math::Round(
$OS.TotalVisibleMemorySize / 1MB,
2
)

這裡可能會讓人疑惑:

1MB

為什麼 TotalVisibleMemorySize 是 KB,卻除以 1MB?

因為 PowerShell 的:

1KB
1MB
1GB

本身都是數值常數。

但為了避免初學時看起來太混亂,也可以寫得更直白:

$TotalMemoryGB = math::Round(
$OS.TotalVisibleMemorySize / 1024 / 1024,
2
)

例如:

15.94 GB

可用記憶體:

$FreeMemoryGB = math::Round(
$OS.FreePhysicalMemory / 1024 / 1024,
2
)
Memory 也設定 Threshold

例如:

< 80% Normal
80~89% Warning

= 90% Critical

可以寫:

if ($MemoryUsage -ge 90) {

$MemoryStatus = "Critical"

}
elseif ($MemoryUsage -ge 80) {

$MemoryStatus = "Warning"

}
else {

$MemoryStatus = "Normal"

}

後面我們就可以直接看到:

Memory : 68% → Normal

而不是自己每次看數字再判斷。

第三個檢查:Disk

磁碟空間幾乎是系統維運一定會檢查的項目。

因為很多 Server 問題最後會發現:

C: 滿了
Log 撐爆
Backup 沒清
Temp 一直增加
Database File 長大

我們可以使用:

Get-CimInstance Win32_LogicalDisk

先看看資料。

只取本機磁碟

Win32_LogicalDisk 不一定只有本機硬碟。

所以可以加:

DriveType = 3

查詢:

Get-CimInstance Win32_LogicalDisk `
-Filter "DriveType=3"

可能看到:

DeviceID Size FreeSpace


C: 255000000000 85000000000
D: 500000000000 320000000000

這些數字都是 Bytes。

人很難直接看。

所以要轉成 GB。

計算 Disk 使用率

可以:

$Disks = Get-CimInstance Win32_LogicalDisk `
-Filter "DriveType=3"

接著:

foreach ($Disk in $Disks) {

$TotalGB = [math]::Round(
    $Disk.Size / 1GB,
    2
)

$FreeGB = [math]::Round(
    $Disk.FreeSpace / 1GB,
    2
)

$UsedGB = [math]::Round(
    $TotalGB - $FreeGB,
    2
)

$UsedPercent = [math]::Round(
    (($Disk.Size - $Disk.FreeSpace) / $Disk.Size) * 100,
    2
)

Write-Host "$($Disk.DeviceID) $UsedPercent %"

}

假設:

C: 72.31 %
D: 36.08 %

現在就開始有巡檢資訊了。

建立 Disk Object

跟前面一樣,我不希望只是:

Write-Host

所以可以建立:

$DiskResult = [PSCustomObject]@{

Drive       = $Disk.DeviceID
TotalGB     = $TotalGB
UsedGB      = $UsedGB
FreeGB      = $FreeGB
UsedPercent = $UsedPercent

}

最後:

Drive TotalGB UsedGB FreeGB UsedPercent


C: 237.8 171.9 65.9 72.29
D: 465.6 168.2 297.4 36.13

這就很適合:

輸出 CSV

或送進後面的巡檢報表。

Disk Threshold 怎麼設定?

假設:

< 80% Normal
80~89% Warning

= 90% Critical

可以加入:

if ($UsedPercent -ge 90) {

$DiskStatus = "Critical"

}
elseif ($UsedPercent -ge 80) {

$DiskStatus = "Warning"

}
else {

$DiskStatus = "Normal"

}

最後:

C: 72% Normal
D: 91% Critical

工程師第一眼就知道:

D: 比較需要先看。

把 CPU、Memory、Disk 串起來

現在我們已經有三組資訊:

CPU
Memory
Disk

可以開始做第一版 Health Check。

先取得電腦名稱:

$ComputerName = $env:COMPUTERNAME
CPU
$CPUUsage = math::Round(
(
Get-CimInstance Win32_Processor |
Measure-Object -Property LoadPercentage
-Average
).Average,
2
)
Memory
$OS = Get-CimInstance Win32_OperatingSystem

$TotalMemory = $OS.TotalVisibleMemorySize
$FreeMemory = $OS.FreePhysicalMemory

$UsedMemory = $TotalMemory - $FreeMemory

$MemoryUsage = math::Round(
($UsedMemory / $TotalMemory) * 100,
2
)
Disk
$Disks = Get-CimInstance Win32_LogicalDisk `
-Filter "DriveType=3"

現在資料就都準備好了。

怎麼決定整台 Server 的 Status?

這裡開始有一個很重要的問題。

假設:

CPU Normal
Memory Normal
Disk C Normal
Disk D Critical

那整台 Server 應該顯示:

Healthy

嗎?

當然不應該。

所以我們可以定義:

只要任何一項 Critical
→ Server = Critical

沒有 Critical,但有 Warning
→ Server = Warning

全部 Normal
→ Server = Healthy

這就是最基本的 Health Check 邏輯。

建立整體 Health Status

先設定:

$OverallStatus = "Healthy"

如果 CPU Critical:

if ($CPUUsage -ge 90) {

$OverallStatus = "Critical"

}

如果 Memory:

if ($MemoryUsage -ge 90) {

$OverallStatus = "Critical"

}

Disk 也一樣。

但這樣寫久了會越來越亂。

所以今天先不急著做得非常漂亮。

Day 7 的重點還是:

把健康資料正確抓出來。

後面我們再重構。

第一版完整 Server Health Check

下面是一個可以直接拿來練習的版本:

=========================================

Windows Server Health Check

CPU / Memory / Disk

=========================================

$ComputerName = $env:COMPUTERNAME
$CheckTime = Get-Date

---------------------------

CPU

---------------------------

$CPUUsage = math::Round(
(
Get-CimInstance Win32_Processor |
Measure-Object -Property LoadPercentage
-Average
).Average,
2
)

---------------------------

Memory

---------------------------

$OS = Get-CimInstance Win32_OperatingSystem

$TotalMemory = $OS.TotalVisibleMemorySize
$FreeMemory = $OS.FreePhysicalMemory

$UsedMemory = $TotalMemory - $FreeMemory

$MemoryUsage = math::Round(
($UsedMemory / $TotalMemory) * 100,
2
)

$TotalMemoryGB = math::Round(
$TotalMemory / 1024 / 1024,
2
)

$FreeMemoryGB = math::Round(
$FreeMemory / 1024 / 1024,
2
)

---------------------------

CPU Status

---------------------------

if ($CPUUsage -ge 90) {

$CPUStatus = "Critical"

}
elseif ($CPUUsage -ge 80) {

$CPUStatus = "Warning"

}
else {

$CPUStatus = "Normal"

}

---------------------------

Memory Status

---------------------------

if ($MemoryUsage -ge 90) {

$MemoryStatus = "Critical"

}
elseif ($MemoryUsage -ge 80) {

$MemoryStatus = "Warning"

}
else {

$MemoryStatus = "Normal"

}

---------------------------

Summary

---------------------------

$Summary = [PSCustomObject]@{

ComputerName  = $ComputerName
CheckTime     = $CheckTime

CPUUsage      = $CPUUsage
CPUStatus     = $CPUStatus

TotalMemoryGB = $TotalMemoryGB
FreeMemoryGB  = $FreeMemoryGB
MemoryUsage   = $MemoryUsage
MemoryStatus  = $MemoryStatus

}

$Summary

輸出可能像:

ComputerName : SERVER01
CheckTime : 2026/09/15 05:10:00
CPUUsage : 18
CPUStatus : Normal
TotalMemoryGB : 15.94
FreeMemoryGB : 5.24
MemoryUsage : 67.13
MemoryStatus : Normal
再把 Disk 加進來

因為一台 Server 可能有:

C:
D:
E:
F:

Disk 跟 CPU、Memory 不太一樣。

CPU 通常是一個整體結果。

Memory 也是一個結果。

Disk 則可能:

一台 Server

多個 Drive

所以 Disk 比較適合另外建立一組 Object。

$DiskResults = @()

$Disks = Get-CimInstance Win32_LogicalDisk `
-Filter "DriveType=3"

foreach ($Disk in $Disks) {

$TotalGB = [math]::Round(
    $Disk.Size / 1GB,
    2
)

$FreeGB = [math]::Round(
    $Disk.FreeSpace / 1GB,
    2
)

$UsedPercent = [math]::Round(
    (($Disk.Size - $Disk.FreeSpace) / $Disk.Size) * 100,
    2
)


if ($UsedPercent -ge 90) {

    $DiskStatus = "Critical"

}
elseif ($UsedPercent -ge 80) {

    $DiskStatus = "Warning"

}
else {

    $DiskStatus = "Normal"

}


$DiskResult = [PSCustomObject]@{

    ComputerName = $ComputerName
    CheckTime    = $CheckTime
    Drive        = $Disk.DeviceID
    TotalGB      = $TotalGB
    FreeGB       = $FreeGB
    UsedPercent  = $UsedPercent
    Status       = $DiskStatus

}

$DiskResults += $DiskResult

}

最後:

$DiskResults

可能得到:

ComputerName Drive TotalGB FreeGB UsedPercent Status


SERVER01 C: 237.8 65.9 72.29 Normal
SERVER01 D: 465.6 42.1 90.96 Critical

這時候問題就非常明顯了。

做成 CSV 報表

延續前幾天的做法:

$ReportFolder = "C:\Temp"

確認資料夾:

if (-not (Test-Path $ReportFolder)) {

New-Item `
    -Path $ReportFolder `
    -ItemType Directory |
    Out-Null

}

日期:

$Date = Get-Date -Format "yyyyMMdd"

Summary:

$Summary |
Export-Csv -Path "$ReportFolder\Server_Summary_$Date.csv"
-NoTypeInformation `
-Encoding UTF8

Disk:

$DiskResults |
Export-Csv -Path "$ReportFolder\Server_Disk_$Date.csv"
-NoTypeInformation `
-Encoding UTF8

最後會得到:

C:\Temp

├── Server_Summary_20260915.csv

└── Server_Disk_20260915.csv
為什麼先拆成兩份報表?

可能有人會問:

為什麼不把 CPU、Memory、C:、D: 全部塞在同一列?

當然可以。

例如:

Server CPU Memory C_Disk D_Disk

但這種寫法有一個問題。

今天:

SERVER01

有:

C:
D:

SERVER02 可能有:

C:
D:
E:
F:

那報表欄位就會越來越難固定。

所以目前我比較傾向:

Summary Report

負責:

Server
CPU
Memory
Status

而:

Disk Report

一個 Drive 一筆。

例如:

Server Drive UsedPercent
SERVER01 C: 72%
SERVER01 D: 91%
SERVER02 C: 45%
SERVER02 D: 62%
SERVER02 E: 82%

後面要篩:

UsedPercent >= 80

也非常容易。

今天的 Threshold 只是範例

今天使用:

CPU
80% Warning
90% Critical

Memory
80% Warning
90% Critical

Disk
80% Warning
90% Critical

這些數字不是所有公司都必須這樣設定。

實際環境要看:

系統用途
Server 規格
Baseline
Application 特性
SLA
歷史使用狀況

例如 Database Server 平常 Memory 使用率就可能很高。

如果:

Memory > 80%

就一直告警,最後反而變成 Alert Fatigue。

所以真正成熟的 Monitoring 不只是:

設一個百分比。

還需要知道:

這台系統正常的樣子是什麼?

CPU 的 LoadPercentage 也不是精密效能監控

今天使用:

Win32_Processor.LoadPercentage

非常適合拿來做:

簡單巡檢
快速 Snapshot
基本健康報表

但如果要做真正持續性的 Performance Monitoring,我不會只靠它。

因為:

它比較適合當某個時間點的觀察值,不等於完整的效能監控歷史。

真正要分析:

CPU 什麼時候升高?
持續了幾分鐘?
每天固定哪個時間?
跟哪個 Process 有關?

會更適合使用:

Performance Counter
Monitoring System
Prometheus
Grafana
SCOM
Zabbix

今天的目標不是取代 Monitoring System。

而是做:

PowerShell Daily Health Check。

把它想成「每日巡檢」

假設以前每天早上要:

RDP Server01
看 Task Manager
看 Memory
打開 C:
看 Disk

RDP Server02
再做一次

RDP Server03
再做一次

現在可以慢慢變成:

PowerShell

取得 CPU

取得 Memory

取得 Disk

套用 Threshold

產生報表

工程師只需要先看:

Warning
Critical

而不是每台都人工檢查一次。

這才是這個系列想做的事情。

如果 CPU 真的很高,下一步是什麼?

假設今天報表:

SERVER01

CPU = 96%
Status = Critical

我們下一個問題一定會是:

到底是哪一個 Process 在吃 CPU?

PowerShell 其實已經可以:

Get-Process |
Sort-Object CPU -Descending |
Select-Object -First 10 `
ProcessName,
Id,
CPU

這跟 Day 2 學的內容又接回來了。

未來我們甚至可以做到:

CPU > 90%

自動取得 Top Process

寫入 Incident Report

這就開始從:

Monitoring

慢慢走向:

Diagnostics
Day 7 小結

今天我們正式開始 Windows Server Health Check。

第一次取得三個真正跟系統健康比較有關係的資訊:

CPU
Memory
Disk

主要使用:

Get-CimInstance Win32_Processor

Get-CimInstance Win32_OperatingSystem

Get-CimInstance Win32_LogicalDisk

再搭配前幾天學過的:

變數
foreach
if / elseif
PSCustomObject
Export-Csv

整個流程現在已經變成:

Windows Server

Get-CimInstance

CPU / Memory / Disk

計算 %

Threshold

Normal / Warning / Critical

PSCustomObject

CSV Report

這就是第一版的:

Windows Server Daily Health Check。

而且我認為今天有一個觀念比 Script 本身更重要:

一個 Server 能 Ping,不代表 Server 是健康的。

健康狀態需要靠多個觀察點一起判斷。

Day 8 預告
Day 8|Windows Server 巡檢進階:Service、Uptime 與最後開機時間

今天我們檢查的是:

CPU
Memory
Disk

但 Server 就算這三項全部正常,也可能有問題。

例如:

重要 Service 已停止
Server 剛剛才重開機
某台 Server 已經 300 天沒重開
Windows Update 重啟過

所以 Day 8 我們會把巡檢內容增加到:

CPU
Memory
Disk
Service
Uptime
Last Boot Time

並開始把巡檢結果整理成更完整的:

SERVER01

CPU Normal
Memory Normal
Disk Warning
Service Normal
Uptime 32 Days

Overall Warning

從 Day 8 開始,我們會慢慢把前幾天零散的 PowerShell 技巧,組成一支真正可以持續擴充的 Windows Server Health Check Script。


上一篇
Day 6|PowerShell 錯誤處理:Script 出錯時,不要讓它直接停下來
下一篇
Day 8|Windows Server 巡檢進階:Service、Uptime 與最後開機時間
系列文
系統工程師的 30 天自動化維運實戰:PowerShell × AD × Windows Server11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言