Windows PowerShell投遞箱

Don Jones

目錄

扮演指令程式
篩選函數
實際設想
實際的應用程式
挪出時間玩耍

在我教授的最近一場 Windows PowerShell 課堂中,我發現有些學生不太能夠意會命令介面的管線。我承認管線並不是很直觀的概念,因此視覺學習者很難發揮想像力。當我開始談到

篩選函數的概念時,因為它是直接在管線裡面運作,所以更是亂成一團,光從一些臉部表情就能看出好幾個人似懂非懂。

為了推他們一把,我隔天準備了一只箱子、一些名牌貼紙和一些乒乓球 (嘿,誰說 Windows PowerShell® 都很沉悶的)。我決定使用下列命令列做些示範:

Get-Process | Where { $_.VM –gt 1000 } | Sort VM
–descending | Export-CSV C:\Procs.csv

扮演指令程式

使用名牌貼紙 — 就是在同學會以及類似的討厭聚會中,被迫戴上的「大家好,我叫…」貼紙 — 我為每一個學生分別指派一個指令程式名稱。我先解釋乒乓球代表 Windows® 處理物件 (更具體的說,是 System.Diagnostics.Process 型別的物件),然後要求學生告訴我,哪一個指令程式名稱聽起來比較可能會產生處理物件。

他們張望著彼此的名牌,勉強猜出 Get-Process 這個明顯的選擇。這也是為什麼我這麼喜歡 Windows PowerShell 中所用的指令程式名稱的主要原因之一:它們通常都很明顯易懂。

所以扮演 Get-Process 的學生拿了所有的乒乓球,把它們全都丟到紙箱中。紙箱是代表命令介面的管線,而這名學生所做的動作,差不多就是指令程式所做的。指令程式會產生一或多個物件,然後把它們傾印到管線中。

接下來就由管線中的下一個指令程式接手。在本例中,代表 Where-Object 指令程式的學生拿起所有的乒乓球一一檢查,看看這些乒乓球的 VM 屬性是否大於 1,000。在命令介面中,「VM」是指虛擬記憶體,而且為了這次練習,我在每個乒乓球上以馬克筆寫了不同數量的虛擬記憶體。

VM 值大於或等於 1,000 的乒乓球 (也稱為處理序) 全部放回紙箱 (有時候稱為管線),而 VM 值比較小的則被丟到垃圾筒裡面,一去不復返 (沒啦,我把它們全部救起來,準備在以後的課堂上使用)。

接下來,扮演 Sort-Object 的學生逐一檢視整箱的乒乓球,然後根據它們的 VM 屬性把它們一一排好。我自認這部分的練習考慮不夠周到 — 因為要讓乒乓球不到處滾動實在需要一點技巧!看來下一堂課我得使用乒乓球隔間,不然就是使用蛋裝盒。

最後,飾演 Export-CSV 的學生拿起球,把資訊寫出到 CSV 檔案。在真人演繹的課堂上,這位扮演指令程式的學生,把處理序的屬性寫在假扮 CSV 檔案的簡報用紙板上。

篩選函數

講解完簡單的管線之後,我們決定探討稍微複雜的篩選函數。我提出的篩選函數和命令列如 [圖 1] 所示。

[圖 1] 範例篩選函數和命令列

Function Do-Something {
 BEGIN { }
 PROCESS {
  $obj = New-Object PSObject
  $obj | Add-Member NoteProperty "TimeStamp" (Get-Date)
  $obj | Add-Member NoteProperty "ProcessName" ($_.Name)
  Write-Output $obj
 }
 END {}
}
Get-Process | Do-Something | Format-Table *

首先,我想迅速討論一下篩選函數應該具備什麼作用。這個函數包含三個指令碼區塊,分別名為 BEGIN、PROCESS 和 END。

當它用在管線時,會先執行 BEGIN 指令碼區塊。這個指令碼可能會進行任何設定工作,例如開啟資料庫連接。

