14. Okt 2019
Lesedauer 9 Min.
Schnell konvertiert
PowerShell und JSON
JSON-Daten kann die PowerShell oft mit weniger Codezeilen verarbeiten als C#/VB.

Die PowerShell unterstützt die JavaScript Object Notation (JSON) seit 2012 in Version 3.0. Damals hieß die PowerShell allerdings noch „Windows PowerShell“. Die Windows PowerShell (auf Basis von .NET Framework) hat Microsoft dann auf dem Versionsstand 5.1 eingefroren. 2018 kam neu die PowerShell Core 6.0 (auf Basis von .NET Core) heraus, davon ist Version 6.2 (2019) aktuell. Ende des Jahres soll dann die PowerShell 7.0 (auf Basis von .NET Core 3.0) als gemeinsamer Nachfolger von Windows PowerShell 5.1 und PowerShell Core 6.2 erscheinen. Das PowerShell-Entwicklungsteam ist damit Vorreiter beim Zusammenführen von klassischer Produktlinie und Core-Produktlinie. In vergleichbarer Weise wird das .NET-Entwicklungsteam das .NET Framework und .NET Core Ende 2020 zu „.NET 5.0“ vereinen.
ConvertFrom-JSON
Ein mächtiges PowerShell-Cmdlet für die JSON-Verarbeitung ist ConvertFrom-JSON; ein anderes ist ConvertTo-JSON. Das erste erzeugt aus einer JSON-Datenstruktur in einer Zeichenkette einen Objektbaum, das zweite serialisiert umgekehrt einen Objektbaum zu Text im JSON-Format.Die JSON-Datenstruktur in Listing 1 beschreibt das Berufsleben des Autors dieses Beitrags. Diese JSON-Daten lassen sich mit Get-Content laden und mit einem in der Pipeline folgenden ConvertFrom-JSON in ein dynamisches .NET-Objekt verwandeln:Listing 1: Beispieldaten im JSON-Format
{ <br/> <span class="hljs-attr">"LetzteÄnderung"</span>: <span class="hljs-string">"2019-07-15T15:04:05.000+0100"</span>, <br/> <span class="hljs-attr">"Anrede"</span>: <span class="hljs-string">"Herr"</span>, <br/> <span class="hljs-attr">"Titel"</span>: <span class="hljs-string">"Dr."</span>, <br/> <span class="hljs-attr">"Name"</span>: <span class="hljs-string">"Holger Schwichtenberg"</span>, <br/> <span class="hljs-attr">"PLZ"</span>: <span class="hljs-number">45257</span>, <br/> <span class="hljs-attr">"Ort/Land"</span>: <span class="hljs-string">"Essen/Deutschland"</span>, <br/> <span class="hljs-attr">"Ist MVP"</span>: <span class="hljs-literal">true</span>, <br/> <span class="hljs-attr">"Themen"</span> : [<span class="hljs-string">".NET"</span>, <span class="hljs-string">".NET Core"</span>, <span class="hljs-string">"Visual Studio"</span>,<br/> <span class="hljs-string">"Softwarearchitektur"</span>, <span class="hljs-string">"Verteilte Systeme"</span>,<br/> <span class="hljs-string">"DevOps"</span>, <span class="hljs-string">"PowerShell"</span>], <br/> <span class="hljs-attr">"Firmen"</span> : [ <br/> { <br/> <span class="hljs-attr">"Name"</span>:<br/> <span class="hljs-string">"5Minds IT-Solutions Gmbh &amp; Co KG"</span>, <br/> <span class="hljs-attr">"Firmensitz"</span>: <span class="hljs-string">"Gelsenkirchen"</span>, <br/> <span class="hljs-attr">"Website"</span>: <span class="hljs-string">"www.5Minds.de"</span>, <br/> <span class="hljs-attr">"Tätigkeitsgebiete"</span>:<span class="hljs-string">"Softwareentwicklung"</span> <br/> }, <br/> { <br/> <span class="hljs-attr">"Name"</span>: <span class="hljs-string">"www.IT-Visions.de"</span>, <br/> <span class="hljs-attr">"Firmensitz"</span>: <span class="hljs-string">"Essen"</span>, <br/> <span class="hljs-attr">"Website"</span>: <span class="hljs-string">"www.IT-Visions.de"</span>, <br/> <span class="hljs-attr">"Tätigkeitsgebiete"</span>: [ <span class="hljs-string">"Beratung"</span>,<br/> <span class="hljs-string">"Schulung"</span> ] <br/> }, <br/> { <br/> <span class="hljs-attr">"Name"</span>: <span class="hljs-string">"Ebner Media Group GmbH &amp; Co. KG"</span>, <br/> <span class="hljs-attr">"Firmensitz"</span>: <span class="hljs-string">"Ulm"</span>, <br/> <span class="hljs-attr">"Website"</span>: <span class="hljs-string">"www.dotnetpro.de"</span>, <br/> <span class="hljs-attr">"Tätigkeitsgebiete"</span>:<br/> [<span class="hljs-string">"Fachinformationen"</span>] <br/> } <br/> ] <br/>}
Get-Content
X:<span class="hljs-symbol">\D</span>okumente<span class="hljs-symbol">\J</span>SON<span class="hljs-symbol">\P</span>erson.json | ConvertFrom-JSON
Bild 1 zeigt, dass dies bereits zu einer strukturierten Ausgabe in der PowerShell führt. Ein Pipe-Zeichen nach Get-Member beweist, dass der Pipeline-Inhalt nicht aus Zeichenketten besteht, sondern ein Objekt vom Typ System.Management.Automation.PSCustomObject ist, das die Elemente des JSON-Wurzelknotens nun als dynamische Eigenschaften enthält (siehe die NoteProperty-Ausgaben in Bild 1).

