Dùng script PowerShell kiểm tra hệ thống: Xuất báo cáo Health Check VPS ra HTML tự động
Cứ đến cuối ngày thứ Sáu, việc đầu tiên của bạn là gì? Có phải là hì hục RDP hoặc SSH vào từng con server, gõ lệnh top, df -h hay căng mắt soi từng biểu đồ lên xuống trong Task Manager? Việc thu thập số liệu thủ công box by box rồi copy-paste ra file Excel để báo cáo không chỉ ngốn của bạn hàng giờ đồng hồ mà còn cực kỳ dễ bỏ sót các dấu hiệu cảnh báo ngầm, dẫn đến downtime hệ thống vào những thời điểm nhạy cảm nhất. Đã đến lúc bạn cần một công cụ tự động hóa để giải phóng bản thân khỏi mớ công việc chữa cháy lặp đi lặp lại này.
Việc xây dựng một script PowerShell kiểm tra hệ thống chuẩn chỉ không chỉ giúp bạn nắm bắt sức khỏe của hàng chục VPS trong nháy mắt mà còn tự động render ra những bản báo cáo HTML cực kỳ trực quan, chuyên nghiệp để gửi cho sếp hay khách hàng. Vậy làm sao để khai thác tối đa sức mạnh của WMI/CIM, bóc tách chính xác metrics và biến những dòng data thô cứng thành một Dashboard tĩnh ngay trên PowerShell?
Nỗi ám ảnh mang tên báo cáo định kỳ: Tại sao Ops Engineer cần tự động hóa ngay?
Làm vận hành (Ops) hay SysAdmin, bạn thừa hiểu hệ thống không bao giờ mất kết nối vào lúc bạn đang ngồi canh màn hình. Việc duy trì thói quen kiểm tra server bằng tay đang gián tiếp tạo ra những rủi ro cực kỳ lớn cho tính ổn định của hạ tầng:
- Dễ bỏ sót cảnh báo sớm (Downtime rình rập): Hạ tầng IT thay đổi từng phút. Các vấn đề như một process bị rò rỉ bộ nhớ (memory leak), kẹt I/O ổ cứng hay các nguyên nhân khiến VPS 100% CPU nếu không được phát hiện sớm sẽ dẫn đến hỏng hóc hệ thống (system failures) và thiệt hại tài chính nặng nề.
- Mãi kẹt trong vòng lặp chữa cháy (Reactive): Rất khó để làm việc chủ động (Proactive) nếu lúc nào cũng phải chạy theo xử lý sự cố. Một hệ thống báo cáo tự động giúp bạn bắt được lỗi từ khi chúng mới chỉ là những cảnh báo màu vàng (Warning).
- Số liệu thủ công dễ gây ảo giác: Nếu chỉ nhìn thoáng qua Task Manager, bạn rất dễ bị lừa. Một luồng xử lý (thread) bị kẹt có thể luân chuyển liên tục giữa các nhân CPU. Trên một VPS 4 nhân, Task Manager có thể chỉ báo CPU Load 25% (không nhân nào full 100%), khiến bạn lầm tưởng server đang rảnh rỗi dù thực tế luồng xử lý đó đang bị thắt cổ chai.
- Thiếu dữ liệu lịch sử để Scale: Kiểm tra bằng mắt không để lại log. Không có dữ liệu về xu hướng tài nguyên (trend analysis), bạn lấy cơ sở nào để làm Capacity Planning và thuyết phục cấp trên duyệt ngân sách nâng cấp RAM/CPU?
Cốt lõi kỹ thuật: Khai thác WMI/CIM bằng script PowerShell kiểm tra hệ thống
Để lấy được dữ liệu sâu của phần cứng Windows Server, Windows Management Instrumentation (WMI) luôn là nền tảng cốt lõi. Tuy nhiên, cách chúng ta gọi dữ liệu ở thời điểm hiện tại đã khác xa so với kỷ nguyên của Windows Server 2008 hay 2012.
Tại sao nên dùng Get-CimInstance thay vì Get-WmiObject?
Rất nhiều bài hướng dẫn cũ trên mạng vẫn dạy bạn dùng lệnh Get-WmiObject. Trong môi trường production hiện đại, việc sử dụng cmdlet này là một rủi ro lớn về hệ thống và bảo mật:
- Bảo mật mạng (Firewall Friendly): WMI cũ giao tiếp qua DCOM/RPC. Để nó hoạt động qua mạng, bạn buộc phải đục thủng tường lửa để mở cổng 135, 445 và một dải port động khổng lồ từ 1024 đến 1034. Đây là lỗ hổng lớn dễ bị khai thác. Ngược lại,
Get-CimInstancedùng chuẩn WS-Man (WinRM), chỉ cần mở đúng port 5985 (HTTP) hoặc 5986 (HTTPS). - Mã hóa mặc định (Secure by Default): Kết nối CIM qua WinRM mã hóa toàn bộ dữ liệu truyền tải ngay cả trên nền HTTP nhờ tận dụng cơ chế Kerberos của Active Directory.
- PowerShell Core đã khai tử WMI v1: Các lệnh
Get-WmiObject,Invoke-WmiMethodđã bị Microsoft loại bỏ hoàn toàn từ bản PowerShell Core 6.0 trở lên. Nếu muốn script của bạn chạy được đa nền tảng hoặc tương thích với các bản Windows tương lai, CIM là con đường duy nhất.

