Powershell-Kommandos modularisieren

1. Februar 2011

Die Version 2.0 der Powershell bietet verschiedene Ansätze, um einen Satz von Kommandos zusammenzustellen – sprich zu modularisieren. Dabei steht dem Administrator eine große Bandbreite – von sehr einfach bis hin zu beliebig komplex – offen. Er kann daraus die jeweils passende Option wählen. Doch er muss dabei die Regeln und Charakteristiken kennen, die für alle Möglichkeiten gleichermaßen gelten.

Zu diesem Thema ist in der Print-Ausgabe Februar 2011 des NT4ADMINS Magazins ein ausführlicher Beitrag erschienen. Anhand einiger Beispiele zeigt unser Powershell-Guru Don Jones, wie sich einzelne Code-Blöcke sinnvoll aufbauen lassen. dabei sind fünf Listings mit angegeben, die wir hier nochmals zum Download (via Copy & Paste) zusammenstellen.

Generell, so Don Jones, sollte eine individuelle Funktion immer auf eine einzige Aufgabe ausgelegt sein. Dabei macht es Sinn, dass sie entweder keine Ausgabe erzeugt (das ist zum Beispiel der Fall, wenn sie eine Aktion ausführt) oder aber sie sollte eine einzige Art von Ausgabe liefern. Das ist der Fall, wenn es darum geht, bestimmte Informationen abzufragen. Wer bei seinen Funktionen das Cmdlet-artige Muster mit „Verb-Hauptwort“ verfolgt, der tut sich generell leichter, sich auf eine Aufgabe in der Funktion zu konzentrieren.

Wer eine kostenlose Leseprobe dieses Beitrags als PDF zugesendet haben möchte, der braucht nur eine Mail an rhh(at)oberland.net mit dem Betreff: Leseprobe Februar 2011 absenden.

Als die hohe Schule der Powershell-Programmierung gilt eine Funktion, die sowohl die Eingabe über die Pipeline als auch mit den Einsatz von Parametern (die über die Stellung auf der Kommandozeile oder über die Benennung des Parameters) funktioniert. Ab der Powershell 2.0 gibt es dazu eine Lösung – die „Advanced Functions“. Sie trägt auch die Bezeichnung „Script Cmdlet“ – weil sie ähnlich wie ein echtes Cmdlet arbeitet.

Ein Problem ergibt sich dabei, wenn über die Pipeline der Input in die Funktion geleitet werden soll. Denn die Funktion wird den Script-Block PROCESS jeweils einmal für jedes Eingabeobjekt ausführen. Dann wird jedes Objekt einzeln als Parameter übergeben.

Der Einsatz eines Parameters hat allerdings zur Folge, dass der PROCESS-Script-Block nur einmal ausgeführt wird. Doch der Parameter enthält ja alle Eingabe-Objekte, die es dann manuell Schritt für Schritt abzuarbeiten gilt.
Die Advanced Functions kann man daher als eine Art von Shell realisieren, die alle beiden Szenarien abhandelt, ansonsten aber keine „echte Arbeit“ erledigt.

Eine andere Lösung wäre das Verlagern der „echten Arbeit“ in eine Support-Funktion, die so ausgelegt ist, dass sie – im hier gezeigten Fall – immer nur mit einem Computer zu einer Zeit arbeitet. Die Advanced Function stellt dabei sicher, dass die Eingabe unterteilt wird, so dass nur ein Computer zu einer Zeit abgehandelt wird, falls das nötig sein sollte. In Listing 5 ist ein Code-Beispiel für eine Advanced Function zu sehen.

Die hier aufgeführten Listings 1 bis 4 gehören alle zum Print-Beitrag "Powershell-Kommandos modularisieren", der als Aufmacher in der Februar-Ausgabe 2011 publiziert wurde.

Bitte beachten sie die üblichen Sicherheitsvorkehrungen bei Skripts: Nie ohne Testen in der Produktivumgebung einsetzen!

Listing 1: Skript speichert WMI-Infos in zwei Variablen

