Archiv der Kategorie: Tutorials

Azure VMs konfigurieren per Custom Script Extension

Die Technik der Custom Script Extension gibt es bei Azure schon einige Jahre. Dahinter steckt ein Agent, der ein zuvor festgelegtes Skript auf die VM „anwendet, in dem es in ein Download-Verzeichnis gespeichert und dann ausgeführt wird. Das Skript kann ein PowerShell-Skript sein, es kann aber auch eine Exe-Datei oder etwas Anderes sein. Eine Custom Script Extension ist für spezielle Aktivitäten gedacht, die nach der Bereitstellung einer VM ausgeführt werden sollen. Sie können aber auch zu einem beliebigen Zeitpunkt ausgeführt werden.

Die sparsame Verwendung der Mehrzahlform deutet es dezent an, es kann pro VM nur eine Custom Script Extension geben. Über sie können aber mehrere Skripte zusammengefasst werden, von denen ein Skript automatisch ausgeführt wird.

Die Custom Script Extension ist z.B. für die automatisierte Installation von Anwendungen in einer VM gedacht.

Das soll ein kleines Beispiel veranschaulichen

Ausgangspunkt ist eine Exe-Datei, die in einem Azure Storage-Blob liegt:

$PackageUri = „https://standardspeicher.blob.core.windows.net/packages/SumatraPDF-3.1.2-64-install.exe“

Das ist aber keine Voraussetzung. Grundsätzlich spielt der Aufenthaltsort der Exe-Datei keine Rolle. Irgendwo in der Cloud oder in einem Ftp-Verzeichnis.

Gesucht ist ein PowerShell-Skript, dass diese Exe-Datei herunterlädt und ausführt. Das folgende Skript erledigt das:

Am Ende wird die Exe-Datei zusammen mit dem Schalter /S per Invoke-Expression ausgeführt. Nicht optimal, aber per Start-Process erhielt ich auch mit Admin-Credentials ein „Access Denied“ beim Versuch die Exe-Datei zu starten.

Das Skript selber befindet sich auch in einem Azure Storage-Account:

$FileUri = „https://standardspeicher.blob.core.windows.net/ps1blob/Azure_InstallAppForVM.ps1“

Das Skript, das die Custom Script Extension hinzufügt, ist wie folgt aufgebaut:

Die entscheidende Rolle spielt das Set-AzureRmVMCustomScriptExtension-Cmdlet aus dem AzureRm.Compute-Modul. Es richtet die Custom Sript Extension für die angegebene VM, die dazu ausführen muss, ein.

Der Run-Parameter legt das Skript fest, das ausgeführt werden soll (bei einem einzigen Skript ist er vermutlich nicht erforderlich – ich hatte keine Lust mehr das auszuprobieren;).

Ein

ruft die Custom Script Extension über ihren Namen ab.

In der Praxis müsste man die Installation z.B. über eine CSV-Datei steuern, in der die Namen, Urls usw. der zu installierenden Anwendungen ethalten sind.

Insgesamt ist die Custom Script Extension eine flexible Technik, die, anders als Azure DSC, auch keine zusätzlichen Kosten verursacht.

Azure VM per PowerShell und DSC konfigurieren

*** Rohentwurf – wird in den nächsten Tagen noch einmal überarbeitet ***

DSC ist eine flexible Angelegenheit, das gilt besonders im Zusammenhang mit der Konfiguration einer Azure VM. Per DSC lässt sich eine beliebige Konfiguration auf beliebige VMs anwenden mit minimalem Aufwand, da sich die Azure-Plattform um die „Feinheiten“ kümmert.

Im Folgenden stelle ich ein sehr einfaches Beispiel vor, bei dem lediglich ein Verzeichnis mit einer Datei und einem bestimmten Inhalt angelegt werden. Da der Name des Verzeichnisses und der Inhalt der Datei über Konfigurationsdaten festgelegt werden, wird es auch bei diesem Beispiel etwas interessanter.

Voraussetzung sind:
>Ein Azure-Konto
>Eine Windows VM (theoretisch funktioniert es auch mit einer Linux VM)
>Ein Azure-Automationskonto, das zuvor gegebenenfalls angelegt werden muss

Schritt 1: Die DSC-Konfiguration wird in einer Ps1-Datei lokal gespeichert

Und was ist mit den Konfigurationsdaten? Die kommen etwas später ins Spiel.

Schritt 2: Anlegen einer DSC-Konfiguration im Automations-Konto
Dieser Schritt ist sehr einfach, denn es wird lediglich die Ps1-Datei hochgeladen.

