Wenn Sie in Ihrer Datenbank externe Informationen über System, Hardware oder Software benötigen, so lassen sich diese unter VBA meist nur über umfangreiche API-Routinen gewinnen. Dabei gibt es mit dem Windows Management Instrumentarium, kurz WMI, eine Schnittstelle, die fast keine Wünsche offen lässt und Ihnen enorm viel Programmierarbeit abnehmen kann. Das ist Grund genug, diese Schnittstelle einmal genauer unter die Lupe zu nehmen.
Beispieldatenbank
Die Beispiele dieses Artikels finden Sie in der Datenbank 1409_WMI.mdb.
Wie funktioniert das WMI
Windows bringt WMI als einen Service mit, der in der Systemverwaltung auf den Namen Windows-Verwaltungsinstrumentation hört und automatisch gestartet wird. Wirklich aktiv wird es allerdings erst, wenn eine Anfrage an den Service gestellt wird.
Dann findet man im Taskmanager den neuen Prozess WmiPrvSE.exe, eine Datei, die aus dem Unterverzeichnis \wbem des Windows-Systemordners heraus gestartet wird. Der Prozess selbst verwaltet nur die Anfragen und hat deshalb den Namen WMI Provider Host.
Bei Anfragen entscheidet er, welche zusätzlichen Dlls zu laden sind, in denen die eigentlichen Funktionen zur Ermittlung der Systemkomponenten untergebracht sind. Diese Dlls sind die Provider, also Datenlieferanten, und ebenfalls im \wbem-Verzeichnis beheimatet.
Sie finden dort außerdem zahlreiche Dateien mit der Endung .mof. Das sind Textdateien, die die Struktur jedes Providers beschreiben.
Wir kommen noch darauf zu sprechen, wie eine Anfrage an den Service gestellt wird. Der Ablauf sieht schematisch danach jedenfalls so aus:
- Anfrage an WMI-Service
- Der Service befragt den Provider Host.
- Der Provider Host durchsucht beim ersten Start das Verzeichnis \wbem nach allen Provider-Definitionen und baut daraus im Speicher eine Art Datenbank auf. Aus dieser wird je nach Anfrageschlüsselwörtern der geeignete Provider herausgesucht und die entsprechende Dll geladen.
- über eine COM-Schnittstelle wird ein Klassenobjekt aus der Dll erzeugt, das zu einem gesuchten Objekt, etwa einer Druckerinstanz, die Eigenschaften zurückliefert. Die Dll weiß selbst, welche weiteren Dlls sie zum Lösen der Aufgabe benötigt, und verwendet dazu normale API-Methoden.
- Die Ergebnisse laufen den Weg zurück bis zum Auslöser der Anfrage
Der gesamte Vorgang ist standardisiert, sodass es keine Rolle spielt, ob Sie Hardware- oder Software-Komponenten abfragen wollen – der Ablauf ist immer identisch.
Da das WMI intern eine Datenbank über alle möglichen abfragbaren Objekte aufbaut, liegt es nahe, dass diese über einen SQL-Dialekt angesprochen werden kann. Das ist auch der Fall: Die Abfragesprache nennt sich WQL. Damit ist es möglich, ein Abbild der WMI-Datenbank in das Datenmodell einer Access-Datenbank zu überführen. Die Lösung dazu finden Sie in der Beispieldatenbank zum Beitrag. Das Datenmodell sieht aus wie in Bild 1 und zeigt eine hierarchische Struktur. Um es zu erläutern und die Entsprechungen zur WMI-Datenbank deutlich zu machen, nehmen wir ein einfaches WQL-Beispiel zur Hand:
Bild 1: Analogie des WMI-Datenmodells unter Access
SELECT Caption FROM Win32_Printer
Analog zu einer Access-Abfrage handelt sich bei Caption offenbar um ein Feld, das aus der Datenherkunft Win32_Printer stammt. Gesucht werden hier alle Bezeichnungen der im System installierten Drucker. Win32_Printer scheint demnach eine Tabelle oder selbst eine Abfrage zu sein. Tatsächlich trifft eher Letzteres zu. Was unter Access eine Tabelle oder Abfrage ist, nennt sich unter WMI allerdings Objektklasse.
WMI-Datenmodell
Welches ist aber die Datenbank, aus der die Objektabfrage stammt Um bei der Analogie zu bleiben, nehmen wir an dieser Stelle das Ergebnis der Routinen der Beispieldatenbank vorweg und verweisen auf Bild 2.
Bild 2: Root-Provider-Liste des WMI
Das ist der Inhalt der im vorherigen Bild dargestellten Tabelle tblWMIRoot. Was unter Access als die aktuelle Datenbank CurrentDb bekannt ist, wäre im WMI die Objektklasse root.
Sie besteht aus einer Auflistung von Grundklassen, bestimmten Standardkategorien, auch Namespaces genannt, von denen der Provider CIMV2 der wichtigste ist. CIMV2 ist eine Abkürzung für Common Information Model Version 2. root wäre der Name der Datenbank, CIMV2 eine Abfrage auf die Tabelle der Root-Provider. Also sähe die Syntax der Abfrage Win32_Printer eigentlich so aus:
SELECT * FROM root.CIMV2 _ WHERE Name = ''Win32_Printer''
Dass diese Abfrage unter WQL so nicht funktioniert, werden wir noch sehen. Wichtig ist hier zunächst das Prinzip.
Ließe man die Bedingung in der Abfrage weg, so käme man auf eine Liste aller CIMV2-Klassen. In der Beispieldatenbank steht dafür die Tabelle tblWMIClasses. Ihr Inhalt ist auszugsweise in Bild 3 zu begutachten. IDNamespace ist ein Bezug zu ID der Root-Klasse; hier die 5 für CIMV2. Die Tabelle ist wiederum mit der Tabelle tblWMI-ClassesProps verlinkt, indem deren Feld IDClass mit der ID verknüpft und als Unterdatenblatt dargestellt wird. In tblWMIClassesProps stehen die Eigenschaften der jeweiligen Klasse und damit eigentlich deren Feldnamen. In der Abbildung sind für die Klasse Win32_CurrentTime (aktuelle Systemzeit) die Eigenschaftsfelder ausgeklappt. Wollte man etwa den Tag des aktuellen Datums abfragen, so wäre der Wert des Feldes Day der Abfrage Win32_CurrentTime zu ermitteln.
Bild 3: WMI-Objektklassen und die Eigenschaftsfelder einer Klasse
Das WMI abfragen
Schauen wir uns nun an, wie das Abfragen der Systemzeit unter VBA konkret realisiert werden kann. Grundsätzlich gibt es da zwei Wege, die beschritten werden können: eine Variante ohne und eine mit Verweis-Bibliothek. Zur verweislosen Lösung finden Sie nachfolgend zwei Routinen.
Sie können eine Instanz des WMI sehr einfach erhalten, indem Sie einen speziellen sogenannten Moniker-String an die VBA-Funktion GetObjekt übergeben:
Set objWMI = GetObject("winmgmts:")
Sie erhalten damit jedoch nicht etwa die oberste Ebene der WMI-Datenbank, also root, sondern bereits den CIMV2-Provider. Denn der voll ausgeschriebene Moniker-String müsste so lauten:
GetObject("winmgmts://./root/cimv2")
WMI verwendet, wenn bestimmte Parameter im String weggelassen werden, automatisch Defaults. Zu diesen Defaults gehören der angesprochene Computer, die root-Datenbank und der CIMV2-Provider. Die Syntax des Moniker-Strings lautet genau genommen
winmgmts://[Computer]/root/[Provider]
Für Computer können Sie einfach einen Punkt verwenden, wenn Sie das lokale System ansprechen wollen. Damit wird bereits deutlich, dass man über das WMI auch andere vernetzte Rechner abfragen kann, soweit die Berechtigungen dazu ausreichen! Für Computer kann man also ebenfalls die IP-Nummer oder den Netzwerknamen des Rechners angeben:
winmgmts://192.168.0.2/root/cimv2 winmgmts://Harry2/root/cimv2
WMI ist übrigens unsensibel, was Groß- und Kleinschreibung angeht. Meist ist es egal, wie Sie einen Ausdruck schreiben.
In der Routine TestWMIDate1 (s. Listing 1) aus dem Modul mdlWMI wird also zunächst ein Objekt objWMI gesetzt, das dem Klassenkatalog des Haupt-Providers CIMV2 entspricht. In Analogie zu Access entspräche dieses Objekt erst einer Datenbank. Und diese kann wie unter Access über OpenRecordset abgefragt werden, nur dass sich der Befehl hier ExecQuery nennt. Im WQL-String werden also die Felder Day, Month und Year des Providers Win32_CurrentTime abgefragt.
Sub TestWMIDate1() Dim objWMI As Object Dim objSet As Object Dim obj As Object Set objWMI = GetObject("winmgmts://./root/cimv2") Set objSet = objWMI.ExecQuery("SELECT Day, Month, Year FROM Win32_CurrentTime") Set obj = objSet.ItemIndex(0) Debug.Print obj.GetObjectText_ End Sub
Listing 1: Erste Variante zum Ermitteln von Datumsangaben über das WMI
Als Resultat bekommen Sie über diese Abfrage nicht ein Recordset, sondern ein WMI-ObjectSet, das in der Routine der Variablen objSet zugewiesen wird. Zwar kann ein Recordset viele Datensätze enthalten, doch in unserem Fall gibt es nun mal nur ein aktuelles Datum.
Das Resultat besteht also nur aus einem Datensatz (WMI-Objekt). Dieser Datensatz muss jetzt für die weitere Verwendung in einer weiteren Objektvariablen obj gespeichert werden. Man erhält ihn über die Funktion ItemIndex([Datensatznummer]) des ObjectSets. Und schließlich lässt sich der Inhalt des Datensatzobjekts extrem einfach über die Anweisung GetObjectText_ ausgeben. Heraus kommt dabei etwa dies:
instance of Win32_LocalTime { Day = 21; Month = 09; Year = 2014; };
GetObjectText_ entspricht damit dem Pendant GetString eines ADO-Recordsets.
Da sich das Ergebnis in dieser Form schlecht weiterverwenden lässt, zeigt die Prozedur TestWMIDate2 in Listing 2, wie man stattdessen die einzelnen Feldwerte des Datensatzes auslesen kann. Sie gleicht anfangs genau der besprochenen Routine, verwendet dann aber die Auflistung Properties_ des Datensatzobjekts, um die Eigenschaftswerte zurückzugeben. Sie werden als String zu einem Ergebnis zusammengesetzt, der das aktuelle Datum wiedergibt:
Sub TestWMIDate2() Dim objWMI As Object Dim objSet As Object Dim obj As Object Set objWMI = GetObject("winmgmts://./root/cimv2") Set objSet = objWMI.ExecQuery ("SELECT Day, Month, Year FROM Win32_CurrentTime") Set obj = objSet.ItemIndex(0) With obj.Properties_ Debug.Print .Item("Day") & "." & .Item("Month") & "." & .Item("Year") End With End Sub
Listing 2: Zweite Variante zum Ermitteln von Datumsangaben über das WMI
21.09.2014
Warum erst hier, werden Sie fragen Hätte man, wie in einer Access-Abfrage, nicht gleich den Datums-String zusammensetzen können
SELECT Day & ''.'' & Month & ''.'' & Year AS DatumAktuell FROM Win32_CurrentTime
Aber das lässt WQL eben nicht zu. Im SQL-String können nur einzelne Felder angegeben, aber nicht weiteren Operationen unterzogen werden, und die Zuweisung an einen Alias-Namen ist ebenso unmöglich.
WQL-Syntax
Die anderen relevanten Unterschiede von WQL zu JET-SQL sollen nicht verschwiegen werden. Was etwa auch nicht geht, ist die Verknüpfung mehrerer Tabellen in der Abfrage per Joins.
Zwar lassen sich Verknüpfungen zwischen Provider-Klassen auf etwas kompliziertere Weise auch herstellen, Erläuterungen dazu würden jedoch den Rahmen des Beitrags sprengen. Forschen Sie bei Interesse nach den WQL-Stichwörtern Associators Of oder References Of.
Möglich hingegen sind Bedingungen per WHERE-Statement und die Zusammensetzung von Bedingungen über OR und AND. Für unser Datumsbeispiel mit nur einem Ergebnisdatensatz macht eine bedingte Filterung keinen Sinn. Daher hier ein anderes Beispiel:
SELECT Caption FROM Win32_Printer _
WHERE Shared=True _
AND Name LIKE ''%Epson%''
Hier wird nach der Bezeichnung von installierten Druckern gefragt, die erstens für die gemeinsame Verwendung freigegeben sind (Shared) und außerdem den Ausdruck Epson im Namen haben. Im Unterschied zu Jet-SQL ist beim LIKE-Operator das Zeichen % statt * zu verwenden.
Ansonsten können in Bedingungen auch die üblichen Vergleichsoperatoren =, >, <, >=, <=, <> sowohl für numerische Werte wie für Text verwendet werden. Auch IS NULL und IS NOT NULL für die Unterscheidung von leeren Feldwerten lassen sich setzen:
SELECT Caption FROM Win32_Printer _ ServerName IS NOT NULL
Das ermittelt alle Drucker, bei denen ein Servername gesetzt ist, also etwa Netzwerkdrucker.
Alle weiteren Informationen zu WQL entnehmen Sie bitte der Referenz von Microsoft: WQL (SQL for WMI)
Geht es auch kürzer
Die bisher vorgestellten Routinen lassen sich noch weiter komprimieren. In TestWMIDate3 (s. Listing 3) wurde auf eine Abfrage per ExecQuery verzichtet, weil der Provider für Win32_CurrentTime bereits in den Moniker-String verfrachtet wurde. Das geht problemlos, und der Schritt über das ObjectSet wird übersprungen. GetObject gibt mit dieser Syntax nun direkt das Datensatzobjekt zurück!
Sub TestWMIDate3() Dim obj As Object Set obj = GetObject("winmgmts:root/cimv2:Win32_CurrentTime") With obj.Instances_.ItemIndex(0).Properties_ Debug.Print .Item("Day") & _ "." & .Item("Month") & _ "." & .Item("Year") End With End Sub
Listing 3: Dritte Variante zum Ermitteln von Datumsangaben über das WMI
In der Prozedur TestWMIDate4 (s. Listing 4) wurde außerdem die Angabe von root/cimv2 weggelassen, da WMI diese Werte ohnehin als Default annimmt. Die Prozedur zeigt darüber hinaus, dass durch komplexe Verschachtelung der Objekte mit nur einer Code-Zeile zum Ergebnis zu kommen ist. In diesem Fall allerdings nur für das aktuelle Jahr. Für den kompletten Datums-String fällt uns keine einzeilige Lösung ein.
Sub TestWMIDate4() Debug.Print GetObject("winmgmts:Win32_CurrentTime").Instances_.ItemIndex(0).Properties_("Year") End Sub
Listing 4: Vierte Variante zum Ermitteln von Datumsangaben über das WMI
Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...
den kompletten Artikel im PDF-Format mit Beispieldatenbank
diesen und alle anderen Artikel mit dem Jahresabo