So sánh giao thức kết nối: Sự vượt trội về bảo mật của CIM (WinRM) so với WMI (DCOM/RPC) cũ.
Bóc tách 4 metrics sống còn (CPU, RAM, Disk, Network)
Để viết script, chúng ta sẽ lần lượt bóc tách 4 thông số phần cứng quan trọng nhất. Dưới đây là cú pháp cốt lõi:
1. CPU Load (Tải vi xử lý):
Chúng ta dùng class Win32_Processor. Trên các máy chủ có nhiều socket hoặc nhiều nhân logic, bạn cần kết hợp Measure-Object để tính ra % Load trung bình chính xác nhất.
$CPUInfo = Get-CimInstance Win32_Processor
$CPULoad = [math]::Round(($CPUInfo | Measure-Object -Property LoadPercentage -Average).Average, 1)
2. RAM Usage (Bộ nhớ):
Khác với CPU có sẵn số % tải, RAM cần được tính toán thủ công giữa tổng bộ nhớ và bộ nhớ khả dụng thông qua class Win32_OperatingSystem. Phải chia cho 1MB (hoặc 1GB) để quy đổi từ byte, sau đó dùng [Math]::Round để làm tròn.
$OSInfo = Get-CimInstance Win32_OperatingSystem
$TotalRAM = [math]::Round($OSInfo.TotalVisibleMemorySize / 1MB, 2)
$FreeRAM = [math]::Round($OSInfo.FreePhysicalMemory / 1MB, 2)
$UsedRAM = $TotalRAM - $FreeRAM
$RAMPercent = [math]::Round(($UsedRAM / $TotalRAM) * 100, 1)
3. Disk Free Space (Dung lượng ổ cứng):
Class Win32_LogicalDisk sẽ liệt kê mọi phân vùng. Bạn bắt buộc phải chèn thêm Filter DriveType=3 để script chỉ lọc ra ổ cứng cục bộ (Local Disk), bỏ qua các ổ đĩa mạng (Mapped drive) hay ổ đĩa ảo.
$Disks = Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" |
Select-Object DeviceID,
@{Name='SizeGB';Expression={[math]::Round($_.Size/1GB,2)}},
@{Name='FreeGB';Expression={[math]::Round($_.FreeSpace/1GB,2)}}
4. Network Configuration (Chuẩn PowerShell hiện đại):
Nhiều tài liệu cũ dạy dùng Win32_NetworkAdapterConfiguration. Cách này không sai, nhưng từ thời Windows Server 2012 trở đi, PowerShell đã tích hợp sẵn các module mạng nội tại cực kỳ mạnh mẽ. Để bóc tách thông số IP và Gateway trực quan, hãy sử dụng ngay Cmdlet gốc này:
$NetData = Get-NetIPConfiguration | Where-Object { $_.IPv4DefaultGateway -ne $null }
Lệnh Get-NetIPConfiguration ngắn gọn và cho ra output rõ ràng, dễ tương tác hơn hẳn so với việc can thiệp sâu vào WMI.
[Nâng cao] Bắt lỗi hệ thống: Top Processes & System Event Log (24h)
Báo cáo Health Check sẽ trở nên vô giá trị nếu chỉ báo RAM đầy mà không cho biết process nào đang chiếm dụng RAM. Hãy thiết lập để script tự động chỉ điểm nguyên nhân.
Truy vết Top 10 Processes chiếm dụng tài nguyên:
Sắp xếp giảm dần (-Descending) theo CPU hoặc RAM (Working Set – WS) và bóc ra 10 cái tên đầu bảng bằng Get-Process:
$TopProcesses = Get-Process | Sort-Object CPU -Descending | Select-Object ProcessName, Id, CPU, WS -First 10
Truy vấn Event Log (Nhật ký sự kiện):
Để truy xuất lỗi (Error) trong 24 giờ qua từ System Log, hãy dùng Get-WinEvent (thay vì Get-EventLog đã cũ). Level 2 đại diện cho mức độ Error.
$SystemEvents = Get-WinEvent -FilterHashtable @{LogName='System'; Level=2; StartTime=(Get-Date).AddDays(-1)} -MaxEvents 10 -ErrorAction SilentlyContinue
Phép thuật render: Biến data khô khan thành Dashboard HTML chuyên nghiệp
Khi đã có data trong tay dưới dạng Object, việc gửi file text thô cho khách hàng là điều tối kỵ. Bạn cần một Dashboard HTML tĩnh.
Giới hạn của ConvertTo-Html và chiến lược dùng Here-String
Lệnh ConvertTo-Html có sẵn của PowerShell rất tiện, nhưng nó có 3 điểm yếu chí mạng khi thiết kế UI:
- Giao diện sinh ra mặc định thô sơ, màu trắng đen nhàm chán.
- Lỗi cấu trúc thẻ: Nếu bạn gộp bảng CPU, RAM, Disk vào chung bằng lệnh này, nó sẽ sinh ra nhiều cặp thẻ
<html><body>lồng vào nhau, vi phạm chuẩn W3C. - Không hỗ trợ định dạng có điều kiện (Conditional Formatting).
Chiến lược thực chiến: Sử dụng kỹ thuật Here-String (@"..."@) kết hợp tham số -Fragment.
Bạn sẽ định nghĩa toàn bộ bộ khung layout (<head>, <style>, <body>) vào một biến chuỗi (ví dụ $HTMLHead). Sau đó, bạn dùng ConvertTo-Html -Fragment chỉ để render thuần túy phần thẻ <table> chứa data. Cuối cùng, nối chuỗi chúng lại.
Nhúng CSS tạo thanh tiến trình (Progress Bar) & cảnh báo màu tự động
DevOps thường lấy 75% là ngưỡng cảnh báo (Warning – Vàng) và 90% là ngưỡng nguy hiểm (Critical – Đỏ). Dưới đây là ví dụ logic tô màu cho phần trăm sử dụng ổ cứng bằng If/Else ngay trong vòng lặp PowerShell:
if ($DiskUsedPercent -ge 90) {
$ColorClass = "danger" # Đỏ
} elseif ($DiskUsedPercent -ge 75) {
$ColorClass = "warning" # Vàng
} else {
$ColorClass = "safe" # Xanh
}
Trên phần thẻ <style> của HTML Header, bạn ánh xạ các class này thành mã màu hiển thị:
.safe { background-color: #28a745; color: white; }
.warning { background-color: #ffc107; color: #333; }
.danger { background-color: #dc3545; color: white; }
Mỗi khi xuất HTML, các ô <td> sẽ tự động nhận class tương ứng. Ổ C sắp đầy? Nó sẽ hiển thị màu đỏ trên màn hình mà không cần bạn phải giải thích thêm.

Tiêu chuẩn DevOps: Tự động hóa cảnh báo màu sắc dựa trên ngưỡng tiêu thụ tài nguyên của hệ thống.
Nếu phát hiện ổ cứng báo động đỏ, bạn có thể kết hợp ngay lệnh PowerShell dọn dẹp ổ C tự động để giải phóng dung lượng.
[Cập nhật công nghệ mới] Thư viện PSWriteHTML: Giải pháp nhanh gọn cho người không chuyên code UI
Cách tự viết Here-String + CSS thủ công ở trên giúp bạn hiểu sâu bản chất và script có thể chạy bare-metal không cần cài đặt thêm gì. Tuy nhiên, ở môi trường Enterprise, tối ưu thời gian là yếu tố cốt lõi.
Nếu bạn không muốn tự tay thiết lập từng dòng HTML, hãy cài đặt ngay module PSWriteHTML:
Install-Module -Name PSWriteHTML -Force
Đây là module được cộng đồng SysAdmin PowerShell sử dụng rộng rãi nhất hiện nay. Lợi ích vượt trội bao gồm:
- Tạo bảng (DataTables) có sẵn thanh tìm kiếm (Search box), phân trang tự động.
- Vẽ biểu đồ (Charts) Pie, Bar cực kỳ mượt mà.
- Conditional Formatting rút gọn: Tự động đổi màu khi số liệu vượt ngưỡng chỉ bằng 1 dòng tham số lệnh thay vì phải viết khối logic if/else dài dòng.
Tự động hóa toàn tập: Task Scheduler & Email Reporting
Khi script đã hoạt động ổn định, bạn cần thiết lập hệ thống tự động. Lên lịch chạy định kỳ và báo cáo tự động là đích đến cuối cùng.
Lên lịch chạy script hàng ngày vào lúc 7:00 sáng
Bạn có thể mở giao diện GUI của Windows Task Scheduler để tạo task, nhưng với vị trí Ops, bạn nên sử dụng lệnh cho chuẩn xác. Cần lưu ý thiết lập quyền RunLevel Highest (quyền Administrator) và chạy dưới tài khoản SYSTEM để có quyền đọc Event Log.
$Time = New-ScheduledTaskTrigger -Daily -At 7am
$Action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-WindowStyle Hidden -ExecutionPolicy Bypass -File C:\Scripts\HealthCheck.ps1"
Register-ScheduledTask -TaskName "VPS Daily Health Check" -Trigger $Time -Action $Action -RunLevel Highest -User "SYSTEM"
Cờ -ExecutionPolicy Bypass sẽ cho phép script chạy ngầm không bị chặn bởi chính sách bảo mật mặc định của Windows.
Mở rộng check nhiều Server (Cluster) và gửi Email báo cáo
Để kiểm tra một cụm 10 VPS, bạn điền IP của chúng vào một file servers.txt, sau đó dùng vòng lặp foreach kết hợp Invoke-Command (qua cổng WinRM 5985) để thu thập dữ liệu từ xa về máy trung tâm.
Sau khi file HTML hoàn thiện, bạn cần cấu hình tự động gửi email. Và đây là điều cực kỳ quan trọng về mặt bảo mật công nghệ:
Lưu ý bảo mật (Security Note): Lệnh Send-MailMessage tích hợp sẵn của PowerShell hiện đã bị Microsoft chính thức đánh dấu là Obsolete (lỗi thời) vì nó không hỗ trợ xác thực hiện đại (Modern Authentication – OAuth). Nếu bạn dùng lệnh này kết nối với các SMTP Server đời mới như Office 365, nguy cơ bị rate-limit, chặn kết nối hoặc rớt vào hòm Spam là 99%.
Giải pháp thay thế chuẩn Enterprise:
- With hệ sinh thái Office 365 / Exchange Online, Microsoft khuyến cáo dùng cmdlet
Send-MgUserMailthuộc bộ thư viện Microsoft Graph PowerShell SDK. - Nếu dùng SMTP độc lập, hãy cài đặt các module cộng đồng như
MailozaurrhoặcMailKit. Chúng hỗ trợ hoàn hảo chuẩn mã hóa TLS 1.2/1.3 và OAuth 2.0, đảm bảo báo cáo Health Check luôn được gửi an toàn vào Inbox của người nhận.- Ngoài ra, nếu đội ngũ Ops của bạn ưu tiên sử dụng ứng dụng nhắn tin, bạn có thể triển khai thêm script PowerShell giám sát VPS và bắn cảnh báo qua Telegram để nhận thông báo tức thời.
Dưới đây là cú pháp gửi mail mẫu bằng Mailozaurr (Yêu cầu cài đặt module thông qua lệnh Install-Module Mailozaurr trước khi chạy):
$HTMLBody = Get-Content "C:\Reports\DailyReport.html" -Raw
Send-EmailMessage -From "[email protected]" -To "[email protected]" -Subject "[CẢNH BÁO] Report Server" -HtmlBody $HTMLBody -SmtpServer "smtp.congty.com"

Luồng hoạt động (Workflow): Từ khâu trích xuất dữ liệu phần cứng đến việc render báo cáo HTML và gửi Email.
Tặng bạn full script PowerShell kiểm tra hệ thống (mẫu tham khảo độc lập)
Dưới đây là một script hoàn chỉnh, độc lập (không cần cài thêm module) sử dụng kỹ thuật CSS Here-String để render Dashboard. Bạn chỉ việc copy, lưu thành file .ps1 và sử dụng.
<#
.SYNOPSIS
Xuất báo cáo Health Check VPS định dạng HTML tĩnh.
.DESCRIPTION
Script khai thác WMI/CIM, render bảng biểu kết hợp CSS cảnh báo màu tự động.
#>
$ServerName = $env:COMPUTERNAME
$ReportPath = "C:\Reports\HealthCheck_$ServerName.html"
New-Item -ItemType Directory -Force -Path "C:\Reports\" | Out-Null
# 1. Thu thập dữ liệu
$OS = Get-CimInstance Win32_OperatingSystem
$Uptime = (Get-Date) - $OS.LastBootUpTime
$CPU = Get-CimInstance Win32_Processor | Measure-Object -Property LoadPercentage -Average
$TotalRAM = [math]::Round($OS.TotalVisibleMemorySize / 1GB, 2)
$FreeRAM = [math]::Round($OS.FreePhysicalMemory / 1GB, 2)
$UsedRAM = $TotalRAM - $FreeRAM
$RAMPct = [math]::Round(($UsedRAM / $TotalRAM) * 100, 1)
# 2. Xử lý Logic màu sắc cho RAM
if ($RAMPct -ge 90) { $ramClass = "danger" }
elseif ($RAMPct -ge 75) { $ramClass = "warning" }
else { $ramClass = "safe" }
# 3. Tạo thẻ Header và CSS
$HTMLHead = @"
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<style>
body { font-family: 'Segoe UI', Tahoma, sans-serif; background: #f4f7f6; padding: 20px; }
.card { background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); display: inline-block; width: 30%; margin-right: 2%; text-align: center; }
.val { font-size: 28px; font-weight: bold; margin: 10px 0; }
.safe { color: #28a745; }
.warning { color: #ffc107; }
.danger { color: #dc3545; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; background: #fff; }
th { background: #2c3e50; color: #fff; padding: 12px; text-align: left; }
td { padding: 10px; border-bottom: 1px solid #ddd; }
</style>
</head>
<body>
<h2>🚀 Báo Cáo Sức Khỏe Máy Chủ - $ServerName</h2>
<p>Thời gian xuất: $(Get-Date -Format 'dd/MM/yyyy HH:mm')</p>
<div>
<div class="card">
<h3>CPU Load</h3>
<div class="val">$([math]::Round($CPU.Average, 1))%</div>
</div>
<div class="card">
<h3>RAM Usage</h3>
<div class="val $ramClass">$RAMPct%</div>
</div>
<div class="card">
<h3>Uptime</h3>
<div class="val" style="font-size:20px;">$($Uptime.Days) Ngày $($Uptime.Hours) Giờ</div>
</div>
</div>
"@
# 4. Render Bảng Disk
$DiskData = Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" | ForEach-Object {
$size = [math]::Round($_.Size / 1GB, 2)
$free = [math]::Round($_.FreeSpace / 1GB, 2)
$usedPct = if($size -gt 0){ [math]::Round((($size-$free)/$size)*100, 1) } else { 0 }
[PSCustomObject]@{
"Ổ Đĩa" = $_.DeviceID
"Tổng (GB)" = $size
"Trống (GB)" = $free
"Sử Dụng (%)" = $usedPct
}
}
$HTMLDisk = $DiskData | ConvertTo-Html -Fragment -PreContent "<h3>💾 Tình trạng Ổ cứng</h3>"
# 5. Đóng thẻ và Lưu File
$HTMLFooter = "</body></html>"
$FinalHTML = $HTMLHead + $HTMLDisk + $HTMLFooter
$FinalHTML | Out-File -FilePath $ReportPath -Encoding UTF8
Write-Host "✅ Đã xuất báo cáo thành công tại: $ReportPath" -ForegroundColor Green
Câu hỏi thường gặp (FAQ)
1. Làm sao để script bỏ qua các phân vùng (ổ đĩa) không quan trọng?
Hãy thêm tham số -Filter vào lệnh Get-CimInstance Win32_LogicalDisk. Ngoài việc lọc DriveType=3 (ổ cứng vật lý), bạn có thể lọc thêm tên ổ đĩa, ví dụ: Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3 AND DeviceID != 'Z:'".
2. Chạy script PowerShell kiểm tra hệ thống này liên tục có làm nặng Server không?
Không đáng kể. Các lệnh CIM chỉ tiêu tốn vài MB RAM và chớp nhoáng vòng tua CPU. Trừ khi bạn ép script chạy mỗi giây một lần, việc đặt Task Scheduler chạy định kỳ mỗi 5-10 phút hoặc mỗi sáng là hoàn toàn an toàn cho Production.
3. Làm sao để giấu mật khẩu Email khi chạy tự động bằng Task Scheduler?
Tuyệt đối không gõ mật khẩu vào file script. Hãy lưu thông tin xác thực ra file XML mã hóa bằng lệnh Export-Clixml. Khi script chạy, dùng Import-Clixml để giải mã. File này chỉ User tạo ra nó mới đọc được, cực kỳ an toàn.
4. Hạ tầng cấm mở port WinRM (5985/5986), tôi phải check nhiều máy từ xa kiểu gì?
Hãy chuyển sang mô hình Local Push. Cài script chạy cục bộ trên từng VPS, sau đó cho script đẩy dữ liệu về một Network Share (SMB) hoặc một Endpoint Webhook (như Telegram/Teams) thay vì quản trị từ xa qua mạng.
5. Nếu Server không có Internet (Air-gapped), dùng module PSWriteHTML có bị lỗi giao diện không?
Có, vì mặc định nó kéo thư viện từ CDN. Cách khắc phục: Khi cài đặt hoặc chạy lệnh render, hãy thêm switch -Offline. Module sẽ tự động nhúng (embed) toàn bộ code CSS/JS vào file HTML, giúp file chạy hoàn hảo dù không kết nối mạng.
6. Lệnh Get-CimInstance có chạy được trên các Windows Server đời cũ không?
Các bản Windows Server 2008/2008 R2 cũ không có sẵn lệnh này. Giải pháp là tải và cài đặt bản cập nhật Windows Management Framework (WMF) 5.1 từ Microsoft để nâng cấp PowerShell lên tiêu chuẩn hiện đại, giúp script tương thích hoàn toàn.
Kết luận
Việc xây dựng hệ thống giám sát không nhất thiết phải đắt tiền hay cài đặt các Agent nặng nề. Với kịch bản sử dụng script PowerShell kiểm tra hệ thống được trang bị CIM và PSWriteHTML, bạn đã có trong tay một giải pháp Monitoring chủ động. Để đồng bộ quy trình vận hành, hãy nhúng ngay báo cáo này vào Cẩm nang 10 script PowerShell quản trị VPS để tự động hóa hoàn toàn máy chủ của bạn.
Tài liệu tham khảo
- Get-CimInstance (CimCmdlets) – PowerShell | Microsoft Learn
- Differences between Windows PowerShell 5.1 and PowerShell 7.x – PowerShell | Microsoft Learn
- Send-MailMessage (Microsoft.PowerShell.Utility) – PowerShell | Microsoft Learn
- ConvertTo-Html (Microsoft.PowerShell.Utility) – PowerShell | Microsoft Learn
- Win32_OperatingSystem class – Win32 apps | Microsoft Learn







