共用方式為


嗨,Scripting Guy!深究 WMI

Microsoft Scripting Guys

目錄

命名空間
WMI 類別
屬性
方法

當某位 Scripting Guy 還年輕時,他熱中於兩件事。第一是暢飲釀造酒;第二則是冬季露營。他相信這兩者是有關聯的。那時他有一位朋友很喜歡提議大家「一路走到底」。那本月專欄我們就要遵照那位老朋友的建議,我們要一路走到底探究 WMI 荒野 (即 Windows Management Instrumentation)。

幸好這次我們不需要長途健行,可能偶爾需要走去加加咖啡。我們將使用指令碼對 WMI 一探究竟。

當然,Scripting Guy 向來是以行事務實聞名。我們要提供真實問題的解決方案,而非向讀者高談闊論卻遺漏一些細節 — 像是如何實際完成任何事。別以為我們也會染上這種不良惡習。雖然本專欄並非針對特定的系統管理工作而撰寫,不過我們的宗旨很明確。首要目的就是要教育讀者什麼是 WMI 基礎結構,而且還要為您提供一些實用的探查指令碼。火速開啟記事本 — 讓我們一路走到底!

命名空間

WMI 存放庫是一種資料庫,用來存放通用訊息模型 (CIM)。這是物件導向的模型,表示它是由代表 WMI 可以管理的內容的一組描述 (WMI 類別) 所構成。譬如說,Win32_Process WMI 類別就代表處理序。WMI 類別是存放在不同的 WMI 存放庫區段中。WMI 存放庫區段就是所謂的命名空間。如果您在荒野中遇到 WMI 存放庫,首先會注意到的應該是它被劃分成這些高層命名空間。[圖 1] 所示的程式碼

[圖 1] 顯示命名空間

strComputer = "."
Call EnumNameSpaces("root")

