iT邦幫忙

2026 iThome 鐵人賽

DAY 3
0
IT Operation

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

Day 3|PowerShell Pipeline 與 Object:為什麼它比單純文字處理更適合維運?

  • 分享至 

  • xImage
  •  

Day 2 我們先認識了幾個常用的 PowerShell 指令,也稍微碰到了 Pipeline:

Get-Service |
Where-Object Status -eq "Running"

當時只是把它理解成:

取得 Service

交給下一個指令

只留下 Running

但如果後面要拿 PowerShell 做 AD 帳號盤點、Windows Server 巡檢、CSV 報表或批次管理,Object 與 Pipeline 是一定要理解的觀念。

因為 PowerShell 真正方便的地方,不只是「可以下 Windows 指令」。

而是:

前一個指令取得的系統資料,可以直接交給下一個指令繼續處理。

今天就把這件事情實際拆開來看。

先從最簡單的 Get-Service 開始

輸入:

Get-Service

畫面可能會看到:

Status Name DisplayName


Running Appinfo Application Information
Stopped BITS Background Intelligent Transfer Service
Running EventLog Windows Event Log
Running LanmanServer Server

乍看之下,很像 PowerShell 只是輸出了一張文字表格。

如果是在一般 CMD 裡看到這種結果,我們很容易認為:

Running Appinfo Application Information

就是一串文字。

但 PowerShell 並不是這樣處理。

實際上每一筆 Service 都是一個:

Object

也就是「物件」。

畫面上看到的 Status、Name、DisplayName,只是這個 Object 裡面其中幾個 Property。

怎麼知道它到底是什麼 Object?

可以使用昨天介紹過的:

Get-Member

例如:

Get-Service | Get-Member

在輸出上方會看到類似:

TypeName: System.ServiceProcess.ServiceController

這就代表:

Get-Service

傳出來的東西不是單純的一行文字,而是:

System.ServiceProcess.ServiceController

這種類型的 Object。

再往下看,會發現裡面有很多成員,例如:

Name
DisplayName
Status
ServiceName
MachineName
CanStop
CanPauseAndContinue
Start()
Stop()
Refresh()

這些資訊平常在 Get-Service 畫面上沒有全部顯示。

但它們其實都存在。

畫面沒看到,不代表資料不存在

這一點我覺得是剛開始使用 PowerShell 很容易誤會的地方。

例如直接:

Get-Service

只看到:

Status
Name
DisplayName

但如果:

Get-Service |
Select-Object *

會看到更多欄位。

例如:

Name
RequiredServices
CanPauseAndContinue
CanShutdown
CanStop
DisplayName
DependentServices
MachineName
ServiceName
ServicesDependedOn
Status
ServiceType
StartType

所以:

PowerShell 顯示出來的表格,只是預設顯示方式,不代表 Object 只有這幾個資料。

這個觀念後面到了 Active Directory 會變得更加重要。

因為:

Get-ADUser

同樣不是只傳回我們眼睛看到的幾個欄位。

User Object 本身還有很多 Property。

Pipeline 到底在傳什麼?

來看這一段:

Get-Service |
Where-Object Status -eq "Running"

Pipeline:

|

不是單純把畫面上的文字複製給下一個指令。

它會把:

Service Object

傳給 Where-Object。

所以 Where-Object 可以直接看這個 Object 裡面的:

Status

Property。

概念上比較接近:

Service Object
{
Name = "EventLog"
DisplayName = "Windows Event Log"
Status = "Running"
}

Where-Object

檢查 Status

留下符合條件的 Object

所以我們才能寫:

Where-Object Status -eq "Running"

而不需要自己先把文字切割。

如果是純文字處理,會變得麻煩很多

假設程式只拿到:

Running EventLog Windows Event Log

那如果我要取得 Service Name,就可能需要開始處理:

第幾個空白?
字串怎麼切?
DisplayName 裡自己也有空白怎麼辦?

這種方式不是不能做。

但資料一複雜,就會變得很麻煩。

PowerShell Object 則可以直接:

$service.Name

或:

$service.Status

因為 PowerShell 知道:

Name 是 Name
Status 是 Status
DisplayName 是 DisplayName

而不是叫我們自己從一整串文字裡猜。

這也是為什麼 PowerShell 特別適合做 Windows 系統管理。

$_ 到底是什麼?

昨天有看到這種寫法:

Get-Service |
Where-Object {
$_.Status -eq "Running"
}

