PowerShell Remoting mit SSH und Public Key-Authentifizierung

Public Key-Authentifizierung bedeutet keine Passwort-Eingabe vor Invoke-Command oder Enter-PSSession mit SSH und ist natürlich ideal.

Klingt für einen reinen Windows-Admins eventuell etwas kompliziert, ist aber sehr einfach.

Da die Schrittfolge an so vielen Stellen im Internet und auch in der offiziellen Micorsoft-Dokumentation beschrieben ist, beschränke ich mich nur auf Stichworte.

Im Folgenden soll gezeigt werden, wie PowerShell-Remoting per SSH zwischen einem Windows- und einem Linux-Computer (mit Ubuntu 18.04) ohne Passworteingabe möglich ist.

Voraussetzung ist, dass unter Windows OpenSSH installiert wurde, auf dem Linux-Computer muss SSH eventuell auch nachinstalliert werden.

Schritt 1: Im ersten Schritt wird per ssh-genkey ein Paar aus privaten und öffentlichem Schlüssel erzeugt. ssh-genkey kann direkt aufgerufen werden, wenn Open SSH installiert wurde.

Das Ergebnis sind zwei Dateien: id_rsa mit dem privaten Schlüssel und id_rsa.pub mit dem öffentlichen Schlüssel.

Schritt 2: Im zweiten Schritt muss der SSH-Agent-Dienst gestartet werden. Wer SSH dauerhaft nutzen möche, setzt den Starttyp auf „Automatic“.

Schritt 3: Im dritten Schritt wird der private Schlüssel dem SSH-Agent übergeben:

ssh-add C:/Users/pemoadmin/.ssh/id_rsa

Damit ist der Windows-Teil erledigt.

Bei mir kam die Frage auf, wie der Public Key auf den Linux-Computer übertragen wird. Als Datei, das ist klar. Aber wo muss die Datei abgelegt werden, spielt der Dateiname eine Rolle und muss ich eventuell noch irgendwelche Berechtigungen setzen?

Nach zahlreichen Versuchen (mit denen ich mir fast einen schönen Sonntagnachmittag etwas verdorben hätte) fand ich am Montagmorgen in einem Blog-Eintrag von Christropher Hart (https://www.chrisjhart.com/Windows-10-ssh-copy-id/) die Lösung. Die Datei muss authorized_keys heißen und wird im (bereits vorhandenen) .ssh-Verzeichnis im home-Verzeichnis auf dem Linux-Computer abgelegt.

Am einfachsten wird die Datei per SSH übertragen:

type $env:USERPROFILE\.ssh\id_rsa.pub | ssh pemo@172.22.153.50 "cat >> .ssh/authorized_keys"

Eine Kleinigkeit fehlt natürlich noch. In der sshd_config-Datei auf dem Linux-Computer muss die Public Key-Authentifizierung durch Ändern eines Eintrags aktiviert werden:

PubkeyAuthentication yes

Die Passwort-Authentifizierung muss nicht deaktiviert werden. Dann stehen beide Varianten zur Auswahl.

Zum Schluss muss der sshd-Dienst auf dem Linux-Computer neu gestartet werden:

service sshd reload

Dank Public Key-Authentifizierung wird ein Invoke-Command jetzt sehr einfach, da weder der SSHTransport– noch der Subsystem-Parameter angegeben werden müssen:

$S1 = New-PSSession -Hostname $Hostname -Username $Username
Invoke-Command -ScriptBlock { Get-Process} -Session $S1
$S1 Remove-PSSession -Session $S1

PS: Eher eine persönliche Erinnerung. Diesen Eintrag habe ich am 21.09.2020 an einem herrlichen Spätsommermorgen auf dem dem Balkon geschrieben.

PowerShell-Remoting mit SSH

PowerShell-Remoting mit SSH ist gut dokumentiert. Zwei sehr gute Artikel sollten für einen erfolgreichen Probelauf genügen:

https://www.thomasmaurer.ch/2020/04/enable-powershell-ssh-remoting-in-powershell-7/

und

https://4sysops.com/archives/enable-powershell-core-6-remoting-with-ssh-transport/

Ein dritter Artikel beschreibt die Verwendung der Public Key-Authentifizierung als Alternative zur Passwort-Eingabe:

https://4sysops.com/archives/powershell-remoting-with-ssh-public-key-authentication/

Was eventuell noch nicht jeder weiß, der sich für das Thema SSH mit PowerShell interessiert. Seit ein paar Monaten gibt es in der PowerShell Gallery ein kleines Modul des PowerShell-Teams, mit dessen einzigem Enable-SSHRemoting-Command das Einrichten von SSH auf dem Remote-Computer sehr einfach wird. Der Eintrag

PasswordAuthentication yes

muss allerdings auch in diesem Fall einkommentiert werden, falls eine Kennwort-Authentifizierung gewünscht ist. Für das erste Kennenlernen ist dies die einfachste Variante.

Das Modul gibt es unter

https://www.powershellgallery.com/packages/Microsoft.PowerShell.RemotingTools/0.1.0

Damit hat es sogar bei mir auf Anhieb funktioniert;)

