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 自動化時會一直使用。