前面幾天,我們做的事情大多還是針對「目前這一台電腦」。
例如:
Get-Service
或是:
Get-Process
到了 Day 4,我們甚至已經可以把結果整理成 CSV:
取得資料
↓
篩選
↓
整理欄位
↓
Export-Csv
但實際做系統維運時,很少永遠只有一台 Server。
可能今天需要檢查:
SERVER01
SERVER02
SERVER03
SERVER04
SERVER05
如果還是一台一台登入、一台一台執行相同指令,自動化帶來的幫助其實還很有限。
所以 Day 5 我想往前走一步:
讓 PowerShell 開始知道「我要處理的不只一台機器」。
今天會碰到三個很重要的觀念:
$Variable
@()
foreach
也就是:
變數、陣列、迴圈。
最後會做一個簡單的 Server 連線檢查,把多台 Server 的結果整理成 CSV。
先從變數開始
其實前一天我們已經偷偷用過變數了。
例如:
$ReportFolder = "C:\Temp"
這裡:
$ReportFolder
就是一個變數。
可以把變數理解成:
幫一個資料取名字,之後可以重複使用。
例如:
$ServerName = "SERVER01"
接著輸入:
$ServerName
結果:
SERVER01
如果之後我們需要使用這個 Server Name,就不用一直重新輸入:
"SERVER01"
而是直接:
$ServerName
為什麼 Script 要使用變數?
假設我寫:
Export-Csv "C:\Temp\ServerReport.csv"
只有一行時,好像沒有什麼問題。
但如果一支 Script 裡面有十幾個地方都使用:
C:\Temp
有一天主管突然說:
報表以後全部放到 D:\Reports。
如果路徑全部寫死,就要一行一行修改。
如果一開始寫:
$ReportFolder = "C:\Temp"
後面全部使用:
$ReportFolder
那就只需要改:
$ReportFolder = "D:\Reports"
這也是為什麼寫 Script 時,我通常會把容易變動的設定放在前面。
例如:
$ReportFolder = "C:\Temp"
$ServerName = "SERVER01"
$Date = Get-Date -Format "yyyyMMdd"
後面程式直接使用這些變數。
變數不只能放文字
PowerShell 變數可以存很多不同東西。
例如文字:
$ServerName = "SERVER01"
數字:
$DiskThreshold = 80
日期:
$CheckTime = Get-Date
甚至可以直接把 PowerShell 指令的結果放進變數。
例如:
$Services = Get-Service
接著:
$Services
就會顯示 Service 資料。
甚至可以:
$Services |
Where-Object Status -eq "Running"
所以變數不只是拿來存一個字。
它也可以暫時保存我們查詢到的一整批 Object。
一台 Server 很簡單,那五台呢?
如果只有一台:
$Server = "SERVER01"
但現在有:
SERVER01
SERVER02
SERVER03
SERVER04
SERVER05
當然可以寫:
$Server1 = "SERVER01"
$Server2 = "SERVER02"
$Server3 = "SERVER03"
$Server4 = "SERVER04"
$Server5 = "SERVER05"
但這樣很快就會失控。
如果今天不是 5 台,而是:
50 台
100 台
總不能一直:
$Server1
$Server2
$Server3
...
$Server100
這時候就需要:
Array,也就是陣列。
建立第一個 Array
PowerShell 可以這樣建立一個 Server 清單:
$Servers = @(
"SERVER01"
"SERVER02"
"SERVER03"
"SERVER04"
"SERVER05"
)
現在:
$Servers
會顯示:
SERVER01
SERVER02
SERVER03
SERVER04
SERVER05
也可以寫成:
$Servers = @("SERVER01", "SERVER02", "SERVER03")
兩種都可以。
如果資料比較多,我自己比較喜歡一行放一個:
$Servers = @(
"SERVER01"
"SERVER02"
"SERVER03"
)
日後增加或刪除比較容易看。
Array 裡面有幾筆資料?
可以:
$Servers.Count
例如結果:
5
代表現在 Server 清單共有 5 台。
也可以取其中一筆。
PowerShell Array 從:
0
開始計算。
所以:
$Servers[0]
會得到:
SERVER01
而:
$Servers[1]
則是:
SERVER02
這點跟很多程式語言一樣:
第一筆 = 0
第二筆 = 1
第三筆 = 2
有了 Server 清單,下一步就是 foreach
現在我們已經有:
$Servers = @(
"SERVER01"
"SERVER02"
"SERVER03"
)
但 PowerShell 還不知道我們要怎麼處理這些 Server。
這時候可以使用:
foreach
例如:
foreach ($Server in $Servers) {
Write-Host $Server
}
輸出:
SERVER01
SERVER02
SERVER03
foreach 可以怎麼理解?
我覺得不用先背語法。
直接把:
foreach ($Server in $Servers)
翻成中文就好:
把 $Servers 裡面的每一台 Server,一次拿一台出來,暫時叫做 $Server。
第一次:
$Server = SERVER01
第二次:
$Server = SERVER02
第三次:
$Server = SERVER03
一直做到整個 Array 處理完。
所以:
foreach ($Server in $Servers) {
Write-Host "Checking $Server"
}
結果:
Checking SERVER01
Checking SERVER02
Checking SERVER03
這就是我們從「單台處理」跨到「批次處理」最重要的一步。
第一個多台 Server 實作:確認網路是否可達
今天暫時不碰 PowerShell Remoting。
先做一件最簡單的事情:
確認 Server 是否能回應 ICMP Ping。
PowerShell 可以使用:
Test-Connection
例如:
Test-Connection -ComputerName SERVER01 -Count 1
它的概念和:
ping SERVER01
有點類似。
如果我們只想知道成功或失敗,可以:
Test-Connection -ComputerName SERVER01
-Count 1 `
-Quiet
成功可能回:
True
失敗:
False
這就非常適合拿來放進 Script。
加進 foreach
現在把 Server Array 和 Test-Connection 合在一起:
$Servers = @(
"SERVER01"
"SERVER02"
"SERVER03"
)
foreach ($Server in $Servers) {
$Result = Test-Connection `
-ComputerName $Server `
-Count 1 `
-Quiet
Write-Host "$Server : $Result"
}
假設結果:
SERVER01 : True
SERVER02 : True
SERVER03 : False
我們就完成了第一個:
多台 Server 批次檢查。
不過 True / False 不太適合拿給人看
如果最後報表長這樣:
SERVER01 True
SERVER02 True
SERVER03 False
自己看得懂。
但如果是給其他人看,我會比較希望是:
SERVER01 Online
SERVER02 Online
SERVER03 Offline
這時候可以加入:
if
加入第一個 if 判斷
例如:
if ($Result -eq $true) {
Write-Host "$Server is Online"
}
else {
Write-Host "$Server is Offline"
}
整段變成:
$Servers = @(
"SERVER01"
"SERVER02"
"SERVER03"
)
foreach ($Server in $Servers) {
$Result = Test-Connection `
-ComputerName $Server `
-Count 1 `
-Quiet
if ($Result -eq $true) {
Write-Host "$Server is Online"
}
else {
Write-Host "$Server is Offline"
}
}
結果:
SERVER01 is Online
SERVER02 is Online
SERVER03 is Offline
到這裡,程式已經開始可以根據不同情況做不同事情。
不要只把結果印在螢幕上
跟前幾天一樣,我們真正想要的是:
Server
Status
CheckTime
例如:
Server Status CheckTime
SERVER01 Online 2026/09/13 08:10
SERVER02 Online 2026/09/13 08:10
SERVER03 Offline 2026/09/13 08:10
所以我們需要把每一次檢查結果變成 Object。
使用 PSCustomObject 建立自己的資料
可以寫:
[PSCustomObject]@{
Server = $Server
Status = "Online"
CheckTime = Get-Date
}
它代表:
我要自己建立一個 PowerShell Object。
裡面有:
Server
Status
CheckTime
例如:
$Result = [PSCustomObject]@{
Server = "SERVER01"
Status = "Online"
CheckTime = Get-Date
}
輸入:
$Result
會得到類似:
Server Status CheckTime
SERVER01 Online 2026/09/13 08:10:30
這跟 Day 3 的 Object 概念又接起來了。
把每台 Server 的結果收集起來
我們先建立一個空 Array:
$Results = @()
接著每檢查完一台,就把結果放進去。
完整一點:
$Servers = @(
"SERVER01"
"SERVER02"
"SERVER03"
)
$Results = @()
foreach ($Server in $Servers) {
$PingResult = Test-Connection `
-ComputerName $Server `
-Count 1 `
-Quiet
if ($PingResult) {
$Status = "Online"
}
else {
$Status = "Offline"
}
$Result = [PSCustomObject]@{
Server = $Server
Status = $Status
CheckTime = Get-Date
}
$Results += $Result
}
最後:
$Results
就會得到:
Server Status CheckTime
SERVER01 Online 2026/09/13 08:15:01
SERVER02 Online 2026/09/13 08:15:02
SERVER03 Offline 2026/09/13 08:15:06
這就是一份真正的批次檢查結果。
再把 Day 4 的 CSV 接回來
既然:
$Results
現在是一批 Object,就可以直接:
$Results |
Export-Csv -Path "C:\Temp\Server_Status.csv"
-NoTypeInformation `
-Encoding UTF8
整個流程就變成:
Server Array
↓
foreach
↓
Test-Connection
↓
if
↓
PSCustomObject
↓
Results Array
↓
Export-Csv
這已經開始有一點真正維運 Script 的樣子。
再把日期放進檔名
延續 Day 4:
$Date = Get-Date -Format "yyyyMMdd"
然後:
$ReportFile = "C:\Temp\Server_Status_$Date.csv"
例如:
C:\Temp\Server_Status_20260913.csv
今天的完整 Script
最後把今天所有東西整理在一起:
$Servers = @(
"SERVER01"
"SERVER02"
"SERVER03"
)
$ReportFolder = "C:\Temp"
$Date = Get-Date -Format "yyyyMMdd"
$ReportFile = "$ReportFolder\Server_Status_$Date.csv"
if (-not (Test-Path $ReportFolder)) {
New-Item `
-Path $ReportFolder `
-ItemType Directory
}
$Results = @()
foreach ($Server in $Servers) {
Write-Host "Checking $Server..."
$PingResult = Test-Connection `
-ComputerName $Server `
-Count 1 `
-Quiet
if ($PingResult) {
$Status = "Online"
}
else {
$Status = "Offline"
}
$Result = [PSCustomObject]@{
Server = $Server
Status = $Status
CheckTime = Get-Date
}
$Results += $Result
}
$Results
$Results |
Export-Csv -Path $ReportFile
-NoTypeInformation `
-Encoding UTF8
Write-Host ""
Write-Host "Check completed."
Write-Host "Report: $ReportFile"
執行之後我們得到什麼?
PowerShell 畫面可能是:
Checking SERVER01...
Checking SERVER02...
Checking SERVER03...
Server Status CheckTime
SERVER01 Online 2026/09/13 08:20:01
SERVER02 Online 2026/09/13 08:20:02
SERVER03 Offline 2026/09/13 08:20:06
Check completed.
Report: C:\Temp\Server_Status_20260913.csv
同時會產生:
C:\Temp\Server_Status_20260913.csv
到這裡,我們已經完成了第一支真正有:
多台設備
+
條件判斷
+
批次處理
+
報表
的 PowerShell Script。
不過 Ping 不通,不一定代表 Server 掛了
這裡一定要補一個維運上的觀念。
如果:
Test-Connection
回傳:
False
不能直接下結論說 Server 已經 Down。
因為可能是:
ICMP 被 Firewall 擋掉
DNS 解析失敗
網路 ACL 阻擋
跨 VLAN 路由問題
主機真的離線
所以這份報表比較精確的說法應該是:
ICMP Connectivity Check
而不是:
Server Health Check
真正的 Server Health Check 之後還需要加入:
TCP Port
Service
Disk
CPU
Memory
Event Log
甚至從多個角度一起判斷。
這也是系統維運很重要的一件事:
一個檢查結果,通常只代表一個觀察點,不代表整台系統的完整狀態。
Server 清單也不用永遠寫死在 Script 裡
目前我們使用:
$Servers = @(
"SERVER01"
"SERVER02"
"SERVER03"
)
如果只有幾台沒有問題。
但如果公司有:
50 台
100 台
300 台 Server
把所有 Hostname 寫進 Script 就不太方便。
我們前一天已經學過:
Import-Csv
所以未來可以準備:
Servers.csv
內容:
Server
SERVER01
SERVER02
SERVER03
然後:
$Servers = Import-Csv "C:\Temp\Servers.csv"
再:
foreach ($Server in $Servers) {
$Server.Server
}
這樣就開始把:
設定資料
和:
程式邏輯
分開。
以後增加 Server,只修改 CSV,不需要一直改 Script。
這會是後面做批次維運時很重要的做法。
foreach 跟 ForEach-Object 有什麼差別?
Day 3 我們碰過:
ForEach-Object
今天又看到:
foreach
兩個都可以逐筆處理資料。
例如 Pipeline 寫法:
$Servers |
ForEach-Object {
Write-Host "Checking $_"
}
也可以寫:
foreach ($Server in $Servers) {
Write-Host "Checking $Server"
}
目前這個系列裡,我會先優先使用第二種:
foreach
因為當程式開始加入:
if
Test-Connection
變數
多個步驟
錯誤處理
時,foreach 通常比較容易閱讀。
等後面對 Pipeline 熟悉之後,再依情況選擇就好。
今天最重要的不是把 foreach 背下來
今天第一次開始有一點「寫程式」的感覺。
我們碰到了:
$Variable
@()
foreach
if
[PSCustomObject]
但這些語法並不是今天最重要的事情。
真正重要的是思考方式已經開始從:
我要怎麼檢查 SERVER01?
變成:
我能不能定義一份 Server 清單,
然後讓同一套邏輯自動處理所有 Server?
這個差別很大。
因為維運自動化真正需要解決的不是:
怎麼讓一個指令變快?
而是:
怎麼讓原本只能處理一台設備的方法,可以重複套用到很多台設備。
Day 5 小結
今天我們第一次把前四天學到的東西真正串起來:
變數
↓
Array
↓
foreach
↓
Test-Connection
↓
if
↓
PSCustomObject
↓
Array
↓
Export-Csv
從:
Test-Connection SERVER01
變成:
SERVER01
SERVER02
SERVER03
SERVER04
SERVER05
↓
自動逐台檢查
↓
統一整理結果
↓
產生 CSV
這也是我們第一次真正跨入:
批次維運。