$os = Get-WmiObject -class Win32_OperatingSystem -computername Server-R2
$bios = Get-WmiObject -class Win32_BIOS -computername Server-R2
$obj = New-Object PSObject
$obj | Add-Member NoteProperty ComputerName ($os.__SERVER)
$obj | Add-Member NoteProperty OSVersion ($os.Caption)
$obj | Add-Member NoteProperty OSBuild ($os.BuildNumber)
$obj | Add-Member NoteProperty SPVersion ($os.ServicePackMajorVersion)
$obj | Add-Member NoteProperty BIOSSerial ($bios.SerialNumber)

Listing 2: Einfache Funktion arbeitet als Wrapper

Function Get-OSInventory {
  $os = Get-WmiObject -class Win32_OperatingSystem -computername Server-R2
  $bios = Get-WmiObject -class Win32_BIOS -computername Server-R2
  $obj = New-Object PSObject
  $obj | Add-Member NoteProperty ComputerName ($os.__SERVER)
  $obj | Add-Member NoteProperty OSVersion ($os.Caption)
  $obj | Add-Member NoteProperty OSBuild ($os.BuildNumber)
  $obj | Add-Member NoteProperty SPVersion ($os.ServicePackMajorVersion)
  $obj | Add-Member NoteProperty BIOSSerial ($bios.SerialNumber)
  Write-Output $obj
}

Listing 3: Der Code von Utilities.ps1

Function Get-OSInventory {
  Param([string]$computername = ‚localhost‘)
  $os = Get-WmiObject -class Win32_OperatingSystem -computername $computername
  $bios = Get-WmiObject -class Win32_BIOS -computername $computername
  $obj = New-Object PSObject
  $obj | Add-Member NoteProperty ComputerName ($os.__SERVER)
  $obj | Add-Member NoteProperty OSVersion ($os.Caption)
  $obj | Add-Member NoteProperty OSBuild ($os.BuildNumber)
  $obj | Add-Member NoteProperty SPVersion ($os.ServicePackMajorVersion)
  $obj | Add-Member NoteProperty BIOSSerial ($bios.SerialNumber)
  Write-Output $obj
}

Listing 4: Die Funktion Pipeline

Function Get-OSInventory {
  BEGIN {}
  PROCESS {
    $computername = $_
    $os = Get-WmiObject -class Win32_OperatingSystem -computername $computername
    $bios = Get-WmiObject -class Win32_BIOS -computername $computername
    $obj = New-Object PSObject
    $obj | Add-Member NoteProperty ComputerName ($os.__SERVER)
    $obj | Add-Member NoteProperty OSVersion ($os.Caption)
    $obj | Add-Member NoteProperty OSBuild ($os.BuildNumber)
    $obj | Add-Member NoteProperty SPVersion ($os.ServicePackMajorVersion)
    $obj | Add-Member NoteProperty BIOSSerial ($bios.SerialNumber)
    Write-Output $obj
  }
  END {}
}

Listing 5: Diese erweiterte Funktion fragt Infos zum Betriebssystem ab.

Function OSInventoryHelper {
  Param([string]$computername)
  $os = Get-WmiObject -class Win32_OperatingSystem -computername $computername
  $bios = Get-WmiObject -class Win32_BIOS -computername $computername
  $obj = New-Object PSObject
  $obj | Add-Member NoteProperty ComputerName ($os.__SERVER)
  $obj | Add-Member NoteProperty OSVersion ($os.Caption)
  $obj | Add-Member NoteProperty OSBuild ($os.BuildNumber)
  $obj | Add-Member NoteProperty SPVersion ($os.ServicePackMajorVersion)
  $obj | Add-Member NoteProperty BIOSSerial ($bios.SerialNumber)
  Write-Output $obj
}
Function Get-OSInventory {
  [CmdletBinding()]
  Param(
    [Parameter(Mandtory=$True,ValueFromPipeline=$true)]
    [String\[]]$ComputerName
  )
  BEGIN {
    $inputWasFromPipeline = -not $PSBoundParameters.ContainsKey(‚computername‘)
  }
  PROCESS {
    If ($inputWasFromPipeline) {
      OSInventoryHelper $computername
    } else {
      foreach ($computer in $computername) {
        OSInventoryHelper $computer
      }
    }
  }
}

Lesen Sie auch