使用 WMI.NET Provider Extension 2.0 撰寫結合的 WMI 提供者
使用 WMI.NET Provider Extension 2.0 撰寫結合的 WMI 提供者
Gabriel Ghizila
Microsoft Corporation
2008 年 1 月
總結:詳細說明如何使用 WMI.NET Provider Extension 2.0 隨附于 .NET Framework 3.5 的結合 WMI 提供者
目錄
簡介
簡單的 .NET 類別
元件層級屬性
類別層級 WMI.NET 屬性
執行時間需求
使用 WMI 註冊
透過繼承擴充類別
實作方法
例外狀況和錯誤報表
其他祕訣
結論
清單 1 – SCMInterop.cs
清單 2 – WIN32ServiceHost.cs
簡介
Windows Management Instrumentation (WMI) 是一種廣泛使用的基礎結構,可用來管理 Windows 和 Windows 應用程式。 儘管在系統管理員和管理應用程式之間非常可擴充且很受歡迎,但許多開發人員仍想撰寫 WMI 提供者,因為需要實作的原生介面複雜度。
雖然初始版本的.NET Framework隨附一組物件和模式來實作 WMI 提供者,但它們僅限於應用程式管理,它們不會讓您定義方法和實例的金鑰會自動產生。 WMI.NET Provider Extension v2 (WMI.NET) 是 Orcas (.NET Framework 3.5) 中的新基礎結構,可讓您實作一組完整的 WMI 提供者功能。 這個新的基礎結構會與舊版 WMI.NET 提供者模型共存,但功能更強大且可擴充。
本文的重點在於如何撰寫 WMI 結合的提供者,這是 WMI.NET 中最重要的功能之一。 在撰寫分離提供者的方式中沒有任何顯著差異,因此文章可為嘗試使用 WMI.NET 撰寫任何種類的 WMI 提供者的讀者提供良好的起點。 本文將示範如何從簡單的 .NET 類別開始建立結合的 WMI 提供者,然後使用一些額外的功能加以擴充。 目標是能夠列舉裝載 Windows 服務的進程,並能夠在每個這類程式中列舉 Windows 服務,並能夠將這項功能整合到 WMI 中。
簡單的 .NET 類別
首先,我們將建立 C# 類別,以建立裝載 Windows 服務之進程的模型。 每個實例都會在相關聯的進程中顯示託管服務清單。 類別具有靜態方法,傳回與系統中執行之服務主機相關聯的所有實例。
公用類別 WIN32ServiceHost
{
類別是 Process 類別的包裝函式。 innerProcess 欄位會儲存 Process 物件的參考。
進程 innerProcess;
類別有一個建構函式,可接受進程物件做為參數。
public WIN32ServiceHost (Process innerProcess)
{
this.innerProcess = innerProcess;
}
我們會包含進程識別碼的存取子。
public int ID
{
get { return this.innerProcess.Id; }
}
Services 屬性會傳回具有託管服務名稱的陣列。 如果進程中沒有執行任何服務,則此屬性為 null。
public string[] Services
{
get
{
閒置進程不會裝載任何服務。
如果 (innerProcess.Id == 0)
傳回 null;
取得系統上所有 Windows 服務的清單
ServiceController[] services = ServiceController.GetServices () ;
列出 < ServiceController > servicesForProcess = new List < ServiceController > () ;
using (SCM scm = new SCM () )
{
for (int svcIndex = 0;svcIndex < 服務。長度;svcIndex++)
{
ServiceController crtService = services[svcIndex];
int processId = scm。GetProcessId (crtService.ServiceName) ;
比較服務正在執行的進程識別碼與目前進程的識別碼。
如果 (processId == innerProcess.Id)
{
servicesForProcess.Add (services[svcIndex]) ;
}
}
}
如果 (servicesForProcess.Count == 0)
傳回 null;
準備、填入陣列並傳回服務的名稱
string[] servicesNames = new string[servicesForProcess.Count];
for (int serviceIdx = 0;serviceIdx < servicesForProcess.Count; serviceIdx++)
{
servicesNames[serviceIdx] = servicesForProcess[serviceIdx]。ServiceName;
}
return servicesNames;
}
}
EnumerateServiceHosts 是靜態方法,會傳回 IEnumerable 以通過所有執行中的服務主機。
static public IEnumerable EnumerateServiceHosts ()
{
Process[] process = Process.GetProcesses () ;
foreach (process crtProcess in process)
{
WIN32ServiceHost crtServiceHost = 新的 WIN32ServiceHost (crtProcess) ;
如果 (crtServiceHost.Services != null)
{
yield return crtServiceHost;
}
}
}
}
開發人員可以從任何 .NET 應用程式使用此類別。 不過,在此情況下,其他各種管理應用程式將無法使用它。 WMI.NET 具有勾點,可在此範例中將 類別公開至 WMI 世界,我們將說明下列段落中的程式。 WMI 提供模型來允許利用這類物件,並將其整合到企業規模管理應用程式中作為 Systems Management Server 或 Operations Manager、提供遠端互動,並可讓您從多個平臺查看和使用這個類別。
元件層級屬性
使用 WMI.NET 公開檢測元件的第一個步驟是在元件層級設定 WmiConfiguration 屬性。 這個屬性會將元件標示為實作 WMI.NET 提供者的元件,並可讓您設定提供者所實作之類別的各種專案,包括公開類別的命名空間。 在我們的範例中,我們將定義 WMI 命名空間為 root\Test ,並將裝載模型設定為 NetworkService 安全性內容中的結合提供者模型。 請注意,所有作業都會透過模擬,在呼叫使用者的安全性內容中執行。
[assembly: WmiConfiguration (@「root\Test」, HostingModel = ManagementHostingModel.NetworkService) ]
類別層級 WMI.NET 屬性
若要使用 WMI.NET 來檢測類別,類別、其方法、欄位和屬性必須公開至 WMI.NET,並以 WMI.NET 屬性正確標示。 屬性可用來產生 WMI 所需的中繼資料,以使用現有的 C# 類別作為 WMI 類別。
檢測 .NET 類別
ManagementEntity屬性會將 .NET 類別標示為已檢測。 在部署期間,WMI.NET 基礎結構會在 WMI 存放庫中產生具有相同名稱的對應 WMI 類別。 若要修改此名稱,我們需要在ManagementEntity屬性的引數清單中提供具名參數Name。 Name 會當做具名參數來改變大部分屬性的檢測名稱。 在我們的範例中,我們選擇命名 WMI 類別 WIN32_ServiceHost 而不重新命名 .NET 類別。
[ManagementEntity (Name = 「WIN32_ServiceHost」) ]
公用類別 WIN32ServiceHost
請注意,任何實體的命名可能有點棘手。 C# 區分大小寫,但 WMI 不區分大小寫。 因此,從 WMI.NET 基礎結構的觀點來看,所有名稱都會視為不區分大小寫。 如果兩個欄位的名稱或兩個屬於相同類別成員的方法只有大小寫不同,元件將無法向 WMI 註冊。
控制 WMI 類別架構
實例的承載是由其屬性所提供。 我們需要使用 ManagementProbe、 ManagementConfiguration 或 ManagementKey 屬性將所有屬性標示為 WMI 世界。 ManagementProbe 是將公開為唯讀的屬性標示為 WMI 的屬性。 在我們的範例中,Services 屬性是我們想要向管理世界公開的承載屬性。 它會顯示無法直接修改的進程狀態,因此我們會將其標示為 ManagementProbe。 對於讀取/寫入屬性,必須使用 ManagementConfiguration 屬性,在此情況下,屬性本身必須同時具有 setter 和 getter。
[ManagementProbe]
public string[] Services
識別碼也是 WIN32ServiceHost 承載的一部分,因為它會識別進程本身。 唯一識別其類別實例的屬性是 WMI 世界中該類別的索引鍵。 WMI.NET 中的所有類別都必須定義索引鍵,並實作它們來唯一識別實例,除非它們是抽象、單一或繼承自已經定義索引鍵的類別。 索引鍵會以 ManagementKey 屬性標示。 在此範例中,進程識別碼可唯一識別進程;因此,它會唯一識別類別的實例。
[ManagementKey]
public int ID
執行時間需求
標記類別及其屬性集合將允許公開類別架構給 WMI,但類別無法讓類別公開實際資料。 我們需要定義取得、建立或刪除實例以及列舉實例的進入點。 我們將提供實例列舉的程式碼,以及擷取功能 WMI 提供者的最小實例。
列舉實例
若要列舉實例,WMI.NET 預期靜態公用方法沒有傳回 IEnumerable 介面的參數。 它必須是靜態的,因為它會實作與整個類別相關的功能。 這個方法必須以 ManagementEnumerator 屬性標示。 已在我們的範例中定義 ,EnumerateServiceHosts 符合所有需求,因此只能將其屬性化並用於此用途。
[ManagementEnumerator]
static public IEnumerable EnumerateServiceHosts ()
這個方法確實很重要,以確保列舉中傳回的每個元素都是已檢測類別的實例。 否則會產生執行階段錯誤。
系結至實例
為了能夠擷取在 WMI.NET) 中呼叫系結的特定實例 (,我們必須有方法,根據識別它的索引鍵值傳回實例。 我們需要一個方法,其參數數目與索引鍵數目相同,且參數的名稱和類型與索引鍵相同。 傳回型別應該是檢測的類別本身。 我們將使用靜態方法,而且為了將類別的索引鍵與參數產生關聯,我們需要為參數指定與索引鍵相同的已檢測名稱。 在我們的範例中, ID 是 WIN32_ServiceHost 類別的索引鍵,我們必須為系結方法 識別碼 具名引數,或使用 ManagementName 屬性將參數公開至 WMI 名稱為 「ID」。 WMI.NET 基礎結構會在使用 ManagementBind 屬性標示時辨識系結方法或建構函式。
[ManagementBind]
static public WIN32ServiceHost GetInstance ([ManagementName (「ID」) ] int processId)
{
嘗試
{
進程 = Process.GetProcessById (processId) ;
WIN32ServiceHost crtServiceHost = 新的 WIN32ServiceHost (程式) ;
如果 (crtServiceHost.Services != null)
{
return crtServiceHost;
}
else
{
傳回 null;
}
}
如果找不到具有指定識別碼的進程,則由 GetProcessById 擲回
catch (ArgumentException)
{
傳回 null;
}
}
請務必注意,如果找不到 實例,方法會傳回 null。 在我們的案例中,如果我們完全找不到所要求的進程,或找到的進程並未裝載任何 Windows 服務,我們會這麼做。
向 WMI 註冊
類別已準備好執行其工作,但我們仍然需要向 WMI 註冊它,並將它放在要載入的磁片上可存取的位置。
全域組件快取 (GAC) 是我們想要元件所在的位置。 如此一來,.NET 就可以透過其完整 .NET 名稱來擷取它。 請注意,元件必須在 GAC 中註冊 .NET 強式名稱。 在我們的範例中,我們將使用 .NET gacutil.exe工具來將元件儲存在 GAC 中。
若要從檢測的元件取得資訊到 WMI 中繼資料,我們需要使用 .NET 工具InstallUtil.exe叫用名為 DefaultManagementInstaller的 WMI.NET 類別。 DefaultManagementInstaller 知道如何剖析已檢測類別的整個元件,並產生對應的 WMI 中繼資料。 由於InstallUtil.exe需要以 RunInstaller 屬性標示的類別,我們將定義衍生自 DefaultManagementInstaller 的空類別,InstallUtil.exe會叫用。
[System.ComponentModel.RunInstaller (true) ]
public 類別 MyInstall :DefaultManagementInstaller
{
}
使用 WMI 註冊可以離線或線上完成。 線上註冊會將元件的檢測中繼資料直接儲存在 WMI 存放庫中。 若要將中繼資料直接安裝到 WMI 存放庫中,InstallUtil.exe命令將會以元件名稱作為參數來叫用。 元件必須在 GAC 中,才能執行InstallUtil.exe。 針對此範例中產生的元件 ,WMIServiceHost.dll 我們將使用下列命令:
C: >gacutil.exe /i WMIServiceHost.dll
C: >Installutil.exe WMIServiceHost.dll
離線註冊需要兩個步驟。 第一個步驟是產生與元件相關聯的 WMI 中繼資料,並將其儲存在 MOF 檔案中。 第二個步驟是使用 mofcomp.exe 工具或安裝套件一部分,在目的電腦上實際註冊。 離線註冊的優點是可以視需要當地語系化和修改 MOF 檔案。 在此範例中,我們可以使用 MOF 參數,在名為 WMIServiceHost.mof 的檔案中產生及儲存 WMI 中繼資料,如下所示:
C: >Installutil.exe /MOF=WMIServiceHost.mof WMIServiceHost.dll
如同在線上案例中,元件必須位於目的電腦上的 GAC 中。 若要驗證部署,我們可以使用 wmic.exe 系統工具來查看此類別的實例和值。
C: > wmic /NAMESPACE:\\root\test PATH win32_servicehost get /value
在開發期間,將符號部署到儲存元件之 GAC 中的相同資料夾會很有用。 如此一來,任何因錯誤或當機而回報的堆疊都會包含完整的來源路徑和行號,以協助識別違規的程式碼片段。
透過繼承擴充類別
WIN32_ServiceHost與服務主機有關,而且它提供的資訊僅限於裝載 Windows 服務的進程。 擴充這項資訊以包含程式特定資訊,例如記憶體使用量、可執行檔路徑、會話識別碼等等。 若要取得這項資訊,我們可以擴充架構,並撰寫更多程式碼來擷取所需的資訊。 撰寫額外程式碼的一個好方法,是利用存在於根\cimv2 命名空間中作業系統上已存在的WIN32_Process類別,並為系統中執行的任何進程提供所有這項額外資訊。 這個類別提供有關執行中進程的詳細資訊,而且我們可以使用 WMI 衍生來使用自己的類別來擴充它。
WMI 繼承會在 WMI.NET 編碼模型中轉譯為類別繼承。 由於我們想要衍生自 的類別是來自我們實際上未在程式碼中實作之 WMI 空間的類別,因此我們必須以特定方式加以標記。
開始撰寫衍生之前,我們需要注意有關 WIN32_Process 類別的兩個重要事項。 第 一個WIN32_Process 位於 root\cimv2 命名空間中,衍生需要我們在相同的命名空間中註冊win32_servicehost類別。 因此,我們會稍微變更 WmiConfiguration 屬性語句。
[assembly: WmiConfiguration (@「root\cimv2」, HostingModel = ManagementHostingModel.NetworkService) ]
此外,我們的超級類別win32_process具有屬性 Handle 定義為索引鍵。 此索引鍵的類型為 CIM_STRING,其會轉譯為 。NET 的 System.String。 我們必須停止使用 識別碼 作為金鑰屬性,並改用 Handle 屬性。
若要在 WMI.NET 中定義外部類別以符合win32_process我們只是鏡像其架構,但只包含我們想要或需要使用的屬性。 類別階層中的索引鍵一律為必要。 目前 Handle 是唯一有趣的屬性,因為它是索引鍵,我們需要它才能系結至特定實例。
[ManagementEntity (External = true) ]
抽象公用類別Win32_Process
{
受保護的字串控制碼;
[ManagementKey]
公用字串控制碼
{
get {
傳回 this.handle;
}
}
}
在ManagementEntity屬性上將External設定為 true 會停止基礎結構在部署時間產生 WMI 中繼資料,但在執行時間需要尋找衍生類別的索引鍵和屬性時,保留執行時間的宣告資訊以供使用。 請注意,當 WMI 子系統用來合併各種提供者的資訊時,控制基類索引鍵的內容非常重要。
若要取得 WMI 類別WIN32_ServiceHost繼承 WMI 類別 Win32Process,我們會從 .NET 世界中新建立的抽象類別衍生 WIN32ServiceHost
[ManagementEntity (Name = 「WIN32_ServiceHost」) ]
public 類別 WIN32ServiceHost:Win32_Process
我們會移除 ID 屬性。
[ManagementKey]
public int ID
{
get { return this.innerProcess.Id; }
}
改變建構函式以填入基類控制碼欄位中的新索引鍵
public WIN32ServiceHost (Process innerProcess)
{
this.innerProcess = innerProcess;
this.handle = innerProcess.Id.ToString () ;
}
修改 GetInstance 以使用名為 Handle的字串引數,只是其餘幾行保持不變。
[ManagementBind]
static public WIN32ServiceHost GetInstance (string Handle)
{
int processId;
如果 (!Int32.TryParse (Handle, out processId) )
{
傳回 null;
}
嘗試
[...]
我們需要在 GAC 中重新編譯並重新部署新的元件。 我們使用InstallUtil.exe來部署新的架構。 比起,我們可以使用稍微修改 的wmic.exe 命令來查詢系統。
C: > wmic /NAMESPACE:\\root\cimv2 PATH win32_servicehost get /value
傳回的實例會填入來自這兩個類別的資訊,win32_process和 win32_servicehost。 在輸出中,服務會來自 win32_servicehost ,而其他所有專案則來自 win32_process。 為了簡化輸出,我們可以指定所需的資料行。
C: > wmic PATH win32_servicehost get Handle,Caption,CommandLine,Services /value
當我們嘗試列舉win32_process時,它更有趣。 這類查詢會傳回所有進程,而且只會針對win32_servicehost實例填入 [服務] 欄位。
C: > wmic PATH win32_process get /value
輸出可能有點壓倒,所以只要在檔案中傾印它 (,方法是在命令列結尾新增 > out.txt) ,然後在記事本中開啟它來搜尋 Services 屬性。 為了瞭解發生什麼事,我們可以顯示系統屬性,以識別每個實例的 WMI 類別。
C: > wmic PATH win32_process get Handle,CommandLine,__CLASS /value
在產生的清單中挑選win32_ServiceHost實例,並顯示其值。
C: > wmic path WIN32_Process.Handle=「536」 get /value
您可以使用 Windows 腳本、Microsoft PowerShell、Managed 或機器碼,從任何 WMI 用戶端應用程式執行類似的作業,系統會以處理任何其他提供者的相同方式處理此元件。
實作方法
WMI.NET 支援靜態和每個實例的方法。 在我們的案例中,我們會新增方法來停止進程所裝載的所有服務,以允許在服務仍在執行時清除停止進程。 為了在 WMI 中顯示公用方法,我們會將它標示為 ManagementTask 屬性。
[ManagementTask]
public bool StopServices (int 毫秒Wait)
{
如果 (innerProcess.Id == 0)
傳回 false;
ServiceController[] services = ServiceController.GetServices () ;
bool oneFailed = false;
using (SCM scm = new SCM () )
{
for (int svcIndex = 0;svcIndex < 服務。長度;svcIndex++)
{
ServiceController crtService = services[svcIndex];
int processId = scm。GetProcessId (crtService.ServiceName) ;
if (processId == innerProcess.Id)
{
嘗試
{
crtService.Stop () ;
如果 (毫秒Wait != 0)
{
crtService.WaitForStatus ( ServiceControllerStatus.Stopped,
new TimeSpan ( (long) 毫秒Wait * 10000) ) ;
}
}
catch (System.ServiceProcess.TimeoutException)
{
oneFailed = true;
}
catch (System.ComponentModel.Win32Exception)
{
oneFailed = true;
}
catch (InvalidOperationException)
{
oneFailed = true;
}
}
}
}
return !oneFailed;
}
若要叫用此方法,我們需要 win32_servicehost 類別的實例。 我們會取得可用的服務主機清單,輸入:
C: > wmic 路徑win32_servicehost取得控制碼,服務
並挑選最良性的服務清單 (,以免關閉您的系統,此外,某些服務也可能無法停止) ,並使用其 Handle 屬性來識別呼叫的實例。
C: > wmic 路徑win32_servicehost。Handle=「540」 CALL StopServices (0)
例外狀況和錯誤報表
例外狀況是 WMI.NET 的重要層面。 基礎結構會使用一些例外狀況來傳達資訊,並將大部分的例外狀況視為未處理。
接受的例外狀況
WMI.NET 只會處理一文中將進一步說明的少數例外狀況。 所有其他例外狀況都會被視為程式設計錯誤,並視為未處理的例外狀況,導致 WMI 提供者主機當機。 WMI.NET 報告 Windows 事件記錄檔中未處理的例外狀況。
接受的例外狀況實際上會轉譯成 WMI 錯誤碼,並傳回用戶端程式代碼,如表 1 所示。 因此,WMI.NET 提供者的行為會如同任何其他原生提供者一樣。
System.OutOfMemoryException |
WBEM_E_OUT_OF_MEMORY |
System.Security.SecurityException |
WBEM_E_ACCESS_DENIED |
System.ArgumentException |
WBEM_E_INVALID_PARAMETER |
System.ArgumentOutOfRangeException |
WBEM_E_INVALID_PARAMETER |
System.InvalidOperationException |
WBEM_E_INVALID_OPERATION |
System.Management.Instrumentation.InstanceNotFoundException |
WBEM_E_NOT_FOUND |
System.Management.Instrumentation.InstrumentationException |
從內部例外狀況,文章中進一步說明 |
Table1 – 將例外狀況轉譯成 WMI 錯誤
在上述清單中,除了兩個 是一般 .NET Framework 例外狀況之外。 您可以擲回列出的所有例外狀況,以在這些例外狀況所代表的特定狀態上向用戶端報告。
System.Management.Instrumentation命名空間中新增了兩個新的例外狀況,如進一步所述。
InstanceNotFoundException
此例外狀況可用來通知找不到要求的實例。 本文中的範例會使用 GetInstance 靜態方法來系結至指定的實例,並在找不到實例時傳回 Null。 建構函式可以用於相同的用途,但當找不到所需的實例時,它必須擲回 InstanceNotFoundException 。 以下是取代 GetInstance 靜態方法的建構函式。
[ManagementBind]
public WIN32ServiceHost (string Handle)
{
int processId;
如果 (!Int32.TryParse (Handle, out processId) )
{
擲回新的 InstanceNotFoundException () ;
}
嘗試
{
進程進程 = Process.GetProcessById (processId) ;
this.innerProcess = process;
this.handle = Handle;
如果 (則為 。Services == null)
{
擲回新的 InstanceNotFoundException () ;
}
}
如果找不到具有指定識別碼的進程,GetProcessById 會擲回
catch (ArgumentException)
{
擲回新的 InstanceNotFoundException () ;
}
}
InstrumentationException
InstrumentationException是已處理例外狀況的包裝函式。 提供者可以選擇通知用戶端在其端發生錯誤。 若要這樣做,它會擲回 InstrumentationException。 請注意,開發人員應該記住例外狀況會回到 WMI 系統。 因此,WMI.NET 會嘗試將其轉換成 COM HRESULT。 若要將精確的錯誤碼擲回給用戶端,我們需要傳入作為 InstrumentationException 例外狀況的內部例外 狀況類別, 以允許直接在基底例外狀況類別中設定內部 HResult。
錯誤報告
上一節中未列出的任何例外狀況最終都會是未處理的例外狀況,這會導致當機回報為「未處理的例外狀況 ('System.ExecutionEngineException') 發生在 wmiprvse.exe[< NNNN >].「,NNNN 是進程編號。 事件記錄檔中將會報告錯誤和堆疊。 在與元件相同的資料夾中擁有符號會顯示違規堆疊完成,並包含檔案名和行號。
另一個錯誤案例是元件因為未部署到 GAC 而無法載入。 WMI.NET 會針對此案例傳回提供者載入失敗 (WBEM_E_PROVIDER_LOAD_FAILURE) 。
提供者開發期間的常見問題是 WMI 架構與程式碼不符。 這可能會在部署新元件而不將架構部署到 WMI 時發生,或在部署使用 InstallUtil.exe 時未攔截到問題,因為資訊只能在執行時間使用。 例如,如果在列舉期間傳回不正確的類型,則為這種情況。 WMI.NET 基礎結構會將提供者失敗回報給用戶端, (WMI 錯誤WBEM_E_PROVIDER_FAILURE) ,而且它會在描述執行時間問題的 Windows 事件記錄檔中產生訊息。
其他祕訣
所有屬性和 WMI.NET 程式碼都位於 System.Management.Instrumentation 命名空間中。 若要建置 WMI.NET 的元件,專案必須分別參考 System.Core.dll 和 System.Management.Infrastructure.dll ,因為它們包含屬性的定義和執行時間程式碼。 稍後會知道如何載入已檢測的元件、比對已檢測的類別與 WMI 存放庫,並據以叫用它們。
將特定應用程式的所有類別保留在相同的元件中。 這可讓維護和部署變得更容易。
文章中的所有程式碼都必須以系統管理員身分部署並執行。 在 Windows Vista 上,以系統管理員身分執行,將需要提升許可權的安全性命令提示字元。 提供者通常不需要用戶端是系統管理員,除非它們存取特別安全的系統資料,如我們的範例所示。
結論
.NET Framework 3.5 中的新管理程式庫可讓開發人員在撰寫 WMI 提供者時提供功能強大的工具。 由於程式設計模型的簡單性,撰寫 WMI 提供者會變成相當簡單的工作,可讓您實作大部分的 WMI 功能。 本文中的範例示範如何撰寫簡單的 WMI 提供者,但程式庫也支援開發分離提供者,以及實作單一提供者、繼承、抽象類別別、參考和關聯類別,讓 WMI.NET 提供者延伸模組 v2 成為使用 WMI 原生介面進行開發的嚴重替代方案。
清單 1 – SCMInterop.cs
using System;
使用 System.Runtime.InteropServices;
使用 System.ServiceProcess;
namespace External.PInvoke
{
[StructLayout (LayoutKind.Sequential) ]
內部結構SERVICE_STATUS_PROCESS
{
public uint dwServiceType;
public uint dwCurrentState;
public uint dwControlsAccepted;
public uint dwWin32ExitCode;
public uint dwServiceSpecificExitCode;
public uint dwCheckPoint;
public uint dwWaitHint;
public uint dwProcessId;
public uint dwServiceFlags;
public static readonly int SizeOf = Marshal.SizeOf (typeof (SERVICE_STATUS_PROCESS) ) ;
}
[旗標]
內部列舉SCM_ACCESS: uint
{
SC_MANAGER_CONNECT = 0x00001,
}
[旗標]
內部列舉SERVICE_ACCESS : uint
{
STANDARD_RIGHTS_REQUIRED = 0xF0000,
SERVICE_QUERY_CONFIG = 0x00001,
SERVICE_CHANGE_CONFIG = 0x00002,
SERVICE_QUERY_STATUS = 0x00004,
SERVICE_ENUMERATE_DEPENDENTS = 0x00008,
SERVICE_START = 0x00010,
SERVICE_STOP = 0x00020,
SERVICE_PAUSE_CONTINUE = 0x00040,
SERVICE_INTERROGATE = 0x00080,
SERVICE_USER_DEFINED_CONTROL = 0x00100,
SERVICE_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED |
SERVICE_QUERY_CONFIG |
SERVICE_CHANGE_CONFIG |
SERVICE_QUERY_STATUS |
SERVICE_ENUMERATE_DEPENDENTS |
SERVICE_START |
SERVICE_STOP |
SERVICE_PAUSE_CONTINUE |
SERVICE_INTERROGATE |
SERVICE_USER_DEFINED_CONTROL)
}
內部列舉SC_STATUS_TYPE
{
SC_STATUS_PROCESS_INFO = 0
}
內部類別 ServiceHandle :SafeHandle
{
public ServiceHandle ()
:base (IntPtr.Zero,true)
{
}
public void OpenService (SafeHandle scmHandle, string serviceName)
{
IntPtr serviceHandle = SCM。OpenService (scmHandle、serviceName、SERVICE_ACCESS。SERVICE_QUERY_STATUS) ;
如果 (serviceHandle == IntPtr.Zero)
{
擲回新的 System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error () ,
「SCM.QueryServiceStatusEx「) ;
}
SetHandle (serviceHandle) ;
}
protected override bool ReleaseHandle ()
{
傳回 SCM。CloseServiceHandle (base.handle) ;
}
public override bool IsInvalid
{
get { return IsClosed || handle == IntPtr.Zero; }
}
}
內部類別 SCM:SafeHandle
{
[DllImport (「advapi32.dll」, EntryPoint = 「OpenSCManagerW」, CharSet = CharSet.Unicode,
SetLastError = true) ]
public static extern IntPtr OpenSCManager (string machineName,
string databaseName,
[MarshalAs (UnmanagedType.U4) ]SCM_ACCESS dwAccess) ;
[DllImport (「advapi32.dll」, EntryPoint = 「OpenServiceW」, CharSet = CharSet.Unicode,
SetLastError = true) ]
public static extern IntPtr OpenService (SafeHandle hSCManager,
[MarshalAs (UnmanagedType.LPWStr) ] 字串 lpServiceName,
[MarshalAs (UnmanagedType.U4) ]SERVICE_ACCESS dwDesiredAccess) ;
[DllImport (「advapi32.dll」, EntryPoint = 「QueryServiceStatusEx」, CharSet = CharSet.Auto,
SetLastError = true) ]
public static extern bool QueryServiceStatusEx (SafeHandle hService,
SC_STATUS_TYPE InfoLevel,
ref SERVICE_STATUS_PROCESS dwServiceStatus,
int cbBufSize,
ref int mbBytesNeeded) ;
[DllImport (「advapi32.dll」, EntryPoint = 「CloseServiceHandle」, CharSet = CharSet.Auto,
SetLastError = true) ]
public static extern bool CloseServiceHandle (IntPtr hService) ;
public SCM ()
:base (IntPtr.Zero,true)
{
IntPtr 控制碼 = OpenSCManager (null、null、SCM_ACCESS。SC_MANAGER_CONNECT) ;
基地。SetHandle (控制碼) ;
}
protected override bool ReleaseHandle ()
{
傳回 SCM。CloseServiceHandle (base.handle) ;
}
public override bool IsInvalid
{
get { return IsClosed || handle == IntPtr.Zero; }
}
public void QueryService (string serviceName,out SERVICE_STATUS_PROCESS statusProcess)
{
statusProcess = 新的 SERVICE_STATUS_PROCESS () ;
int cbBytesNeeded = 0;
using (ServiceHandle serviceHandle = new ServiceHandle () )
{
serviceHandle.OpenService (this,serviceName) ;
bool scmRet = SCM。QueryServiceStatusEx (serviceHandle,
SC_STATUS_TYPE。SC_STATUS_PROCESS_INFO、
ref statusProcess,
SERVICE_STATUS_PROCESS。SizeOf,
ref cbBytesNeeded) ;
如果 (!scmRet)
{
擲回新的 System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error () ,
「SCM.QueryServiceStatusEx「) ;
}
}
}
public int GetProcessId (string serviceName)
{
SERVICE_STATUS_PROCESS serviceStatus;
這。QueryService (serviceName,out serviceStatus) ;
return (int) serviceStatus.dwProcessId;
}
}
}
清單 2 – WIN32ServiceHost.cs
using System;
使用 System.Collections;
using System.Collections.Generic;
使用 System.Linq;
using System.Text;
使用 System.ServiceProcess;
使用 System.Diagnostics;
使用 External.PInvoke;
使用 System.Management.Instrumentation;
[assembly: WmiConfiguration (@「root\cimv2」, HostingModel = ManagementHostingModel.NetworkService) ]
namespace TestWMI.Hosted
{
[System.ComponentModel.RunInstaller (true) ]
public 類別 MyInstall :DefaultManagementInstaller
{
}
[ManagementEntity (External = true) ]
抽象公用類別Win32_Process
{
受保護的字串控制碼;
[ManagementKey]
公用字串控制碼
{
get {
傳回 this.handle;
}
}
}
[ManagementEntity (Name = 「WIN32_ServiceHost」) ]
public 類別 WIN32ServiceHost:Win32_Process
{
處理 innerProcess;
<總結>
///
</總結>
<param name=「innerProcess」 >< /param>
public WIN32ServiceHost (Process innerProcess)
{
this.innerProcess = innerProcess;
this.handle = innerProcess.Id.ToString () ;
}
public int ID
{
get { return this.innerProcess.Id; }
}
[ManagementProbe]
public string[] Services
{
get
{
如果 (innerProcess.Id == 0)
傳回 null;
ServiceController[] services = ServiceController.GetServices () ;
列出 < ServiceController > servicesForProcess = 新的 List < ServiceController > () ;
using (SCM scm = new SCM () )
{
for (int svcIndex = 0;svcIndex < 服務。長度;svcIndex++)
{
ServiceController crtService = services[svcIndex];
int processId = scm。GetProcessId (crtService.ServiceName) ;
if (processId == innerProcess.Id)
{
servicesForProcess.Add (services[svcIndex]) ;
}
}
}
如果 (servicesForProcess.Count == 0)
傳回 null;
string[] servicesNames = new string[servicesForProcess.Count];
for (int serviceIdx = 0;serviceIdx < servicesForProcess.Count; serviceIdx++)
{
servicesNames[serviceIdx] = servicesForProcess[serviceIdx]。ServiceName;
}
return servicesNames;
}
}
[ManagementEnumerator]
static public IEnumerable EnumerateServiceHosts ()
{
Process[] process = Process.GetProcesses () ;
foreach (process crtProcess in process)
{
WIN32ServiceHost crtServiceHost = 新的 WIN32ServiceHost (crtProcess) ;
如果 (crtServiceHost.Services != null)
{
yield return crtServiceHost;
}
}
}
[ManagementBind]
public WIN32ServiceHost (string Handle)
{
int processId;
如果 (!Int32.TryParse (Handle, out processId) )
{
擲回新的 InstanceNotFoundException () ;
}
嘗試
{
進程 = Process.GetProcessById (processId) ;
this.innerProcess = process;
this.handle = Handle;
如果 (,則為 。Services == null)
{
擲回新的 InstanceNotFoundException () ;
}
}
如果找不到具有指定識別碼的進程,則由 GetProcessById 擲回
catch (ArgumentException)
{
擲回新的 InstanceNotFoundException () ;
}
}
static public WIN32ServiceHost GetInstance (string Handle)
{
int processId;
如果 (!Int32.TryParse (Handle, out processId) )
{
傳回 null;
}
嘗試
{
進程 = Process.GetProcessById (processId) ;
WIN32ServiceHost crtServiceHost = 新的 WIN32ServiceHost (程式) ;
如果 (crtServiceHost.Services != null)
{
return crtServiceHost;
}
else
{
傳回 null;
}
}
如果找不到具有指定識別碼的進程,則由 GetProcessById 擲回
catch (ArgumentException)
{
傳回 null;
}
}
[ManagementTask]
public bool StopServices (int 毫秒Wait)
{
如果 (innerProcess.Id == 0)
傳回 false;
ServiceController[] services = ServiceController.GetServices () ;
bool oneFailed = false;
using (SCM scm = new SCM () )
{
for (int svcIndex = 0;svcIndex < 服務。長度;svcIndex++)
{
ServiceController crtService = services[svcIndex];
int processId = scm。GetProcessId (crtService.ServiceName) ;
if (processId == innerProcess.Id)
{
嘗試
{
crtService.Stop () ;
如果 (毫秒Wait != 0)
{
crtService.WaitForStatus ( ServiceControllerStatus.Stopped,
new TimeSpan ( (long) 毫秒Wait * 10000) ) ;
}
}
catch (System.ServiceProcess.TimeoutException)
{
oneFailed = true;
}
catch (System.ComponentModel.Win32Exception)
{
oneFailed = true;
}
catch (InvalidOperationException)
{
oneFailed = true;
}
}
}
}
return !oneFailed;
}
}
}
© 2008 Microsoft Corporation. 著作權所有,並保留一切權利。