Sub EnumNameSpaces(strNameSpace)
    On Error Resume Next
    WScript.Echo strNameSpace
    Set objWMIService=GetObject _
        ("winmgmts:{impersonationLevel=impersonate}\\" & _ 
            strComputer & "\" & strNameSpace)

    Set colNameSpaces = objWMIService.InstancesOf("__NAMESPACE")

    For Each objNameSpace in colNameSpaces
        Call EnumNameSpaces(strNameSpace & "\" & objNameSpace.Name)
    Next
End Sub

顯示 strComputer 中指定電腦上的 WMI 存放庫裡,所有命名空間的名稱 (一點代表本機電腦)。

命名空間可以包含子命名空間。您可以將這整個視為目錄結構。因此假使我們只要求所有命名空間 (從頂層根命名空間開始),我們將只取回根目錄中第一層命名空間,而不包括任何子命名空間。我們得改用遞迴的技巧,建立稱為 EnumNameSpaces 的副程式,將命名空間當作參數來傳回它的所有子命名空間。

我們的方法就是使用根目錄當作參數來呼叫 EnumNameSpaces。這麼做會傳回根命名空間內的所有命名空間,接下來我們執行遞迴。請注意,EnumNameSpaces 實際上是自我呼叫,然後傳入它識別的各個子命名空間。大啖一口咖啡,好好想一想我們的作法。結果是每個命名空間都會處理,如果有子命名空間,也都會顯示出來。

要注意的是,我們在副程式開頭加入了 On Error Resume Next 陳述式。這是為了預防您在不存取所有命名空間的安全性內容底下執行指令碼。在這種情況下,指令碼仍然會執行,不過因為要等待逾時,所以執行速度會比較慢。

沒錯,您可以直接使用 wbemtest.exe (任何安裝 WMI 的電腦上都找得到) 或 Scriptomatic (go.microsoft.com/fwlink/?LinkId=125976) 執行這個工作。不過只要使用一段開頭指令碼,再加上您卓越的指令碼技巧,您就可以篩選這些命名空間或將它們輸出到 Excel,或是比較兩台電腦間的命名空間。

既然我們已經見識到存放庫的分割方式,就來開發一段指令碼,以便檢視這些區段內各自包含什麼內容。我們知道 WMI 類別是儲存在各個命名空間中,因此我們先從列出這些命名空間著手。

WMI 類別

還記得我們說過 WMI 存放庫是用來存放通用訊息模型 (CIM) 的嗎?基本上,該模型是存放在 CIMV2 (V2 代表版本 2) 命名空間內。查看一下 CIMV2 命名空間,您應該會發現構成該模型的所有 WMI 類別。下列指令碼會執行這個工作:

strComputer = "."
Set objWMIService=GetObject("winmgmts: _
    {impersonationLevel=impersonate}!\\" & _
    strComputer & "\root\cimv2")

For Each objclass in 
    objWMIService.SubclassesOf()
    Wscript.Echo objClass.Path_.Class
Next

我們先看一下此指令碼到底如何運作。呼叫 GetObject 會傳回 SWbemServices 物件。GetObject 是一個 VBScript 函數,它會傳回對可編寫指令碼的 COM 物件的參考。在本例中,由於我們傳遞給它「winmgmts:…」的字串,因此 GetObject 從 WMI Scripting Library 傳回物件。請注意,我們傳送到 GetObject 的字串包含我們所連接的命名空間,也就是本例中的 \root\cimv2。這麼一來,我們要處理的 SWbemServices 物件會繫結到我們指定的特定命名空間。您可以參閱 SWbemServices 說明文件,以了解在指令碼中還可以採取哪些動作。

其中有一個您可能很眼熟的動作 ExecQuery。這個方法可讓您對連接的命名空間執行 WQL (Windows Management Instrumentation 查詢語言) 查詢。不過只要建立 SWbemServices 物件與命名空間的關聯性之後,其他還有一堆可讓您執行的動作。

我們想要檢視命名空間中的所有 WMI 類別,而 SubClassesOf 可以完成這個使命。說明文件指出它會傳回 SWbemObjectSet。聽起來有點嚇人,您可不想在荒郊野外獨自碰上 SWbemObjectSet!只要注意最後三個字母 — 這是一個集合。而 WMI 指令碼作者都知道,您可以使用 For Each 來逐步完成集合。

不出所料,SWbemObjectSet 的每個成員都是 SWbemObject。這些 SWbemObjects 各自代表 CIMV2 命名空間中的某個 WMI 類別。參閱一下 SWbemObject 的說明文件,您就知道可以從這些類別輸出的所有資訊。

在我們的指令碼中,我們選擇只顯示類別名稱。若要這麼做,我們存取 Path_ 屬性。而 Path_ 屬性本身就是一個物件。它是 SWbemObjectPath,既然是物件,因此包含很多自己的屬性。我們使用 Class,也就是類別的名稱。

現在,您不僅有一個指令碼可以顯示命名空間中所有 WMI 類別,還有一個指令碼能夠輕鬆更新以顯示其他與這些類別相關的各種資訊。比方說,WMI 類別可以是其他 WMI 類別的延伸。假設您有一個汽車模型 (Win32_Car),但您實際上需要管理的是旅行車。汽車模型中的所有項目都適用於旅行車。

但您還需要其他一些項目,例如使用布林值指出有無花俏的原木面板。您不想重新建立所有 Win32_Car 功能。您需要的是一個延伸 Win32_Car 類別的機制,藉此包含所有新的屬性。WMI 正好具有這樣的機制。

若要查看 WMI 類別是否繼承另一個 WMI 類別的屬性,您可以檢查與該 WMI 類別相關的 SWbemObject 類別的 Derivation_ 屬性。[圖 2] 的指令碼顯示 CIMV2 命名空間中的 WMI 類別,並列出這些類別衍生自哪些類別。

[圖 2] CIMV2 類別衍生

strComputer = "."
Set objWMIService=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _
    strComputer & "\root\cimv2")

For Each objclass in objWMIService.SubclassesOf()
    WScript.StdOut.Write objclass.Path_.Class
    arrDerivativeClasses = objClass.Derivation_ 
    For Each strDerivativeClass in arrDerivativeClasses 
       WScript.StdOut.Write " <- " & strDerivativeClass
    Next
    WScript.StdOut.Write vbNewLine
Next

    For Each objNameSpace in colNameSpaces
        Call EnumNameSpaces(strNameSpace & "\" & objNameSpace.Name)
    Next
End Sub

這個指令碼的開頭與上一個指令碼一樣。它使用 WScript.StdOut.Write 取代 WScript.Echo,以免在顯示的字串後面自動加入新行字元 (注意:您必須使用 Cscript.exe 而非 Wscript.exe 來執行指令碼,StdOut.Write 才能運作)。

在 SWbemObject 說明文件中,您會看到它有一個 Derivation_ 屬性。該屬性是字串陣列,其中包含目前類別所衍生的來源類別名稱。在我們的指令碼中,我們使用 For Each 來逐一檢查該陣列,並以 ASCII 箭號分隔顯示所有類別。一旦您習慣從 GetObject 傳回的 SWbemServices 物件開始做起,並細讀 WMI Scripting Library 說明文件中的可能用法,您就可以在這些名字古怪的物件上測試屬性及方法來探索各種可能性。

了解在您的指令碼任一點中使用的是哪個 WMI Scripting Library 物件之後,您就可以準備進入下一層次的 WMI 指令碼。不光是使用我們的指令碼,您也會了解我們為何能夠呼叫 ExecQuery 或參考 Properties_ 屬性。現在您可以更進一步發揮您的技巧。

Scriptomatic 工具不符合您的期望?沒問題,放手修改它,完成您要的功能。或許別人也會購買您的創作喔。In which case, just e-mail instructions to scripter@microsoft.com detailing how we will receive our royalty checks.

屬性

每個 WMI 類別都會使用一組屬性及方法來塑造您可以管理的項目,而屬性就是其特色。舉例來說,處理序包含了識別碼及優先順序,並且使用特定的記憶體量。這些屬性全都包含在 Win32_Process WMI 類別中。

當您識別出管理實體的類別時,查看可用的屬性來確定您想管理的項目是否在管理模型中。SWbemObject 類別包含稱為 Properties_ 的屬性。有趣吧?該屬性值是一個內含 SWbemProperty 物件集合的 SWbemPropertySet 物件。這些 SWbemProperty 物件分別都對應到與 SWbemObject 相關的 WMI 類別中的屬性。我懂,一堆 SWbem* 名稱聽起來很複雜,但其實也沒那麼糟啦。看一下 [圖 3]

fig03.gif

[圖 3] SWbemObject 公開所繫結的 WMI 類別的屬性 (按一下以放大影像)

請記住,以 SWbem* 開頭的類別都是 WMI Scripting Object 程式庫的成員。您可透過這些物件來使用 WMI。它們並非您可以管理的 WMI 模型的一部份。

[圖 3] 中,SWbemObject 代表 WMI 類別 Win32_SomeClass,其中包含以下屬性:Property_1、Property_2 及 Property_3。它經由本身的 Properties_ 屬性公開這些屬性。當然,如果它繫結到另一個 WMI 類別 Win32_SomeOtherClass,其屬性的名稱並不會改變,依舊是 Properties_。但是其繫結的類別屬性很可能不同。

基本上,SWbemObject 會採用其所繫結的特定 WMI 類別的屬性,不過您可使用相同的 Properties_ 機制來取得不同的屬性。這樣瞭解了嗎?再喝一大口咖啡,仔細思索一下剛才的圖表。一切將一目了然。

[圖 4] 中的指令碼運用 SWbemObject 與其 Properties_ 屬性來擷取並顯示 Win32_Service WMI 類別的所有屬性。您應該很熟悉指令碼開頭。主要差別在於我們分解了命名空間和 WMI 類別,以便更容易變更。例如,您可以將 strClass 的值直接變更成 Win32_BIOS,藉此查看該類別屬性而非 Win32_Service 的屬性。在 For Each 迴圈中,我們逐一處理 SWbemPropertySet 集合 (objClass.Properties) 並顯示每個 SWbemProperty 的 Name。

[圖 4] 取得 Win32_Service 的屬性

strComputer = "."
strNameSpace = "root\cimv2"
strClass = "Win32_Service"

Set objClass = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _ 
    strComputer & "\" & strNameSpace & ":" & strClass)

WScript.Echo strClass & " Class Properties"
WScript.Echo "------------------------------"

For Each objClassProperty in objClass.Properties_
    WScript.Echo objClassProperty.Name
Next

方法

最後,有些 WMI 類別不只是塑造可管理實體的屬性或特性,還提供方法來存取實體可以採取或接受的行為或動作。

傳回 WMI 類別的所有方法的指令碼格式 (如 [圖 5] 所示) 與傳回屬性的指令碼格式雷同。

[圖 5] 取得 WMI 類別的方法。

strComputer = "."
strNameSpace = "root\cimv2"
strClass = "Win32_Service"

Set objClass = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _ 
    strComputer & "\" & strNameSpace & ":" & strClass)

WScript.Echo strClass & " Class Methods"
WScript.Echo "---------------------------"

For Each objClassMethod in objClass.Methods_
    WScript.Echo objClassMethod.Name
Next

其差別當然就是使用了 Methods_ property 來替代 Properties_ 屬性。現在我想知道有沒有辦法可以顯示這些方法取用的參數類型?我們如何只顯示實際上包含方法的 WMI 類別?讀完本專欄後,您應該試著撰寫指令碼來解答這類問題。

希望我們已經為您提供一些有用資訊,幫助您開始了解如何探究 WMI。不過您必須自己努力穿越 SWbem* 叢林。但是指令碼能夠提供較輕鬆的良好機制來協助您深入探險。不過那兩位冬季露營伙伴的運氣就沒這麼好了。結果他們還是沒有一路走到底,因為,想要在隆冬運送足夠的釀造酒必需品到森林深處不是這麼容易的事。

Scripting Guy 為 Microsoft 做事,也就是受雇於 Microsoft。他們在不玩、不教或不看棒球 (或者其他各種活動) 的時候,就負責管理 TechNet 指令碼中心。請造訪他們的網站:www.scriptingguys.com