Convert a hashtable into its string representation

Recently I had to find a technique to update a module manitest file (psd1). Using the Update-ModuleManifest cmdlet is the official way to achieve this. Although using this command is really simple it completely overwrites the current psd1 file and inserts all the „junk“ that I want to keep out from that file.

Another approach is to load the psd1 file with the Import-LocalizedData cmdlet which results to a hashtable.

Updating single entries is easy:

But how do you turn the the whole hashtable with all its keys and values into a string again? Not so easy. Luckily I found an old blog entry by June Blender (when she was still working for Sapien Technologies):

https://www.sapien.com/blog/2014/10/21/a-better-tostring-method-for-hash-tables/

But June restricted her function to simple hashtables with no values that are hashtables themselves.

So I have extend her function so that it works also with hashtable that contains other hashtable values which is necessary to convert a hashtable that represents a psd1 file. Ny function calls itself recursively which which is not hard to accomplish in PowerShell. The difficult part can be to find an exit condition or threat the last value differently. For this reason I am not using foreach but the for command with an index variable to enumerate all the values. For the last value I add „}“ to complete the hashtable string.

As PowerShell expert Kirk Munro suggested in his comments to Junes blog post converting the hashtable to json might be a simpler solution. I have not tried this yet although it probably could have saved me a lot of typing and trying. But hey, why choosing a simple solution if you can spend a couple of hours fiddling with Powershell script code;)

So this should work. I have tested the function with a specific psd1 file. If not please leave a comment or send me a mail.

PowerShell Core 6.1 mit interessanten Neuerungen

Am Anfang fand ich die Idee gut, dann war ich etwas skeptisch, inzwischen bin aber überzeugt, dass PowerShell Core die Zukunft der PowerShell ist. Seit einigen Monaten arbeite ich nahezu ausschließlich mit PowerShell Core was mir insofern leicht fällt, da ich kein Administrator bin, der beruflich die PowerSHell einsetzt und damit auf Module angewwiesen ist, die sich unter PowerShell Code nicht nutzen lassen, wenngleich dies nur noch auf wenige Module zutreffen dürfte.

Im Windows-Umfeld bringt PowerShell Core nur wenige Vorteile. Im Gegenteil, bedingt durch den Umstand, dass „Core“ bedeutet, dass PowerShell Code auf .NET Core und nicht .NET Framework basiert und und in .NET Core 2.0 einige APIs fehlten, die von PowerShell-Commands vorausgesetzt werden, waren bestimmte Aufrufe, etwa WMI, einfach nicht möglich. Unter Windows ist das natürlich kein Thema, denn man bleibt ganz einfach bei der Windows PowerShell.

Microsoft wäre nicht Microsoft, wenn es seine Anmwender/Kunden nicht zu irgendetwas „zwingen“ würde. Wir sollen alle in die Cloud gehen, also gibt es bei Windows Server mit jeder Version weniger Innovationen (wenngleich auch Windows Server 2019 interessante Neuerungen enthält). Da in Zukunft nur noch PowerShell Core weiterentwickelt wird, sollen Administratoren in Zukunft eher mit diseser Version arbeiten. Da bei .NET Core in der Vergangenheit wichtige APis fehlten, die es nur unter Windows gibt, wurden diese in Gestalt der „Compatibilty Packs“ inzwischen nachgereicht. Damit ist auch bei PowerShell Core z.B. WMI wieder möglich. Natürlich nur unter Windows.

Trotzdem fallen mir nur wenige Gründe ein, warum ein Administrator unter Windows anstelle der Windows PowerShell PowerShell Core verwenden sollte.

Nachdem im Januar 2018 die Version 6.0 freigegeben wurde, erschien im September 2018, also etwa mehr als ein halbes Jahr später, die Version 6.1.

Sie wird im Blog des PowerShell-Teams ausführlich vorgestellt:

https://blogs.msdn.microsoft.com/powershell/2018/09/13/announcing-powershell-core-6-1/

und vorallem

https://docs.microsoft.com/en-us/powershell/scripting/whats-new/what-s-new-in-powershell-core-61?view=powershell-6

Der Vollständigkeit halber habe ich die wichtigsten Neuerungen noch einmal stichwortartig zusammengefasst:

>Umstellung auf .NET Core 2.1
>Performance-Verbesserungen, z.B. bei Import-CSV
>Über die Eigenschaft PSEdition lässt sich bei Modulen abfragen, ob wie für Core, Desktop oder beide in Frage kommen. Diese Information muss aber in die Manifestdatei eingetragen werden, damit sie abgefragt werden kann
>Es gibt Cmdlets für den Umgang mit Markdown (sehr praktisch)
>Das Invoke-WebRequest-Cmdlet wurde verbessert
>Das Enable-PSSession-Cmdlet wurde verbessert
>Ein cd – führt zum letzten Verzeichnis zurück
>Ein cd– führt in das $Home-Verzeichnnis
>Für Update-Help muss die PowerShell nicht mehr als Administrator gestartet werden
>Ein [PSCustomObject]-Objekt besitzt jetzt eine Length- und Count-Eigenschaft

