Windows PowerShellLeistungsfähige Filterung

Don Jones

Im Artikel des letzten Monats wurde die Leistungsstärke und Flexibilität der Windows PowerShell-Pipeline erläutert, mit der ein Datensatz oder, genauer gesagt, ein Objektstrom von einem Cmdlet an ein anderes übergeben werden kann, sodass dieser Satz weiter verfeinert wird, bis er genau Ihren Vorgaben entspricht. Es wurde darauf hingewiesen, dass auch Ihre eigenen Skripts,

nicht nur Cmdlets, die Pipeline nutzen können. In diesem Monat soll ausführlich auf dieses Thema eingegangen werden.

Häufig werden in Windows PowerShell™ Skripts geschrieben, die für mehrere Remotecomputer agieren – normalerweise durch die Windows®-Verwaltungsinstrumentation (Windows Management Instrumentation, WMI). Wie bei jeder Aufgabe im Zusammenhang mit Remotecomputern besteht die Möglichkeit, dass einer oder mehrere dieser Computer beim Ausführen des Skripts nicht verfügbar sind. Daher müssen Skripts solche Situationen handhaben können.

Es gibt natürlich eine Reihe von Möglichkeiten, wie ein Skript mit der Fähigkeit ausgestattet werden kann, ein WMI-Verbindungstimeout zu behandeln, doch mir gefällt dieser Ansatz nicht besonders, da die Timeoutdauer relativ lang ist (standardmäßig etwa 30 Sekunden). Das Skript würde sehr viel langsamer ausgeführt, wenn es auf mehrere Timeouts warten müsste. Stattdessen soll das Skript eine schnelle Überprüfung durchführen, um zu sehen, ob ein bestimmter Computer online ist, bevor ein Verbindungsversuch durchgeführt wird.

Das Windows PowerShell-Paradigma

In anderen Skriptsprachen wie VBScript wird in der Regel jeder Computer für sich behandelt. Ein Computername wird also (möglicherweise aus einer Liste mit Namen, die in einer Textdatei gespeichert sind) abgerufen, bevor eine Pinganforderung an das System gesendet wird, um seine Verfügbarkeit festzustellen. Wenn der Computer verfügbar ist, wird die WMI-Verbindung hergestellt, und die erforderlichen Arbeiten werden durchgeführt. Dies ist ein allgemeiner Ansatz für den Skriptstil. Wahrscheinlich wird der gesamte Code in einer Schleife geschrieben und diese Schleife dann für jeden Computer wiederholt, für den eine Verbindung hergestellt werden muss.

Windows PowerShell ist jedoch besser für Batchvorgänge geeignet, da sie auf Objekten basiert und direkt mit Gruppen oder Objektsammlungen arbeiten kann. Das Paradigma in Windows PowerShell besteht nicht in der Arbeit mit einzelnen Objekten oder Datenelementen, sondern in der Arbeit mit ganzen Gruppen, wobei die Gruppe Stück für Stück verfeinert wird, bis das gewünschte Ergebnis erzielt wird. Statt beispielsweise jeweils einen Computernamen aus der Liste abzurufen, wird die ganze Namenssammlung auf einmal eingelesen. Statt Pinganforderungen an jeden Computer in einer Schleife zu senden, schreiben Sie eine einzelne Routine, die eine Sammlung von Namen akzeptiert, eine Pinganforderung an sie sendet und dann die Namen ausgibt, zu denen eine Verbindung hergestellt werden konnte. Der nächste Prozessschritt besteht darin, die WMI-Verbindung zu den verbliebenen Namen herzustellen, an die eine Pinganforderung gesendet werden konnte.

In Windows PowerShell wird genau dieser Ansatz für eine Reihe von Aufgaben eingesetzt. Um beispielsweise eine Liste von auszuführenden Diensten abzurufen, gehen Sie wie folgt vor:

Get-Service | Where-Object { $_.Status –eq "Running" }

Abbildung 1 zeigt, wie die Ausgabe auf dem Computer aussieht. Statt jeden Dienst nacheinander zu überprüfen, wurden alle Dienste mithilfe von Get-Service abgerufen. Diese wurden an Where-Object geleitet, und anschließend wurden alle nicht ausgeführten Dienste ausgefiltert. Genau dies soll mit dem Skript erzielt werden: Abrufen einer Liste von Computernamen, Ausfiltern der Namen, an die keine Pinganforderung gesendet werden kann, und Übergabe der reagierenden Computer an den nächsten Schritt.

Abbildung 1 Abrufen von Computern, an die eine Pinganforderung gesendet werden kann