Schritt 3: Die Konfiguration wird in eine Mof-Datei kompiliert und damit ein Knoten angelegt
Dieser Schritt wird bei einfachen Konfiguration direkt im Azure-Portal durchgeführt. Eine Ausnahme liegt vor, wenn Konfigurationsdaten im Spiel sind. In diesem Fall muss die Kompilierung (Stand: Mail 2017) noch per PowerShell-Cmdlets lokal ausgeführt werden.

Das erledigt die folgende Befehlsfolge:

Der Name der VM lautet in diesem Beispiel „TestVM2“.

Die Bereitstellung dauert ein paar Minuten. Ging alles gut, werden am Ende aufgetretene Meldungen ausgegeben. Fehler sollten keine dabei sein, lediglich ein Warnung, die besagt, dass das Microsoft.PowerShell.Managagement-Snapim nicht geladen wurde. Dies ist aber nur eine Warnung, kein Fehler.

Ob das Kompilieren erfolgreich war, erfährt man aus dem Azure-Portal.

Schritt 4: Der Knoten wird einer VM zugeordnet
Im letzten Schritt muss lediglich der Pull Server-Knoten einer VM zugeordnet werden. Dieser Schritt besteht aus weiteren Teilschritten und wird im Portal nicht wirklich intuitiv angeboten:

>Hinzufügen einer Azure VM über +Azure-VM hinzufügen
>Auswahl der VM und Bestätigen mit OK
>Registrieren des Knotens und Bestätigen der Konfigurationsdaten und Bestätigen mit OK
>Erstellen

Ist dies erledigt, dauert es ca. 15 Minuten bis die Konfiguration angewendet wird.

Azure VM per PowerShell und CustomScript Extension vorkonfigurieren

*** noch im Rohmodus – der Beitrag wird in Kürze noch einmal überarbeitet ***

Möchte man man eine Azure VM skriptgesteuert vorbereiten, z.B. bestimmte Anwendungen vorabinstallieren, gibt es dafür grundsätzlich zwei Alternativen, die keine Tools anderer Hersteller oder Microsoft SCCM voraussetzen:

>Azure DSC
>Eine Extension in Gestalt der CustomScriptExtension, die entweder beim Anlegen der VM oder nachträglich als Erweiterung ausgewählt wird

