PowerShell and WMI

Mit Windows PowerShell ist der Zugriff auf die Windows Management Instrumentation Schnittstellen WMI im Vergleich zu VBSkript noch einfacher geworden.

Ich will hier eine kurze Einführung geben, wie der Zugriff auf WMI Klassen mit VBSkript und PowerShell Skript erfolgt.

Ein VBSkript zum Anzeigen der Windows Dienste sieht in der Regel wie folgt aus.

strComputer = "anyServer"
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery("Select * From Win32_Service")
For Each objItem in colItems
   Wscript.Echo objItem.Name, objItem.StartMode, objItem.State
Next

Mit strComputer = "anyServer" wird der Rechner festgelegt, der abgefragt werden soll. Der lokale Rechner wird mit "." adressiert.
Die Methode GetObject() für den Zugriff auf WMI mit VBSkript ist unter WMI Skripte - Basics beschrieben. In einer For Each Schleife werden für jeden Dienst der Name, der Starttyp und der Status ermittelt.
Ein ähnliches Skript hat vermutlich jeder von uns schon zigmal geschrieben oder zumindest gesehen.

Wie würde das nun in einem PowerShell Skript aussehen?

$strComputer = "anyServer"
$colItems = get-wmiobject -class "Win32_Service" -namespace "root\cimv2" -computername $strComputer
foreach ($objItem in $colItems) {
   write-host objItem.Name, objItem.StartMode, objItem.State
}

Das sieht erstmal dem VBSkript ziemlich ähnlich - obwohl es noch einfacher geht, wie wir sehen werden. Die Syntax der Sprache erinnert nicht von ungefähr an C#, der Sprache in der die PowerShell entwickelt wurde.

Was wir oben als Skript ausgeführt haben, kann interaktiv in der PowerShell auch als Einzeiler eingegeben werden. Das folgende Beispiel zeigt auch gleich noch Möglichkeiten der Abkürzung:

gwmi "Win32_Service" | % { write-host $_.name $_.startmode $_.state }

Der Alias gwmi - für Get-Wmiobject - ist schnell getippt und wie man sieht, müssen die Parameter -Class und -Namespace nicht angegeben werden, wenn im Default Namespace root\cimv2 gearbeitet wird. Fehlt der Parameter -computername, dann wird mit dem lokalen Rechner verbunden.
Für das Cmdlet Foreach-Object kann der kurze Alias % verwendet werden.
Die PowerShell Variable $_ enthält das jeweilige Dienst-Objekt aus der Foreach-Schleife, welches in die Pipe gestellt wurde.

Alle drei Beispiele liefern uns die Eigenschaften Name, Startmode und State der WMI-Klasse Win32_Service.

Wie komme ich nun an die Namen der Eigenschaften einer WMI Klasse?

gwmi "Win32_Service" | get-member -membertype property

Name     MemberType     Definition
----     ----------     ----------
Name     Property    System.String Name {get;set;}
PathName    Property    System.String PathName {get;set;}
ProcessId    Property    System.UInt32 ProcessId {get;set;}

Nahezu alles, was von der PowerShell zurückgeliefert wird, ist ein Objekt. Mit dem Cmdlet Get-Member hat man Zugriff auf die Eigenschaften mit -membertype property dieser Objektinstanz.
Die Ausgabe zeigt auch den Variablen Typ (String, Int32 ...) der jeweiligen Property.

Für viele WMI Klassen gibt es auch Methoden, die einen modifizierenden Zugriff auf ein Objekt erlauben. Welche Methoden zur Verfügung stehen, sehe ich mit ...

gwmi "Win32_Service" | get-member -membertype method

Für uns sind natürlich die Methoden StartService und StopService von Interesse - dazu gleich mehr.
Hinweis: Die Methoden von WMI-Klassen lassen sich auch mit meiner .NET Applikation WMI multi anzeigen.

Um den Status eines bestimmten Dienstes, z.B. den Browser Service, abzufragen, verwenden wir in einem VBSkript ...