Insgesamt sind damit folgende Schritte auf dem SSH-Server zu erledigen:

  1. PowerShell 7 installieren
  2. OpenSSH Client und OpenSSH Server installieren
  3. Enable-SSHRemoting ausführen oder den subsystem-Eintrag in der sshd-config-Datei „zu Fuß“ anlegen
  4. sshd-Dienst (nicht ssh) neu starten – unter Linux service ssh restart (eigentlich gar nicht so schwer;)

Damit kann per New-PSSession, Enter-PSSession und Invoke-Command unter einer PowerShell ab Version 6.0 SSH über den SSHTransport-Parameter verwendet werden:

#requires -version 
$hostname="Ubunti
#$hostname="172.23.25.12"
$Username="pemo"
$S1 = New-PSSession -Hostname $Hostname -Username $Username -SSHTransport -Subsystem powershell
Invoke-Command -ScriptBlock { Get-Process} -Session $S1
Remove-PSSession -Session $S1

Der Subsystem-Parameter wählt den Namen des auf dem SSH-Server angelegten subsystem-Eintrag in sshd_config aus. Das Passwort wird bei jedem (!) Aufruf abgefragt. Wer das nicht möchte, muss Public Key-Authentizierung verwenden. Wie das geht, wird in einem der genannten Blog-Artikel beschrieben.

Viel Erfolg!

Abb. Dank snap besteht die Installation der PowerShell unter Ubuntu aus einem Aufruf (sofern sie nicht bereits vorinstalliert ist)

Anlegen einer Custom Rule für Scriptanalyzer

Der PowerShell Scriptanalyzer ist das Werkzeug, um für PowerShell-Skripte mit einfachen Mitteln eine umfassende Qualitätsanalyse durchzuführen.

Eine einer Stärken ist seine Erweiterbarkeit. Durch das Definieren von Custom Rules lassen sich theoretisch beliebige Merkmale eines Skriptes testen (z.B. ob Kommentare irgendwelche unflätigen Begriffe enthalten;).

Grundsätzlich ist das Definieren einer solchen Custom Rule einfach, denn sie besteht nur aus einer Function, die in einer Psm1-Datei abgelegt werden muss. Beim Aufruf von Invoke-ScriptAnalyzer wird der Pfad der Psm1-Datei über den CustomRulePath-Parameter angegeben.

PowerShell-Experte Mathieu Buisson beschreibt in einem sehr guten Blog-Beitrag ein kleines Beispiel, das bei mir auf Anhieb funktioniert hat:

https://mathieubuisson.github.io/create-custom-rule-psscriptanalyzer/

Ich war für sein Beispiel, das man sich lediglich aus dem GitHub-Repo kopieren muss, sehr dankbar, da ich zuvor „stundenlang“ herumprobiert hatte, um eine eigene Regel zum Laufen zu bringen (ich hatte vor Jahren bereits einmal eine Custom Rule als Psm1-Datei erstellt, war aber zu bequem, das Beispiel von damals zu suchen). Was ich auch probiert hatte, das Ergebnis war eine Fehlermeldung, in der behauptet wurde, dass meine psm1-Datei keine Regeln enthielt. Wirklich nervig. Irgendwann kam ich auf die Lösung. Ich hatte offenbar übersehen, dass der Function-Parameter ScriptBlockAst heißen muss. Außerdem müssen die Typen der Input- und Output-Werte genau angegeben werden.

