Praxistipp: Wurden alle Zip-Dateien ausgepackt?

Große Laufwerksdateien können auch als eine Sammlung von kleineren Zip-Dateien zur Verfügung gestellt werden, die alle heruntergeladen, extrahiert und mit einem Tool wie 7Zip zu einer großen Datei oder einer Verzeichnisstruktur zusammengesetzt werden. Da nach dem Extrahieren mehrere Dutzend Zip-Dateien mit einer durchgehenden Nummerierung (z.B. Datei001, Datei002, Datei004 usw.) vorliegen stellt sich die Frage, ob alle Dateien vollständig sind.

Als PowerShell-Einsteiger würde man vielleicht versuchen, ein kleines Skript zu schreiben, das alle Dateien einliest, irgendwie die Nummern aus dem Dateinamen extrahiert und in einem weiteren Schritt z.B. im Rahmen einer Schleife von 1 bis n prüft, ob jede Zahl in der Liste der Dateinamen vorkommt.

Es geht natürlich auch etwas einfacher. Dank dem praktischen Group-Object-Command lässt sich alles in einer Befehlszeile zusammenfassen. Voraussetzung ist, dass sich alle Zip-Dateien im aktuellen Verzeichnis befinden


dir | select-object -expand Name | select-string "\.(\d+)$" | select-object @{n="Nr";e={$_.Matches[0].Groups[1].Value}} | Group-Object Nr | Where Count -eq 1

PowerShell Conference Europe 2020 – viele spannende Themen

Die PowerShell Conference Europe 2020 hat zwar corona-bedingt nur als reine Online-Konferenz stattgefunden, beim Überfliegen der im Januar veröffentlichten Themen im PowerShell Magazine wird schnell deutlich, dass auch offline eine sehr gute Konferenz geworden wäre:

Auch wenn ich den letzten ca. 15 Jahren bereits einiges mit der PowerShell gemacht habe, es ist immer wieder faszinierend zu sehen, wie kreativ viele Menschen sie einsetzen und vor allem, welche Erweiterungen sie sich einfallen lassen (u.a. Import-Excel oder das Universal Dashboard, um nur einmal zwei der Erweiterungen zu nennen, die sich jeder einmal anschauen sollte).

Ich bin sicher, dass die meisten der ausgefallenen Vorträge 2021 nachgeholt werden. Gleichzeitig ist mein Eindruck, dass die Themen, so spannend sie auch sind, für die meisten Anwender der PowerShell „over the top“, also sehr weit weg sind. Wer den Anschluss nicht ganz verlieren möchte, muss sich lediglich ein wenig ausführlicher und eventuell auch systematischer mit den PowerShell-Grundlagen beschäftigen (einfach einmal per Get-Help about_* alle About-Themen auflisten und sich jeden Tag ein Thema vornehmen) und vielleicht auch in seiner Arbeitsumgebung (sprich Firma) einen Rahmen schaffen, in dem PowerShell-Skripte sinnvoll und langfristig eingesetzt werden.

Tipp des Tages: Einzelaktionen ausführen mit Invoke-Command statt ForEach-Object

Eine der größten Hürden, die PowerShell-Einsteiger (bildlich gesprochen) überwinden müssen ist zu verstehen, wie auf eine Get-Abfrage eine individuell festgelegte Aktion folgen kann.

Beispiel: Wenn ein bestimmter Dienst nicht ausführt, soll eine Mail verschickt und ein Eintrag in einer Log-Datei geschrieben werden. Das Beispiel wirkt vielleicht ein wenig konstruiert. Es geht im Grunde darum, dass auf eine Get-Abfrage eine Aktion folgen soll, die sich nicht nur ein einzelnes Cmdlet abbilden lässt.

Die Lösung besteht normalerweise im Aufruf des ForEach-Object-Cmdlets, in dessen Process-Scriptblock alle Aktionen aneinandergereiht werden:

Get-Service -Name WinRM| Where-Object Status -ne "Running" | ForEach-Object {
"Aktion1"
"Aktion2"
}

Was mir an der Umsetzung nicht ganz so gut gefällt ist der Umstand, dass ForEach-Object auch dann verwendet wird, wenn nur eine Aktion ausgeführt werden soll.

Eine Alternative ist das Invoke-Command-Cmdlet. Auch wenn es in erster Linie für PowerShell-Remoting verwendet wird, kann es natürlich auch für lokale Ausführungen verwendet werden. Im einfachsten Fall wie folgt.

Get-Service -Name WinRM | Where-Object Status -ne "Running" | Invoke-Command {
"Aktion1"
"Aktion2"
}

Ganz optimal ist diese Variante sicher auch nicht. Vielleicht sollte es einfach nur mit „Invoke-Scriptblock“ einen weiteren Alias für ForEach-Object geben:

Set-Alias -Name Invoke-Scriptblock -Value ForEach-Object

Get-Service -Name WinRM | Where-Object Status -ne "Running" | Invoke-Scriptblock {
"Aktion1"
"Aktion2"
}

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.