Bemerkenswert und im Grunde naheliegend ist, dass einige der Verbesserungen aus der Community stammen.

Tipps für Zwischendurch: Fenster auflisten mit der EnumWindows-API-Funktion

Vor kurzem fand ich nach der Suche nach einer Lösung auf Stackoverflow einen Link auf ein PowerShell-Skript, das ich vor vielen Jahren erstellt hatte. Leider hatte der Autor meinen Namen nicht erwähnt, sonst wäre ich bei Stackoverflow zu unverhofftem Ruhm gekommen. Außerdem verwies der Autor des Beitrags auf meinen alten Blog powershell-knowhow.de, den es schon länger nicht mehr gibt. Da ich das Skript von damals auf diversen Festplatten nicht wiederfinden konnte, musste ich mich noch einmal an die Arbeit machen. Wie sooft war das Ergebnis besser als das Original, da es deutlich kürzer geworden ist. Das ist generell ein Erfahrungswert, den ich aus über 10 Jahren PowerShell ziehen kann, und der vermutlich universell gilt. Um auf die kürzeste Lösung zu kommen, muss man sich in den Bereich, in dem die Lösung gesucht wird, bereits ziemlich gut auskennen.

Wie dem auch sei, die folgende Function Get-WindowData gibt Fensterhandle und Text zu jedem Top-Window-Fenster aus (und das sind bereits erstaunlich viele). Theoretisch lassen sich durch eine Art rekursiven Aufruf natürlich auch die Kindfenster eines Fensters auflisten, aber diese Mühe habe ich mir dann doch nicht gemacht.

Interesant und lehrreich an dem Beispiel ist die Leichtigkeit, mit der sich ein „Callback“, also eine Adresse auf eine Funktion, die von der API-Funktion pro Fenster aufgerufen wird, von PowerShell an eine Win32-API-Funktion übergeben lässt. Merkregel: Überall, wo ein Delegate erwartet wird, darf auch ein Scriptblock übergeben werden.

Kleine Tipps für Zwischendurch: Kopfzeile und restliche Zeilen einer Textdatei mit einem Befehl getrennt einlesen

Bei einer Textdatei sollen Kopfzeile und die restlichen Zeilen getrennt eingelesen werden. Aber mit einem einzigen Befehl.

Mit folgender Konstruktion ist mir das gelungen.

Zunächst ein paar Testdaten als Here-String:

Dann der Befehl, der die Textzeilen in eine erste Zeile und die restlichen Zeilen aufsplittet und das Ergebnis zwei Variablen zuweist:

Warum verwende ich kein ForEach-Object? In erster Linie weil ich es etwas „übertrieben“ finde, wenn ein Wiederholungsbefehl für einen einzigen Verarbeitungschritt verwendet werden muss. Auch wollte ich keine Variable verwenden. Dann wäre auch Folgendes möglich:

Die Variable Zeilen wird gebraucht, da ich nur so an die Länge des Array beim Zugriff auf dasselbige herankomme.

Es soll direkt nach dem Schema Textzeilen | Verarbeitung => Ergebnis funktionieren. Ohne Zwischenschritte und Variablen.

Was nicht jeder wissen dürfte, auch eim harmloser Scriptblock kann in einen Begin-, Process- und End-Block unterteilt werden. Möchte man in einen Scriptblock etwas pipen, geht das nur mit eimem vorangestellten &, das den Scriptblock ausführt.

Tja, was soll man davon halten? Es funktioniert, ok. Ist das Ganze intuitiv nachvollziehbar? Eher weniger. Auf der anderen Seite, im Vergleich zu einem Befehl in Bash oder Perl, der zum selben Resultat führt, ist die PowerShell-Variante regelrecht leicht verdaulich, denn es kommen weder reguläre Ausdrücke noch kryptische Variablen wie %%S vor;)

Tipps für Zwischendurch: COM-Methoden mit Enum-Werten aufrufen

Dieser Tipp ist etwas spezieller.

Ausgangspunkt ist eine COM-Dll (früher OLE Server oder COM Server genannt), die eine Methode Calculate anbietet, die drei Parameter besitzt. Der erste Parameter ist vom Typ eines Enum mit dem Namen „OpType“, die anderen beiden Parameter sind vom Typ int.

Das Anlegen eines COM-Objekts mit der ProgId ist einfach:

Der Aufruf von Calculate funktioniert zunächst nur, wenn für den Enum-Wert eine Zahl übergeben, z.B. eine 0.

Der Enum OpType ist zunächst nicht bekannt. Ein „Trick“ besteht darin, ihn per Enum-Befehl nachzubauen, den es ab Version 5.0 gibt.