接下來,PROCESS 指令碼區塊會針對每個輸送到函數的物件各執行一次。在 PROCESS 指令碼區塊內,$_ 變數會自動填入目前的管線物件。所以,如果輸送 10 個物件,PROCESS 指令碼區塊便會執行 10 次。

最後,當所有輸送進去的物件全部執行完畢後,便開始執行 END 指令碼區塊,並且進行所有必要的清理工作,像是關閉資料庫連接。

在這些指令碼區塊中,所有使用 Write-Output 寫入的東西都會留在管線中,供下一個指令程式使用。而沒有使用 Write-Output 寫入的東西則全部捨棄。

由於我們是以 Get-Process 命令開頭,因此扮演這個命令的學生便把所有的乒乓球撿拾起來 (我們只不過稍微休息片刻,球便在整間教室被丟得到處都是 — 想也知道),全部丟到管線箱裡面。她的工作結束之後,就由扮演「Do-Something」篩選函數的學生接手。

他先執行他的 BEGIN 指令碼區塊。因為是空的,所以不費吹灰之力。接著他拿起所有的管線物件 (乒乓球),一次查看一個。這就有點好玩了,因為算算總共有一打的球,全都要放在大腿上 — 您得在場才能體會。

總之,他一次拿起一個處理物件,然後執行他的 PROCESS 指令碼區塊。那個指令碼區塊幫他建立了一個全新的自訂物件 (我特別使用黃色乒乓球,因為這在當地的運動用品店都買得到)。他在這些新的乒乓球上,除了寫上目前正在查看的處理物件名稱外,也寫上目前的日期和時間。

接著這些新的自訂物件會寫入管線中,因此他把黃色的乒乓球放入紙箱,而把原始的處理物件乒乓球丟掉。紙箱中的每一個處理物件乒乓球都要進行這項作業,總共約 12 次。大功告成之後,所有的白色乒乓球便全部換成黃色的自訂物件乒乓球。最後再執行 END 指令碼區塊,這也是空的,所以沒花什麼時間。

總結部分是由 Format-Table 學生負責。她拿起紙箱中所有的東西 (目前為止全都是黃色「自訂」物件),開始以每個球上所寫的兩個屬性來建構資料表。最後得出一份寫在簡報用紙板上、內含兩個資料行 (TimeStamp 和 ProcessName) 的資料表,約有 12 個資料列。

實際設想

這項練習讓課堂裡面的每個人徹底瞭解了管線和篩選函數。以乒乓球代表物件這一招的確很成功,因為命令介面有點抽象,所以非常適合。

每個人都看得到指令程式如何操控這些物件、如何將結果放到管線中,還有下一個指令程式又是如何取得這些結果,然後進一步操控物件。連續事件在篩選函數的成效更是顯而易見,這都要拜 PROCESS 指令碼區塊一次處理一個輸入物件的方法所賜。

這也讓大家看到建立新自訂物件以及將新物件放入管線的方式。另外,與簡單文字相較之下,此舉也彰顯了產生自訂物件的優點 — 因為其他指令程式都可以取用新的自訂物件 (例如 Format-Table),為資料的運用和呈現提供了很大的彈性。

實際的應用程式

學生最常提出的問題是,如何將我們剛剛示範的管線和篩選函數,應用到實際的應用程式。這對我來說不難回答,因為我最近才剛為幾場研討會製作了幾個範例 (您可以自己下載範例,網址是 scriptinganswers.com/essentials/index.php/2008/03/25/techmentor-2008-san-francisco-auditing-examples)。

其中一個範例的目的,是列出幾部電腦上 Windows Management Instrumentation (WMI) 中的 Win32_UserAccount 類別的幾個屬性。假設電腦名稱是列在 C:\Computers.txt 中,這個簡單的命令就可以一手包辦:

Gwmi win32_useraccount –comp (gc c:\computers.txt)

但問題是 Win32_UserAccount 類別並不包含可以告訴您每個執行個體是來自哪個電腦的屬性,因此您所得到的只是混入許多電腦帳戶的無用清單。我的解決方法是建立一個包含來源電腦名稱的新自訂物件,並且選取我感興趣的類別屬性。這個自訂物件的程式碼如 [圖 2] 所示。

[圖 2] 使用自訂物件收集電腦名稱以及選取屬性

function Get-UserInventory {
  PROCESS {
    # $_ is a computer name
    $users = gwmi win32_useraccount -ComputerName $_
    foreach ($user in $users) {
      $obj = New-Object
      $obj | Add-Member NoteProperty Computer $_
      $obj | Add-Member NotePropertyPasswordExpires ($user.PasswordExpires)
      $obj | Add-Member NoteProperty Disabled ($user.Disabled)
      $obj | Add-Member NotePropertyFullName ($user.FullName)
      $obj | Add-Member NoteProperty Lockout ($user.Lockout)
      $obj | Add-Member NoteProperty Name ($user.Name)
      $obj | Add-Member NotePropertyPasswordRequired ($user.PasswordRequired)
      $obj | Add-Member NotePropertyPasswordChangeable ($user.PasswordChangeable)
    }
  }
}

Get-Content c:\computers.txt | 
  Get-UserInventory | 
  where { $_.PasswordRequired -eq $False } | 
  selectComputer,Name | 
  Export-Csv c:\BasUsersBad.csv

最後一行命令列會將所有電腦名稱全部傳遞到篩選函數,此函數會產生自訂物件。接下來,我把 PasswordRequired 屬性不是 False 的使用者全部篩選掉 (目的是要產生一份問題帳戶的稽核報告),只保留 Computer 和 Name 屬性,所以最終報告所列的是,因為沒有過期密碼而需要留意的電腦名稱和帳戶名稱。篩選函數能製作出這樣的多電腦報告,是因為可以把來源電腦名稱加到輸出當中,同時能把屬性精簡成我只想查看的屬性。

雖然還有其他類似的方法可以完成這樣的工作,但是這個方法可能是最簡單明瞭的了。它另外也闡明了重要的概念和技巧。

挪出時間玩耍

即使您已經摸熟管線,但還是有些教訓要謹記在心。利用實體物件來思考可幫助您理清目的。

所以下次您再想不透某個 Windows PowerShell 概念時,不妨將電腦暫撇一邊,以日常物件作為例子,重複同樣的工作。我誠懇的建議您,為了這個用途,還是保留一小袋乒乓球吧 (如果找得到的話,也可以用立方塊代替)。

本月指令程式:Out-File

您是不是常使用 > 符號把指令程式的輸出導向檔案?像是 Get-Process > Processes.txt。您可知道您其實是假裝使用 Out-File 指令程式的嗎?下面是執行相同功能的命令:Get-Process | Out-File Processes.txt。

沒錯,是得多打幾個字,可是何苦要打出完整的 Out-File 指令程式呢?其中一個理由是 Out-File 讀起來要清楚得多。比方說,六個月後要是有人查看您的指令碼,可能會納悶 > 指的是什麼 (它畢竟是有點過時的用法)。

相反地,Out-File 就很明顯的表示即將建立和寫入檔案。另外,Out-File 還提供 -append 參數 (就是 >> 啦),還有 -force 和 -noClobber 參數,讓您控制是否要覆寫現有的檔案。最後一點,打出完整的 Out-File 還可以存取 -whatif 參數。這個實用的參數只會告訴您 Out-File 的作法,但不會實際進行。這個方法不但可以測試複雜的命令列,而且沒有任何危險,可說是相當不錯的方法。

Don Jones 是《Windows PowerShell:TFM》的作者之一,並且負責教授 Windows PowerShell 課程 (www.ScriptingTraining.com)。

© 2008 Microsoft Corporation and CMP Media, LLC.著作權所有,並保留一切權利。未經許可,不得部分或全部重製。