![]() |
|
URL dieses Artikels:
zu Ausgabe:
1.2005
Windows Management Instrumentation
Standardisierter Zugriff auf Systeminformationen und Managementmethoden, Teil 2
von Dirk Frischalowski
Im ersten Teil wurde WMI als eine standardisierte Möglichkeit vorgestellt, auf unterschiedlichste Informationen unter Windows zuzugreifen. Als Programmierer müssen Sie sich nicht mehr mit unterschiedlichen APIs und Klassenbibliotheken herumschlagen. Stattdessen verwenden Sie zum Auslesen immer denselben Weg. Nun kann man über WMI nicht nur Informationen abfragen, sondern auch auf Systemereignisse reagieren, andere Anwendungen starten oder mit den Methoden und Daten eines Objekts arbeiten. Genau dies soll Gegenstand dieses Teils sein.
Rückblick WMI (Windows Management Instrumentation) ist ein API, das eine standardisierte Schnittstelle zu unterschiedlichsten Windows-Funktionen und Datenlieferanten herstellt. WMI steht standardmäßig unter Windows XP, 2000 und Me zur Verfügung. Für Windows NT oder Windows 9x kann es nachinstalliert werden. Sie können WMI in Delphi verfügbar machen, indem Sie die Typbibliothek Microsoft WMI Scripting V1.2 Library (Version 1.2) einbinden. Markieren Sie dazu die Option Komponenten-Wrapper generieren und danach den Button Unit anlegen. Die Versionsnummer der Bibliothek kann sich bei Ihnen unterscheiden. Die Beispiele im Artikel wurden mit Delphi 7 unter Windows XP erzeugt. Sie liegen aber auf CD ebenfalls für Delphi 5 vor. Die Verwendung mit anderen Delphi-Versionen ist meist durch einfache Änderungen möglich. So wird z.B. in Delphi 5 die Unit Variants.pas nicht benötigt. Namespaces, Klassen und Instanzen auflisten Beim letzten Mal wurden in einem Beispiel Informationen zum Bios über die WMI-Klasse Win32_Bios ausgegeben. Das folgende Beispiel soll noch einmal den Zugriff auf Namespaces, deren Klassen und wiederum den Instanzen einer Klasse beleuchten. Für eine solche Anwendung wird schon etwas mehr Sourcecode benötigt. Letzten Endes erhalten Sie aber eine Anwendung, die bereits so gut wie alle Informationen, die über WMI bereitgestellt werden, anzeigt (Abb. 1). Im linken Bereich werden nach dem Klicken auf den Button Start alle Namespaces aufgelistet. Rechts oben werden nach einem Doppelklick auf einen Namespace dessen Klassen und beim Doppelklick auf eine Klasse deren Instanzen rechts unten dargestellt. Um alle Namespaces zu ermitteln, verbinden Sie sich jeweils mit einem Namespace und erhalten über den Objektpfad __Namespace (2 Unterstriche) alle seine untergeordneten Namespaces. Sie beginnen mit dem Namespace root und iterieren dann immer über die gefundenen untergeordneten Namespaces. In der Methode InstancesOf im Listing 1 wird deshalb als Pfad __Namespace angegeben. Als einzige Eigenschaft wird der Name des Namespaces ausgewertet. Das Durchlaufen durch die ermittelten Instanzen gleicht dem gezeigten Beispiel zur Ermittlung der Bios-Eigenschaften. Die gefundenen Namespaces werden zuerst in eine String List SortedNameList eingefügt, sortiert und dann in den Treeview links eingegliedert. ![]() Abb. 1: WMI-Browser Listing 1 FServices := FLocator.ConnectServer('.', NamespaceName, '',
Im nächsten Schritt wühlen wir uns durch die Klassen eines Namespaces. Dazu verbindet man sich wieder mit einem Namespace und ruft die Klassen eines Namespaces über die Methode SubClassesOf des Interfaces ISWbemServices ab. Alternativ kann man auch die Abfrage über WQL definieren und mit ExecQuery ausführen. Um die Klassen zu ermitteln ist SELECT * FROM meta_class anzugeben. Über eine WHERE-Klausel kann auch nur eine bestimmte Klasse zurückgegeben werden, z.B. SELECT * FROM meta_class WHERE __Class = "__Win32Provider" (Listing 2). Listing 2 FServices := FLocator.ConnectServer('.',
Zum Abschluss werden beim Doppelklick auf eine Klasse deren Instanzen ermittelt. Damit der verwendete List View auch korrekte Spaltenüberschriften verwendet, müssen wir zusätzlich zur ausgewählten Klasse deren Eigenschaftsnamen herausbekommen. Die Instanzen der Klasse werden wieder über die Methode InstancesOf ermittelt. Danach werden von FWMIObj, das eine Instanz der Klasse ISWbemObject enthält, dessen Eigenschaften über die Eigenschaft Properties_, die vom Typ des Interfaces ISWbemPropertySet ist, zurückgegeben. Diese werden nun über eine Schleife durchlaufen. Der Name einer Eigenschaft wird über das Interface ISWbemProperty und der Eigenschaft Name geholt. Die Eigenschaftsnamen werden im List View lvInstances als Spaltenüberschriften hinzugefügt (Listing 3).Listing 3 FPropSet := FWMIObj.Properties_;Zu guter Letzt müssen wir noch die Eigenschaftswerte bestimmen. Doch hier gibt es einiges zu beachten. Eigenschaften können in WMI einfache Typen wie Zahlen oder Strings sein, aber auch Arrays oder Objekte. Dies muss beim Auslesen der Werte beachtet werden. Weiterhin können die Werte null oder leer sein. Delphi liefert aber schon die benötigten Funktionen mit, über die Sie den Typ und den Inhalt einer Eigenschaft bestimmen können (Tabelle 1). Sämtliche Funktionen finden Sie unter Delphi 7 in der Unit Variants.pas. Als Parameter erwarten sie einen Variant- bzw. OLEVariant-Wert. Im Beispiel wird nur zwischen Arrays und allen anderen Typen unterschieden. Die Werte eines Arrays werden über eine separate Methode bestimmt, alle anderen als String zurückgegeben. Dies führt dazu, dass es bei einigen Instanzen noch zu Fehlern bei der Ergebnisausgabe kommen kann. Auf Ereignisse reagieren Ändern sich Daten oder Zustände innerhalb von WMI, werden verschiedene Ereignisse ausgelöst, auf die eine Anwendung reagieren kann. So wird beispielsweise beim Erzeugen eines neuen Prozesses ein __InstanceCreationEvent vom entsprechenden WMI-Provider ausgelöst und es wird eine neue Instanz für die Klasse Win32_Process erzeugt. Der Eventprovider (Produzent) informiert intern den CIMOM und dieser leitet wiederum das Ereignis an die Konsumenten, z.B. eine Anwendung, weiter. Auf Ereignisse kann synchron oder asynchron gewartet werden. Während im ersten Fall eine Anwendung blockiert (man kann natürlich mit Threads arbeiten) arbeitet die zweite Variante wie die Ereignisbehandlung Delphis. Um sich als Konsument anzumelden, wird über WQL ein Eventfilter definiert. Dieser legt die Bedingungen fest, unter denen er an einer Information zu einem Ereignis interessiert ist. Der Aufbau eines Eventfilters besitzt einige zusätzliche WQL-Klauseln, über die ein Intervall festgelegt werden kann und eventuell noch eine Prüfung des Instanztyps durchgeführt wird. Es gibt verschiedene Ereignistypen. Für Klassen, Instanzen und Namespaces existieren beispielsweise Ereignisse, die beim Erstellen, Löschen oder Ändern von Objekten ausgelöst werden. Die Namen sind entsprechend __ClassCreationEvent, __InstanceModificationEvent oder __NamespaceDeletionEvent (immer mit zwei Unterstrichen beginnend). Über die Klausel WITHIN wird ein Intervall in Sekunden angegeben, in dem man auf ein Ereignis reagieren möchte. Das Ereignis __InstanceCreationEvent, das beim Erzeugen eines Objekts einer beliebigen Klasse ausgelöst wird, besitzt drei Eigenschaften, wobei die Eigenschaft TargetInstance das erzeugte Objekt kennzeichnet. Die WQL-Abfrage wird in der Beispielanwendung über die Methode ExecNotificationQueryAsync erzeugt (Listing 4). Damit wartet man nicht, sondern der Aufruf kehrt sofort zurück. Wie erhält man nun eine Nachricht, wenn das betreffende Ereignis eingetreten ist? Dazu erzeugt man ein TSWbemSink-Objekt. Der Eigenschaft OnObjectReady wird dann eine Methode zugewiesen die aufgerufen wird, wenn das betreffende Ereignis eingetreten ist. Listing 4 FServices := FLocator.ConnectServer('.', 'root\cimv2', '',
Der Methode, die beim Auftreten des Ereignisses aufgerufen wird, werden zwei Parameter übergeben, die in Zusammenhang mit dem Ereignis ausgewertet werden können (Listing 5). Dabei ist der Parameter vom Typ ISWbemObject hier interessant, da er die neu erzeugte Instanz der Klasse Win32_Process kennzeichnet. Auf die Eigenschaften der Instanz kann über den Eigenschaftsnamen zugegriffen werden. In diesem Fall sind dies die Eigenschaften eines Win32_Process-Objekts. Über die Methode GetObjectText können Sie einfach alle Informationen in Textform ausgeben. Dies ist zur Übersicht hilfreich, sollte aber natürlich nicht für Benutzerausgaben genutzt werden. Starten Sie zum Test die Anwendung und klicken Sie auf Start. Starten Sie nun eine beliebige andere Anwendung. Dieser Start sollte ein Ereignis auslösen, da ein neuer Prozess und damit ein neues Objekt der Klasse Win32_Process erzeugt wird.Listing 5 procedure TfrmMain.ProcessCreated(ASender: TObject; const objWbemObject: ISWbemObject; const objWbemAsyncContext: ISWbemNamedValueSet);Anwendungen starten Einige WMI-Klassen besitzen Methoden, die von Ihnen aufgerufen werden können. Welche Klassen Methoden besitzen, erkennt man am besten daran, dass sich vor den Klassennamen in der Referenz ein Pluszeichen befindet (Abb. 2). Am Beispiel des Startens einer Anwendung mithilfe der Klasse Win32_Process soll der Aufruf einer Methode gezeigt werden. Das Starten einer Anwendung bedeutet, einen neuen Prozess zu erzeugen. Dazu wird ein Klassenobjekt der Klasse Win32_Process über die Methode Get des Interfaces ISWbemServices geholt. Danach wird für die Methode Create über die Auflistung Methods_ vom Typ ISWbemMethodSet ein ISWbemMethod-Objekt ermittelt. Als nächstes muss ein Eingabeparameterobjekt erzeugt werden, welches den aufzurufenden Prozess spezifiziert. Dazu wird über das Objekt InParameters und dem Aufruf der Methode SpawnInstance_ eine neue Instanz für ein Parameterobjekt erzeugt. Dem Parameterobjekt wird die Eigenschaft CommandLine hinzugefügt, die den Namen der zu startenden Anwendung zugewiesen bekommt. Zum Abschluss wird die Methode Create des WMI-Objekts über ExecMethod_ aufgerufen und die Eingabeparameter werden übergeben (Listing 6). ![]() Abb. 2: WMI-Klassen-Referenz Das ganze sieht wie üblich sehr kompliziert aus, und das ist es vielleicht auch das erste Mal. Die Aufrufe lassen sich aber auch gut in Prozeduren und Funktionen verpacken, da sie sich für andere Anwendungsgebiete nur in ein paar Parametern unterscheiden. Listing 6 varDaten eines Objekts bearbeiten In der Klassenbeschreibung der WMI-Klassen finden Sie bei einigen Eigenschaften den Zugriffstyp (AccessType) Read/Write. Sie können also den Wert nicht nur lesen, sondern auch ändern. Da das Ändern von Werten ein Sicherheitsrisiko darstellt (wie übrigens auch das Ausführen von Methoden), müssen Sie den Impersonation Level setzen. Dieser legt fest, unter welchem Benutzer Sie sich bei WMI identifizieren (vgl. auch Tabelle 2). Da WMI als Service läuft, hat es die volle Zugriffskontrolle auf das System. Bei jedem Verbindungsaufbau zu WMI muss daher eine gültige Benutzerkennung verwendet werden. Nach dem Setzen des ImpersonationLevels holen Sie ein Klassenobjekt der gewünschten Klasse über die Methode Get und erzeugen eine neue Instanz FWMIObj. Dieser Instanz werden dann beispielsweise neue Eigenschaften hinzugefügt. In diesem Beispiel soll eine neue Umgebungsvariable definiert werden. Dazu wurde auf dem beschriebenen Weg ein Win32_Environment-Objekt erzeugt. Um eine Systemvariable zu setzen, wird der Eigenschaft UserName der Wert Die Fehlerbehandlung erfolgt in Delphi wie üblich über Exceptionhandling. Da Sie mit COM/DCOM arbeiten, werden im Fehlerfall OLEExceptions erzeugt. Deren Fehlercode korrespondiert mit den Fehlercodes, die in der WMI-Referenz verwendet werden. Dazu wird im Beispiel der Hex-Wert ausgegeben, der auch in der WMI-Referenz angegeben wird. Listing 7 FServices.Security_.Set_ImpersonationLevel(Remote-Zugriff Richtig spannend wird WMI bei der Administration remoter Rechnersysteme. Sie benötigen dazu aber auf dem anderen System einen Account. Beide Accounts, der auf dem aktuellen und dem remoten System, müssen Administrator-Accounts sein. Weiterhin darf das Passwort auf dem Client-PC nicht leer sein. Dies sollte in Firmennetzen kein Problem darstellen, da ein Systemadministrator immer bestimmte Arbeiten an den PCs durchführen muss und auch standardmäßig Passwörter verwendet werden. Um die Verbindung zum anderen Rechner herzustellen, wird die Methode ConnectServer diesmal mit zusätzlichen Parametern ausgeführt, die den Rechnernamen, den Benutzernamen sowie das Passwort enthalten: FServices := FLocator.ConnectServer('Rechnername',
Wird der Benutzername und das Passwort frei gelassen, wird der aktuelle Benutzer verwendet. Der Parameter wbemConnectFlagUseMaxWait bedeutet übrigens, dass zur Verbindung zum Namespace höchstens zwei Minuten gewartet wird bis die Methode zurückkehrt. Dies verhindert ein einfrieren, wenn WMI nicht zur Verfügung steht. Alternativ kann noch der Wert 0 verwendet werden, der dann unendlich lange auf eine erfolgreiche Verbindung wartet (wie vielleicht der Anwender des Programms). Die weitere Vorgehensweise gleicht dem Zugriff auf die lokalen Daten.Natürlich ist der Remote-Zugriff auf einen Rechner über WMI nicht in jedem Fall erwünscht. Da z.B. unter Windows XP der WMI-Service standardmäßig gestartet wird, müssen Maßnahmen ergriffen werden, die den Zugriff auf den Rechner unterbinden. Allein die Hoffnung, dass der Rechner- und Benutzername sowie das Passwort nicht bekannt sind, reicht zum Schutz vor Hackern nicht unbedingt aus. Unter den Systemeigenschaften finden Sie unter dem Register Remote die Einstellungen zur Remoteunterstützung und dem Remotedesktop. Beide dienen dazu, den remoten Zugriff auf einen Rechner zu erlauben. Wenn Sie die Dienste deaktivieren, ist damit auch der Zugriff über WMI nicht mehr möglich. Der Remote-Zugriff hat leider noch ein paar Einschränkungen. So werden bestimmte Konstellationen bei der Herstellung einer Verbindung nicht unterstützt, z.B. der Zugriff auf Windows XP Home-PCs. In anderen Fällen werden zusätzliche Anforderungen an die betreffenden Betriebssysteme gestellt (installierte Service Packs etc.). Weiter Informationen finden Sie unter [1]. Zusammenfassung Wie man sieht ist WMI mehr als nur ein Informationslieferant. Sie können Methoden von WMI-Objekten aufrufen, selbst neue Objekte erzeugen und dies auch auf remoten Rechnern durchführen. Damit tut sich ein unglaublich breit gefächertes Anwendungsgebiet auf. Als Delphi-Programmierer findet man im Internet oder im Buchhandel wenig Unterstützung bei der Arbeit mit WMI. Die Vorgehensweisen über Scripting oder .NET lassen sich aber weitestgehend adaptieren, sodass die dafür existierende Literatur auch genutzt werden kann. Beachten Sie, dass beide hier vorgestellte Artikel eher praxisorientiert an die Vermittlung des Themas gegangen sind, sodass Sie schon einmal lauffähige Anwendungen erstellen können. Das Studium der Klassen, deren Methoden, Parametern und Rückgabewerten kann und sollte jeder selbst in der WMI-Dokumentation nachlesen [2]. Links und Literatur [1] Herstellen der Verbindung zu einem remoten PC: msdn.microsoft.com/library/en-us/wmisdk/wmi/connecting_to_wmi_on_a_remote_computer.asp [2] Startseite der WMI-Dokumentation: msdn.microsoft.com/library/en-us/dnanchor/html/anch_wmi.asp |
||
|