PowerShell 7

PowerShell 5.1 ist die letzte Version, die auf dem NET-Framework basiert. Die "Powershell 6 Core" (Siehe PowerShell Core) hingegen nutzte das NET Core-Framework und konnte so auch auf diversen Linux-Derivaten und MacOS installiert werden. War aber in der Funktion noch eingeschränkt, z.B. gab es keine Windows Performance Counter etc. Mit der PowerShell 7 (Release 4. März) wird vieles besser und ich nutze bis auf wenige Aufgaben nur noch PWSH.

Installation

Sie können die PowerShell problemlos "neben" der früheren PowerShell installieren. So gar die Parallelinstallation der verschiedenen Release-Kandidaten ist möglich, denn jede PowerShell bringt ihre eigene NET-Laufzeitumgebung mit. Daher ist die Powershell 7 auf meinem PC auch etwas umfangreicher.

C:\Program Files\PowerShell\7\pwsh.exe

Unter Linux sind es ca. 150MByte. Die Installation könne Sie auf unterschiedliche Wege durchführen.

Downloadquelle GitHub
https://github.com/PowerShell/PowerShell/releases

  • Download und Installation des ZIP-Files
    Sie können einfach das passende ZIP-Archiv für ihre Umgebung herunterladen und irgendwo auspacken. Darin befindet sich dann die PWSH.EXE
  • Download und Starten des MSI
    Natürlich gibt es in den Quellen auch einen MSI-Installer, der letztlich aber auch nur den Code ins Programmverzeichnis kopiert. Zusätzlich gibt es dann aber einen Eintrag im Startmenü, im Suchpfad und der Liste der installierten Programme
  • "DotNet"-Installation
    Das ist dann die dritte und vermutlich am wenigsten bekannte Installationsweise. Sobald Sie nämlich das .NET Core-Framework installiert haben, gibt es das "dotnet"-Tool, mit dem Sie auch entsprechende Programme nachladen können.

Hier mal das Beispiel mit DOTNET, die ich einfach als "Benutzer" in einer CMD-Shell ausgeführt habe.

C:\>dotnet tool install --global powershell

Willkommen bei .NET Core 3.1!
---------------------
SDK-Version: 3.1.101

Telemetrie
---------
Die .NET-Tools erfassen Nutzungsdaten, damit wir die Plattform stetig verbessern können. 
Die Daten sind anonym. Sie werden von Microsoft erfasst und mit der Community geteilt. 
Sie können das Erfassen von Telemetriedaten deaktivieren, indem Sie die Umgebungsvariable 
DOTNET_CLI_TELEMETRY_OPTOUT in Ihrer bevorzugten Shell auf "1" oder "true" festlegen.

Hier können Sie mehr zu Telemetriedaten in .NET Core-CLI-Tools erfahren: https://aka.ms/dotnet-cli-telemetry

----------------
Erkunden Sie die Dokumentation: https://aka.ms/dotnet-docs
Melden Sie Probleme, und finden Sie Quellen in GitHub: https://github.com/dotnet/core
Informieren Sie sich über Neuheiten: https://aka.ms/dotnet-whats-new
Erfahren Sie mehr über das installierte HTTPS-Entwicklerzertifikat: https://aka.ms/aspnet-core-https
Verwenden Sie "dotnet --help", um die verfügbaren Befehle anzuzeigen, oder besuchen 
Sie diese Website: https://aka.ms/dotnet-cli-docs
Schreiben Sie Ihre erste Anwendung: https://aka.ms/first-net-core-app
--------------------------------------------------------------------------------------
Da Sie gerade das .NET Core SDK installiert haben, müssen Sie das Eingabeaufforderungsfenster neu öffnen, bevor Sie das installierte Tool ausführen.
Sie können das Tool über den folgenden Befehl aufrufen: pwsh
Das Tool "powershell" (Version 7.0.0) wurde erfolgreich installiert.

Der Aufruf erfolgt, zumindest wenn das Programm auch im Suchpfad ist wie bei PowerShell 6 durch den Start von "PWSH.EXE". Beachten Sie auch die Links zu Einführungsvideos

Sie haben nicht direkt etwas mit PowerShell zu tun, sondern eher mit dem genutzten NET-Core.

PS7 Neuerungen

Ich kann und will gar nicht auf alle Neuerungen eingehen. Dazu ändert sich die neue PowerShell sehr schnell und selbst von Version 6 auf 7 gibt es viele weitere Verbesserungen. Das interpretiere ich aber auch so, dass hier die Musik spielt.

ForEach mit Parallel

Parallele Programmierung war mit PowerShell 1-5 nicht immer einfach. Start-Process war sehr ressourcenhungrig und selbst Start-ThreadJob erfordert immer noch eine Job-Verarbeitung. Wenn Sie For-Schleifen nutzen, dann sollten Sie den Parameter "-parallel" kennen und den Scope von Variablen kennen.

1..10 | foreach-object -parallel {
   write-host "Loop $($_)  Test $($test)"
   start-sleep -milliseconds 500
}

Loop 4
Loop 3
Loop 1
Loop 2
Loop 5
Loop 6
Loop 7
Loop 8
Loop 9
Loop 10