Die größte Herausforderung beim Erstellen einer Custom Rule sind aber nicht die kleinen Formalitäten, sondern der Umstand, dass man sich in Grundzügen mit den Abstract Syntax Trees (AST) der PowerShell auskennen muss.

Meine Regel prüft, ob ein Skript Cmdlets oder Functions enthält, die keinen WhatIf-Parameter anbieten. Warum kann das wichtig sein? Weil das Commands sind, die nicht von der globalen WhatIfPreference-Einstellung betroffen sind. Möchte man ein unbekanntes Skript in einer „Sandbox“ ausführen, in der nichts „Schlimmes“ passieren kann, wären diese Commands davon nicht betroffen.

Auch wenn man in der Regel davon ausgehen kann, dass ein Command ohne WhatIf-Parameter grundsätzlich nichts macht, was negative Folgen haben könnte, 100% sicher kann man sich nicht sein, da der Autor des Cmdlets oder der Functions das Implementieren von Whatif über SupportsShouldProcess = $true im Rahmen von [CmdletBinding()] nicht für erforderlich gehalten oder schlicht vergessen haben könnte.

Wer daher wissen will, welche Commands von einem WhatIf-Modus unbeeindruckt bleiben, sollte diese Regel ausprobieren.

Die gesamte Datei gibt es in meinem GitHub-Repo

https://github.com/pemo11/whatifrule

Ich werde die einzelnen Schritte, insbesondere den AST-Teil, in naher Zukunft noch ausführlicher beschreiben.

Den folgenden Kommentar wollte ich in dem Blog von Mathieu Buisson schreiben, aber aus irgendeinem Grund war das Kommentieren nicht möglich:

Your article is very helpful, thank you – but I think that you should mention that the parameter name has to be ScriptBlockAst otherwise the rule is not found by ScriptAnalyzer by my experience.

Tipp des Tages: Text-Dateien im BOM-Format finden

Die PowerShell ISE hat die etwas lästige Eigenart, dass Textdateien mit einem sog. BOM-Header gespeichert. Das bedeutet konkret, dass die Datei mit den Byte-Werten 255 und 254 beginnt oder FF EF als Hexadezimalzahlen. BOM steht für Byte Order Mark und soll einen Hinweis darauf geben, dass als Zeichencode UTF-8 verwendet wird (ich hoffe, ich gebe das korrekt wieder). Mehr dazu unter https://de.wikipedia.org/wiki/Byte_Order_Mark.

Dass eine einfache Angelegenheit erstaunlich kompliziert sein kann, macht eine ausführliche Abhandlung deutlich, die das Zusammenspiel von VS Code mit der PowerShell in Bezug auf die Zeichencodierung beschreibt:

https://docs.microsoft.com/en-us/powershell/scripting/dev-cross-plat/vscode/understanding-file-encoding?view=powershell-7

Ich muss leider zugegeben, dass ich immer wieder in Situationen komme, in denen in die Umlaute einer in VS-Code gespeicherten Ps1-Datei durch „seltsame“ Sonderzeichen ersetzt werden (die in der oben genannten Doku natürlich alle beschrieben werden).

Wie dem auch sei, mich hat es einfach einmal interessiert, welche Ps1-oder allgemein Textdateien einen solchen BOM-Header besitzen. Der folgende Befehl gibt zunächst für die Windows PowerShell alle iese Dateien aus, in dem er jeweils die ersten beiden Bytes der Datei liest und diese als Hex-Werte mit FFEF vergleicht:

Der obige Befehl funktioniert nur mit der Windows PowerShell, da es den Wert Byte für den Encoding-Parameter nicht bei PowerShell 7 gibt. Dafür gibt es mit dem AsByteStream-Parameter zwar ein flexibleres Konzept, allerdings darf der TotalCount-Parameter nicht mit diesem Parameter kombiniert werden, so dass ein Select-Object mit -First 2 erforderlich wird.

