Schreiben gekoppelter WMI-Anbieter mit WMI.NET Provider Extension 2.0
Schreiben gekoppelter WMI-Anbieter mit WMI.NET Provider Extension 2.0
Gabriel Ghizila
Microsoft Corporation
Januar 2008
Zusammenfassung: Details zum Schreiben eines gekoppelten WMI-Anbieters mit WMI.NET Anbietererweiterung 2.0, die in .NET Framework 3.5 ausgeliefert ist
Inhalte
Einführung
Eine einfache .NET-Klasse
Attribut auf Assemblyebene
Attribute auf Klassenebene WMI.NET
Laufzeitanforderungen
Registrierung bei WMI
Erweitern von Klassen durch Vererbung
Implementieren von Methoden
Ausnahmen und Fehlerberichte
Weitere Tipps
Zusammenfassung
Auflistung 1 – SCMInterop.cs
Eintrag 2 – WIN32ServiceHost.cs
Einführung
Die Windows-Verwaltungsinstrumentation (Windows Management Instrumentation, WMI) ist eine weit verbreitete Infrastruktur zum Verwalten von Windows- und Windows-Anwendungen. Obwohl sie sehr erweiterbar und bei Systemadministratoren und Verwaltungsanwendungen beliebt sind, denken viele Entwickler aufgrund der Komplexität der nativen Schnittstellen, die implementiert werden müssen, über das Schreiben von WMI-Anbietern nach.
Während die ersten Versionen der .NET Framework eine Reihe von Objekten und Mustern zum Implementieren von WMI-Anbietern enthielten, waren diese auf die Anwendungsverwaltung beschränkt, konnten Sie keine Methoden definieren, und die Schlüssel für Instanzen wurden automatisch generiert. WMI.NET Provider Extension v2 (WMI.NET) ist eine neue Infrastruktur in Orcas (.NET Framework 3.5), mit der Sie einen vollständigen Satz von WMI-Anbieterfunktionen implementieren können. Diese neue Infrastruktur besteht zusammen mit dem WMI.NET-Anbietermodell aus früheren Versionen, ist aber viel leistungsstärker und erweiterbarer.
Der Fokus des vorliegenden Artikels liegt auf dem Schreiben von WMI-gekoppelten Anbietern, die eine der wichtigsten Funktionen in WMI.NET. Es gibt keine signifikanten Unterschiede in der Art und Weise, wie die entkoppelten Anbieter geschrieben werden, sodass der Artikel einen guten Start für einen Leser bieten kann, der versucht, WMI-Anbieter jeglicher Art mit WMI.NET zu schreiben. In diesem Artikel wird gezeigt, wie Sie einen gekoppelten WMI-Anbieter erstellen, beginnend mit einer einfachen .NET-Klasse, und dann mit einigen zusätzlichen Features anreichern. Das Ziel besteht darin, Prozesse aufzuzählen, die Windows-Dienste hosten, und windows-Dienste in jedem solchen Prozess aufzuzählen und diese Funktionalität in WMI integrieren zu können.
Eine einfache .NET-Klasse
Zunächst erstellen wir eine C#-Klasse, die einen Prozess modellieren wird, der Windows-Dienste hostt. Jede instance zeigt eine Liste der gehosteten Dienste im zugeordneten Prozess an. Die -Klasse verfügt über eine statische Methode, die alle Instanzen zurückgibt, die Diensthosts zugeordnet sind, die im System ausgeführt werden.
public-Klasse WIN32ServiceHost
{
Die -Klasse ist ein Wrapper um die Process-Klasse. Im InnerProcess-Feld wird ein Verweis auf das Process-Objekt gespeichert.
InnerProcess verarbeiten;
Die -Klasse verfügt über einen Konstruktor, der ein Prozessobjekt als Parameter akzeptiert.
public WIN32ServiceHost(Process innerProcess)
{
this.innerProcess = innerProcess;
}
Wir fügen einen Accessor für die Prozess-ID ein.
public int ID
{
get { return this.innerProcess.Id; }
}
Die Services-Eigenschaft gibt ein Array mit den Namen der gehosteten Dienste zurück. Diese Eigenschaft ist NULL, wenn keine Dienste im Prozess ausgeführt werden.
public string[] Services
{
get
{
Der Leerlaufprozess hostt keine Dienste.
if (innerProcess.Id == 0)
gibt NULL zurück;
Abrufen einer Liste aller Windows-Dienste im System
ServiceController[] services = ServiceController.GetServices();
List<ServiceController> servicesForProcess = new List<ServiceController>();
using (SCM scm = new SCM())
{
for (int svcIndex = 0; svcIndex-Dienste < . Länge; svcIndex++)
{
ServiceController crtService = services[svcIndex];
int processId = scm. GetProcessId(crtService.ServiceName);
Vergleichen Sie die ID des Prozesses, in dem ein Dienst ausgeführt wird, mit der ID des aktuellen Prozesses.
if (processId == innerProcess.Id)
{
servicesForProcess.Add(services[svcIndex]);
}
}
}
if (servicesForProcess.Count == 0)
gibt NULL zurück;
Vorbereiten, Auffüllen und Zurückgeben des Arrays mit den Dienstnamen
string[] servicesNames = new string[servicesForProcess.Count];
for (int serviceIdx = 0; serviceIdx < servicesForProcess.Count; serviceIdx++)
{
servicesNames[serviceIdx] = servicesForProcess[serviceIdx]. Servicename;
}
return servicesNames;
}
}
EnumerateServiceHosts ist eine statische Methode, die eine IEnumerable zurückgibt, um alle ausgeführten Diensthosts zu durchlaufen.
static public IEnumerable EnumerateServiceHosts()
{
Process[] processes = Process.GetProcesses();
foreach (Prozess crtProcess in Prozessen)
{
WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(crtProcess);
if (crtServiceHost.Services != null)
{
return crtServiceHost;
}
}
}
}
Ein Entwickler kann diese Klasse aus jeder .NET-Anwendung verwenden. In diesem Fall haben jedoch verschiedene andere Verwaltungsanwendungen keine Möglichkeit, sie zu verwenden. WMI.NET verfügt über die Hooks, um die -Klasse in diesem Beispiel für die WMI-Welt verfügbar zu machen, und wir erläutern den Vorgang in den folgenden Absätzen. WMI bietet ein Modell, um die Nutzung solcher Objekte zu ermöglichen und sie in eine Verwaltungsanwendung auf Unternehmensebene als Systems Management Server oder Operations Manager zu integrieren, Remoteinteraktionen bereitzustellen und es Ihnen zu ermöglichen, diese Klasse von mehreren Plattformen aus anzuzeigen und zu verwenden.
Attribut auf Assemblyebene
Der erste Schritt zum Verfügbarmachen einer Assembly für die Instrumentierung mit WMI.NET ist das Festlegen eines WmiConfiguration-Attributs auf Assemblyebene. Mit diesem Attribut wird die Assembly als eine Assembly markiert, die einen WMI.NET Anbieter implementiert, und Sie können verschiedene Informationen zu den vom Anbieter implementierten Klassen konfigurieren, einschließlich des Namespaces, in dem sie verfügbar gemacht werden. In unserem Beispiel definieren wir den WMI-Namespace als root\Test und legen das Hostingmodell im Sicherheitskontext NetworkService auf das gekoppelte Anbietermodell fest. Beachten Sie, dass alle Vorgänge im Sicherheitskontext des aufrufenden Benutzers durch Identitätswechsel ausgeführt werden.
[assembly: WmiConfiguration(@"root\Test", HostingModel = ManagementHostingModel.NetworkService)]
Attribute auf Klassenebene WMI.NET
Um eine Klasse mit WMI.NET zu instrumentieren, müssen die Klasse, ihre Methoden, Felder und Eigenschaften, die WMI.NET verfügbar gemacht werden, öffentlich sein und ordnungsgemäß mit WMI.NET Attributen gekennzeichnet sein. Die Attribute werden verwendet, um die Metadaten zu generieren, die von WMI erforderlich sind, um die vorhandene C#-Klasse als WMI-Klasse zu verwenden.
Instrumentieren einer .NET-Klasse
Das ManagementEntity-Attribut markiert eine .NET-Klasse als instrumentiert. Zur Bereitstellungszeit generiert die WMI.NET-Infrastruktur eine entsprechende WMI-Klasse mit demselben Namen im WMI-Repository. Um diesen Namen zu ändern, müssen wir den benannten Parameter Name in der Argumentliste für das ManagementEntity-Attribut angeben. Name wird als benannter Parameter verwendet, um den Instrumentierungsnamen für die meisten Attribute zu ändern. In unserem Beispiel wählen wir den Namen der WMI-Klasse WIN32_ServiceHost , ohne die .NET-Klasse umzubenennen.
[ManagementEntity(Name = "WIN32_ServiceHost")]
public-Klasse WIN32ServiceHost
Beachten Sie, dass die Benennung von Entitäten ein wenig schwierig sein kann. Bei C# wird die Groß-/Kleinschreibung beachtet, WMI jedoch nicht. Daher wird bei allen Namen aus Sicht der WMI.NET-Infrastruktur die Groß-/Kleinschreibung nicht beachtet. Wenn sich die Namen von zwei Feldern oder zwei Methoden, die Member derselben Klasse sind, nur nach Groß-/Kleinschreibung unterscheiden, kann die Assembly nicht bei WMI registriert werden.
Steuern des WMI-Klassenschemas
Die Nutzlast eines instance wird durch seine Eigenschaften angegeben. Wir müssen alle Eigenschaften markieren, die in der WMI-Welt reflektiert werden sollen, mit den Attributen ManagementProbe, ManagementConfiguration oder ManagementKey . ManagementProbe ist das Attribut zum Markieren von Eigenschaften, die in WMI als schreibgeschützte Eigenschaften verfügbar gemacht werden. In unserem Beispiel ist die Services-Eigenschaft die Nutzlasteigenschaft, die der Verwaltungswelt zur Verfügung stehen soll. Es wird ein Zustand eines Prozesses angezeigt, der nicht direkt geändert werden kann. Daher markieren wir ihn mit managementProbe. Für Lese-/Schreibeigenschaften muss das ManagementConfiguration-Attribut verwendet werden. In diesem Fall muss die Eigenschaft selbst über einen Setter und einen Getter verfügen.
[ManagementProbe]
public string[] Services
DIE ID ist auch Teil der Win32ServiceHost-Nutzlast, da sie den Prozess selbst identifiziert. Die Eigenschaften, die eine instance ihrer Klasse eindeutig identifizieren, sind die Schlüssel für diese Klasse in der WMI-Welt. Alle Klassen in WMI.NET müssen Schlüssel definieren und implementieren, um Instanzen eindeutig zu identifizieren, es sei denn, sie sind abstrakt, singleton oder erben von einer Klasse, die bereits Schlüssel definiert. Schlüssel werden mit dem ManagementKey-Attribut gekennzeichnet. In diesem Beispiel identifiziert die Prozess-ID einen Prozess eindeutig. Daher werden Instanzen unserer -Klasse eindeutig identifiziert.
[ManagementKey]
public int ID
Laufzeitanforderungen
Das Markieren der Klasse und ihrer Auflistung von Eigenschaften ermöglicht es, das Klassenschema verfügbar zu machen, das für WMI verfügbar gemacht werden soll, aber es reicht nicht aus, wenn die Klassen die tatsächlichen Daten verfügbar machen. Wir müssen Einstiegspunkte zum Abrufen, Erstellen oder Löschen eines instance sowie zum Aufzählen von Instanzen definieren. Wir stellen Code für instance-Enumeration bereit und ruft eine bestimmte instance ab, was für einen funktionalen WMI-Anbieter praktisch das Minimum ist.
Auflisten der Instanzen
Zum Aufzählen von Instanzen erwartet WMI.NET eine statische öffentliche Methode ohne Parameter, die eine IEnumerable-Schnittstelle zurückgibt. Sie muss statisch sein, da sie funktionen implementiert, die für die gesamte Klasse relevant sind. Diese Methode muss mit dem ManagementEnumerator-Attribut gekennzeichnet werden. EnumerateServiceHosts ist bereits in unserem Beispiel definiert und erfüllt alle Anforderungen, sodass es nur zugeordnet und für diesen Zweck verwendet werden kann.
[ManagementEnumerator]
static public IEnumerable EnumerateServiceHosts()
Für diese Methode ist es wirklich wichtig sicherzustellen, dass jedes element, das in der Enumeration zurückgegeben wird, ein instance der instrumentierten Klasse ist. Andernfalls tritt ein Laufzeitfehler auf.
Bindung an eine instance
Um eine bestimmte instance abrufen zu können (die in WMI.NET als Bindung bezeichnet wird), benötigen wir eine Methode, die eine instance basierend auf den Werten der Schlüssel zurückgibt, die sie identifizieren. Wir benötigen eine Methode, die über die gleiche Anzahl von Parametern wie Schlüssel verfügt, wobei die Parameter die gleichen Namen und Typen wie die Schlüssel aufweisen. Der Rückgabetyp sollte die instrumentierte Klasse selbst sein. Wir verwenden eine statische Methode, und um die Schlüssel der Klasse parametern zuzuordnen, müssen wir die gleichen instrumentierten Namen für Parameter wie für Schlüssel angeben. In unserem Beispiel ist ID der Schlüssel für die WIN32_ServiceHost-Klasse , und wir müssen entweder den Parameter für die Bindungsmethoden-ID benennen oder das ManagementName-Attribut verwenden, um den Parameter für WMI unter dem Namen "ID" verfügbar zu machen. Die WMI.NET-Infrastruktur erkennt eine Bindungsmethode oder einen Konstruktor, wenn sie mit dem ManagementBind-Attribut markiert ist.
[ManagementBind]
static public WIN32ServiceHost GetInstance([ManagementName("ID")] int processId)
{
Versuch
{
Prozessprozess = Process.GetProcessById(processId);
WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(process);
if (crtServiceHost.Services != null)
{
return crtServiceHost;
}
else
{
gibt NULL zurück;
}
}
wird von GetProcessById ausgelöst, wenn kein Prozess mit der angegebenen ID gefunden wird
catch (ArgumentException)
{
gibt NULL zurück;
}
}
Beachten Sie, dass die Methode NULL zurückgibt, wenn die instance nicht gefunden wird. In unserem Fall tun wir dies, wenn wir den angeforderten Prozess überhaupt nicht finden oder wenn der gefundene Prozess keine Windows-Dienste hosten.
Registrierung bei WMI
Die -Klasse ist bereit, ihre Arbeit zu erledigen, aber wir müssen sie weiterhin bei WMI registrieren und an einem barrierefreien Speicherort auf dem Datenträger platzieren, von dem geladen werden soll.
Der globale Assemblycache (GAC) ist der Ort, an dem sich die Assembly befinden soll. Auf diese Weise kann .NET sie mit dem vollständigen .NET-Namen abrufen. Beachten Sie, dass die Assembly über einen starken .NET-Namen verfügen muss, um im GAC registriert zu werden. In unserem Beispiel verwenden wir das Tool .NET gacutil.exe, um die Assembly im GAC zu speichern.
Um die Informationen aus der instrumentierten Assembly in WMI-Metadaten abzurufen, müssen wir das .NET-Tool InstallUtil.exe verwenden, um eine WMI.NET Klasse namens DefaultManagementInstaller aufzurufen. DefaultManagementInstaller weiß, wie die gesamte Assembly für instrumentierte Klassen analysiert und die entsprechenden WMI-Metadaten generiert werden. Da InstallUtil.exe eine Klasse benötigt, die mit dem RunInstaller-Attribut gekennzeichnet ist, definieren wir eine leere Klasse, die von DefaultManagementInstaller abgeleitet ist, die InstallUtil.exe aufrufen wird.
[System.ComponentModel.RunInstaller(true)]
public class MyInstall : DefaultManagementInstaller
{
}
Die Registrierung bei WMI kann offline oder online erfolgen. Bei der Onlineregistrierung werden die Instrumentierungsmetadaten einer Assembly direkt im WMI-Repository gespeichert. Um die Metadaten direkt im WMI-Repository zu installieren, wird InstallUtil.exe Befehl mit dem Assemblynamen als Parameter aufgerufen. Die Assembly muss sich im GAC befinden, bevor InstallUtil.exe ausgeführt wird. Für die in diesem Beispiel generierte Assembly mit dem NamenWMIServiceHost.dll verwenden wir die folgenden Befehle:
C:>gacutil.exe /i WMIServiceHost.dll
C:>Installutil.exe WMIServiceHost.dll
Die Offlineregistrierung erfordert zwei Schritte. Der erste Schritt besteht darin, die der Assembly zugeordneten WMI-Metadaten zu generieren und in einer MOF-Datei zu speichern. Der zweite Schritt ist die eigentliche Registrierung auf Zielcomputern mithilfe des mofcomp.exe-Tools oder als Teil eines Installationspakets. Der Vorteil der Offlineregistrierung besteht darin, dass die MOF-Datei bei Bedarf lokalisiert und geändert werden kann. In diesem Beispiel können wir die WMI-Metadaten mithilfe des MOF-Parameters wie folgt in einer Datei mit dem Namen WMIServiceHost.mof generieren und speichern:
C:>Installutil.exe /MOF=WMIServiceHost.mof WMIServiceHost.dll
Wie im Onlinefall muss sich die Assembly im GAC auf dem Zielcomputer befinden. Um die Bereitstellung zu überprüfen, können wir das wmic.exe-Systemtool verwenden, um die Instanzen und Werte dieser Klasse anzuzeigen.
C:>wmic /NAMESPACE:\\root\test PATH win32_servicehost get /value
Während der Entwicklung ist es hilfreich, die Symbole in demselben Ordner im GAC bereitzustellen, in dem die Assembly gespeichert ist. Auf diese Weise enthält jeder Stapel, der als Ergebnis eines Fehlers oder Absturzes gemeldet wird, den vollständigen Quellpfad und die Zeilennummern, die zur Identifizierung des beleidigenden Codeteils beitragen.
Erweitern von Klassen durch Vererbung
WIN32_ServiceHost geht es um Diensthosts, und die bereitgestellten Informationen sind auf Prozesse beschränkt, die Windows-Dienste hosten. Es wird interessant sein, diese Informationen um prozessspezifische Informationen wie Speicherauslastung, ausführbarer Pfad, Sitzungs-ID usw. zu erweitern. Um diese Informationen zu erhalten, können wir das Schema erweitern und weitere Code schreiben, um so viele Informationen abzurufen, wie wir benötigen. Eine gute Alternative zum Schreiben von zusätzlichem Code besteht darin, die bereits vorhandene WIN32_Process Klasse zu nutzen, die sich im Betriebssystem im Root\cimv2-Namespace befindet und all diese zusätzlichen Informationen für alle im System ausgeführten Prozesse bereitstellt. Diese Klasse bietet umfassende Informationen zu einem ausgeführten Prozess, und wir können die WMI-Ableitung verwenden, um ihn mit unserer eigenen Klasse zu erweitern.
Die WMI-Vererbung wird im WMI.NET Codierungsmodell in klassenvererbung übersetzt. Da die Klasse, von der wir ableiten möchten, eine Klasse aus dem WMI-Bereich ist, die wir nicht tatsächlich im Code implementieren, müssen wir sie auf bestimmte Weise markieren.
Bevor wir mit dem Schreiben der Ableitung beginnen, müssen wir zwei wichtige Dinge zur WIN32_Process-Klasse beachten. Der erste WIN32_Process befindet sich im Namespace root\cimv2 , und für die Ableitung müssen wir die win32_servicehost-Klasse im gleichen Namespace registrieren. Daher ändern wir die WmiConfiguration-Attributanweisung geringfügig.
[assembly: WmiConfiguration(@"root\cimv2", HostingModel = ManagementHostingModel.NetworkService)]
Darüber hinaus ist für unsere Superklasse win32_process die Eigenschaft Handle als Schlüssel definiert. Dieser Schlüssel ist vom Typ CIM_STRING was in übersetzt wird. SYSTEM.String von NET. Wir müssen die Verwendung von ID als Schlüsseleigenschaft beenden und stattdessen die Eigenschaft Handle verwenden.
Um eine externe Klasse in WMI.NET zu definieren, um win32_process abzugleichen, Spiegel wir einfach deren Schema, aber nur Eigenschaften einschließen, die wir verwenden möchten oder verwenden müssen. Die Schlüssel in der Klassenhierarchie sind immer erforderlich. Zu diesem Zeitpunkt ist Handle die einzige interessante Eigenschaft, da es der Schlüssel ist, und wir benötigen ihn für die Bindung an eine bestimmte instance.
[ManagementEntity(External = true)]
abstract public class Win32_Process
{
geschütztes Zeichenfolgenhandle;
[ManagementKey]
Öffentliches Zeichenfolgenhandle
{
get {
zurückgeben this.handle;
}
}
}
Das Festlegen von External auf true für das ManagementEntity-Attribut verhindert, dass die Infrastruktur WMI-Metadaten zur Bereitstellungszeit generiert, behält jedoch die deklarierten Informationen für die Verwendung zur Laufzeit bei, wenn Schlüssel und Eigenschaften für abgeleitete Klassen gefunden werden müssen. Beachten Sie, dass es sehr wichtig ist, den Inhalt der Schlüssel der Basisklasse zu steuern, da die Schlüssel vom WMI-Subsystem verwendet werden, um Informationen von verschiedenen Anbietern zusammenzuführen.
Um die WMI-Klasse WIN32_ServiceHost erben der WMI-Klasse Win32Process abzurufen, leiten wir WIN32ServiceHost von der neu erstellten abstrakten Klasse in der .NET-Welt ab.
[ManagementEntity(Name = "WIN32_ServiceHost")]
öffentliche Klasse WIN32ServiceHost: Win32_Process
Wir entfernen die ID-Eigenschaft.
[ManagementKey]
public int ID
{
get { return this.innerProcess.Id; }
}
ändern Sie den Konstruktor, um den neuen Schlüssel im Handle-Feld der Basisklasse aufzufüllen.
public WIN32ServiceHost(Process innerProcess)
{
this.innerProcess = innerProcess;
this.handle = innerProcess.Id.ToString();
}
Ändern Sie GetInstance, um mit einem Zeichenfolgenargument namens Handle zu arbeiten, nur die ersten Zeilen davon, da der Rest unverändert bleibt.
[ManagementBind]
static public WIN32ServiceHost GetInstance(string Handle)
{
int processId;
wenn (! Int32.TryParse(Handle, out processId))
{
null zurückgeben;
}
Versuch
[...]
Wir müssen die neue Assembly in GAC neu kompilieren und erneut bereitstellen. Wir verwenden InstallUtil.exe, um das neue Schema bereitzustellen. Dann können wir das System mit einem leicht geänderten wmic.exe-Befehl abfragen.
C:>wmic /NAMESPACE:\\root\cimv2 PATH win32_servicehost get /value
Die zurückgegebenen Instanzen werden mit Informationen aus beiden Klassen aufgefüllt, win32_process und win32_servicehost. In der Ausgabe stammen Dienste von win32_servicehost , während alles andere von win32_process stammt. Um die Ausgabe zu vereinfachen, können wir die gewünschten Spalten angeben.
C:>wmic PATH win32_servicehost Abrufen von Handle,Caption,CommandLine,Services /value
Noch interessanter wird es, wenn wir versuchen, win32_process aufzulisten. Eine solche Abfrage gibt alle Prozesse zurück und füllt das Feld Dienste nur für die Instanzen von win32_servicehost auf.
C:>wmic PATH win32_process get /value
Die Ausgabe kann ein wenig überwältigend sein, also speichern Sie sie einfach in einer Datei (indem Sie out.txt am Ende der Befehlszeile hinzufügen > ), und öffnen Sie sie im Editor, um nach der Services-Eigenschaft zu suchen. Um zu verstehen, was vor sich geht, können wir die Systemeigenschaften anzeigen, um für jede instance zu identifizieren, aus welcher WMI-Klasse sie stammt.
C:>wmic PATH win32_process handle,CommandLine,__CLASS /value abrufen
Wählen Sie aus der resultierenden Liste eine win32_ServiceHost instance aus, und zeigen Sie dessen Werte an.
C:>wmic path WIN32_Process.Handle="536" get /value
Ähnliche Vorgänge können von jeder WMI-Clientanwendung mit Windows-Skripting, Microsoft PowerShell, verwaltetem oder nativem Code ausgeführt werden. Das System behandelt diese Assembly genauso wie jeden anderen Anbieter.
Implementieren von Methoden
WMI.NET unterstützt Methoden, sowohl statisch als auch pro instance. In unserem Fall fügen wir eine Methode hinzu, um alle von einem Prozess gehosteten Dienste zu beenden, damit ein Prozess sauber beendet werden kann, ohne ihn zu beenden, während die Dienste noch ausgeführt werden. Um eine öffentliche Methode in WMI sichtbar zu machen, markieren wir sie mit dem ManagementTask-Attribut .
[ManagementTask]
public bool StopServices(int millisecondsWait)
{
if (innerProcess.Id == 0)
false zurückgeben;
ServiceController[] services = ServiceController.GetServices();
bool oneFailed = false;
using (SCM scm = new SCM())
{
for (int svcIndex = 0; svcIndex-Dienste < . Länge; svcIndex++)
{
ServiceController crtService = services[svcIndex];
int processId = scm. GetProcessId(crtService.ServiceName);
if (processId == innerProcess.Id)
{
Versuch
{
crtService.Stop();
if (millisecondsWait != 0)
{
crtService.WaitForStatus( ServiceControllerStatus.Stopped,
new TimeSpan((long)millisecondsWait * 10000));
}
}
catch (System.ServiceProcess.TimeoutException)
{
oneFailed = true;
}
catch (System.ComponentModel.Win32Exception)
{
oneFailed = true;
}
catch (InvalidOperationException)
{
oneFailed = true;
}
}
}
}
return !oneFailed;
}
Zum Aufrufen dieser Methode benötigen wir eine instance win32_servicehost Klasse. Wir erhalten eine Liste der verfügbaren Diensthosts, die Folgendes eingeben:
C:>wmic path win32_servicehost get handle,Services
und wählen Sie die mit der besten Liste von Diensten aus (damit Ihr System nicht heruntergefahren wird, und einige Dienste sind möglicherweise auch nicht stoppbar) und verwenden Sie die Handle-Eigenschaft, um die instance des Aufrufs zu identifizieren.
C:>wmic-Pfad win32_servicehost. Handle="540" CALL StopServices(0)
Ausnahmen und Fehlerberichterstattung
Ausnahmen sind ein wichtiger Aspekt der WMI.NET. Die Infrastruktur verwendet einige Ausnahmen, um Informationen zu kommunizieren, und die meisten Ausnahmen werden als unbehandelt behandelt.
Akzeptierte Ausnahmen
WMI.NET behandelt nur wenige Ausnahmen, die im Artikel weiter beschrieben werden. Alle anderen Ausnahmen werden als Programmierfehler betrachtet und als nicht behandelte Ausnahmen behandelt, die zum Absturz des WMI-Anbieterhosts führen. WMI.NET meldet nicht behandelte Ausnahmen im Windows-Ereignisprotokoll.
Die akzeptierten Ausnahmen werden tatsächlich in WMI-Fehlercodes übersetzt und wie in Tabelle 1 dargestellt an den Clientcode zurückgesendet. Daher verhalten sich die WMI.NET-Anbieter wie jeder andere native Anbieter.
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 |
Aus der internen Ausnahme, die im Artikel weiter beschrieben wird |
Tabelle1 : Übersetzung von Ausnahmen in WMI-Fehler
In der Liste sind nur zwei allgemeine .NET Framework-Ausnahmen. Alle aufgeführten Ausnahmen können ausgelöst werden, um dem Client einen Bericht für den bestimmten status diese Ausnahmen darstellen.
Zwei neue Ausnahmen wurden dem System.Management.Instrumentation-Namespace hinzugefügt, wie weiter beschrieben.
Instancenotfoundexception
Diese Ausnahme dient dazu, zu benachrichtigen, dass ein angeforderter instance nicht gefunden wird. Das Beispiel in diesem Artikel verwendet die statische GetInstance-Methode für die Bindung an eine bestimmte instance und gibt NULL zurück, wenn die instance nicht gefunden wird. Ein Konstruktor kann für den gleichen Zweck verwendet werden, er muss jedoch eine InstanceNotFoundException auslösen, wenn die erforderliche instance nicht gefunden werden kann. Hier ist der Konstruktor, der die statische GetInstance-Methode ersetzen soll.
[ManagementBind]
public WIN32ServiceHost(string Handle)
{
int processId;
wenn (! Int32.TryParse(Handle, out processId))
{
neue InstanceNotFoundException();
}
Versuch
{
Prozessprozess = Process.GetProcessById(processId);
this.innerProcess = process;
this.handle = Handle;
if (this. Dienste == null)
{
neue InstanceNotFoundException();
}
}
wird von GetProcessById ausgelöst, wenn kein Prozess mit der angegebenen ID gefunden wird
catch (ArgumentException)
{
neue InstanceNotFoundException();
}
}
Instrumentationexception
Eine InstrumentationException ist der Wrapper für behandelte Ausnahmen. Ein Anbieter kann den Client über einen Fehler informieren, der auf seiner Seite aufgetreten ist. Dazu wird eine InstrumentationException ausgelöst. Beachten Sie, dass der Entwickler bedenken sollte, dass die Ausnahme auf das WMI-System zurückgeht. Daher wird WMI.NET versuchen, es in ein COM HRESULT zu transformieren. Um einen präzisen Fehlercode zurück an den Client auszulösen, müssen wir als interne Ausnahme für die InstrumentationException eine Exception-Klasse übergeben, die das direkte Festlegen des internen HResult in der Basisausnahmeklasse ermöglicht.
Fehlerberichterstattung
Jede Ausnahme, die nicht im vorherigen Abschnitt aufgeführt ist, endet als nicht behandelte Ausnahme, die zu einem Absturz führt, der als "Eine nicht behandelte Ausnahme ("System.ExecutionEngineException") in wmiprvse.exe[<NNNN>]." gemeldet wird, wobei NNNN eine Prozessnummer ist. Fehler und Stapel werden im Ereignisprotokoll gemeldet. Wenn Sich Symbole im selben Ordner wie die Assembly befinden, wird der fehlerhafte Stapel mit Dem Dateinamen und der Zeilennummer angezeigt.
Ein weiterer Fehlerfall ist, wenn die Assembly nicht geladen werden kann, weil sie nicht im GAC bereitgestellt wurde. WMI.NET gibt für diesen Fall einen Fehler beim Laden des Anbieters (WBEM_E_PROVIDER_LOAD_FAILURE) zurück.
Ein häufiges Problem während der Anbieterentwicklung ist der Konflikt zwischen dem WMI-Schema und dem Code. Dies kann vorkommen, wenn die neue Assembly bereitgestellt wird, ohne das Schema in WMI bereitzustellen, oder wenn die Probleme bei der Bereitstellung mit InstallUtil.exe nicht abgefangen werden, da die Informationen nur zur Laufzeit verfügbar sind. Dies wäre beispielsweise der Fall, wenn während einer Enumeration ein falscher Typ zurückgegeben wurde. Die WMI.NET-Infrastruktur meldet dies dem Client als Anbieterfehler (WMI-Fehler WBEM_E_PROVIDER_FAILURE) und generiert eine Meldung im Windows-Ereignisprotokoll, die das Laufzeitproblem beschreibt.
Weitere Tipps
Alle Attribute und der WMI.NET Code befinden sich im System.Management.Instrumentation-Namespace . Um eine Assembly für WMI.NET zu erstellen, muss das Projekt Verweise auf System.Core.dll und System.Management.Infrastructure.dll aufweisen, da sie die Definition von Attributen bzw. den Laufzeitcode enthalten. Der spätere Benutzer weiß, wie instrumentierte Assemblys geladen, die instrumentierten Klassen mit dem WMI-Repository abgeglichen und entsprechend aufgerufen werden.
Behalten Sie alle Klassen für eine bestimmte Art von Anwendung in derselben Assembly bei. Dies erleichtert Die Wartung und Bereitstellung erheblich.
Der gesamte Code im Artikel muss bereitgestellt und als Administrator ausgeführt werden. Unter Windows Vista erfordert die Ausführung als Administrator eine Eingabeaufforderung mit erhöhten Sicherheitsfunktionen. Anbieter verlangen in der Regel nicht, dass Clients Administratoren sind, es sei denn, sie greifen wie in unserem Beispiel auf speziell gesicherte Systemdaten zu.
Zusammenfassung
Die neue Verwaltungsbibliothek in .NET Framework 3.5 bietet Entwicklern ein leistungsstarkes Tool für das Schreiben von WMI-Anbietern. Aufgrund der Einfachheit des Programmiermodells wird das Schreiben von WMI-Anbietern zu einer ziemlich einfachen Aufgabe, mit der Sie die meisten WMI-Funktionen implementieren können. Das Beispiel in diesem Artikel zeigt, wie Sie nur einen einfachen WMI-Anbieter schreiben, aber die Bibliothek bietet auch Unterstützung für die Entwicklung entkoppelter Anbieter und die Implementierung von Singletons, Vererbung, abstrakten Klassen, Verweisen und Zuordnungsklassen, wodurch WMI.NET Anbietererweiterung v2 eine ernsthafte Alternative zur Entwicklung mit nativen WMI-Schnittstellen ist.
Auflistung 1 – SCMInterop.cs
using System;
Verwenden von System.Runtime.InteropServices;
mit System.ServiceProcess;
Namespace External.PInvoke
{
[StructLayout(LayoutKind.Sequential)]
interne Struktur 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));
}
[Flags]
interne Enumeration SCM_ACCESS : uint
{
SC_MANAGER_CONNECT = 0x00001,
}
[Flags]
interne Enumeration 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)
}
interne Enumeration SC_STATUS_TYPE
{
SC_STATUS_PROCESS_INFO = 0
}
interne ServiceHandle-Klasse : 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);
if (serviceHandle == IntPtr.Zero)
{
neue System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(),
"SCM. QueryServiceStatusEx");
}
SetHandle(serviceHandle);
}
protected override bool ReleaseHandle()
{
gibt SCM zurück. CloseServiceHandle(base.handle);
}
public override bool IsInvalid
{
get { return IsClosed || handle == IntPtr.Zero; }
}
}
interne Klasse 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)] string 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 pcbBytesNeeded);
[DllImport("advapi32.dll", EntryPoint = "CloseServiceHandle", CharSet = CharSet.Auto,
SetLastError = true)]
public static extern bool CloseServiceHandle(IntPtr hService);
public SCM()
: base(IntPtr.Zero, true)
{
IntPtr-Handle = OpenSCManager(null, null, SCM_ACCESS. SC_MANAGER_CONNECT);
Basis. SetHandle(handle);
}
protected override bool ReleaseHandle()
{
gibt SCM zurück. CloseServiceHandle(base.handle);
}
public override bool IsInvalid
{
get { return IsClosed || handle == IntPtr.Zero; }
}
public void QueryService(string serviceName, out SERVICE_STATUS_PROCESS statusProcess)
{
statusProcess = new 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);
if (!scmRet)
{
neue System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(),
"SCM. QueryServiceStatusEx");
}
}
}
public int GetProcessId(string serviceName)
{
SERVICE_STATUS_PROCESS serviceStatus;
das. QueryService(serviceName, out serviceStatus);
return (int)serviceStatus.dwProcessId;
}
}
}
Eintrag 2 – WIN32ServiceHost.cs
using System;
verwenden von System.Collections;
using System.Collections.Generic;
mit System.Linq;
using System.Text;
mit System.ServiceProcess;
mithilfe von System.Diagnostics;
mit External.PInvoke;
mit System.Management.Instrumentation;
[assembly: WmiConfiguration(@"root\cimv2", HostingModel = ManagementHostingModel.NetworkService)]
Namespace TestWMI.Hosted
{
[System.ComponentModel.RunInstaller(true)]
public class MyInstall : DefaultManagementInstaller
{
}
[ManagementEntity(External = true)]
abstract public class Win32_Process
{
geschütztes Zeichenfolgenhandle;
[ManagementKey]
Öffentliches Zeichenfolgenhandle
{
get {
zurückgeben this.handle;
}
}
}
[ManagementEntity(Name = "WIN32_ServiceHost")]
public class WIN32ServiceHost: Win32_Process
{
InnerProcess verarbeiten;
<Zusammenfassung>
///
</Zusammenfassung>
<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
{
if (innerProcess.Id == 0)
gibt NULL zurück;
ServiceController[] services = ServiceController.GetServices();
List<ServiceController> servicesForProcess = new List<ServiceController>();
using (SCM scm = new SCM())
{
for (int svcIndex = 0; svcIndex-Dienste < . Länge; svcIndex++)
{
ServiceController crtService = services[svcIndex];
int processId = scm. GetProcessId(crtService.ServiceName);
if (processId == innerProcess.Id)
{
servicesForProcess.Add(services[svcIndex]);
}
}
}
if (servicesForProcess.Count == 0)
gibt NULL zurück;
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[] processes = Process.GetProcesses();
foreach (Prozess crtProcess in Prozessen)
{
WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(crtProcess);
if (crtServiceHost.Services != null)
{
return crtServiceHost;
}
}
}
[ManagementBind]
public WIN32ServiceHost(string Handle)
{
int processId;
wenn (! Int32.TryParse(Handle, out processId))
{
neue InstanceNotFoundException();
}
Versuch
{
Prozessprozess = Process.GetProcessById(processId);
this.innerProcess = process;
this.handle = Handle;
if (this. Dienste == null)
{
neue InstanceNotFoundException();
}
}
wird von GetProcessById ausgelöst, wenn kein Prozess mit der angegebenen ID gefunden wird
catch (ArgumentException)
{
neue InstanceNotFoundException();
}
}
static public WIN32ServiceHost GetInstance(string Handle)
{
int processId;
wenn (! Int32.TryParse(Handle, out processId))
{
gibt NULL zurück;
}
Versuch
{
Prozessprozess = Process.GetProcessById(processId);
WIN32ServiceHost crtServiceHost = new WIN32ServiceHost(process);
if (crtServiceHost.Services != null)
{
return crtServiceHost;
}
else
{
gibt NULL zurück;
}
}
wird von GetProcessById ausgelöst, wenn kein Prozess mit der angegebenen ID gefunden wird
catch (ArgumentException)
{
gibt NULL zurück;
}
}
[ManagementTask]
public bool StopServices(int millisecondsWait)
{
if (innerProcess.Id == 0)
"false" zurückgeben;
ServiceController[] services = ServiceController.GetServices();
bool oneFailed = false;
using (SCM scm = new SCM())
{
for (int svcIndex = 0; svcIndex-Dienste < . Länge; svcIndex++)
{
ServiceController crtService = services[svcIndex];
int processId = scm. GetProcessId(crtService.ServiceName);
if (processId == innerProcess.Id)
{
Versuch
{
crtService.Stop();
if (millisecondsWait != 0)
{
crtService.WaitForStatus( ServiceControllerStatus.Stopped,
new TimeSpan((long)millisecondsWait * 10000));
}
}
catch (System.ServiceProcess.TimeoutException)
{
oneFailed = true;
}
catch (System.ComponentModel.Win32Exception)
{
oneFailed = true;
}
catch (InvalidOperationException)
{
oneFailed = true;
}
}
}
}
return !oneFailed;
}
}
}
© 2008 Microsoft Corporation. Alle Rechte vorbehalten.