Abbildung 1** Abrufen von Computern, an die eine Pinganforderung gesendet werden kann **(Klicken Sie zum Vergrößern auf das Bild)

Filterfunktionen

Obwohl Sie Ihr eigenes Cmdlet schreiben könnten, gibt es eine einfachere Lösung. Zum Schreiben von Cmdlets ist Visual Basic® oder C# und einige Entwicklungserfahrung mit Microsoft® .NET Framework erforderlich. Der wichtigere Aspekt ist jedoch, dass mehr Arbeit erforderlich ist, als Sie für diese Aufgabe wahrscheinlich aufwenden wollen. Glücklicherweise können Sie in Windows PowerShell eine besondere Funktion schreiben, die als Filter bezeichnet wird und in der Pipeline agieren kann. Die grundlegende Gliederung einer Filterfunktion sieht folgendermaßen aus:

function <name> {
  BEGIN {
    #<code>
  }
  PROCESS {
    #<code>
  }
  END {
    #<code>
  }
}

Diese Funktion enthält drei unabhängige Skriptblöcke mit der Bezeichnung BEGIN, PROCESS und END. Eine Filterfunktion, also eine Funktion, die in der Pipeline Objekte filtern soll, kann abhängig von Ihrem angestrebten Ziel aus einer beliebigen Kombination dieser drei Skriptblöcke bestehen. Sie funktionieren folgendermaßen:

  • Der BEGIN-Block wird einmal ausgeführt, wenn die Funktion erstmalig aufgerufen wird. Sie können ihn bei Bedarf für Setuparbeiten verwenden.
  • Der PROCESS-Block wird einmal für jedes Pipelineobjekt ausgeführt, das an die Funktion übergeben wird. $_variable stellt das aktuelle Pipelineeingabeobjekt dar. Der PROCESS-Block ist in einer Filterfunktion erforderlich.
  • Der END-Block wird einmal ausgeführt, nachdem alle Pipelineobjekte verarbeitet wurden. Er kann bei Bedarf für etwaige Beendigungsarbeiten verwendet werden.

Im hier angegebenen Beispiel soll eine Filterfunktion erstellt werden, die eine Sammlung von Namen als Eingabeobjekte übernimmt und dann versucht, an jedes einzelne eine Pinganforderung zu senden. Jedes Objekt, das erfolgreich ein Pingsignal erhält, wird in der Pipeline ausgegeben. Systeme, bei denen dies nicht möglich ist, werden einfach entfernt. Da für die Pingfunktionalität kein besonderes Setup oder Beendigung erforderlich ist, kann einfach der PROCESS-Skriptblock verwendet werden. Der Code in Abbildung 2 stellt das vollständige Skript dar.

Figure 2 Pingadresse und Computerneustart