ConvertFrom-JSONin Aktion auf dem JSON-Dokument in Listing 1(Bild 1)
Autor
Folglich kann man über dieses Objekt nun auf die Daten aus dem JSON-Dokument zugreifen, zum Beispiel $p.Name. Dabei ist – wie meistens in der PowerShell – die Groß- und Kleinschreibung nicht von Bedeutung; $p.name funktioniert ebenso.Für die Eigenschaftsnamen des dynamischen Objekts bekommt der Nutzer auch Eingabehilfen: Das PowerShell Integrated Scripting Environment (ISE), Visual Studio mit PowerShell Tools und auch Visual Studio Code mit PowerShell-Extension [1] unterstützen die Eingabe mit IntelliSense, siehe Bild 2 – allerdings nur unter der Voraussetzung, dass die JSON-Struktur bereits einmal konvertiert wurde und die Variable, die das dabei entstandene Objektmodell enthält, nach Skriptende weiterhin gefüllt ist.

IntelliSense-Eingabeunterstützungin Visual Studio Code mit der kostenfreien Erweiterung PowerShell von Microsoft(Bild 2)
Autor
Wie in Bild 2 zu erkennen ist, muss also der folgender Aufruf von Get-Content zumindest einmal erfolgt sein, um die Eingabehilfe genießen zu können:
<span class="hljs-variable">$p</span> = <span class="hljs-built_in">Get-Content</span>
X:\Dokumente\JSON\Person.json
| <span class="hljs-built_in">ConvertFrom-JSON</span>
Auch die PowerShell-Konsole (einschließlich der in Visual Studio und Visual Studio Code enthaltenen Varianten) hilft bei der Eingabe von Property-Namen; allerdings hier nicht im IntelliSense-Stil, sondern per Tabulatortaste, die durch die Vorschläge blättert.Was allerdings nicht funktioniert, ist folgende Aufspaltung der obigen Pipeline:
<span class="hljs-variable">$pfad</span> = <span class="hljs-string">"X:\Dokumente\JSON\Person.json"</span>
<span class="hljs-variable">$dateiinhalt</span> = <span class="hljs-built_in">Get-Content</span> <span class="hljs-variable">$pfad</span>
<span class="hljs-built_in">ConvertFrom-JSON</span> <span class="hljs-variable">$dateiinhalt</span>
Dies beantwortet die PowerShell nur mit der Fehlermeldung „ConvertFrom-JSON : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'InputObject'“.Das liegt daran, dass das Cmdlet Get-Content den Inhalt einer Datei immer zeilenweise in einzelne Zeichenketten aufspaltet. Die Variable $dateiinhalt enthält nicht nur eine Zeichenkette, sondern ein Array von Zeichenketten. Mit diesem Array als Eingabeparameter kommt ConvertFrom-JSON aber nicht klar.Der Nutzer kann jedoch die zeilenweise Aufspaltung bei Get-Content mit dem Zusatzparameter -raw verhindern:
<span class="hljs-variable">$pfad</span> = <span class="hljs-string">"X:\Dokumente\JSON\Person.json"</span>
<span class="hljs-variable">$dateiinhalt</span> = <span class="hljs-built_in">Get-Content</span> <span class="hljs-variable">$pfad</span> -raw
<span class="hljs-built_in">ConvertFrom-JSON</span> <span class="hljs-variable">$dateiinhalt</span>
Ohne den Parameter -raw gibt es noch die Möglichkeit, das Zeichenketten-Array per Pipeline zu ConvertFrom-JSON zu senden. Die einzelnen Zeilen wird das Cmdlet dann als ein JSON-Dokument interpretieren:
<span class="hljs-variable">$pfad</span> = <span class="hljs-string">"X:\Dokumente\JSON\Person.json"</span>
<span class="hljs-variable">$dateiinhalt</span> = <span class="hljs-built_in">Get-Content</span> <span class="hljs-variable">$pfad</span>
<span class="hljs-variable">$dateiinhalt</span> | <span class="hljs-built_in">ConvertFrom-JSON</span>
Sonderzeichen
Wie Bild 1 zeigt, erstellt die PowerShell in dem dynamischen Objekt Eigenschaften mit der gleichen Schreibweise wie die Felder in der JSON-Datei. Eine kleine Herausforderung stellen die JSON-Felder Ort/Land und Ist MVP dar, denn diese sind keine erlaubten Namen für Eigenschaften in .NET und sind auch nicht in PowerShell-Ausdrücken erlaubt.Hier sind beim Zugriff auf die Eigenschaft einfache oder doppelte Anführungszeichen zu verwenden:$p.<span class="hljs-string">'Ort/Land'</span>
$p.<span class="hljs-string">"Ort/Land"</span>
Nachfolgende Aufrufe von Eigenschaften können dann aber wieder normal geschrieben werden, zum Beispiel:
<span class="hljs-variable">$p</span>.<span class="hljs-string">'Ort/Land'</span><span class="hljs-selector-class">.Length</span>
<span class="hljs-variable">$p</span>.<span class="hljs-string">"Ort/Land"</span>.Length
Gleiches gilt für Ist MVP, hier in einem Beispiel mit Write-Host und Bedingung:
<span class="hljs-built_in">Write-Host</span> <span class="hljs-string">"MVP-Status?"</span>
<span class="hljs-built_in">Write-Host</span> $(<span class="hljs-keyword">if</span> (<span class="hljs-variable">$p</span>.<span class="hljs-string">'Ist MVP'</span>) { <span class="hljs-string">"Ja"</span> } <span class="hljs-keyword">else</span> { <span class="hljs-string">"Nein"</span> })
Datentypen
Bild 1 zeigt, dass ConvertFrom-JSON die Datentypen im Fall des JSON-Dokuments in Listing 1 richtig erkennt:- LetzteÄnderung wird als DateTime-Datentyp verstanden.
- PLZ wird als long verstanden.
- Ist MVP wird als bool verstanden.
<span class="hljs-keyword">Write</span>-Host <span class="hljs-string">"Zuletzt geändert im Jahr:"</span>
<span class="hljs-keyword">Write</span>-Host <span class="hljs-built_in">$p</span>.LetzteÄnderung.Year
Das Erkennen von Datumsformaten funktioniert aber erst seit PowerShell Core 6.0. Die Windows PowerShell lieferte immer nur eine Zeichenkette, die dann manuell konvertiert werden musste [2]. Auch andere Detailunterschiede sind möglich, denn die Windows PowerShell 1.0 bis 5.1 basiert auf dem .NET Framework und den dort enthaltenen alten JSON-(De-)Serializer im Namensraum System.Json. PowerShell Core 6.x und PowerShell 7.0 dagegen bauen auf .NET Core auf. Dort gibt es diesen Namensraum aber nicht mehr. Hier wird Newtonsoft JSON (alias JSON.NET) verwendet [3]. Offen ist, wie das PowerShell-Team bei PowerShell 7.0 verfahren wird, denn Microsoft wird dort ja Newtonsoft JSON durch eine eigene Implementierung (System.Text.Json) ablösen [4].Die geladene Themenliste ist ein Array von Zeichenketten, das mit $p.Themen angesprochen werden kann; die PowerShell gibt die Liste aus, siehe Bild 3. Hingegen ist $p.Firmen ein Array von Objekten, das die PowerShell als Tabelle ausgibt, wie ebenfalls in Bild 3 zu sehen ist.

