Windows PowerShell:把命令化為可以重複使用的工具

您可以重新封裝並且重複使用您在 Windows PowerShell 命令和 cmdlet 所投下的心血。

Don Jones

無論您在最初開始使用 Windows PowerShell 時有多麼地缺乏經驗,您始終都有很大的進步空間。 您可以在最初階段運行簡單的命令,而後漸漸學會使用更複雜的命令,並最終將這些命令重新封包到一個在外觀和使用方面幾乎類似于本機 cmdlet 的工具中。 此類工具稱作高級函數,俗稱“腳本 cmdlet”。

假設您要從電腦中檢索一些重要的清單資訊。 您需要 Windows 版本、BIOS 序號、Service Pack 版本以及處理器體系結構。 您可以使用以下三個命令獲取此資訊:

Get-WmiObject –class Win32_OperatingSystem –computername SERVER-R2 | Select-Object –property __SERVER,BuildNumber,Caption,ServicePackMajorVersion
Get-WmiObject –class Win32_BIOS –computername SERVER-R2 | Select-Object –property SerialNumber
Get-WmiObject –class Win32_Processor –computername SERVER-R2 | Select-Object –property AddressWidth

問題是這些命令將生成三個不同的結果集。 您無法將所有結果直接通過管道傳輸到一個 CSV 檔(例如,用來存儲清單資料)或一個 HTML 檔(用以將此清單顯示為網頁)。 最好將這些資料綁定到一個即便是經驗不足的使用者也可以使用的參數化命令中。 您需要此命令:

  • 通過管道接受一個或多個以字串形式提供的電腦名稱,如下所示:
Get-Content names.txt | Get-OSInfo | ConvertTo-HTML | Out-File info.html
  • 通過 –computername 參數接受一個或多個電腦名稱,如下所示:
Get-OSInfo –computername Server-R2,ServerDC4 | Format-Table
  • 通過管道接受一個或多個分別包含一個 computername 屬性的物件,如下所示:
Get-ADComputer –filter * -searchbase "ou=West,dc=company,dc=com" | Select-Object @{label='computername';expression={$_.Name}} | Get-OSInfo | Export-CSV inventory.csv

採用這種方式,您將無需擔心電腦名稱的出處, 也不必擔心您將創建的輸出類型。 相關任務將由 Shell 處理。

此外,您還需要有一個 –logfile 參數,該參數將接受日誌檔的路徑和檔案名。 由於此命令將仍使用 Windows Management Instrumentation (WMI) 連接和查詢資訊,因此您有可能無法訪問一台或多台電腦。 您仍需要將這些電腦的名稱寫入該日誌檔,隨後您可以使用此檔解決問題,甚至重新嘗試訪問這些電腦。

處理錯誤

您可以使用 PowerShell 中的 Try…Catch 構造完成最後這一部分。 只需將第一個 WMI 查詢打包到 Try…Catch 塊中。 然後指定 –ErrorAction Stop 參數,以便它能夠捕獲命令生成的任何錯誤。 設置一個用於跟蹤是否出錯的變數,以便您的腳本確定是否嘗試進行隨後的兩個 WMI 查詢:

$continue = $True
Try {
  Get-WmiObject –class Win32_BIOS –computername $computername –EA Stop
} Catch {
  $continue = $False
  $computername | Out-File –path $logfile –append
}

處理輸入

比較棘手的部分是處理輸入。 您將通過兩種方法之一接收輸入 — 通過管道或通過參數。 您實際上可以使用 cmdlet 樣式的參數綁定將通過管道傳入的輸入定向到參數。 以下是此操作的基本結構:

Function Get-OSInfo {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
  [string[]]$computername,
  [string]$logfile 
 )
  BEGIN {}
  PROCESS {}
  END {}
}

cmdlet 中的 PROCESS 塊可保證至少執行一次;如果未提供管道輸入,PROCESS 塊將執行一次。 如果提供了管道輸入,PROCESS 塊將針對每個通過管道傳入的專案執行一次。 它會將該專案置於 $computername 變數中。

問題就出在這裡:如果僅通過參數指定輸入(如第二個專案符號標記的示例所示),則 PROCESS 將僅執行一次。 $computername 將包含提供給該參數的每個電腦名稱。 您必須自行枚舉或“展開”這些名稱。 如果這些專案通過管道傳入,則您一次只須處理一個專案。 您還可以在 $computername 變數中獲得它們。

技巧在於創建一個執行實際工作的從屬函數。 使用高級函數接受任一類型的輸入,並將相關操作分解為一次顯示一個電腦名稱。 然後,調用每次處理一個電腦名稱的從屬“輔助”函數。 以下是基本結構,此結構將位於主函數的 PROCESS 塊中:

PROCESS {
  if ($PSBoundParameters.ContainsKey('computername')) {
    foreach($computer in $computername) {
      OSInfoWorker –computername $computer –logfile $logfile
    }
  } else {
    OSInfoWorker –computername $computername –logfile $logfile
  }
}

結束語

解決主要的技術難題後,將所有內容綜合在一起,並添加一些其他細節,例如在每次運行函數之前清理舊的日誌檔(此操作非常適合於 BEGIN 塊)。 由兩個函陣列成的最終腳本有一點長。 但其中只有很少一部分需要實際的“程式設計”。大多數內容只是一些類似于在命令列上運行的 Windows PowerShell 命令。 它們只是被大量聲明性結構所包圍,從而使其行為類似于 cmdlet。

全部內容發佈在我的網站 ow.ly/39YcX 上,同時還提供了最終代碼的視頻演示。 這是一個絕對可以重用的範本。 實際上,它的主要功能只是處理輸入,因此您幾乎可以按原樣使用它。 您只需根據自己的特定需要更改參數,僅此而已。 實際的工作在單獨的函數中執行。

下個月,我將介紹如何將所有這些內容打包為一個易於分發的腳本模組。 此外,我還將介紹如何為您的所有管理員同事可以使用的腳本模組設置一個共用位置。

Don Jones

Don Jones 是 Concentrated Technology 的創始人,他會在 ConcentratedTech.com 上解答有關 Windows PowerShell 和其他技術的問題。 他也是 Nexus.Realtimepublishers.com 的撰稿人,他的許多著作還在他的網站上以電子版的形式提供。

 

獲取更多內容

本月的文章隨附了視頻演示以及可下載的示例代碼。 從此處獲取這些額外的內容

相關內容