Der folgende Befehl entspricht dem letzten Befehl, nur dass er ab PowerShell 6 funktioniert:

Die ersten Schritte mit den neuen PowerShell-Notebooks

Notebooks sind eine interessante Alternative zu den bekannten Benutzeroberflächen, in dem sie das Zusammenstellen von Texten (in der Regel im Markdown-Format), Grafiken und ausführbarem Programmcode kombinieren.

Ein Notebook ist damit ein interaktives Dokument, in dem beliebige statische Daten mit dynamisch generierten Daten beliebig kombiniert werden. Die dynamisch generierten Daten werden mit den Befehlen einer Programmiersprache erzeugt sobald die Befehlsfolge vom Anwender ausgeführt wird. Der Begriff „interaktives Notizblatt“ gibt für mich Funktion und Aufgabe eines solchen Notebook sehr gut wieder.

Notebooks werden seit einigen Jahren im Bereich der Datenanalyse (z.B. im Rahmen des Jupyter-Projekts und der dahinterstehenden Plattform jupyterlab.com) und im wissenschaftlichen Bereich eingesetzt (im Jahr 2016 wurden einige der Datensätze, mit deren Hilfe Wissenschaftlern der erste Nachweis von Gravitationswellen gelungen war, über ein Jupyter-Notebook der Allgemeinheit zur Verfügung gestellt, so dass theroretisch jeder Interessierte die Datenauswertung selber nachvollziehen kann).

Ein Wikipedia-Eintrag beschreibt das Jupyter-Projekt sehr gut.

https://de.wikipedia.org/wiki/Project_Jupyter

Darüber hinaus gibt es natürlich jede Menge Webseiten, auf denen der Umgang mit Juypter-Notebooks anschaulich beschrieben wird. Alles ist kostenlos, da die Software Open Source ist (und auch lokal installiert werden kann).

Inzwischen hat auch Microsoft das Thema für sich entdeckt und bietet Notebooks seit einiger Zeit im Rahmen ihrer Azure-Platform für das interaktive Auswerten von im Rahmen des Monitoring erfassten Performance-Daten an. Darüber hinaus gibt es auf der Grundlage von .NET Interactive eine Erweiterung für Jupyter, die neben der interaktiven Ausführung von C#-Code auch die Ausführung von PowerShell-Code in einem Jupyter-Notebook ermöglicht. Einen zwingenden Grund PowerShell anstelle von Python zu verwenden sehe ich zwar nicht, aber es ist trotzdem natürlich schön, dass so etwas berhaupt möglich ist.

Das Ganze Thema ist aber im Moment noch sehr speziell und setzt eine umfangreiche „Installationsorgie“ von .NET Core, .NET Interactive, Python, Juypyter und eventuell weiterem Zubehör voraus. So interessant diese Technik auch sein mag, für den typischen „PowerShell-Admin“ kommt sie nicht in Frage.

Sehr viel interessanter ist dagegen eine Erweiterung der PowerShell Extensions für Visual Code, über sich (einfache) PowerShell-Notebooks in Visual Studio Code erstellen und anzeigen lassen. Das Besondere daran ist, dass PowerShell-Notebooks auf regulären Ps1-Dateien basieren und daher natürlich auch in der Konsole oder der ISE ausgeführt werden können – dann werden sie natürlich als Skripte angezeigt. Wie auch sonst.

Doch warum sollte man das tun? Ein PowerShell-Notebook ist immer dann interessant, wenn man beliebige Befehlsfolgen mit (formatierten) Texten kombinieren und dem Anwender die Ausführung der Befehsfolgen in einer beliebiger Reihenfolge ermöglichen möchte. In der Vergangenheit musste man jeweils einen Block in einem Skript markieren und mit [F8] ausführen. Dieselbe Befehlsfolge als Teil einer Zelle eines Notebooks ist eine „visuelle Insel“ und damit besser als solche erkennbar. Auch das Ausführen über einen eigenen Button ist komfortabler. Schritt für Schritt-Anleitungen und Material für Schulungen fallen mir spontan ein, es gibt sicher noch weitere Anwendungsfälle.