Die Mengenmagieder PowerShell(Bild 3)
Autor
Diese Standardausgabe muss der PowerShell-Nutzer aber nicht hinnehmen. Er kann zur Auswertung alle PowerShell-Cmdlets verwenden, sie zum Beispiel mit Where-Object filtern, mit Sort-Object sortieren und die Ausgaben mit einem der Format-Cmdlets, etwa Format-List, formatieren:
<span class="hljs-variable">$p</span>.Firmen | <span class="hljs-built_in">Where-Object</span> { <span class="hljs-variable">$_</span>.name <span class="hljs-nomarkup">-like</span> <span class="hljs-string">"*gmbh*"</span> }
| <span class="hljs-built_in">Sort-Object</span> Firmensitz
| <span class="hljs-built_in">Format-List</span> Name, Firmensitz
Der Anwender kann die Ausgabe auch in einem anderen Format speichern, beispielsweise mit Export-Csv:
<span class="hljs-variable">$p</span>.Firmen | <span class="hljs-built_in">Where-Object</span> { <span class="hljs-variable">$_</span>.name <span class="hljs-nomarkup">-like</span> <span class="hljs-string">"*gmbh*"</span> }
| <span class="hljs-built_in">Sort-Object</span> Firmensitz
| <span class="hljs-built_in">Export-Csv</span> X:\Dokumente\JSON\Firmen.csv
Objekt-Magie
Spannend aus der Sicht der Objektorientierung ist ein PowerShell-Ausdruck wie dieser:<span class="hljs-variable">$p</span><span class="hljs-selector-class">.Firmen</span><span class="hljs-selector-class">.Name</span>
In C++, C#, Java und den meisten anderen objektorientierten Sprachen würde dieser Ausdruck den Compiler oder Interpreter zu einer Fehlermeldung veranlassen, denn $p.Firmen ist ja laut Bild 1 ein Array von Objekten. Das Array an sich besitzt aber keine Eigenschaft Name, sondern nur die einzelnen im Array enthaltenen Objekte verfügen über Name. In „normalen“ objektorientierten Programmiersprachen müsste erst einmal ein Element der Liste angesprochen werden:
<span class="hljs-variable">$p</span><span class="hljs-selector-class">.Firmen</span>[<span class="hljs-number">0</span>]<span class="hljs-selector-class">.Name</span> oder <span class="hljs-variable">$p</span><span class="hljs-selector-class">.Firmen</span>[<span class="hljs-number">1</span>].Name
Die PowerShell abstrahiert aber an vielen Stellen von den tatsächlichen Objektstrukturen und leitet .Name an die einzelnen Elemente der Menge weiter, sodass $p.Firmen[0].Name tatsächlich eine Liste aller Namen ausgibt.Noch interessanter ist dieser Aufruf:
<span class="hljs-variable">$p</span><span class="hljs-selector-class">.Firmen</span><span class="hljs-selector-class">.T</span>ätigkeitsgebiete
Wie man in der JSON-Datei in Listing 1 sieht, verhält sie sich bei ”Tätigkeitsgebiete” inkonsequent: Bei der ersten Firma ist ein einzelner Eintrag als Zeichenkette hinterlegt, bei der zweiten ein Array mit zwei Zeichenketten und beim dritten Unternehmen ein Array mit einer Zeichenkette.Dies sollte der Autor eines JSON-Dokuments natürlich vermeiden, allerdings lehrt die Praxis: So etwas kommt immer mal wieder vor und bringt streng typisierte Sprachen wie C# in Not.Die PowerShell mit ihrer dynamischen Typisierung steckt die verschieden aufgebaute Objekte einfach in die Menge Firmen, wie in Bild 4 zu sehen ist. Beim Einsatz von Get-Member (abgekürzt gm) sieht das dann so aus:

Die Objektesind verschieden aufgebaut(Bild 4)
Autor
- Bei $p.Firmen | Get-Member bekommt man die Metadaten zu dem ersten Objekt in der Liste, das eine Zeichenkette bei Tätigkeitsgebiete besitzt.
- $p.Firmen[0] | Get-Member ist das Gleiche wie $p.Firmen | Get-Member.
- $p.Firmen[1] | Get-Member liefert hingegen das zweite Element, und dessen Metadaten zeigen nun bei Tätigkeitsgebiete ein Objekt-Array an.
JSON-Daten von Web-APIs
In der Praxis kommen JSON-Daten oft bei REST-basierten Webdiensten (alias Web APIs) ins Spiel. Auch mit diesen kann die PowerShell umgehen. Das Cmdlet Invoke-WebRequest löst einen HTTP-Aufruf aus und gibt ein Objekt vom Typ Microsoft.PowerShell.Commands.HtmlWebResponseObject zurück. Darin ist der Inhalt in der Eigenschaft RawContent enthalten.Die folgenden drei Befehlszeilen liefern vom REST-Web-API von www.nuget.org eine Liste aller NuGet-Pakete, die das Wort „Math“ im Namen tragen:<span class="hljs-variable">$json</span> = Invoke-WebRequest
<span class="hljs-string">"https://api-v2v3search-0.nuget.org/query?q=Math"</span>
<span class="hljs-variable">$daten</span> = ConvertFrom-JSON <span class="hljs-variable">$json</span><span class="hljs-selector-class">.RawContent</span>
<span class="hljs-variable">$daten</span><span class="hljs-selector-class">.Data</span><span class="hljs-selector-class">.Title</span>
Es funktioniert auch ohne expliziten Zugriff auf RawContent. ConvertFrom-JSON erwartet eine Zeichenkette, und wenn die PowerShell das Objekt vom Typ Microsoft.PowerShell.Commands.HtmlWebResponseObject anliefert, wird sie automatisch die Methode ToString() aufrufen. Diese Methode liefert das Gleiche wie RawContent:
<span class="hljs-variable">$json</span> = Invoke-WebRequest
<span class="hljs-string">"https://api-v2v3search-0.nuget.org/</span>
<span class="hljs-string"> query?q=Math"</span>
<span class="hljs-variable">$daten</span> = ConvertFrom-JSON <span class="hljs-variable">$json</span>
<span class="hljs-variable">$daten</span><span class="hljs-selector-class">.Data</span><span class="hljs-selector-class">.Title</span>
JSON-Daten aus Kommandozeilenwerkzeugen
Auch andere Kommandozeilenbefehle können JSON liefert, zum Beispiel das Azure-DevOps-Kommandozeilenwerkzeug (Azure DevOps CLI). Mit ihm lassen sich Microsofts Cloud-Dienst Azure DevOps Services und der lokale Azure DevOps Server (früher Team Foundation Server) per Kommandozeile steuern. Leider gibt es für die administrativen Aufgaben rund um Azure DevOps keine PowerShell-Cmdlets, die Objekte direkt liefern würden. Daher müssen die JSON-Ausgaben des Azure DevOps CLI verarbeitet werden.Nachdem das Azure CLI und die Azure DevOps-Erweiterungen dafür installiert wurden und Sie sich bei Azure DevOps angemeldet sowie die dazu nötigen Angaben zu Organisation und Projekt konfiguriert haben [5], können Sie Befehle wie diesen abschicken, der eine Liste aller Work-Items in der persönlich definierten Abfrage Nicht behobene Fehler liefert:<span class="hljs-symbol">az</span> <span class="hljs-keyword">boards </span>query
--path <span class="hljs-string">"my queries/Nicht behobene Fehler"</span>
Die Ausgabe erfolgt üblicherweise im JSON-Format. Nur wenn Sie das Standardausgabeformat geändert haben, ist explizit zu ergänzen, dass JSON gewünscht ist:
<span class="hljs-comment">az</span> <span class="hljs-comment">boards</span> <span class="hljs-comment">query</span>
<span class="hljs-comment"> </span> <span class="hljs-literal">-</span><span class="hljs-literal">-</span><span class="hljs-comment">path</span> <span class="hljs-comment">"my</span> <span class="hljs-comment">queries/Nicht</span> <span class="hljs-comment">behobene</span> <span class="hljs-comment">Fehler“</span>
<span class="hljs-comment"> </span> <span class="hljs-literal">-</span><span class="hljs-literal">-</span><span class="hljs-comment">output</span> <span class="hljs-comment">json</span>
Das Ergebnis können Sie in der PowerShell an ConvertFrom-JSON senden:
<span class="hljs-string">az </span><span class="hljs-string">boards </span><span class="hljs-string">query</span>
<span class="hljs-string"> </span> <span class="hljs-built_in">--path</span> <span class="hljs-string">"my queries/Nicht behobene Fehler"</span>
<span class="hljs-built_in">--output</span> <span class="hljs-string">json </span>| <span class="hljs-string">ConvertFrom-JSON</span>
Nun lässt sich das Ergebnis strukturiert auswerten:
(az boards query
--path <span class="hljs-string">"my queries/Nicht behobene Fehler"</span>
--<span class="hljs-keyword">output</span> json | ConvertFrom-JSON).Fields)
| <span class="hljs-built_in">Where</span>-Object { $_.'<span class="hljs-keyword">System</span>.<span class="hljs-built_in">Title</span>' -like ‚*Eingabe*'}
| <span class="hljs-keyword">Format</span>-Table '<span class="hljs-keyword">System</span>.ID','<span class="hljs-keyword">System</span>.<span class="hljs-built_in">Title</span>',
'<span class="hljs-keyword">System</span>.State'
Probleme mit Mengen an der Wurzel
Leider gibt es ein Problem bei ConvertFrom-JSON, wenn das Wurzelelement des JSON-Dokuments kein einzelnes Objekt ({ … }), sondern ein Array ([ … ]) ist. Der nachstehende Befehl sollte die Liste der definierten Azure-DevOps-Pipelines liefern, deren Name „Client“ enthält. Das Ergebnis sollte nach dem Feld Name sortiert in einer Tabelle ausgegeben werden:az pipelines list -o JSON | <span class="hljs-type">ConvertFrom</span>-JSON
| <span class="hljs-type">Where</span>-Object { $<span class="hljs-keyword">_</span>.name -notlike <span class="hljs-string">"*Client*"</span> }
| <span class="hljs-type">Sort</span>-Object name | <span class="hljs-type">Format</span>-<span class="hljs-keyword">Table</span> name, url
Allerdings verwundert, dass zwar die Tabellenausgabe mit den zwei gewünschten Spalten, aber weder der Filter mit dem Cmdlet Where-Object noch die Sortierung mit Sort-Object wirken. Das liegt am Cmdlet ConvertFrom-JSON, bei dem das PowerShell-Entwicklerteam bei der Implementierung gravierend von den Standards abgewichen ist. Falls sich an der Wurzel der JSON-Datei ein Array befindet, dann legt das Cmdlet das Array in die Pipeline. Normalerweise entpacken aber Cmdlets ein Array in Einzelobjekte und legen diese Einzelobjekte in die Pipeline.Microsoft hat dieses Problem leider bis heute nicht behoben [6] – das Verhalten nachträglich zu ändern würde bestehende Skripte zu anderen Ergebnissen kommen lassen. Eine Umgehung wäre, einen neuen Parameter einzuführen. Den aber gibt es noch nicht.Die aktuell verfügbare Umgehung dieses Problems ist, entweder ein zusätzliches Foreach-Object in die Pipeline zu setzen:
az pipelines list -o JSON | <span class="hljs-type">ConvertFrom</span>-JSON
| <span class="hljs-type">Foreach</span>-Object { $<span class="hljs-keyword">_</span>}
| <span class="hljs-type">Where</span>-Object { $<span class="hljs-keyword">_</span>.name -notlike <span class="hljs-string">"*Client*"</span> }
| <span class="hljs-type">Sort</span>-Object name | <span class="hljs-type">Format</span>-<span class="hljs-keyword">Table</span> name, url
Eine Alternative ist, den vorderen Teil mit dem Aufruf von az und ConvertFrom-JSON in runde Klammern zu setzen:
(az pipelines list -o JSON | <span class="hljs-built_in">ConvertFrom-JSON</span>)
| <span class="hljs-built_in">Where-Object</span> { <span class="hljs-variable">$_</span>.name <span class="hljs-nomarkup">-notlike</span> <span class="hljs-string">"*Client*"</span> }
| <span class="hljs-built_in">Sort-Object</span> name| <span class="hljs-built_in">format-table</span> name, url
Test-JSON
Seit PowerShell Core 6.1 gibt es auch ein drittes eingebautes Cmdlet mit „JSON“ im Namen: Test-JSON. Mit diesem Cmdlet lässt sich prüfen, ob eine Zeichenkette ein gültiges JSON-Dokument enthält. Allerdings gibt es hier wieder die Besonderheit, dass das JSON-Dokument bei diesem Cmdlet nicht zeilenweise per Pipeline ankommen darf. Die folgende Befehlsfolge funktioniert also nicht:Get-Content X:<span class="hljs-symbol">\D</span>okumente<span class="hljs-symbol">\J</span>SON<span class="hljs-symbol">\P</span>erson.json | Test-JSON
Vielmehr ist bei Get-Content explizit der Parameter -raw zu verwenden:
Get-Content X:<span class="hljs-symbol">\D</span>okumente<span class="hljs-symbol">\J</span>SON<span class="hljs-symbol">\P</span>erson.json -raw
| Test-JSON
Mit Test-JSON ist auch eine Validierung gegen ein JSON-Schema-Dokument [7] möglich:
<span class="hljs-variable">$doc</span> = Get-Content X:\Dokumente\JSON\Person<span class="hljs-selector-class">.json</span> -raw
<span class="hljs-variable">$schema</span> = Get-Content
X:\Dokumente\JSON\Person<span class="hljs-selector-class">.schema</span><span class="hljs-selector-class">.json</span> -raw
Test-JSON -Json <span class="hljs-variable">$doc</span> -Schema <span class="hljs-variable">$schema</span>
Im Erfolgsfall liefert Test-JSON ein lapidares true, wie es bei allen Test-Cmdlets in der PowerShell üblich ist. Bei einem Fehler kommt zusätzlich zum false eine Fehlermeldungsliste zurück. In Bild 5 bemängelt Test-JSON, dass bei der ersten Firma in Listing 1 die Tätigkeitsgebiete nicht aus einem Array, sondern nur aus einer einfachen Zeichenkette bestehen.

Test-JSONin Aktion(Bild 5)
Autor
JSON-Strukturen erzeugen
Natürlich gibt es auch einen Gegenspieler zu ConvertFrom-JSON mit Namen ConvertTo-JSON. Der folgende Befehl speichert die Liste der laufenden Windows-Systemdienste (die das Cmdlet als Objekt vom Typ System.ServiceProcess.ServiceController liefert) als JSON-Dokument:<span class="hljs-built_in">Get-Service</span> | <span class="hljs-built_in">Where-Object</span> status <span class="hljs-nomarkup">-eq</span> <span class="hljs-string">"Running"</span>
| <span class="hljs-built_in">ConvertTo-Json</span> -Depth <span class="hljs-number">3</span> | <span class="hljs-built_in">Out-File</span> t:\dienste.json
Dabei wird eine Serialisierungstiefe für den Objektbaum von 3 angegeben; der voreingestellte Wert ist 2. Hier ist 2 mindestens notwendig, um die Namen der abhängigen Dienste zu erhalten. Bei Tiefe 1 ergibt sich die Liste in Listing 2.
Listing 2: Das Ergebnis bei einer Serialisierungstiefe von 1
{ <br/> <span class="hljs-attr">"CanPauseAndContinue"</span>: <span class="hljs-literal">false</span>, <br/> <span class="hljs-attr">"CanShutdown"</span>: <span class="hljs-literal">true</span>, <br/> <span class="hljs-attr">"CanStop"</span>: <span class="hljs-literal">true</span>, <br/> <span class="hljs-attr">"DisplayName"</span>: <span class="hljs-string">"DHCP Client"</span>, <br/> <span class="hljs-attr">"DependentServices"</span>: [ <br/> <span class="hljs-string">"System.ServiceProcess.ServiceController"</span>, <br/> <span class="hljs-string">"System.ServiceProcess.ServiceController"</span>, <br/> <span class="hljs-string">"System.ServiceProcess.ServiceController"</span>, <br/> <span class="hljs-string">"System.ServiceProcess.ServiceController"</span>, <br/> <span class="hljs-string">"System.ServiceProcess.ServiceController"</span>, <br/> <span class="hljs-string">"System.ServiceProcess.ServiceController"</span>, <br/> <span class="hljs-string">"System.ServiceProcess.ServiceController"</span> <br/> ], <br/> <span class="hljs-attr">"MachineName"</span>: <span class="hljs-string">"."</span>, <br/> <span class="hljs-attr">"ServiceName"</span>: <span class="hljs-string">"DHCP"</span>, <br/> <span class="hljs-attr">"ServicesDependedOn"</span>: [ <br/> <span class="hljs-string">"System.ServiceProcess.ServiceController"</span>, <br/> <span class="hljs-string">"System.ServiceProcess.ServiceController"</span> <br/> ], <br/> <span class="hljs-attr">"ServiceHandle"</span>: <span class="hljs-literal">null</span>, <br/> <span class="hljs-attr">"Status"</span>: <span class="hljs-number">4</span>, <br/> <span class="hljs-attr">"ServiceType"</span>: <span class="hljs-number">48</span>, <br/> <span class="hljs-attr">"StartType"</span>: <span class="hljs-number">2</span>, <br/> <span class="hljs-attr">"Site"</span>: <span class="hljs-literal">null</span>, <br/> <span class="hljs-attr">"Container"</span>: <span class="hljs-literal">null</span>, <br/> <span class="hljs-attr">"Name"</span>: <span class="hljs-string">"DHCP"</span>, <br/> <span class="hljs-attr">"RequiredServices"</span>: [ <br/> <span class="hljs-string">"System.ServiceProcess.ServiceController"</span>, <br/> <span class="hljs-string">"System.ServiceProcess.ServiceController"</span> <br/> ] <br/>}
Mit Tiefe 2 ergeben sich dann statt System.ServiceProcess.ServiceController die Eigenschaften der Dienste inklusive der Namen der referenzierten anderen Systemdienste.
Fazit
Die JSON-Serialisierung und -Deserialisierung in der PowerShell machen Spaß, denn es sind nur wenige Tastenanschläge nötig. Die Inkonsistenz bei der Mengenverarbeitung im Cmdlet ConvertFrom-JSON ist aber lästig, weil man die Klammern leicht vergessen kann und es dann unbemerkt bleiben kann, dass die definierten Filter nicht wirken. Auch dass Test-JSON anders als ConvertFrom-JSON zeilenweise Eingaben nicht unterstützt, ist nicht schön. Auch hier zeigt sich, dass verschiedene Softwareentwickler am Werk waren und die Qualitätssicherung besser sein könnte.Fussnoten
- Visual Studio Market Place, PowerShell Language Support for Visual Studio Code, http://www.dotnetpro.de/SL1911DataAccess1
- GitHub: PowerShell, ConvertFrom-JSON breaking change: automagically detects timestamps and deserializes to DateTime #3378, http://www.dotnetpro.de/SL1911DataAccess2
- Json.NET, http://www.newtonsoft.com/json
- NuGet, System.Text.Json, http://www.dotnetpro.de/SL1911DataAccess3
- Microsoft, Installieren des Azure CLI, http://www.dotnetpro.de/SL1911DataAccess4
- GitHub: PowerShell, ConvertFrom-JSON sends objects converted from a JSON array as an *array* through the pipeline #3424, http://www.dotnetpro.de/SL1911DataAccess5
- JSON Schema, https://json-schema.org