Neues zur PowerShell – In Zukunft nur noch „PowerShell“

Auf der letzten Ignite-Konferenz von Microsoft (2019) gab es natürlich wieder einen „PowerShell Unplugged“-Vortrag von PowerShell-Erfinder Jeffrey Snover zusammen mit Jason Helmick, der inzwischen als Program Manager im PowerShell -Team arbeitet.

Das Anschauen lohnt sich wie immer, ein paar Details schon einmal vorab:

>Die nächste Version der PowerShell heißt nur noch „PowerShell“. Die Bezeichnung „PowerShell Core“ gibt es damit nicht mehr. Mit PowerShell 7 wird es eine PowerShell für alle Plattformen geben. Die vielen vertraute „Windows PowerShell“ wird es aber immer als Teil von Windows geben, auch wenn sie nicht mehr weiterentwickelt werden wird.

>Es ist bemerkenswert, wie nahtlos sich die PowerShell inzwischen in das Linux- und Mac OS-Ökosystem einfügt. Bei Kali Linux ist sie inzwischen Teil der Distribution, bei Mac OS kann die Preview über den AppStore installiert werden. Nett ist z.B auch, dass ein dir unter Linux die Ausgabe so aufbereitet wie es Bash-Nutzer gewohnt sind,

>Es gibt Verbesserungen, die auch langfähigen Nutzern gefallen wird. Kompaktere Fehlermeldungen, ein Get-Error-Command für das Abrufen der letzten Fehlerinformation, ein „Break on Error“, so dass ein Skript bei Auftreten eines Fehlers automatisch in den Debug-Modus übergeht und einiges mehr.

>Technisch ist die Parallelausführung von Commands natürlich ein echtes Highlight. Ob man beim Einrichten mehrerer VMs (wie oft kommt das vor?) jetzt aber vielleicht 3 Minuten statt 7 Minuten warten muss, sehe ich nicht als echte Verbesserung an. Aber generell hat das Features natürlich Potential.

Toll ist auch das neue Terminal-Programm, das als kleines Gimmick auch ein animiertes Gif anzeigen kann.

PowerShell 7 soll, wenn alles gut geht, im Januar 2020 offiziell wird.

Neben dem „Klassiker“ gab es auf der Ignite 2019 noch eine Reihe weiterer interessanter Sessions, die für PowerShell-User interessant sind: Eine Q&A mit Joe Aiello aus dem PowerShell-Team zu PowerShell 7, eine sehr interessante Session zum Zusammenspiel mit Microsoft Graph (es gibt ein neues PowerShell-Modul , aktuell noch als Preview, durch das einiges einfacher werden wird) und eine sehr gute Session von Martina Grom und Toni Pohl zu Office 365 Groups und PowerShell). Eine Suche im Ignite-Session-Katalog lohnt sich definitiv.

Tipp des Tages: Dienste entfernen auch ohne PowerShell

Triviale Aufgaben können ihre Tücken haben. Beispiel: Wie ich entferne ich einen Systemdienst, der sich auch per WMI nicht mehr entfernen lässt, da z.B. im onStop-Event eine Exception auftritt? Ganz einfach durch Beenden der Exe-Datei. Doch welche Exe-Datei ist es genau? Eine Kombination aus Sc.exe und Taskkill.exe löst das kleine Problem.

Warum Sc.exe und nicht Get-Service? Weil Ersteres mit dem queryex-Parameter auch die Prozess-ID mit ausgibt.

sc.exe queryex VISEFService

Anschließend genügt der Aufruf von

Taskkill /pid 1234 /F

wenn 1234 die Prozess-ID ist.

Natürlich geht das alles auch per PowerShell, aber inzwischen bin ich an dem Punkt angekommen wo es in erster Linie darum geht, dass ich nicht zu viel tippen muss (man wird älter und bequemer;). Und geht es nur darum, eine bestimmte Aufgabe zu erledigen, sind die alten Kommandozeilentools immer noch eine sehr gute Wahl.

Per PowerShell sähe der Aufruf in etwa so aus:

stop-process (Get-CimInstance Win32_Service -Filter "Name='VISEFService'").ProcessId

Es ist nicht unbedingt der Umfang der Zeile, sondern eher der andere Denkansatz, an den man sich erst gewöhnen muss. Im Moment habe ich eher das Gefühl, dass wir uns wieder zur klassischen „Kommandozeilen-Denkweise“ zurückbewegen.

PowerShell versus Python – GetMember versus dir()

Bei PowerShell ist Get-Member nicht umsonst eines der wichtigsten Cmdlets. Dank Get-Member erfährt man, welche Members eine Objekt besitzt. Und das ist bei einer Scripting-Umgebung, bei der alles ein Objekt ist, sehr viel wert.

Auch bei Python ist alles ein Objekt (eigentlich andersherum, da Python ca. 15 Jahre älter ist), leider gibt es ein direktes Pendant zu Get-Member (zumal es auch keine Pipeline gibt). Es gibt aber eine Function, die Get-Member relativ nahe kommt. Sie trägt den ungewöhnlichen Namen dir(). Warum ungewöhnlich? Weil dieser Name seit den Zeiten von GW-Basic für einen Befehl/Function steht, die einen Verzeichnisinhalt auflistet. Aber ganz unpassend ist der Name nicht.

Der folgende Befehl geht noch ein wenig weiter, in dem dank List Comprehensions nur Members eines Objekts ausgegeben werden, die nicht mit einem Unterstrich beginnen (damit wird die Ausgabe bereits etwas übersichtlicher).

[name for name in dir(hits) if not name.startswith("_")]

Die Ausgabe besteht natürlich nicht aus Objekten, sondern lediglich aus Strings. Aber immerhin.

In diesem Vergleich punktet PowerShell. Nicht nur, weil Get-Member mehr Möglichkeiten bietet, sondern weil bei der Rückgabe von dir() etwas Wichtiges fehlt: Die Metadaten über ein Member, also z.B. den Datentyp einer Property.

Tipp des Tages: Verzeichnisse aus der path-Umgebungsvariablen entfernen

Hin und wieder kann es erforderlich sein, einzelne Verzeichnisse aus der path-Umgebungsvariablen zu entfernen. Was sich zunächst schwierig anhören könnte, ist in der Praxis dann doch ein relativ einfacher Befehl.

Der folgende Befehl entfernt alle Verzeichnisse, die ein Python36 enthalten, aus der Path-Umgebungsvariablen.

$env:path = $env:path.split(";").where{$_ -notmatch "Python36"} -join ";"

Die Änderung gilt natürlich nur für die aktuelle PowerShell-Sitzung. Soll sie dauerhaft sein, muss der neue Wert der Umgebungsvariablen über die SetEnvironmentVariable()-Methode der Environment-Klasse für den aktuellen Benutzer oder systemweit gespeichert werden.

Praxistipp: Das Anmeldekonto für einen Systemdienst aktualisieren

Zu den wenigen Einstellungen, die für einen Systemdienst nicht per Set-Service vorgenommen werden können gehört das Ändern des System- oder Benutzerkontos, unter dem der Dienst ausgeführt werden soll. Diese Einstellung muss per WMI, der Klasse Win32_Service und ihrer Change()-Methode vorgenommen werden. Benutzername und Kennwort werden dabei im Klartext übergeben. Eine kleine Herausforderung besteht darin, dass für alle nicht verwendeten Parameter ein $null übergeben werden muss.

Bill Stewart hat ein schönes Skript erstellt ( Set-ServiceCredential.ps1), mit dem das Systemkonto für einen Systemdienst aktualisiert werden kann:

https://gist.github.com/Bill-Stewart/ab3a228903c5d6fb3c12dc1d92d3d1e8

Mir ist das Skript aber etwas zu umfangreich und formal. Die folgende Function erledigt die Einstellung etwas kürzer:

function Set-ServiceAccount { [CmdletBinding()] param([String]$Servicename, [String]$Username, [String]$Password) Stop-Service -Name $ServiceName $service = Get-WmiObject Win32_Service -Filter "Name='$ServiceName'" $ret = $service.change($null,$null,$null,$null,$null,$null,$Username,$Password,$null,$null,$null) if ($ret.ReturnValue -eq 0) { Write-Verbose "Kontoinformation wurde erfolgreich aktualisiert." Start-Service -Name $ServiceName } else { Write-Warning "Die Kontoinformation konnte nicht aktualisiert werden - ReturnValue=$($ret.ReturnValue)" } }

Ging alles gut, ist der ReturnValue = 0. Ansonsten eine Zahl, die einen Fehlercode darstellt. 22 bedeutet z.B., das das Konto nicht stimmt (in dem Skript von Bill Stewart werden alle Fehlercodes abgefragt und durch Fehlermeldungen ersetzt).

Tipp des Tages: Befehlsverlauf (History) durchsuchen mit PsReadline

Der Befehlsverlauf (History) der PowerShell-Konsole besitzt den (kleinen) Nachteil, das er nicht automatisch gespeichert wird und damit beim nächsten Start der Konsole nicht mehr vorhanden ist. Was nicht jeder PowerShell-Anwender wissen dürfte, sobald PSReadline verwendet wird, gibt es einen eigenen Verlauf, der über die Pfeiltasten abgerufen wird (nicht per Get-History). Praktisch ist, dass sich dieser Verlauf auch durchsuchen lässt.

Ex-Scripting Guy Ed Wilson hatte ja bereits schon 2014 (also vor mehr als 5 Jahren) darüber geschrieben:

Lange her, aber immer noch aktuell – der Umgang mit dem Befehlsverlauf der PowerShell-Konsole

Im Grunde läuft es auf den Tastaturshortcut [Strg]+[r] hinaus. Über ihn wird der Befehlsverlauf von PsReadline nach allen Zeilen rückwärts durchsucht, die mit der danach eingegebenen Zeichenfolge beginnen. Für jeden weiteren Treffer drückt man erneut [Strg]+[r]. Für was die Vorwärtssuche per [Strg]+[s] gut sein soll, ist mir dagegen nicht klar. Vielleicht hat jemand eine Erklärung.

Im „neuen“ PSReadline zeigt Get-PSReadlineKeyHandler die Tastaturshortcuts auch sehr übersichtlich an.

Tipp des Tages: Welche Prozesse lauschen an welchen Portnummern?

Der Klassiker neu aufgelegt. Anstatt den Textoutput von Netstat zerlegen zu müssen, geht es mit einer Kombination aus Get-Process und Get-NetTcpConnection etwas „einfacher“ (es kommt vor allem auf den Output an).

Registry-Suche per PowerShell – wieder einmal

Das Durchsuchen der Registry mit Get-ChildItem und Get-ChildItemProperty ist eigentlich nicht so schwer, wenn man sich ein paar Besonderheiten gewöhnt hat.

Die Anforderung ist schnell umschrieben. Eine Desktop-Anwendung werden sog. OCX-Controls. Das sind auf Dateien mit der Erweiterung .ocx basierende Programmkomponenten, die in der Registry enthalten sein müssen, damit sie von einer Anwendung verwendet werden können. Gesucht ist ein Befehl, der z.B. sämtliche Ocx-Controls auflistet. Eine kleine Herausforderung ergibt sich durch den Umstand, dass der Pfad der Ocx-Controls im (Default)-Eintrag eines Schlüssels abgelegt ist. Es gilt daher, diesen Eintrag anzusprechen (per „(Default)“, um es kurz zu machen;).

Der folgende Befehl listet alle Ocx-Einträge im Schlüssel Hkey_Local_Machine auf:

dir HKLM:\SOFTWARE\ -Recurse -ErrorAction Ignore | Get-ItemProperty -Name "(default)" -ErrorAction Ignore | Where-Object "(Default)" -like "*codejock*ocx" | Select-Object "(default)", PsPath

Etwas unschön ist natürlich der lange Pfad, der aus der PSPath-Eigenschaft resultiert. Mit einem Convert-Path lässt sich nur der relevante Teil abtrennen:

dir HKLM:\SOFTWARE\ -Recurse -ErrorAction Ignore | Get-ItemProperty -Name "(default)" -ErrorAction Ignore | Where-Object "(Default)" -like "*codejock*ocx" | Select-Object @{n="OcxPfad";e={$_."(default)"}},@{n="Regpfad";e={Convert-Path $_.PsPath}}

Eine naheliegende Erweiterung besteht natürlich daran, in einer weiteren Spalte anzugeben, ob die jeweilige Ocx-Datei auch existiert. Das lässt sich per Test-Path einfach feststellen.

dir HKLM:\SOFTWARE\ -Recurse -ErrorAction Ignore | Get-ItemProperty -Name "(default)" -ErrorAction Ignore | Where-Object "(Default)" -like "*codejock*ocx" | Select-Object @{n="Vorhanden";e={Test-Path $_.PsPath}}, @{n="OcxPfad";e={$_."(default)"}},@{n="Regpfad";e={Convert-Path $_.PsPath}}

Tipp des Tages: Die öffentliche IP-Adresse als Objekt erhalten

Auf https://techibee.com/powershell/powershell-get-public-ip-address-of-my-computer/3027 hat der Autor einen genauso praktischen wie anschaulichen Tipp veröffentlicht, den ich an dieser Stelle 1:1 wiedergebe.

Die Abfrage der öffentlichen IP-Adresse geht sehr einfach mit Hilfe eines kleinen Webservice unter der Adresse http://ipinfo.io.

Wie ruft man einen (REST-) Webservice per PowerShell auf? Natürlich per Invoke-RestMethod-Cmdlet:

Nett ist, dass der Aufruf in Bruchteilen einer Sekunde erstaunlich akkurat die Adresse des nächsten Telekom-Knotens liefert (entweder gibt es inzwischen einen in Esslingen und Umgebung oder diese Information wurde aus anderen Angaben abgeleitet). Da die Rückgabe ein Objekt ist, stehen Angaben wie Stadt und Land und natürlich die Ip als Eigenschaften zur Verfügung.

Python versus PowerShell – Folge 1

Bereits seit 1-2 Jahren ist Python meine neue „Lieblingssprache“, PowerShell verwende ich natürlich weiterhin, da sie Python für administrative Aufgaben aufgrund ihres „eingebauten“ Befehlssatzes in Gestalt der zahlreichen Module überlegen ist. Python hat viele Stärken, wäre aber für die Administration eines Exchange Server nur bedingt zu empfehlen.

Eine der Stärken von Python ist der Umgang mit Listen. PowerShell bietet eine ähnliche Stärke durch ihre Pipeline und dem Umstand, dass stets Objekte übertragen werden.

Die folgende kleine „Challenge“ ist grundsätzlich einfach. Ausgangspunkt sind Zeichenfolgen wie z.B. 2 UU/100 14 B ER. Das Ziel soll es sein, dass alle Zeichengruppe, die z.B. nur zwei Zeichen lang sind, zurückgegeben werden. Auch wenn ein Regex immer die flexibelste Lösung ist, in diesem Fall tut es auch die split-Methode, die sowohl in Python als auch in PowerShell gleich angewendet wird.

Zuerst die PowerShell-Lösung:

Bei Python gibt es die flexible List Comprehensions, durch die sich aus einer Liste eine neue Liste ableiten lässt.

Wer hat gewonnen? Weder Python noch PowerShell, Es ist ein typisches Unentschieden, wobei es bei diesen Vergleichen natürlich nicht darum geht, welche Sprache besser ist, sondern eher um eine „Umstiegshilfe“ an kleinen Beispielen. Die Gegenüberstellung macht deutlich, dass sich Python und PowerShell gar nicht so groß unterscheiden und insgesamt mehr Gemeinsamkeiten als Unterschiede besitzen. Während unter Windows nur weniger Anwender sich mit beiden Sprachen beschäftigen werden (auf einer der letzten PowerShell Europe-Konferenzen hat es einen Session zum Thema Python gegeben), die Frage stellt sich eher unter Linux wo Python als Allround-Werkzeug für die Shell deutlich verbreiteter sein dürfte.