Wer das Ganze selber ausprobieren möchte, muss Stand August 2020 noch zwei Besonderheiten beachten, die in ein paar Wochen keine Rolle mehr spielen werden:

  1. Es funktioniert nur mit der Insider Version von VIsual Studio Code, die einen eigenen Download darstellt (dieser kann aber problemlos zur regulären Visual Code-Installation installiert werden – das Produkticon ist grün und nicht blau).
  2. Es setzt die aktuellste Version der PowerShell-Extension voraus (ab Version v2020.7.0 – mit älteren Versionen funktioniert es nicht).

Ob alle Voraussetzungen erfüllt sind erkennt man ledlgich an einem unscheinbaren Button am rechten Rand der Bereichsleiste mit dem zwischen Code- und Notebook-Ansicht umgeschaltet wird.

.

Abb. Über einen Button wird zwischen Notebook- und Code-Ansicht umgesschaltet

Außerdem muss die Umschaltoption in den Einstellungen zu den PowerShell Extensions aktiviert werden.

Abb. Damit der Toggle Button sichtbar ist, muss eine EInstellung gesetztt sein.

Ein Notebook besteht aus Zellen. Eine Zelle enthält entweder (beliebigen) Text oder ausführbare Befehle. Auch wenn es naheliegend ist, den Text mit Markdown auszuzeichnen, damit z.B. eine Überschift als solche formatiert ausgegeben wird, eine Voraussetzung ist das natürlich nicht.

Das Besondere an einem PowerShell-Notebook ist, dass es eine reguläre Ps1-Datei ist, die daher auch wie jede andere Ps1-Datei ausgeführt wird. In diesem Fall spielt der enthaltene Text keine Rolle, da es lediglich Kommentare sind. Lediglich in der Notebook-Ansicht in Visual Studio Code wird der MarkDown-Text „gerendert“ und formatiert angezeigt.

Abb. Ein PowerShell-Notebook fasst formatierten Text und ausführbare Befehle in Zellen auf einer Seite zusammen

Den Komfort eines Jupyter-Notebooks gibt es daher im Moment noch nicht (eventuell ist das geplant), was auch daran deutlich wird, dass die Ausgaben einer „Zelle“ wie üblich im Konsolenfenster ausgegeben werden. Es wäre natürlich etwas schöner, wenn der Output unterhalb der Zelle erscheinen würde (was bei umfangreicherem Output natürlich auch nicht ganz optimal ist).

Auch wenn es am Ende (im Moment) nur um eine „Aufhübschung“ eines Skriptes und ein komfortableres Ausführen von Befehlsfolgen geht, sind die neuen PowerShell-Notebooks eine interessante und willkommene Erweiterung und Alternative, die ich in Zukunft (sofern sie ein offizeiller Teil von Visual Studio Code geworden sind) in meinen PowerShell-Schulungen einsetzen werde.

Ein Dank an das eine kleine Entwicklerteam der PowerShell Extensions, dass sie das Thema aufgegriffen (die Visual Studio Code-Entwickler stellen für die Implementierung von Notebooks Schnittstellen zur Verfügung) und bislang so gut umgesetzt haben.

Tipp des Tages: Befehlszeilentools mit –% ausführen

Hin und wieder kommt es vor, dass sich ein Befehlszeilentools nicht in der PowerShell-Konsole ausführen lässt. Der Grund dafür hat etwas mit der Art und Weise zu tun, wie das Befehlszeilentool seine Argumente verarbeitet, insbesondere was Anführungszeichen betrifft. Das klassissche Beispiel ist Find.

Der folgende Aufruf funktioniert nicht:

Abgesehen davon, dass es eigentlich keinen Grund für diesen Aufruf gibt, dürfte es zumindestens von theoretischem Interesse sein, warum bei diesem harmlosen Befehl ein „FIND: Parameterformat falsch“-Fehler die Folge ist.

Die Antwort: Die Anführungszeichen müssen escaped werden. Für diesen und möglichweisere andere solcher Spezialfälle bietet die PowerShell bereits seit der Version 3.0 (?) den praktischen Schalter –%, der einfach den Argumenten vorangestellt wird:

–% bewirkt, dass die PowerShell den folgeden Text nicht mehr selber interpretiert. Etwas einfacher geht es natürlich mit Where-Object bzw. einfach nur mit einem ?.

Tipp des Tages: Quelltextdateien durchsuchen und das Ergebnis sortieren mit einer Prise Regex

Dieser kleine Tipp ist für Entwickler interessant, die ihre Quelltetdateien nach einem Eintrag wie Substring(22,1) oder Substring(37,1) durchsuchen und die Rückgabe nach der ersten Ziffer in der runden Klammern sortieren möchten. Klingt vielleicht kompliziert, ist aber mit der üblichen Prise Regex dann doch relativ einfach.

Der Regex bedeutet: Suche alle Zeilen, die ein strZulassung:Subtring(<Zahl>, <Zahl>) enthalten, wobei <Zahl> jeweils für eine Zahl steht. Die erste Zahl soll als Gruppe abgreifbar sein, daher noch ein Paar runder Klammern. Die runden Klammern der Function müssen per \ escaped werden. Ausgangspunkt ist eine Verzeichnishierarchie mit Quelltextdateien (hier .prg-Dateien).

Ich kann nur empfehlen, sich mit dem Thema reguläre Ausdrücke etwas zu beschäftigen, denn bereits mit den absoluten Basics kommt man relativ weit (die PowerShell-Hilfe about_regular_expressions ist für den Einstieg absolut ausreichend).

Natürlich kennt auch die Visual Studio-Suche reguläre Ausdrücke, nur lässt sich das Ergebnis nicht so einfach sortieren.

$PSVersionTable.Platform -eq „Unix“ ? „Cool“ : „Windows, what else?“

Ich bin immer wieder fasziniert, wie selbstverständlich es ist inzwischen geworden ist, die PowerShell 7 unter einem Linux wie Cent OS 7 zu installieren. Die ganze Installation dauert noch nicht einmal eine Minute, wenn man die Installationsschritte von der Projektwebseite in die Konsole des SSH-Terminalfensters (ich empfehle MobaXTerm) kopiert.

Abb. 1. Die „Installationsanleitung“ für die Installation der PowerShell 7 unter CentOS

Ging alles gut, wird die PowerShell durch Eingabe von „pwsh“ gestartet. Der Rest ist selbsterklärend. Vielleicht nicht ganz, denn bei PowerShell 7 gibt es ein paar nette Neuerungen wie den sehr praktischen Tenäroperator ? (ergänzt durch ein :).

Abb. 2: Eine kurze PowerShell-Session unter Cent OS

Wer die Neuerungen der PowerShell 7 ausprobieren möchte, muss dazu natürlich kein Linux installieren. Die PowerShell 7 gibt es natürlich auch für Windows;)

Tipp des Tages: Verzeichnis anlegen und dahin wechseln

In Unix-Shells gibt es den beliebten &&-Operator zum Ausführen eines zweiten Prozesses, z.B. um in ein per mkdir angelegtes Verzeichnis per cd zu wechseln. Bei PowerShell geht es dank der Objekte etwas einfacher:

Zuerst legt mkdir ein neues Verzeichnis an. Da dieses als (DirectoryInfo-) Objekt zurückgeben wird, kann der Pfad des neuen Verzeichnisses per FullName-Property an cd übergeben wird.

Allerdings ist die Schreibweise nicht gerade intuitiv. Vor allem, wenn man die PowerShell nur sporadisch verwendet.

PowerShell-Tipp: Variablen in WMI-Abfrage einbauen

Der folgende Tipp ist sehr allgemein und eher harmlos, aber praktisch. WMI-Filter verwenden bekanntlich eine andere Syntax, u.a. müssen Zeichenketten in einem Vergleich in Apostrophe gesetzt werden.

Das funktioniert (natürlich) auch mit einer Variablen:

Anders als man es als PowerShell-Neuling vielleicht vermuten könnte, wird eine Variable auch dann ersetzt, wenn sie in Apostrophen steht.