這裡:

$_

第一次看到很容易覺得很奇怪。

可以先把它想成:

Pipeline 現在正在處理的這一筆 Object。

例如 Pipeline 裡面目前傳進來:

Name = EventLog
Status = Running

那:

$_

就是這個 Service Object。

而:

$_.Status

就是:

Running

接著下一筆可能變成:

Name = BITS
Status = Stopped

這時候:

$_.Status

就是:

Stopped

PowerShell 就會一筆一筆檢查。

Where-Object:從一堆資料中找出我要的

例如只找停止的 Service:

Get-Service |
Where-Object {
$_.Status -eq "Stopped"
}

如果要找名稱裡有 Windows 的 DisplayName:

Get-Service |
Where-Object {
$_.DisplayName -like "Windows"
}

這裡:

-like

就是比對字串。

而:

可以理解為任意字元。

所以:

"Windows"

代表只要文字中有 Windows 就符合。

例如:

Windows Audio
Windows Event Log
Windows Update

都有可能被找出來。

Pipeline 可以一直往下接

例如:

Get-Service |
Where-Object {
$_.Status -eq "Running"
} |
Sort-Object Name |
Select-Object Name, DisplayName, Status

看起來比較長,但其實可以一段一段閱讀。

Get-Service

取得所有 Service

Where-Object

只保留 Running

Sort-Object

按照 Name 排序

Select-Object

只留下 Name、DisplayName、Status

所以我們不用把它當成一條很難懂的指令。

把每個 Pipeline 看成一個處理步驟就好。

Select-Object:重新決定我要看到什麼

例如:

Get-Service |
Select-Object Name, Status

結果:

Name Status


Appinfo Running
BITS Stopped
EventLog Running
LanmanServer Running

也可以加入 StartType:

Get-Service |
Select-Object Name, DisplayName, Status, StartType

這就比預設畫面多了一個很有用的資料。

例如我們之後想找:

啟動類型設定為 Automatic,但是目前卻停止的 Service。

就可以開始利用這些 Property。

例如:

Get-Service |
Where-Object {
$.StartType -eq "Automatic" -and
$
.Status -eq "Stopped"
} |
Select-Object Name, DisplayName, Status, StartType

這已經開始比較像實際維運會用的查詢。

Sort-Object:資料多的時候先整理

例如想按照 Service Name 排序:

Get-Service |
Sort-Object Name

如果要倒過來:

Get-Service |
Sort-Object Name -Descending

也可以先篩選,再排序:

Get-Service |
Where-Object Status -eq "Stopped" |
Sort-Object Name

後面如果查 AD 帳號:

1000 筆
2000 筆
5000 筆

排序就會比用眼睛慢慢找實際很多。

ForEach-Object:對每一筆資料做事情

除了篩選資料,我們有時候會希望:

對 Pipeline 裡的每一筆 Object 都做一件事情。

這時候可以使用:

ForEach-Object

例如:

Get-Service |
Select-Object -First 5 |
ForEach-Object {
Write-Host "Service Name: $($_.Name)"
}

可能顯示:

Service Name: AarSvc
Service Name: AppIDSvc
Service Name: Appinfo
Service Name: AppMgmt
Service Name: AppReadiness

這裡:

$($_.Name)

就是取得目前這一筆 Service 的 Name。

之後我們可能會拿這個概念來做:

每一台 Server

檢查 Disk

每一個 User

檢查 LastLogonDate

每一個 Computer

整理 Computer Name

所以 ForEach-Object 後面也會一直出現。

Measure-Object:資料不只是列出來,也可以統計

例如我想知道目前電腦上總共有多少 Service:

Get-Service |
Measure-Object

結果可能看到:

Count : 312
Average :
Sum :
Maximum :
Minimum :
Property :

如果只想拿 Count:

(Get-Service | Measure-Object).Count

可能得到:

312

再例如算現在有多少 Running Service:

Get-Service |
Where-Object Status -eq "Running" |
Measure-Object

也可以:

(Get-Service |
Where-Object Status -eq "Running" |
Measure-Object).Count

假設結果:

145

代表現在有 145 個 Running Service。

Pipeline 的好處開始出現了

假設今天我被問:

「這台 Server 目前總共有多少 Automatic Service?其中多少是停止的?」

如果純 GUI 操作,我可能要:

services.msc

按照 Startup Type 排序

找 Automatic

再看 Status

自己統計