1  function Ping-Address {
2    PROCESS {
3      $ping = $false
4      $results = Get-WmiObject -query `
5      "SELECT * FROM Win32_PingStatus WHERE Address = '$_'"
6      foreach ($result in $results) {
7        if ($results.StatusCode -eq 0) {
8          $ping = $true
9        }
10      }
11      if ($ping -eq $true) {
12        Write-Output $_
13      }
14    }
15  }
16
17  function Restart-Computer {
18    PROCESS {
19      $computer = Get-WmiObject Win32_OperatingSystem -computer $_
20      $computer.Reboot()
21    }
22  }
23
24  Get-Content c:\computers.txt | Ping-Address | Restart-Computer

Es ist zu beachten, dass zwei Funktionen definiert wurden: Pingadresse und Computerneustart. In Windows PowerShell müssen Funktionen definiert werden, bevor sie aufgerufen werden können. Demzufolge ist die erste ausführbare Zeile des Skripts Zeile 24, in der das Get-Content-Cmdlet verwendet wird, um eine Liste von Computernamen aus einer Datei abzurufen (ein Computername pro Zeile). Diese Liste, bei der es sich aus technischer Sicht um eine Sammlung von Zeichenfolgeobjekten handelt, wird in die Pingadressfunktion geleitet, die alle Computer ausfiltert, die kein Pingsignal erhalten. Die Ergebnisse werden in die Neustartfunktion geleitet, die WMI zum Remoteneustart aller Computer verwendet, die ein Pingsignal erhalten haben.

Die Pingadressfunktion implementiert einen PROCESS-Skriptblock, was bedeutet, dass die Funktion eine Eingabesammlung von Objekten erwartet. Der PROCESS-Skriptblock behandelt diese Eingabe automatisch. Es mussten keine Eingabeargumente zum Eingrenzen der Eingabe definiert werden. Der Prozess beginnt in Zeile 3 durch Einstellen der Variablen „$ping“ auf „$false“, wobei es sich um eine integrierte Windows PowerShell-Variable handelt, die den booleschen Wert „False“ darstellt.

Dann wird die lokale Win32_PingStatus-WMI-Klasse verwendet, um eine Pinganforderung an den angegebenen Computer zu senden. Beachten Sie, dass die $_-Variable in Zeile 5, die das aktuelle Pipelineobjekt darstellt, in die WMI-Abfragezeichenfolge eingebettet ist. Wenn eine Zeichenfolge mit doppelten Anführungszeichen versehen ist, versucht Windows PowerShell immer, Variable wie $_ durch ihren Inhalt zu ersetzen, sodass Sie sich nicht um die Zeichenfolgeverkettung kümmern müssen. Diese Funktionsfähigkeit wird in Zeile 5 verwendet.

Zeile 6 ist eine Schleife, die die Pingergebnisse prüft. Wenn diese Ergebnisse erfolgreich zurückgegeben werden (d. h. mit dem StatusCode Null), wird $ping auf $true gesetzt, was den Erfolg anzeigt. Prüfen Sie in Zeile 11, ob $ping auf $true gesetzt wurde. Wenn dies der Fall ist, wird das ursprüngliche Eingabeobjekt in den standardmäßigen Ausgabestrom ausgegeben. Windows PowerShell verwaltet den standardmäßigen Ausgabestrom automatisch. Wenn sich diese Funktion am Ende der Pipeline befindet, wird der Ausgabestrom in eine Textdarstellung umgewandelt. Wenn in der Pipeline mehr Befehle enthalten sind, werden die Objekte des Ausgabestroms (in diesem Fall Zeichenfolgeobjekte, die Computernamen enthalten) an den nächsten Pipelinebefehl übergeben.

Die Computerneustartfunktion ist etwas einfacher, aber auch hier wird ein PROCESS-Block verwendet, sodass sie leicht an der Pipeline beteiligt werden kann. In Zeile 19 stellt die Funktion eine Verbindung zum genannten Computer her und ruft seine Win32_OperatingSystem-WMI-Klasse ab. Zeile 20 führt die Methode „Reboot“ der Klasse aus, um den Remotecomputer neu zu starten.

Hier ist es Zeile 24, in der alles tatsächlich ausgeführt wird. Natürlich ist beim Ausführen dieses Skripts Vorsicht geboten. Es startet jeden in c:\computers.txt genannten Computer neu, was verheerende Auswirkungen haben könnte, wenn Sie die Namen in der Textdatei nicht genau überprüfen!

Nächste Schritte

Bei diesem Skript kann es allerdings Probleme geben. Es sollte die Möglichkeit eines WMI-Fehlers berücksichtigt werden, der nicht im Zusammenhang mit grundlegender Konnektivität steht, beispielsweise eine Firewall, die die erforderlichen Ports beim Remotecomputer sperrt, oder ein WMI-Sicherheitsfehler. Diese Probleme können durch Implementieren eines Traphandlers behandelt werden, ein perfektes Thema für einen zukünftigen Artikel.

Zudem führt dieses Skript eine ziemlich gravierende Aktion durch: den Neustart eines Remotecomputers. In jedem Skript, das solch eine möglicherweise gefährliche Aktion versucht, sollten zwei gebräuchliche Windows PowerShell-Parameter (–Confirm und –WhatIf) implementiert werden. Eine genaue Erläuterung dieses Vorgangs ist im Rahmen dieses Artikels nicht möglich, aber auch dies eignet sich hervorragend als zukünftiges Thema. In der Zwischenzeit finden Sie weitere Informationen im Windows PowerShell-Teamblog. Architekt Jeffrey Snover behandelt dieses Thema.

Selbst ohne eine nähere Erklärung dieser Features erhalten Sie eine gute Vorstellung davon, wozu Filterfunktionen fähig sind. Ich werde in weiteren Artikeln näher auf diese Technik eingehen und erläutern, in welcher Weise Funktionen eine hervorragende Möglichkeit zum Modularisieren von wiederverwendbarem Code bieten und die allgemeine Funktionalität Ihrer Skripts verbessern.

Don Jones ist der führende Experte für die Skripterstellung bei SAPIEN Technologies und Mitverfasser von Windows PowerShell: TFM (SAPIEN Press, 2007). Sie erreichen ihn unter www.ScriptingAnswers.com.

© 2008 Microsoft Corporation und CMP Media, LLC. Alle Rechte vorbehalten. Die nicht genehmigte teilweise oder vollständige Vervielfältigung ist nicht zulässig.