Damit klappt auch der folgende Aufruf:

Tipps für Zwischendurch: Registry-Suche – besser ohne PowerShell

Das Durchsuchen der Registry ist nicht gerade die Stärke der PowerShell. Einfacher geht es mit dem reg-Kommando, dessen Syntax zudem übersichtlich per /? abrufbar ist.

Hier eine Kostprobe. Gesucht sind alle Einträge, die ein „MathLibV1“ enthalten.

Per PowerShell wäre eine solche Suche deutlich umständlicher und vor allem langsamer. Das beginnt bereits mit dem Umstand, dass es keinen Befehl gibt, der die Registry nach Werten durchsucht. Das mit Version 5.0 dazu gekommende Get-ItemPropertyValue-Cmdlet klingt praktisch, ist aber sehr limitiert, da der Name-Parameter keine Platzhalter akzeptiert.

Geht es nur um eine schnelle Suche, ist reg einfach unschlagbar.

Kleine Tipps für Zwischendurch: Namespaces mit using abkürzen

Ich war sehr überrascht als ich in einem Diskussionsthread zu PowerShell Core folgende Zeile fand:

Sollte es wie bei C# auch bei der PowerShell einen using-Befehl geben mit dem sich Namespacenamen abkürzen lassen? Gibt es diese praktische Abkürzung auch bei der Version 5.1.

Die Antwort ist ja, Namespaces lassen sich tatsächlich auf diese Weise abkürzen was gerade größere Skripte mit vielen Typenangaben deutlich übersichtlicher machen wird.

Dazu noch ein Beispiel.

Nicht spektakuär, aber auf alle Fälle eine praktische Erweiterung. Ich frage mich nur, wie ich sie bislang habe übersehen können.

Kleine Tipps für Zwischendurch: Warum beim Vergleich $null links stehen sollte

Wer mit Visual Studio Code scriptet oder öfter den PSScriptAnalyzer verwendet, was immer eine gute Idee ist, wird sich schon öfter über folgende Warnung gewundert haben:

$null should be on the left side of equality comparisons. (PSPossibleIncorrectComparisonWithNull)

Warum sollte es eine Rolle spielen, ob $null links oder rechts vom -eq Operator steht? In praktisch jeder Programmiersprache, spielt dies keine Rolle. Nun, PowerShell ist einigen Kleinigkeiten etwas anders („seltsam“ wäre als Umschreibung wohl etwas zu subjektiv;).

Hier ist ein kleines Beispiel.

Im ersten Schritt lege ich ein leeres Array an.

Die Variable $Zahlen ist eigentlich leer, daher sollte die folgende Abfrage einen $true-Wert liefern.

Es kommt aber weder $true noch $false heraus, es kommt gar nichts heraus. Was zunächst sehr seltsam erscheint (warum liefert ein Vergleich kein Ergebnis?), ergibt einen Sinn, wenn man das Prinzip des Vergleichs kennt. Da das Array leer ist, wird kein Vergleich durchgeführt. Also kein Ergebnis. Der Vergleich wird also nicht mit der Variablen $Zahlen durchgeführt, sondern mit allen Elementen des Arrays. Und da das Array kein Element enthält, wird kein Vergleich durchgeführt. Und damit resultiert kein Ergebnis. Was am Anfang eventuell noch als etwas seltsam erschien, ergibt jetzt einen Sinn. So sollte es sein.

Möchte man die Variable $Zahlen richtig vergleichen, muss $null auf der linken Seite stehen:

Jetzt wird das Array als Ganzes verglichen. Der Vergleich führt zu einem $false, da die Variable mit einem leeren Array einen Wert besitzt.

PowerShell Module schreiben – mit ein paar Tools geht es leichter

Das Schreiben eines PowerShell Moduls ist grundsätzlich nicht schwer. Im einfachsten Fall speichert man lediglich eine Ps1-Datei mit ein paar Functions als Psm1-Datei in ein leeres Verzeichnis und fertig ist das Modul. Möchte man es richtig machen, steckt eine Menge Arbeit dahinter (ich muss es wissen, denn in meiner Freizeit arbeite ich seit ein paar Wochen an meinem Osmium-Modul, das es in ein paar Wochen auf GitHub bzw. in der PowerShell Gallery geben wird).

Welche Tools Modulautoren unterstützen können, hat PowerShell-Experte David Christian in einem Blog-Eintrag zusammengestellt:

https://overpoweredshell.com//Tools-Module-Authors-Should-Leverage/

Kleine Tipps für Zwischendurch: ForEach-Wiederholung mit Zählvariable

Ein kleiner Nachteil der praktischen ForEach-Methode von Arrays ist, dass es keine Zählvariable gibt. Mit dem ++-Operator spart man sich zumindestens einen weiteren Befehl, der die Variable inkrementiert.