Beide Ansätzen besitzen, wie könnte es anders sein, ihre kleineren Vor- und Nachteile. Variante 1, die ich in seinem separaten Blog-Eintrag in naher Zukunft vorstellen werde, setzt etwas mehr Vorbereitungsaufwand voraus und kann aktuell nicht vollständig im Portal umgesetzt werden, ist aber flexibler, da sich eine beliebige Anzahl an VMs damit konfigurieren lassen. Variante 2 ist einfach, da lediglich ein vorhadenes PowerSHell-Skript geladen wird, dafür aber nicht so flexibel (ich bin mir im Moment nicht sicher, ob die Erweiterung per Azure-Cmdlets einer beliebigen Anzahl an VMs zugeordnet werden kann. Generell empfehle ich DSC, aber mehr nur ein paar VMs vorkonfigurieren möchte, kommt mit Variante 2 schneller zum Ziel.

Schritt 1: Auswahl der Erweiterungen für die VM

Im ersten Schritt meldet man sich am Azure-Portal aus, selektiert die bereits vorhandene VM und danach Erweiterungen.

Auswahl der Erweiterungen für eine VM

Schritt 2: Hinzufügen der CustomScriptExtension

Sollte die CustomScriptExtension noch nicht in der Liste der Erweiterungen enthalten sein, muss sie über das +-Zeichen hinzugefügt werden. Dabei wird ein vorhandenes Ps1-Skript hochgeladen. Theoretisch sollte es auch aus einem Storage Account geladen werden können.

Anlegen einer CustomScript Extension


Schritt 3: Anwenden der CustomScriptExtension
Wurde die CustomScriptExtension eingerichtet, erscheint sie in der Liste der Erweiterungen.

Die CustomScriptExtension wurde bereitgestellt

DSC in der Praxis – Anwendungen installieren über die Ressourcen Package und WindowsProcess

DSC lässt sich für viele Konfigurationsaufgaben einsetzen, z. B. auch für die Installation von Anwendungen. Warum nicht? Die Idee ist, dass man eine Liste von Exe- und Msi-Dateien zusammen mit Argumenten zusammenstellt, die auf einer Reihe von Nodes installiert werden sollen. Man schießt die DSC-Konfiguration per Start-DSCConfiguration ab („Make it so“), DSC verteilt diese auf die einzelnen Nodes und der LCM auf jedem Node arbeitet die Liste ab und installiert jede Anwendung durch Ausführen der Exe-Datei. Hört sich in der Theorie einfach an, die Umsetzung in der Praxis ist auch nicht allzu kompliziert, sieht man von den üblichen Stolpersteinen ab.

Einen guten Überblick für den Einstieg gibt ein Artikel vom PowerShell-Experte Adam Betram: https://4sysops.com/archives/installing-software-with-powershell-dsc. Er beschreibt, wie sich per DSC und der Package Ressource eine Anwendung (7-Zip) installieren lässt. In seinem Beispiel befindet sich die Msi-Datei aber in einem lokalen Verzeichnis. Die Frage ist, wie kommt sie dort hin? Entweder durch Kopieren aus einer Freigabe, die z.B. für ein Azure Storage-Blob angelegt wurde. In diesem Fall kommt aber die Berechtigungsproblematik ins Spiel und das Kennwort muss per Zertfikat verschlüsselt werden. Einfacher und unkomplizierter geht es per direkten Download der Datei. Da es dafür aber offenbar keine Ressource gibt, habe ich solche selber gebastelt.

Meine Lösung soll insgesamt etwas flexibler sein, in dem mehrere Apps installiert werden, deren Namen in den externen Konfigurationsdaten abgelegt sind. Außerdem sollen sich die Exe-Dateien in der Cloud befinden, z. B. in einem Azure Storage Account.

Das folgende Listing zeigt die gesamte DSC-Konfiguration, allerdings ohne die Konfigurationsdaten.

Die Konfigurationsdaten habe ich in einer separaten Psd1-Datei untergebracht.

Ausgeführt wird die Konfiguration per Start-DSCConfiguration:

Ich habe mich in dem Beispiel auf Exe-Installationsprogramme beschränkt, damit es übersichtlich bleibt. Für Msi-Pakete ist die Package-Ressource die etwas bessere Wahl.

Den Download der Exe-Dateien aus meinem Azure Storage-Blob übernimmt eine DSC-Ressource, die ich dafür geschrieben habe. Sie besteht aus zwei Dateien:

>xDownloadFileResource.psd1
>xDownloadFileResource.psm1

Die Psd1-Datei ist wie folgt aufgebaut:

Die Logik der Ressource ist in der Psm1-Datei enthalten:

Leg unter C:\Program Files\WindowsPowerShell\Modules ein Verzeichnis „xDownloadFileResource“ und darin ein Unterverzeichnis „1.0“ an und leg die Psd1- und die Psm1-Datei in diesem Verzeichnis ab.

Grafische Oberflächen für PowerShell-Skripte – ein WPF-Template

Ein PowerShell-Skript mit einer Benutzeroberflächen ausstatten ist grundsätzlich nicht schwer – die größte Herausforderung für einen Admin, der kein Entwickler ist, ist es, das Prinzip zu verstehen und einen Einstieg zu finden. Es gibt natürlich sehr viel Material im Web, unzählige Tutorials und Beispiele. Aber auch die Fülle des Angebots macht die Übersichtlichkeit nicht unbedingt besser, da viele Beispiele bereits einige Jahre alt und teilweise auch ein wenig umständlich sind.

Ich bevorzuge in der Regel WPF, da mir der Umstand, dass das Fenster in XML, genauer gesagt in XAML, definiert wird, etwas besser gefällt. Auch wenn weder Eingabehilfen noch keinen Designer gibt (sieht man von Visual Studio ab), ist die Umsetzung etwas einfacher als bei Windows Forms, wenngleich ich seit vielen Jahren mit WPF programmierer, so dass mir die ganze Syntax sehr vertraut ist (man muss lediglich daran denken, dass es auf die Groß-/Kleinschreibung ankommt).

Um nicht jedes Mal bei 0 beginnen und sich von Irgendwo her im Web ein XAML-Grundgerüst besorgen zu müssen, stelle ich im Folgenden ein Grundgerüst für ein PowerShell-Skript vor, das ein Fenster mit einem Label, einer Textbox und einem Button anzeigt. Nach einem Klick auf den Button wird der Inhalt der Textbox in einer Messagebox angezeigt und das Fenster wird geschlossen.

Das Template ist als Ausgangspunkt für eigene Dialogfelder sehr gut geeignet. Das erforderliche Know-how zu WPF findet man an vielen Stellen im Web, z. B. http://wpftutorial.net und http://www.wpf-tutorial.com. Auf beiden Seiten findet man viele kleine XAML-Beispiele für die Standard-Controls, die man 1:1 in die XAML-Definition des PowerShell-Skripts übernehmen kann.

Auch wenn das natürlich eine Menge Text ist, ist das Ganze am Ende doch eine relativ überschaubare Anlegenheit.

Die Farbwahl es zugegeben etwas gewagt, aber es sollte kein Problem sein, dass sich jeder passendere Farben ausdenkt.

Tipp: Wer hauptsächlich mit der ISE arbeitet, kann den gesamten Text per New-ISESnippet-Cmdlet als Textausschnitt ablegen und ihn dadurch als Ausschnitt verfügbar machen. In Visual Studio Code ist es etwas mehr Arbeit. Alles Weitere findet ihr unter der folgenden Adresse:

https://code.visualstudio.com/docs/editor/userdefinedsnippets

Abbildung: Das WPF Dialogbox-Template in Aktion

Runspace-Debugging in 5 Minuten

Mit der Version 5.0 der PowerShell wurde das Debuggen eines Runspaces ermöglicht. In der Praxis wird man diese Technik vermutlich eher selten benötigen. Wer sich dennoch aus welchen Gründen auch immer für das Thema interessieren sollte, der Umgang mit Runspaces ist relativ einfach (ein Runspace ist der „Bereich“, in dem ein PowerShell-Befehl ausgeführt wird – jede Sitzung/Session enthält mindestens einen Runspace – es lassen sich beliebig viele weitere Runspaces anlegen, in denen beliebige Befehle ausgeführt werden – interessant ist diese Option immer dann, wenn die Befehle asynchron ausgeführt werden).

Das folgende kleine Beispiel führt einen Scriptblock in einem weiteren Runspace aus. Per Wait-Debugger wird der Debuggmodus aktiviert. Anschließend kann man sich per Debug-Runspace mti dem Runspace verbinden und die Befehle des Scriptblocks debuggen. Die Id des Runspace erhält man per Get-Runspace-Cmdlet.

Im nächsten Schritt muss man sich per Get-Runspace die Id des neuen Runspace holen. Danach kann man diesen Runspace mit dem Debug-Runspace-Cmdlet debuggen und die einzelnen Befehle über die Debug-Kommandos (s, v, l usw.) ausführen.

Runspaces debuggen

Seit der PowerShell 5.0 ist es möglich, Skripts zu debuggen, die in einem zusätzlich angelegten Runspace ausführen. Doch warum sollte man das tun? Zum Beispiel, weil ein Skript in einem asynchron, also per BeginInvoke ausgeführten Runspace parallel zu anderen Runspaces ausführen kann und sich damit eine effektive Form des „Multitaskings“ bei der Ausführung eines Skripts ergibt.

Runspaces gab es bei der PowerShell von Anfang an, denn ein Runspace spielt eine zentrale Rolle für die Ausführung von Befehlen. Jeder Befehl wird in einem Runspace ausgeführt. In der Regel ist es jener Default-Namespace, der automatisch mit dem Start der Host-Anwwendung angelegt wird. Über ein [RunspaceFactory]::CreateRunspace() werden weitere Runspaces angelegt. Anschließend wird eine Pipeline [PowerShell]::Create() angelegt, mit Befehlen oder einem Skript gefüllt und mit einem Runspace verbunden.

Erst seit der Version 5.0 ist es möglich, ein Skript in einem separaten Runspace zu debuggen. Im Mittelpunkt stehen die Cmdlets Enter-PSHostProcess, Get-Runspace und Debug-Runspace.

Das folgende Beispiel geht von einem kleinen Skript aus mit dem Namen Skript.ps1. Sein Inhalt spielt keine Rolle. Wichtig ist nur, dass es in einem separaten Runspace (synchron) ausgeführt wird. Dies geschieht in einem Skript, das in der PowerShell ISE ausgeführt wird.

Damit das Skript bei seiner Ausführung nicht einfach durchrauscht, wird in der Zeile mit dem Invoke-Aufruf ein Haltepunkt gesetzt und das Skript gestartet. Dadurch wird ein Runspace angelegt, ein Skript in den Runspace geladen, aber noch nicht ausgeführt.

Ein Skript wird in einem weiteren Runspace ausgeführt

Im zweiten Schritt wird die PowerShell-Konsole gestartet und der ISE-Prozess per Enter-PSHostProcess betreten:

Der Prompt der Konsole ändert sich zu [Prozess: 1234], wobei 1234 für die Prozess-ID steht.

Ein Get-Runspace listet alle Runspaces im ISE-Prozess auf. Wichtig ist der „RunspaceX“-Runspace und seine Id. Seine State-Eigenschaft muss den Wert „Opened“ und seine Avaivalibilty-Eigenschaft den Wert „Available“ besitzen.

Im Konsolenhost werden die Runspaces des ISE-Prozesses aufgelistet

Dieser Runspace wird per Debug-Runspace-Cmdlet mit der Id des Runspaces debuggt:

Neben dem Standard-Runspace steht auch der Runspace „RunspaceX“ zur Auswahl

Damit wird der Runspace in den Debug-Modus versetzt. Damit auch etwas passiert, muss der Invoke-Aufruf in der ISE ausgeführt werden. Damit steht der Debugger in der PowerShell-Konsole zur Verfügung und das Skript kann bei der Ausführung in seinem Runspace debuggt werden. Die einzige Einschränkung besteht darin, dass die Ausgaben gesammelt und erst am Ende zusammen ausgegeben werden.

Der Debugger wird über [Strg]+[C], der Host-Prozess über exit verlassen.

Der Runspace „RunspaceX“ wird debuggt

Die Möglichkeit jeden Runspace debuggen zu können ist eine wichtige Neuerung bei der Version 5.0, auch wenn sie nur weniger Anwender verwenden werden.

Jetzt muss ich noch herausbekommen wie ich ein Skript debuggen kann, das in einer von mir umgesetzten Host-Anwendung in einem Remote-Runspace ausgeführt wird.

Flexible Typenkonvertierung dank PSTypeConverter – ein kleines Beispiel erklärt wie die Typenkonvertierung bei der PowerShell funktioniert

Die in diesem Blog-Eintrag vorgestellte Technik ist zwar unabhängig von einer bestimmten PowerShell-Version – für die Umsetzung des Beispiels wird die Version 3.0 vorausgesetzt, wenngleich es mit der Version 2.0 theoretisch auch funktionieren sollte

Eine der Stärken der PowerShell ist die flexible Typenkonvertierung und -erweitererung. Dazu gehört vor allem die Fähigkeit, durch Typenkonvertierung an Objekte weitere Members anzuhängen. Dies geschieht immer so, dass es der Anwender nicht mitbekommt, sofern er oder sie überhaupt mit dem etwas anspruchsvolleren Konzept der Typen vertraut ist und/oder sich überhaupt dafür interessiert.

Wer z.B. ein Get-Process-Cmdlet ausführt, erhält zwar Objekte vom Typ System.Diagnostics.Process zurück, doch in „Wirklichkeit“ basieren die Objekte auf einem dynamischen Typ, der aus dem PowerShell-Typensystem hervorgegangen ist und um zahlreiche Members erweitert wurde. Das „wahre“ Objekt stellt jedes PowerShell-Objekt über seine PsObject-Eigenschaft zur Verfügung. Die Grundlage für die Typenerweiterung beim Process-Objekt ist die Datei Types.ps1xml im PowerShell-Installationsverzeichnis, in der die nachträglich angehängten Members für den Typ System.Diagnostics.Process definiert werden. Ein Beispiel: Die Eigenschaft Ws wird als AliasProperty nachträglich angehängt, damit die Eigenschaft mit dem etwas sperrigen Namen Workingset64 (bzw. Workingset) über einen kurzen Namen abgesprochen werden kann.

Die Typenkonvertierung eines Objekts kann auch dynamisch erfolgen, also ohne, dass die zusätzlich hinzugefügten Members bereits festgelegt wurden. Ein Beispiel ist der Typ-Alias [Xml], der Text in ein System.Xml.XmlDocument-Objekt konvertiert, sofern es sich um wohlgeformtes XML handelt, und dabei die Elemente und Attribute als Eigenschaften anhängt.

Durch den Xml Typalias findet eine Typenkonvertierung statt. Aus Text vom Typ String wird ein XmlDocument-Objekt, das auf dem gleichnamigen Typ basiert. Die Grundlage für die Typenkonvertierung ist eine Klasse, die sich von der PSTypeConverter-Klasse im System.Management.Automation ableitet. Benötigt man einen eigenen Typenkonvertierer, muss man „nur“ eine .Net-Assembly erstellen, in der diese Klasse abgeleitet und die Typenkovnertierung in der überschriebenen ConvertFrom-Methode durchgeführt wird.

Beispiele für den praktischen Einsatz von PsTypeConverter sind etwas schwer zu finden. Ein sehr guter Blog-Eintrag von Glenn Sizemore macht das Prinzip der Umsetzung deutlich und hat mich dazu motiviert, das Thema ebenfalls endlich einmal aufzugreifen (eigentlich steht das seit jenem PowerShell Deep Dive aus, der 2010 in Frankfurt a.M. stattfand):

http://practical-admin.com/blog/powershell-custom-types-type-conversion-and-ets/

Dynamische Typenkonvertierung an einem Beispiel

Das folgende Beispiel zeigt die Umsetzung eines Typenkonverters, der aus einer Ini-Datei ein Objekt macht, über das die Sektionen der Datei und die Einträge innerhalb einer Sektion zur Verfügung gestellt werden. Das Objekt ist ebenfalls Teil des Projekts. Voraussetzung für die Umsetzung der kleinen Übung sind Visual Studio (es kann auch eine ältere Version sein). Ich empfehle die aktuelle Version Visual Studio 2015 Community Edition. Auch wenn es im Folgenden um richtige Programmierung in der Programmiersprache C+ geht, muss man kein Profi-Entwickler sein, um die Umsetzung selber durchführen zu können. Theoretisch könnte man das kleine Projekt auch komplett in der PowerShell ISE umsetzen, doch müsste man dann auf den wertvollen C#-Debugger verzichten.

Schritt 1: Erstellen der Typenkonverter-Assembly

Im ersten Schritt wird mit Visual Studio eine .Net-Assembly erstellt, in der eine Klasse sich von PSTypeConverter ableitet. Voraussetzung ist, dass ein Verweis auf System.Management.Automation eingebunden wurde was am einfachsten über den Paket-Manager geschieht. Dieser wird über das Tools-Menü und die Einträge Nuget Package Manager und Package Manager Console geöffnet. Ein Install-Package System.Management.Automation fügt die PowerShell-Assembly hinzu.

Die vorhandene Klassendatei Class1.cs wird in PSIniTypeConverter.cs umbenannt und der folgende Befehlscode eingefügt:

Die Klasse enthält relativ wenige Befehle, da die eigentliche Konvertierung in der Klasse IniObject erledigt wird. Die wichtigste Methode ist ConvertFrom, denn hier wird die Konvertierung angestoßen. Die Abfrage auf den Typ des Parameters destinationType wäre in diesem Beispiel nicht erforderlich. Ich habe sie eingebaut damit deutlich wird, dass sich auch mehrere Typen konvertieren lassen.

Schritt 2: Ein neuer Typ wird definiert

Im nächsten Schritt wird eine weitere Klasse eingefügt mit dem Namen IniObject.cs. Diese Klasse definiert den Typen, in den ein String konvertiert werden soll. Die Klasse besitzt den folgenden Inhalt.

Damit ist die Programmierung bereits abgeschlossen. Das Projekt sollte ich zum jetzigen Zeitpunkt ohne Fehler erstellen lassen. Das Ergebnis ist eine Assembly-Datei mit dem Namen PSIniTypeConverter.dll.

Schritt 3: Die Assembly soll in einem PowerShell-Skript geladen werden

Um die PowerShell-Typenkonvertierung per F5-Taste im Rahmen von Visual Studio testen zu können, wird in den Projekteigenschaften im Register Debug dafür gesorgt, dass mit dem Projektstart Powershell.exe gestartet wird. Dazu muss unter „Start external programm“ der vollständige Pfad von Powershell.exe eingetragen werden.

In den Projekteigenschaften wird eingestellt, dass mit dem Projektstart die PowerShell startet

In den Projekteigenschaften wird eingestellt, dass mit dem Projektstart die PowerShell startet

über die „Command line arguments“ wird ein kleines PowerShell-Skript mit dem Namen „Init.ps1“ gestartet, das dem Projekt hinzuzgefügt wurde (dank der genialen PowerShell Tools for Visual Studio von Doug Finke gibt es eine Vorlage für Ps1-Dateien mit Intellisense), und über das u.a. die Assembly geladen wird:

Per „-noexit“ wird erreicht, dass das Konsolenfenster geöffnet bleibt. Da in diesem Fall ein Profilskript ausgeführt werden soll, fehlt der Schalter „-Noprofile“ (in der Regel ist es sinnvoll, dass keine Profilskripte gestartet werden).

Das PowerShell-Skript ist wie folgt aufgebaut:

Zuerst wird die Assemblydatei und damit der Typenkonverter per Import-Module geladen. Anschließend werden der Typ, der eine Ini-Datei repräsentieren soll, und der Typenkonverter über eine Typendefinitionsdatei bekannt gemacht (mehr dazu gleich). Außerdem wird ein Typenalias mit dem Namen „IniObject“ definiert, damit bei der Typenkonvertierung nicht jedes Mal der Namespace vorangestellt werden muss. Außerdem wird für einen ersten Test eine Variable IniText vordefiniert, die den Inhalt einer Ini-Datei darstellen soll.

Schritt 4: Der Typenkonvertierer wird bekannt gemacht


Damit die Powershell sowohl den neuen Typ PSIniTypeConverter.IniObject als auch den Typenkonvertierer PSIniTypeConverter.IniTypeConverter kennt, werden diese über eine Typendefinitionsdatei bekannt gemacht. Diese heißt IniObject.Types.ps1xml, wird im Rahmen des Skripts über das Update-TypeData-Cmdlet geladen. Die Typendefinitionsdatei ist wie folgt aufgebaut:

Wird das Projekt jetzt per F5 gestartet, wird die PowerShell-Konsole gestartet und das Skript wird ausgeführt. Außerdem liegt bereits eine Variable IniText vor.

Das Visual Studio-Projekt wurde erfolgreich erstellt

Das Visual Studio-Projekt wurde erfolgreich erstellt

Der folgende Aufruf sollte eine Typenkonvertierung von String nach IniObject durchführen.

Das Beispielprojekt gibt es unter der folgenden Adresse: psinitypeconverter.

Zusammenfassung

PowerShell-Typenkonvertierer sind eine innovative Idee, die die Übernahme von Textdaten, die einem beliebigen Format vorliegen können, vereinfachen. Mit einem einzigen Typenalias kann eine komplette Textdatei in PowerShell-Objekte konvertiert werden. Ohne diese Option müsste man die Daten eventuell Zeile für Zeile einlesen, zerlegen und daraus Objekte machen. Ob sich der Aufwand für eigene Typenkonvertierer wirklich lohnt sei einmal dahin gestellt. Da es für wichtige Datentypen, etwa IP-Adressen, bereits Konvertierer gibt, dürfte sich die Anzahl der echten Anwendungsfälle in Grenzen halten.

Das kleine Beispiel, das ich in diesem Blogpost vorgestellt habe, soll in erster Linie dazu dienen ein wichtiges Merkmnal der PowerShell, das sie seit der Version 1.0 besitzt, die dynamische Typenkonvertierung, besser nachvollziehen zu können.

Rekursive Functions in PowerShell

Eine rekursive Function ist eine Function, die sich selber aufruf. Warum sollte sie das tun? Damit ein Befehlsfolge eine beliebige Anzahl oft wiederholt werden kann. Im Unterschied zu seiner Schleife wiederholt sich die Befehlsfolge selber. Eine praktische Anwendung ist eine Verzeichnissuche – nicht immer gibt es einen Recurse-Parameter oder ein Cmdlet wie Get-ADuser, das eigentlich Find-ADUser heißen müsste, da es eine Suche in der gesamten (Verbund-) Verzeichnisstruktur durchführt.

Rekursive Functions sind grundsätzlich sehr einfach – das Problem liegt in erster Linie darin, sich eine Abbruchbedingung zu überlegen. Außerdem ist das Konzept vielen zu abstrakt, so dass sie lieber die „iterative“ Variante in Gestalt einer regulären Schleife bevorzugen.

Ein einfaches Beispiel zur Einstimmung ist die Berechnung der Fakultät einer ganzen Zahl, da hier lediglich bei der Zahl 1 beginnend eine Zahl mit der um eins größeren Zahl multipliziert wird.

Aufgerufen wird die Function wie folgt:

Etwas kniffliger ist die Berechnung einer Fibonacci-Zahlenfolge, bei der eine folgende Zahl aus der Summe ihrer beiden Vorgängerzahlen gebildet wird. Also die Reihenfolge 0 1 1 2 3 5 8 13 21 34 55 usw. entsteht. Offiziell lautet die Berechnungsformel fib(n-1) + fib(n-2), wobei die Zahl n >= 2 sein muss. Mit dieser Formel ist die Umsetzung relativ einfach im Gegensatz zum Hochzählen von n was mir auch nach längerem Herumprobieren nicht gelungen ist.

Hier ist die Umsetzung der „einfachen“ Variante mit Abbruchbedingung n = 0 und n = 1.

Bei z 0 und bei z = 1 bricht die Function jeweils mit den Werten 0 und 1 ab, die auf den Rückgabewert aufaddiert werden. Ungewohnt und für Jemanden, der mit der typischen „Programmierer-Logik“ noch nicht viel zu tun hat, auch reichlich verwirrend dürfte der Umstand sein, dass für den Rückgabewert keine Variable im Spiel ist und dieser einfach aus der Function selber entsteht. Der break-Befehl verlässt nicht die Function, sondern nur den switch-Befehl. Da in diesem Fall aber die Function nicht erneut aufgerufen wird, entspricht dies indirekt einem Abbruch der Function. Einfach und genial. Ein return-Befehl ist offiziell nicht erforderlich, wäre aber sinnvoll, damit die Function etwas besser nachvollziehbar wird.

Aufgerufen wird die Function wie folgt:

Es ist faszienierend wie lange die Berechnung bei etwas größeren Zahlen dauert. Bereits mit z = 100 dauert die Berechnung „ewig“ – und das auf einer halbwegs modernen 4-Kerne-CPU.

Ereignisse in einer Remote-Session weiterleiten

Die PowerShell bietet bereits seit der Version 2.0 einen leistungsfähigen Ereignismechanismus. Zum einen lassen sich relativ Events von Objekten auswerten, die auf vordefinierten Klassen wie System.Timer oder System.IO.FileWatcher basieren. Interessant wird es beim Thema Event-Weiterleitung („Forwarding“) aus einer Remoting-Session. Damit kann jedes im Rahmen der Remoting-Session entstandene Ereignis lokal ausgewertet werden. Zuständig ist der unscheinbare Forward-Parameter beim Register-ObjectEvent-Cmdlet. Da das Thema in der PowerShell-Hilfe etwas zu kurz kommt (das einzige Beispiel verwendet WMI-Events), habe ich im Folgenden ein kleines Beispiel zusammengestellt. Dabei wird per PS-Remoting ein FileSystemWatcher auf einem Remote-Computer eingerichtet. Wird ein Ereignis ausgelöst, z.B. weil in dem überwachten Verzeichnis eine Datei angelegt wurde, wird das Event an den lokalen Computer weitergeleitet und führt dort zur Ausgabe einer Meldung, in der u.a. der Name des betroffenen Verzeichnisses enthalten ist.

Im ersten Schritt wird die Remoting-Session angelegt. Die Befehlsfolge richtet den FileSystemWatcher für das Verzeichnis C:\Temp ein. Anschließend werden per Register-ObjectEvent-Cmdlet die Ereignisse „Created“ und „Deleted“ registriert. Es wird keine Aktion angegeben, da diese lokal ausgeführt werden soll. Stattdessen erhält jede Registrierung lediglich einen Source Identifier. Der wichtigste Parameter ist Forward. Er bewirkt, dass ein Ereignis an den lokalen Computer weitergereicht wird. Der Parameter MessageData ist optional. Über ihn lassen sich beliebige Daten mit dem Event übergeben. In diesem Beispiel ist es lediglich der Name des Remote-Computers.

Im zweiten Schritt werden die beiden Ereignisse mit den Source Identifiern „TempWatch1“ und „TempWatch2“ auch lokal registriert. Da es in diesem Fall kein Objekt mit einem Event gibt, wird das Ereignis nicht per Register-ObjectEvent, sondern per Register-EngineEvent registriert, da es hier keinen InputObject-Parameter gibt. Per Action-Parameter wird der Scriptblock festgelegt, der lokal ausgeführt wird, wenn aus der Remote-Session ein Ereignis gemeldet wird.

Im nächsten Schritt wird der Scriptblock definiert, der immer dann ausgeführt werden soll, wenn aus der Remote-Session ein Ereignis gemeldet wurde.

Im Unterschied zu einer rein lokalen Ereignisverarbeitung muss bei der EventArgs-Variablen die Eigenschaft SerializedRemoteEventArgs „zwischenschaltet werden, um an die Ereignisargumente heranzukommen. Bei der Variablen Event ist das nicht erforderlich.

Zum Schluss wird die PS-Remoting-Session gestartet. Da das Ereignis zu einem späteren Zeitpunkt eintritt, z.B. wenn irgendwann eine Datei in dem überwachten Verzeichnis angelegt oder gelöscht wird, wird die Remoting-Session explizit per New-PSSession angelegt und nicht implizit und der Scriptblock für das Einrichten des FileSystemWatchers wird per Invoke-Command gestartet.

Wird jetzt auf dem Remote-Computer im überwachten Verzeichnis eine Datei angelegt oder gelöscht, wird das resultierende Ereignis an den lokalen Computer weitergeleitet und führt zur Ausgabe einer Meldung, da der Scriptblock, der über die Variable SBAction festgelegt wird, ausgeführt wird.

Hier noch einmal das komplette Beispiel auf einen Blick.