Windows PowerShell建置自己的軟體清查工具

Don Jones

目錄

尋找資訊
設計原型
讀取電腦名稱
模組化
管線函數

在本期的 Windows Power-Shell 專欄中,我要示範一個非常務實的用法:我要建置一項工具來清查一份電腦清單中的作業系統組建編號 (最能夠確定作業系統版本的方法之一) 和 Service Pack 版本號碼。但是我不打算直接了當地提供解決方案給您。我將帶您逐步了解我用來開發這類指令碼的流程。

雖然這項工具本身的確很好用,但我認為此工具的開發流程更是重要。您一旦了解並且能夠將此發發流程反覆應用到自己的工作後,就可以準備開始使用 Windows PowerShell 來解決幾乎所有系統管理問題 (常言道:「給他解決之道,不如教他建造工具」),我們現在就開始吧。

尋找資訊

第一件 (通常也是最難的) 工作就是找出到底在哪裡可以找到作業系統與 Service Pack 版本號碼。您可能會想要探訪登錄。登錄有時的確是這類資訊的答案,不過我通常把它當作最後的手段,因為登錄處理起來有點棘手。

「擷取」和「資訊」等字眼立即讓我聯想到一個可能的答案:Windows Management Instrumentation (WMI)。另一個讓我想到 WMI 的關鍵字是「遠端」,因為在第一版的 Windows PowerShell 中,唯一能執行任何一種遠端資訊清查或管理作業的選項,非 WMI 莫屬了。

遺憾的是,大多數的 Windows 系統中都包含成千上萬的 WMI 類別,因此很難找到包含所需資訊的正確 WMI 類別。我通常會從 Web 搜尋引擎著手,打入類似「wmi service pack version number (wmi service pack 版本號碼)」這樣的詞組。值得注意的是,您需要使用蠻長的搜尋字詞 (如上) 才能產生比較準確的結果。

您甚至還需要嘗試一些有變化的搜尋詞彙,不過不要使用縮寫 (「wmi sp version」很難產生有用的結果)。請考慮替代的字詞。比方說,您說「patch (修補程式)」,有人說「hotfix」,也有人說「quick fix engineering (快速修復工程)」或「qfe patch (qfe 修補程式)」。您可能需要試著輸入所有這些用語,才能得到正確的結果。

如果您要搜尋與電腦硬體或核心 Windows OS 相關的內容,在搜尋字詞中加入「Win32」應該會有幫助,因為大部分相關的 WMI 類別都是以「Win32_」首碼作為開頭。在我目前的搜尋中加入「Win32」的確產生最佳的結果。我看到許多結果的標題都包含 Win32_OperatingSystem,這是一個 WMI 類別名稱。

這裡有個重要的訣竅,就是避免點選任何搜尋結果 (這樣只會產生混亂)。首先我要尋找該類別真正的說明文件頁面,因此我重新搜尋剛才找到的類別名稱。藉由這麼做,頭幾筆結果中通常會出現 msdn.microsoft.com 網站連結,而這個連結應該會引導您直接進入類別說明文件頁面。

[圖 1] 顯示部份的頁面。我捲動到該頁面中特別重要的表格,其中列出類別適用的 OS 版本。我不知道有多少次,為一些事費盡心力,到頭來發現我嘗試的動作在所使用的 Windows 版本中根本不存在。所以現在我都養成先檢查這個表格的習慣。

fig01.gif

[圖 1] 查詢 WMI 類別的相關資訊 (按一下以放大影像)

將頁面向上稍微捲動,我看到兩個感興趣的屬性:BuildNumber 和 ServicePackMajorVersion。事實上,ServicePackMinorVersion 可能也很有用,但是我從未見過 Microsoft 有 2.1 的 Service Pack 號碼。無論如何,為了徹底一些,也一起選取這個屬性。

本月指令程式:Export-CliXML 和 Import-CliXML

Windows PowerShell 能夠以特殊的 XML 格式儲存物件的靜態快照,將這些物件的資訊保存在檔案中,並載入記憶體以供日後檢查。您可以直接將物件輸送到 Export-CliXML,把這些物件儲存成檔案:

Get-Process | Export-CliXML c:\processes.xml

稍後將這些物件匯入到殼層時,您可以像檢視任何其他物件一樣來檢視它們。這些不是「真正的」物件,不過可以啟用更多報告選項。假設您將指令碼排程於凌晨 3 點 (當某些維護工作正在執行時) 在指定伺服器上匯出所有處理序。等到您上班時,便可載入這些處理序檢視一番,也可以根據虛擬記憶體量將它們排序,如下所示:

Import-CliXML c:\processes.xml | Sort VM -descending

設計原型

在確認這些確實是我要的屬性之前,我不想貿然行事。而 Windows PowerShell 讓這件工作進行起來易如反掌。一開始我會先在我的本機電腦上檢查此資訊:

Get-WmiObject Win32_OperatingSystem | Select
BuildNumber,ServicePackMajorVersion,ServicePack­MinorVersion

好了,次要版本是零,跟我預期的一樣,那我就不管它了。其他資訊也符合我的預期,我的 Windows Server 2008 電腦的組建編號是 6001,Service Pack 版本是 1。

現在我要在遠端電腦 (也就是我知道我在上面是系統管理員的那一部) 上執行相似的測試:

Get-WmiObject Win32_OperatingSystem –computer Server2 | 
Select BuildNumber,ServicePackMajorVersion

如果測試行不通,在繼續之前,我必須先停下來找出原因。問題可能是跟連線能力、防火牆或使用權限有關,這些都超出 Windows PowerShell 自身範圍。一切都能適當運作之後,就可以繼續到問題的下一步:如何從檔案中取得一連串電腦名稱。

讀取電腦名稱

假設我的電腦名稱清單保存在文字檔中,每一行都列出一個電腦名稱,那麼最簡單的辦法就是使用 Get-Content Cmdlet (如果您的電腦名稱不在文字檔中或不是逐行列出也不必擔心,在後續專欄中我將介紹處理不同情況的技巧)。

每個名稱都以獨立字串物件的方式傳回。Get-WmiObjectcmdlet 有一個功能很方便,就是它的 –computerName 參數,這個參數可接受電腦名稱集合,因此您可以這麼做:

$names = Get-Content c:\computernames.txt
Get-WmiObject Win32_OperatingSystem –comp $names | Select
BuildNumber,ServicePackMajorVersion

現在的問題在於我的輸出是一份數字清單,而且不會指出哪個數字屬於哪台電腦。所幸,Win32_OperatingSystem 類別剛好有另一個屬性 CSName,裡面包含電腦名稱。因此我可以在輸出中加入該屬性,就能取得不賴的清查結果:

$names = Get-Content c:\computernames.txt
Get-WmiObject Win32_OperatingSystem –comp $names | Select
CSName,BuildNumber,ServicePackMajorVersion

模組化

對於欠缺經驗的技術人員來說,使用上述技巧可能有點困難,因此最後一步就是將所有這些步驟模組化成單一函數。其中一個方法是撰寫可接受檔案名稱的函數,然後讓函數執行全部工作:

Function Get-SPInventory ([string]$filename) {
  $names = Get-Content $filename
  Get-WmiObject Win32_OperatingSystem –comp  $names | Select   CSName,BuildNumber,ServicePackMajorVersion
}

如您所見,我只是將工作程式碼包裝成名為 Get-SPInventory 的函數。我使用名為 $filename 的輸入參數定義它,只要使用以下方法就能呼叫此函數:

Get-SPInventory c:\computernames.txt

您仍舊可以使用標準 Windows PowerShell 命令將結果輸送到 CSV 檔案、轉換成 HTML,或採用不同的格式,如下所示:

Get-SPInventory c:\computernames.txt | 
Export-CSV SPInventory.csv

但這個函數仍不完美。萬一我也想要清查未包含在 Win32_OperatingSystem 類別中的某些資訊 (例如每部電腦的 BIOS 序號),該怎麼辦?許多設定管理資料庫 (CMDB) 都使用 BIOS 序號當作電腦的唯一識別碼,因此這類資訊很有用。

我可能也希望函數輸出更有彈性,像是讓我更輕鬆地排序或篩選結果。譬如說,我隨後可以選擇在最終輸出中只包含裝有舊版 Service Pack 的 Windows Server 2003 電腦,藉此建立一份需要更新的電腦清單。

管線函數

現在我要改寫函數,讓它在 Windows PowerShell 管線中發揮更大用處。更具體的說,我希望函數直接接受來自管線的電腦名稱,如此一來,每次使用函數時,我都能決定要從何處取得電腦名稱。電腦名稱可能保存在檔案中,也可能在 Active Directory 中,因此我希望函數在這兩種情況下都能順利運作。以下就是針對管線改寫的函數:

Function Get-SPInventory {
  PROCESS {
    $wmi = Get-WmiObject Win32_OperatingSystem       –comp $_ | Select     CSName,BuildNumber,ServicePackMajorVersion
    Write-Output $wmi
  }
}

這種特殊的函數類型是使用 PROCESS 指令碼區塊,它會針對我輸送到函數的每個管線物件執行一次 (忠實讀者應該記得,我在本專欄的 2008 年 7 月份文章中曾討論過 PROCESS 指令碼區塊,網址是 technet.microsoft.com/magazine/cc644947.aspx)。特殊的 $_ 變數會自動填入管線輸入;只要我輸入的是電腦名稱,它就能順利運作。新函數的使用方式如下:

Get-Content c:\computernames.txt | Get-SPInventory

如您所見,在從不同來源取得電腦名稱時,這麼做能提供更多彈性。我只是將命令的 Get-Content 部分取代成擷取電腦名稱所需的命令。請注意,我也把它設定成可以透過在建構輸出之前查詢不同 WMI 類別 (或其他資料來源),好建立更可靠的輸出。

不過相關討論尚未結束,下個月我將擴展這個函數,在輸出中加入 BIOS 序號。

Don Jones 是 Concentrated Technology 的創辦人之一,同時也是眾多 IT 書籍的作者。您可以到以下網址閱讀他的 Windows PowerShell 每週秘訣或與他連絡:www.ConcentratedTech.com