PowerShell 可以直接做:

Get-Service |
Where-Object {
$_.StartType -eq "Automatic"
} |
Measure-Object

再查 Automatic 但停止:

Get-Service |
Where-Object {
$.StartType -eq "Automatic" -and
$
.Status -eq "Stopped"
} |
Select-Object Name, DisplayName, Status, StartType

這就是我覺得 Object + Pipeline 最實際的地方。

不是指令比較帥。

而是當資料量開始變大時,我們可以明確地告訴 PowerShell 要怎麼處理資料。

做一個比較像維運的小實作

今天最後做一個簡單的 Service 巡檢。

需求是:

找出啟動類型為 Automatic,但目前沒有 Running 的 Service。

輸入:

Get-Service |
Where-Object {
$.StartType -eq "Automatic" -and
$
.Status -ne "Running"
} |
Sort-Object Name |
Select-Object Name, DisplayName, Status, StartType

這裡又出現:

-ne

它代表:

Not Equal
不等於

所以:

$_.Status -ne "Running"

就是:

Status 不是 Running。

最後結果可能像:

Name DisplayName Status StartType


MapsBroker Downloaded Maps Manager Stopped Automatic
WSearch Windows Search Stopped Automatic
...

不過看到這個結果時,不要直接認定所有項目都是故障。

有些 Windows Service 即使 StartType 是 Automatic,也可能受到觸發啟動、系統狀態或其他機制影響。

所以這支指令真正的作用比較像:

快速幫我們產生「值得進一步確認」的清單。

這跟實際做系統維運很像。

Automation 很適合:

收集資料

找出可疑項目

縮小範圍

但最後到底是不是異常,仍然要由工程師判斷。

一個很容易踩到的坑:Format-Table

PowerShell 還有一個很好用的指令:

Format-Table

例如:

Get-Service |
Format-Table Name, Status

顯示結果很好看。

但有一點我覺得最好一開始就知道:

Format-Table 是拿來控制畫面顯示的,不適合拿來當資料處理 Pipeline 的中間步驟。

例如之後我們會學:

Export-Csv

不要寫成:

Get-Service |
Format-Table Name, Status |
Export-Csv services.csv

這樣輸出的東西通常不是我們真正想要的 Service 資料。

比較正確的概念是:

Get-Service |
Select-Object Name, Status |
Export-Csv services.csv

也就是:

資料處理

Select-Object

Export

畫面呈現

Format-Table

這兩個用途要分開。

這個坑我覺得越早知道越好,因為後面開始產生 CSV 報表時會很常遇到。

Object 為什麼特別適合系統維運?

到這裡大概可以看出來。

當 PowerShell 從 Windows 取得一個 Service 時,它拿到的不是:

"Running EventLog Windows Event Log"

而比較像:

Service Object

Name → EventLog
DisplayName → Windows Event Log
Status → Running
StartType → Automatic

因此後面可以很自然地:

Filter

Sort

Select

Count

Export

同樣的方式到了 AD 也一樣。

例如未來:

Get-ADUser

拿到 User Object。

我們可以取:

Name
SamAccountName
Enabled
LastLogonDate
DistinguishedName

再篩選:

長期未登入
帳號仍 Enabled
特定 OU
特定 Group

最後輸出報表。

所以 Day 3 學的其實不只是 PowerShell 語法。

而是在建立後面整個系列都會使用的資料處理模式:

取得資料

取得 Object

篩選

排序

選擇欄位

處理 / 統計

輸出結果
Day 3 小結

今天主要碰到這幾個 Cmdlet:

Get-Member
Where-Object
Select-Object
Sort-Object
ForEach-Object
Measure-Object

但我覺得不用急著背語法。

最重要的是先理解:

PowerShell Pipeline 傳遞的是 Object,而不是只把畫面上的文字往下一個指令丟。

當知道這件事情之後,很多 PowerShell 指令就會開始變得比較合理。

例如:

Get-Service |
Where-Object Status -eq "Running" |
Sort-Object Name |
Select-Object Name, DisplayName, Status

其實就是在說:

拿資料

挑我要的

排好順序

留下需要的欄位

這個思考方式後面做 Windows Server 與 Active Directory 自動化時會一直使用。


上一篇
Day 2|開始使用 PowerShell:系統工程師真正需要先學會哪些指令?
系列文
系統工程師的 30 天自動化維運實戰:PowerShell × AD × Windows Server3
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言