So werden die Befehle in der Schleife parallel ausgeführt. Per Default laufen aber nur 5 Threads parallel, so dass diese Schleife dann ca. 1 Sekunde statt 5 Sekunden läuft. An der Ausgaben sehen Sie aber auch, dass die Reihenfolge veränderlich sein kann. Auch kann ein Thread einfach pausiert sein was bei einfachen Operationen wie "$a++" zu Problemen führen kann. Daher sind per Default Variablen in der Schleife immer nur "Lokal. Globale Variablen kann man über "$using" dann in der Schleife referenzieren aber sollte natürlich sicherstellen, dass der Zugriff "Thread-Safe" ist, z.B. mit

$wert = 10;   # variable belegen
$refWert=[ref]$wert
1..10 | foreach-object -parallel {
   write-host "Loop $($_)  Test $([System.Threading.Interlocked]::Read($using:refWert))"
   [System.Threading.Interlocked]::Read($using:refWert)
   write-host "Loop $($_)  Test $([System.Threading.Interlocked]::Increment($using:refWert))"
   start-sleep -milliseconds 500
}

Loop 1 Test 10
10
Loop 2 Test 10
10
Loop 1 Test 11
Loop 2 Test 12
Loop 3 Test 10
12
Loop 3 Test 13
Loop 4 Test 10

Damit wir das Skript aber etwas länger laufen, da die Abstimmung der Thread aufeinander die parallele Ausführung etwas hindert. Die Abarbeitung ist etwas "serieller" aber immer noch schneller als ohne "-parallel"

Performance Counter

Das Commandlet "Get-Counter" war in der alten PowerShell schon lange da und in der PowerShell 6 hat es gefehlt. Erst seit der Powershell 7 ist der Befehl wieder im Standard enthalten.

CIM statt WMI

WMI ist COM und daher "Old Style". PS6+ kann aber keine COM-Objekte mehr nutzen und damit auch nicht mehr auf WMI-Klassen zugreifen. Hier kommen dann die CIM-Commandlets zum Einsatz.

PS C:\Users\fcarius> Get-Command *-*cim*

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Cmdlet          Get-CimAssociatedInstance                          6.1.0.0    CimCmdlets
Cmdlet          Get-CimClass                                       6.1.0.0    CimCmdlets
Cmdlet          Get-CimInstance                                    6.1.0.0    CimCmdlets
Cmdlet          Get-CimSession                                     6.1.0.0    CimCmdlets
Cmdlet          Invoke-CimMethod                                   6.1.0.0    CimCmdlets
Cmdlet          New-CimInstance                                    6.1.0.0    CimCmdlets
Cmdlet          New-CimSession                                     6.1.0.0    CimCmdlets
Cmdlet          New-CimSessionOption                               6.1.0.0    CimCmdlets
Cmdlet          Register-CimIndicationEvent                        6.1.0.0    CimCmdlets
Cmdlet          Remove-CimInstance                                 6.1.0.0    CimCmdlets
Cmdlet          Remove-CimSession                                  6.1.0.0    CimCmdlets
Cmdlet          Set-CimInstance                                    6.1.0.0    CimCmdlets

Operatoren

Interessant sind auch Neuerungen bei Operatoren, die den Code kürzer und übersichtlicher werden lassen, wenn Sie denn die Befehle auch kennen. Hier ein paar Beispiele:

Alt Neu Beschreibung

if ($a -eq "test") {
  write-host "Gleich"
}
else {
  write-host "Ungleich"
}

($a -eq "test") ? (write-host "Gleich") : (Write-Host "Ungleich")

Verkürzt den Code für einfache IF-Anweisungen, die in eine Zeile passen.

get-item "user2 -ev err
if (!$err) {
  remove-item $user"
}

get-item "user" && remove-item "user"

Verkettung von zwei Befehlen, bei dem der zweite nur ausgeführt wird, wenn der erste Befehl fehlerfrei durchlief

get-item "user2 -ev err
if ($err) {
  new-item $user"
}

get-item "user" || new-item "user"

Verkettung von zwei Befehlen, bei dem der zweite nur ausgeführt wird, wenn der erste Befehl NICHT fehlerfrei durchlief

if ($null eq $var2) {
  $var1 = ?? "Ungenutzt"
}

$var1 = $var2 ?? "Ungenutzt"

Wenn $var2 nicht "$null" ist, dann wird sie $var1 zugewiesen. Ansonsten wird der Wert hinter dem "??" zugewiesen.

if ($null -eq $var1) {
  $var1 = "startwert"
}

$var1 ??= "startwert"

$var1 wird nur dann mit "startwert" belegt, wenn sie vorher $null ist.

Diese kleinen Verbesserungen sind wohl in anderen Programmiersprachen in der ein oder anderen Form schon vorhanden. Sie können den Code kürzer und übersichtlicher gestalten, wenn Sie denn die neuen Schreibweisen auch kennen.

Dauerlast

Interessant ist eine Dauerlast. Ich habe dazu ein paar Skripte aus meiner Ende zu Ende Monitoring-Kollektion über viele Wochen während der Corona laufen lassen.

Interessant war, dass die Prozesse kein Speicher gefressen haben und auch sonst sehr sparsam unterwegs sind.

Weitere Links