...
Set colItems = objWMIService.ExecQuery("Select * From Win32_Service Where Name='Browser'")
...

In einem PowerShell Skript sieht das so ...

Get-Wmiobject -query "select * from win32_service where name='browser'"

... oder auch so aus:

gwmi Win32_Service -filter "name='Browser'"

Das ist doch elegant oder?

Wie bereits kurz angesprochen, können wir auch auf Methoden zugreifen, um einen Dienst zu manipulieren. Das soll das folgende Beispiel zeigen.

gwmi Win32_Service -filter "name='Browser'" | Start-Service -PassThru

Nicht schwer zu verstehen, dass wir damit den Dienst Computer Browser starten. Das Cmdlet Start-Service erledigt das für uns. Da dieses Cmdlet normalerweise keine Ausgabe macht, erzeugen wir sie mit dem Argument -PassThru, um damit den Status des Dienstes angezeigt zu bekommen.

Das folgende Beispiel startet den Dienst auf einem Remoterechner, wobei hier die InvokeMethod des .NET Objekts System.Management.ManagementObject#root\cimv2\Win32_Service angesprochen werden muss.

(gwmi -computer "RemoteServer" Win32_Service -filter "name='Browser'").InvokeMethod("StartService", $null)

Die Objekt-Methode InvokeMethode() erwartet zwei Parameter. Der erste ist unsere Dienst Methode, die wir mit gwmi "Win32_Service" | get-member -membertype method ermittelt haben und als zweiten Parameter übergeben wir einfach die PowerShell Variable für einen NULL-Wert.


Zum Schluss noch ein kleines PowerShell Skript, mit dem gestoppte Windows Dienste ermittelt werden und das Ergebnis in einer HTML-Seite angezeigt wird. Das Skript zeigt auch, wie mit der PowerShell auf ein COM-Objekt, hier der Internet Explorer, zugegriffen wird.
Dienste mit der Startart 'automatisch' werden in der Ausgabe rot markiert.

Set-ExecutionPolicy unrestricted
$strComputer = Read-Host "Enter Computername (leave blank for local Computer) "
if ($strComputer.Length -lt 5) { $strComputer = "." }
$s = gwmi -computer $strComputer "Win32_Service" -filter "state='stopped'"
$f = "C:\Temp\services_stopped.htm"
$s | ConvertTo-Html -property name, state, startmode, startname, pathname | `
foreach { $_ -replace "<td>Auto</td>", "<td bgcolor='red'>AUTO</td>" } > $f
$ie = new-object -comobject InternetExplorer.Application
$ie.navigate2($f)
$ie.Document.title = "stopped Services on Computer " + $strComputer.ToUpper()
$ie.visible = $true

Mit Set-ExecutionPolicy unrestricted wird die PowerShell angewiesen jedes Skript auszuführen, was wir aber nur für Tests erlauben wollen.
Mit dem Cmdlet Read-Host fragen wir an der Konsole den Computernamen ab, von dem die stopped Services ermittelt werden sollen. Bei Computernamen mit weniger als 5 Zeichen, wird immer der lokale Rechner genommen.
Auf eine kleine Besonderheit am Ende der 6. Zeile will ich noch hinweisen. Das ` Zeichen entspricht dem _ Zeichen in VBSkript und ermöglicht einen Zeilenumbruch.

Das Skript kann z.B. unter C:\Pscripts\stopped_services.ps1 gespeichert und mit powershell C:\Pscripts\stopped_services.ps1 aufgerufen werden.
In eine Function{} eingepackt, kann das obige Skriptfragment natürlich auch in die Profile Datei geschrieben werden. Dann steht es zur Laufzeit der PowerShell Konsole durch Aufruf des Funktionsnamen zur Verfügung.

Ich hoffe, dass diese Einführung ein wenig Lust gemacht hat, mit den Möglichkeiten der PowerShell zu spielen.