PowerShell Klasse mit Variable

Dieser Weg einer Klasse ist der "alte" Weg. Seit PowerShell 2.0 gibt es das Commandlet "Add-Type", welches sehr viel einfacher die Definition von Klassen erlaubt
Add-Type
http://technet.microsoft.com/de-de/library/dd315241.aspx

Die Programmierer von heute entwickeln Software überwiegend mit "Klassen", d.h. Code und Daten ein Module eingekapselt, auf die ein strukturierter Zugriff über Methoden und Eigenschaften möglich ist. Die frühere prozedurale Programmierung, ist zwar noch nicht überflüssig, aber nur noch in wenigen Anwendungsfällen im Einsatz. Es muss dabei nicht immer eine "Hochsprache" sein. Schon VBScript kann selbst Klassen erstellen und verwenden. Der Vorteil ist, dass eine Klasse, wenn Sie einmal instanziert ist, einfach länger als ein Aufruf einer Prozedur besteht. Eine Prozedur wurde aufgerufen, hat ihre Arbeit getan aber am Ende wurden die Ergebnisse übergeben und alle Strukturen wieder aufgeräumt. Die Prozedur konnte aber keine Zwischenergebnisse oder aufgebaute Strukturen behalten, es sei denn diese wurden in globalen Variablen abgelegt. Kein schöner Trick. Eine Klasse hingegen kann beständig sein.

Nun gibt es ja die PowerShell, die vieles besser machen soll und auch tatsächlich mir viel angenehmer als VBScript geworden ist. Aber es gibt fast gar keine Beschreibung, wie man eine Klasse in PowerShell selbst schreibt. Immer heißt es, dass es nicht geht oder es zu schwer wäre.

Although it is possible to create a class in Windows PowerShell, it’s not a very straightforward process and definitely goes beyond the scope of this introductory manual. So, für the time being, forget we even mentioned it.
Quelle: Converting VBScript's Class Statement http://technet.microsoft.com/en-us/library/ee156807.aspx

Vermutlich ist jeder bei Microsoft davon ausgegangen, dann Klassen besser in Visual Studio entwickelt werden und PowerShell dieses dann über die DLL einfach nutzt.

Die drei Wege nach..

Es gibt genau genommen vier Wege um in PowerShell eine Klasse zu erstellen:

  1. Native CustomDLL
    Wer keine Scheu vor Visual Studio hat, kann einfach hiermit sich eine DLL bauen, die die gewünschten Funktionen nachrüstet. Unschön ist, dass Sie diese DLL dann natürlich mitliefern, mit installieren und mit warten müssen und Sie die gewohnte PowerShell-Umgebung ein Stück weit verlassen
  2. Add-Type mit C# als Code
    Ich bin immer wieder erstaunt, was PowerShell kann. Über Add-Type können Sie tatsächlich im PowerShell-Code direkt C# Code einbetten. Sieht nett aus aber das Debuggen stelle ich mir dann auch nicht gerade einfach vor
  3. Module als CustomObjekt
    Wer heute schon Module für PowerShell entwickelt (Siehe PSModule), kann diese auch direkt als "CustomObjekt" importieren.
  4. PSCustomObject
    Meine erste Klasse haben ich damit gebaut und die möchte ich im folgenden Vorstellen.

Auf dieser Seite zeige ich den Trick mit einem CustomObject.

Ersatz für Write-Host / Write-Verbose / Write-Debug

Diese Seite will aber einen Trick zeigen wie zumindest kleinere eigenen Klassen mit PowerShell für die Nutzung innerhalb der PowerShell erstellt werden. Auf der Seite VBSToolbox habe ich einige Klassen für VBScript erstellt. Die dort verwendete "DebugWriter"-Kasse habe ich in vereinfachter Form umsetzen wollen.

PowerShell bietet ja allein drei "Write-*" Commandlets, um Ausgaben auf den Bildschirm zu geben. Aber was ist, wenn ich andere Abstufungen für eine Art "Debuglevel" möchte oder wenn ich diese Ausgabe in eine Logdatei oder ins Eventlog schreiben möchte. Und ein Zeitstempel wäre natürlich auch noch nett. Das möchte ich nicht immer hinter jeder "Write-*"-Zeile schreiben.

Zudem sind die Parameter für "Write-Host" und "Write-Verbose/Write-Debug" unterschiedlich, so dass ich nicht einfach mal die Befehle untereinander ersetzen kann. Damit war die Idee geboren, meine eigene Diagnosefunktion zu bauen.

Sicher könnte ich einfach eine "function" bauen, und die stattdessen aufrufen. Eine Funktion vergisst aber alle Einstellungen, wenn diese nicht als Parameter mit übergeben oder als globale Variablen bereitgestellt werden. Beides wollte ich nicht. Eine Klasse ist doch genau dazu da, dass diese einmal instanziiert wird und über Methoden darauf zugegriffen wird

Der Trick mit PSObject

Ich habe etwas gesucht aber letztlich eine Lösung für mein Problem gefunden. PowerShell kennt "Custom Objects", welche als Speicher von Informationen dienen. Diese Objekte könne aber nicht nur statische Variablen sondern auch Code enthalten, der ausgeführt wird.

Eine Klasse ist dann ein PSObject, welches vor der ersten Verwendung  mit Code gefüllt wird. Das ganze habe ich als Funktion erstellt, die dann diese Objekt "zurück" übergibt.

function new-msxfaqlogger {
	param (
		[string]$filename="\Lo\g\geryyyyMMdd.lo\g", # using DateTime Stamp
		[int]$debugscreen=3,
		[int]$debugfile=3,
		[int]$debugevt=3,
		[string]$timeformat="yyyyMMdd HH:mm"
	)
	
	$log = New-Object PSCustomObject
	$log | add-member -MemberType NoteProperty -name filename -Value $filename
	$log | add-member -MemberType NoteProperty -name debugscreen -Value $debugscreen
	$log | add-member -MemberType NoteProperty -name debugfile -Value $debugfile
	$log | add-member -MemberType NoteProperty -name debugevt -Value $debugevt
	$log | add-member -MemberType NoteProperty -name timeformat -Value $timeformat
	$log | add-member -MemberType ScriptMethod -name write -Value {
		# Arg0 = Modul string
		# Arg1 = Severity integer
		# Arg2 = Message string
		[string]$timestamp = (Get-Date -Format $this.timeformat)
		[string]$modulname = ([string]$args[0]).padright(13).substring(0,12)
		switch ([int]$args[1]) 
		    { 
			0 {[string]$severity="Inf0"} 
			1 {[string]$severity="Err1"} 
			2 {[string]$severity="Wrn2"} 
			3 {[string]$severity="dbg3"} 
			default {[string]$severity=("dbg"+[string]$args[0])}
		    }
		[string]$message = [string]$args[2]
		$line=($timestamp+","+$modulname+","+$severity+","+$message)
		
		if ($args[1] -le $this.debugscreen) {
			write-host $line
		}
		if ($args[1] -le $this.debugfile) {
			$filename = (get-date -format $this.filename)
			$line| out-file -filepath $filename -append
		}
	}
	$log
}

Sie können schon sehen, dass die Konfiguration beim Instanziieren per Parameter übergeben werden. Sie können aber auch diese Werte nachträglich einfach über die Properties ändern.

Einsatz

Sobald die Funktion aufrufbar ist, kann die Klasse relativ einfach verwendet werden. Hier ein Beispielcode:

# Musterskript zum Einsatz von MSXFAQLogger

import-module ".\new-msxfaqlogger.psm1"
$log = new-msxfaqlogger
$log.write("Skriptname" ,0,"Started")
$log.write("Skriptname",3,"Information")
$log.write("Skriptname",0,"Ende")

Der Aufruf gibt die Daten auf der Konsole aus

PS C:\> .\test-msxfaqlogger.ps1
20120521 14:01,Skriptname  ,Inf0,Started
20120521 14:01,Skriptname  ,dbg3,Information
20120521 14:01,Skriptname  ,Inf0,Ende
PS C:\>

Parallel dazu wird eine Datei geschrieben.

Damit wäre bewiesen dass es technisch möglich ist

Bewertung

Das ist nur eine ganz einfache Lösung, die aber zumindest einen Weg aufzeigt, etwas die "Klassen" auch in PowerShell selbst zu erstellen. Sonderlich bequem ist es aber nicht. Wer also etwas umfangreicher arbeiten möchte, wird irgendwann doch dazu übergehen, die Klassen direkt in Visual Studio als DLL zu bauen und als Modul einzubinden.

Dennoch ist dies eine ganz einfache Möglichkeit, da die Funktion ja problemlos ebenfalls in eine PSM1-Datei ausgelagert und damit eingebunden werden kann. Der Charme einer Klasse in PowerShell selbst ist, dass der Sourcecode leicht erweitert und korrigiert werden kann.

Der "bessere Weg"

Der Umweg über ein PSCustomObjekt, welches mit Script-Objekten beladen wird, die letztlich in einer Art "Variable" als Speicher landen ist vielleicht nicht der ideale Ansatz. Es zeigt eher die Machbarkeit über diesen Weg aber vielleicht möchten Sie doch eine "richtige Klasse" schreiben.

Weitere Links