© Daria Tskhovrebova/Shutterstock.com
Ein Überblick über das Typsystem der PowerShell aus Entwicklerperspektive

Flexibler Typwandler


Die PowerShell ist mehr als nur eine moderne Alternative zur guten, alten „DOS-Box“, wie manche Entwickler die Eingabeaufforderung Cmd.exe nach wie vor liebevoll nennen. Ein herausragendes, wenn nicht sogar das herausragende Merkmal der PowerShell ist ihr flexibles Typsystem. Es vereinigt Typen aus unterschiedlichen Quellen (u. a. WMI, COM, XML und natürlich die .NET-Laufzeit) und ermöglicht es, die Typen auf einheitliche Weise zu verwenden, zu erweitern und jedem Typ eine individuelle Ausgabeformatierung zuzuordnen.

Das Typsystem ist nicht weniger als der Schlüssel zum Verständnis der PowerShell-Philosophie, die PowerShell-Erfinder Jeffrey Snover bereits 2004 in seinem Monad-Manifesto beschrieben hat [1]. Bevor es ins Detail geht, hier noch zwei formale Hinweise:

  • Die PowerShell ist eine Anwendung, die bis zur Version 5.1 auf dem .NET Framework 4.x, ab Version 6.0 auf .NET Core 2.x basiert. Folglich lassen sich alle .NET Assemblies und ihre Typen 1:1 in der PowerShell verwenden. Das gilt auch für generische Typen. Die Assembly-Datei wird dabei über das Add-Type-Cmdlet geladen, anschließend stehen alle Typen zur Verfügung.

  • Die in diesem Artikel vorgestellten Beispiele lassen sich sowohl mit der Windows PowerShell als auch mit PowerShell Core verwenden. Das Typsystem der PowerShell gibt es unverändert seit Version 1.0. Lediglich das Erweitern von Typen über das Update-TypeData-Cmdlet wurde mit Version 3.0 etwas vereinfacht. Das Verwenden einer XML-Datei für eine Typerweiterung kann ab dieser Version entfallen.

Schreibweisen für Typen

C#-Entwickler, die die PowerShell kennen lernen möchten, müssen sich an ein paar einfache Schreibregeln gewöhnen. Die vertrauten Typen und Namespaces gibt es 1:1 natürlich auch bei der PowerShell, lediglich die Schreibweise ist etwas anders. Die Namen von Typen werden in der Regel in eckige Klammern gesetzt. Dadurch entsteht ein Typobjekt, das über zahlreiche Members verfügt. Wird die Typbezeichnung benötigt, um daraus ein neues Objekt zu machen, werden keine eckigen Klammern verwendet. Das Pendant zum new-Operator ist das New-Object-Cmdlet. Der folgende Befehl legt ein SqlConnection-Objekt an:

$Cn = New-Object –TypeName System.Data.SqlClient.SqlConnection

Da die Groß-/Kleinschreibung bei der PowerShell grundsätzlich kein Thema ist und die Namen der Parameter oft weggelassen werden dürfen, sind auch andere Schreibweisen denkbar:

$Cn = New-Object System.Data.SqlClient.SqlConnection

Eine weitere Vereinfachung besteht darin, dass in Namespacenamen das System. entfallen kann. Der Befehl wird damit noch etwas kürzer:

$Cn = New-Object Data.SqlClient.SqlConnection

Ab Version 5.0 der PowerShell gibt es bei jedem Typobjekt eine statische new-Methode, sodass eine weitere Variante ins Spiel kommt:

$Cn = [Data.SqlClient.SqlConnection]::new()

C#-Entwickler vermissen oft den using-Befehl. Was auch viele erfahrene PowerShell-Anwender nicht wissen dürften: Es gibt ihn auch bei der PowerShell, er ist lediglich nicht offiziell dokumentiert:

using namespace System.Data.SqlClient $Cn = [SqlConnection]::new()

Das sieht doch gleich sehr viel vertrauter aus. Cn ist der Name einer Variable, die nicht deklariert wird. Per $ wird festgelegt, dass der Wert der Variable angesprochen wird. Die Variable ist trotzdem typisiert. Möchte man den Typ eines Objekts abfragen, geht das am einfachsten über die GetType()-Methode:

$Cn.GetType().FullName

Auch bei der Konstruktorwertübergabe unterscheidet sich die Schreibweise nur in Nuancen von der unter C# üblichen Syntax:

$Cn = New-Object Data.SqlClient.SqlConnection($ConnectionString)

ConnectionString ist eine weitere Variable, die die Verbindungszeichenfolge enthält. Anwender, die die formale Syntax bevorzugen, schreiben stattdessen:

$Cn = New-Object –Typename Data.SqlClient.SqlConnection – ArgumentList $ConnectionString

Wer die praktische New-Methode bevorzugt, schreibt

$Cn = [Data.SqlClient.SqlConnection]::new($ConnectionString)

Ein Tipp, der sowohl für die Konsole als auch für PowerShell ISE und Visual Studio mit PowerShell-Erweiterung gilt: Auch bei der Eingabe von Typnamen stehen Autovervollständigung und Eingabehilfen zur Verfügung (Abb. 1).

monadjemi_powershell_1.tif_fmt1.jpgAbb. 1: Dank der Befehlszeileneditoralternative PSReadline werden die Members der „Path“-Klasse in einer Auswahlliste angeboten

Ein weiterer Tipp, der das Kennenlernen von Klassen erleichtert: Um sich alle Konstruktorvarianten ausgeben zu lassen, gibt man new ohne Klammerpaar ein:

[Data.SqlClient.SqlConnection]::new

Diese Schreibweise kann generell bei jeder Methode angewendet werden, um sich auf diese Weise die vorhandenen Überladungen ausgeben zu lassen.

.NET-Bibliotheken laden

Die wichtigste Frage, die Entwickler beim Kennenlernen der PowerShell interessieren dürfte, ist, wie eine in C# erstellte Assembly-Bibliothek in die PowerShell geladen wird. Dafür sind mit Add-Type und Import-Module gleich zwei Cmdlets zuständig. Import-Module wird immer dann verwendet, wenn die Assembly Cmdlet-Definitionen enthält. In diesem Fall lädt Import-Module die Assembly als binäres Modul. Die enthaltenen Cmdlets können anschließend ausgeführt werden. Ihre Namen liefert das Get-Module-Cmdlet. Für alle anderen Assemblies wird das Add-Type-Cmdlet verwendet. Es bietet über seine Parameter einige Optionen für die Auswahl der Assembly. Soll die Assembly über den Pfad der .dll-Datei geladen werden, folgt er auf den Path-Parameter. Soll die Assembly über ihren Namen geladen werden, folgt er auf den AssemblyName-Parameter. Voraussetzung ist, dass sich die Assembly im GAC befindet. Der folgende Befehl lädt die WinForms-DLL:

Add-Type –Name System.Windows.Forms

Anschließend steht die MessageBox-Klasse mit ihrer statischen Show-Methode zur Verfügung.

using namespace System.Windows.Forms [Messagebox]::Show("Alles klar mit PowerShell!")

Wer die WPF-Variante der MessageBox-Klasse bevorzugt, muss zwei Assemblies laden:

Add-Type -AssemblyName PresentationFrameWork, PresentationCore [System.Windows.Messagebox]::Show("Geht auch!")

In den meisten Fällen genügt für das Laden einer Assembly ihr einfacher Name. Der folgende Befehl funktioniert unter Umständen nicht:

Add-Type –AssemblyName Microsoft.SqlServer.Smo

Der Grund ist, dass es mehrere SMO-Assemblies im GAC geben kann und bei der Angabe des Namens eventuell die falsche Version geladen wird. Eine Alternative ist das Laden über den Pfad der .dll-Datei, doch ist diese Lösung nicht optimal, da ein bestimmter Pfad nicht auf jedem System vorausgesetzt werden kann. Auch der in Foren oft vorgeschlagene Aufruf der LoadWithPartialName()-Methode ist nicht ganz optimal, da diese Methode von Microsoft als veraltet eingestuft wurde:

[Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo.dll")

Lässt sich eine Assembly denn nicht über ihren vollständigen Namen laden? Das geht natürlich, wenngleich der Name zwangsläufig ein wenig lang ist:

Add-Type -Assemblyname "Microsoft.SqlServer.Smo, Version=14.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91"

Ein kleiner Nachteil der bisher vorgestellten Varianten ist, dass eine geladene Assembly gesperrt ist und z. B. nicht gelöscht werden kann. Ist das aus irgendeinem Grund nicht erwünscht, muss die Assembly anders geladen werden:

$AssPfad = Resolve-Path –Path .\Poshtyp.dll $AssBytes = [System.IO.File]::ReadAllBytes($AssPfad) [System.Reflection.Assembly]::Load($AssBytes)

Bliebe noch zu klären, wie sich herausfinden lässt, welche Typen eine Assembly exportiert. Am einfachsten geht es mit dem unscheinbaren PassThru-Parameter beim Add-Type-Cmdlet. Er bewirkt, dass alle Typen als RuntimeType-Objekte ausgegeben werden:

Add-Type -Assemblyname "Microsoft.SqlServer.Smo, Version=14.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" –PassThru

Möchte man von einer bereits geladenen Assembly die in ihr enthaltenen Typen auflisten, wird der Befehl zwangsläufig etwas umfangreicher:

[AppDomain]::CurrentDomain.GetAssemblies() | W...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang