PowerShell-Challenge der Woche

Gesucht ist ein Befehl, der alle Common-Parameters eines Commands ausgibt, also z.B. Verbose, ErrorAction, WarningVariable usw.

Ich würde dazu die Metadaten eines Parameter-Objekts abfragen, die per Get-Command, dem Commandnamen und der Parameters-Property geliefertert werden.Die Parameters-Property ist eine „hässliche“ Hashtable, also muss sowohl die Parameters- als auch die Values-Properties expandiert werden.

Der folgende Befehl liefert alle Parameter einzeln:

Get-Command Get-Printjob | Select-Object -ExpandProperty Parameters | Select-Object -ExpandProperty Values

Wie an der Ausgabe zu erkennen ist, kommt bei einigen der Parameter-Objekte in der Attributes-Eigenschaft ein „CommonParameters“ vor. Dahinter steckt die TypeId-Property. DIe Frage ist daher, wie man nur solche Parameter erhält, bei denen das der Fall ist.

Das ist mein Versuch, der nach einer halben Stunde herumprobieren, herauskam (am Beispiel des Get-PrintJob-Commands):

Get-Command get-printjob | Select-Object -ExpandProperty Parameters | Select-Object -ExpandProperty Values | Where-Object { ($_.Attributes.TypeId.Where{$_ -match "Common"}).Count -eq 0 }

Es gibt sicher kürzere und elegantere Wege die Namen der Common-Parameters zu erhalten, zumal nicht alle Common Parameters einen System.Management.Automation.Internal.CommonParameters-Typ besitzen. Warum auch immer;)

Die Challenge ist damit nicht gelöst und lautet: Wie erhalte ich die Namen der Common Parameters eines Commands aus den Metadaten des Commands?

Natürlich sind die Common Parameters immer gleich und lassen sich allgemein über die Properties der Klasse System.Management.Automation.Internal.CommonParameters abfragen. Aber die Challange soll, sofern überhaupt möglich, über die Metadaten eines Commands gelöst werden.

Das „Geheimnis“ von Set-Culture

Wie viele Stunden habe ich schon damit verbracht herauszufinden, wie sich während der Ausführung eines PowerShell-Befehls die „Kultur“ vorübergehend ändern lässt? Die Frage kann natürlich außer mir niemand beantworten. Ich kenne die Antwort auch nicht, aber es waren einige Stunden (mehr als 3). Dabei ist alles sehr einfach.

Set-Culture setzt die Kultur auf die angegebene Einstellung für das aktuelle Benutzerkonto. Aber nicht in der aktuellen PowerShell-Sitzung, sondern erst in der nächsten. Ganz witzig ist auch, dass sich unmittelbar nach der Ausführung des Befehls sich das Datum im SysTray-Feld der Taskleiste an die neue Kultur anpasst. Praktisch ohne Verzögerung, was für einen PowerShell schon etwas ungewöhnlich ist.

Fazit: Das Set-Culture-Cmdlet funktioniert auch ohne irgendwelche Klimmzüge, aber nicht so wie man es vielleicht erwarten würde.

PowerShell-Know how als Teil der Windows-Problemlöser

Auf diesen Blog-Eintrag wollte ich schon seit Jahren hinweisen, da ich ihn sehr gut finde:

https://chentiangemalc.wordpress.com/2011/05/08/windows-7-can-teach-you-powershellinbuilt-wealth-of-scripts/

Der Autor hat sich die Mühe gemacht, die vielen PowerShell-Skripte, die unter der Haube der zahlreichen Diagnosehilfen von Windows ihren Dienst verrichten, aufzulisten und ihren Inhalt zu untersuchen.

Auch wenn, wie es PowerShell-Experte Jeffrey Hicks es in einem Kommentar korrekt anmerkt, einige (oder eher viele?) der vorgestellten Techniken veraltet bzw. nicht ganz optimal sind, zum Lernen der verschiedenen PowerShell-Techniken sind die Skripts dann doch gut geeignet.

Auf alle Fälle ist es erstaunlich, welche Fülle an PowerShell-Know how abseits der offiziellen Module bereits bei Windows 7 im Windows-Verzeichnis dabei war.

Systemdienste vom Typ Kernel Driver auflisten und Spezialfall npcap

Das Auflisten von Systemdiensten ist mit der PowerShell eigentlich sehr einfach, doch wie sooft lauern auch kleine „Stolperfallen“. Insbesondere der Netzwerkdienste npcap, der u.a. von Wireshark verwendet wird, fällt so richtig aus der Rolle und kann einem „gestressten“ Admin Nerven und vor allem Zeit kosten.

Eigentlich ist alles ganz einfach. Ein Get-Service listet alle vorhanden Systemdienste auf, die Eigenschaft ServiceType gibt an, ob es sich um einen prozessbasierten Dienst oder einen „Kernel Service“ handelt.

Der folgende Aufruf gibt für den Dienst npcap (der über Nmap isntalliert wird) auch die korrekte Bezeichnung zurück.

Npcap wird als Dienst vom Typ „KernelDriver“ ausgegeben.

Die erste „Überraschung“ entsteht beim Gruppieren aller Dienste nach der ServiceType-Property.

Der Grund für die Überraschung: „KernelDriver“ erscheint nicht als Gruppenname, wird also nicht abgefragt. Der Dienst ist auch in keiner der Gruppen enthalten. Stattdessen erscheinen als Gruppennamen Zahlen wie 224, 208 oder 240, bei denen es sich um zusammengesetzte Zweierpotenzen handelt (eventuell setzt sich der Wert für ServiceType bei einigen Diensten aus mehreren Zahlenwerten zusammen – dafür spricht, dass für den ServiceType dieser Dienste bei einer WMI-Abfrage per Win32_Service als „Unknown“ eingetragen wird).

Es bleibt mysteriös, denn die folgende Abfrage gibt nichts zurück;

Auf einmal gibt es keinen Dienst mit dem Namen npcap mehr (der Platzhalter funktioniert natürlich beim Name-Parameter von Get-Service). Dies könnte eventuell mit der Art und Weise zu tun haben, wie npcap in der Registry unter HKey_Local_Machine\System\CurrentControlSet\Services eingetragen wurde.

Eine Abfrage der Systemdienste funktioniert etwas besser per WMI:

Hier ist auch die Verwendung von Platzhaltern kein Problem:

Warum aber ein get-service npca* nichts zurückgibt, bleibt im Moment ein (weiteres) ungelöstes Rätsel (genauso, dass sich der Dienst auf meinem PC mit Windows 8.1 nicht starten lässt).

Praxistipp: Abfrage der zuletzt installierten Systemdienste

Anders als bei Anwendungen gibt es bei Diensten keinen Installationszeitpunkt, der sich über den Dienst selber abfragen ließe. Wenn man herausbekommen möchte, ob eine Anwendung auch einen Systemdienst installiert hat, geht dies sehr einfach über die Abfrage des System-Ereignisprotokolls. Am besten mit „Service Controll Manager“ als Quelle und eventuell auch gleich der InstanceId 1073748869.

Da ich vor einiger Zeit wieder einmal Wireshark intalliert hatte, wollte ich herausfinden, welcher Systemdienst während der Installation installiert wurde (ok, es ist natürlich Npcap, aber Kontrolle ist bekanntlich besser…)

Der folgende Befehl liefert schon ein erstes Ergebnis:

Für die Praxis wichtige Details zur Installationsort liefert die Eigenschaft ReplacementStrings:

Seltsame PowerShell-Phänome (Teil 45)

Manchmal kann das Arbeiten mit der PowerShell nervig oder faszinierend sein. Je nachdem, ob eine Anforderung unbedingt umsetzen möchte, oder man viel Zeit hat sich mit besonderen „Phänomen“ zu beschäftigen. Das folgende, eigentlich ganz simple Phänom befindet sich bei mir im Moment noch in der Kategorie ungelöst.

Ausgangspunkt ist das Abfragen der Inhalte einer PowerPoint-Datei über die DocumentFormat.OpenXml-Assembly aus dem Open XML SDK.

Die Anforderung ist die Ausgabe aller Slides bzw. deren Ids. Auch wenn die folgende Vorgehensweise nicht die Richtige ist, sollte doch ein konsistentes Verhalten resultieren.

Der folgende Befehl sollte alle „Abkömmlinge“ vom Typ „SlideId“ holen

Die Abfrage gibt aber nichts zurück. Es funktioniert, wenn anstelle des -eq-Operators der -match- oder -like-Operator verwendet wird:

Die Rückgabe besteht aus einem Objekt vom Typ SlideIdList und mehreren Objekten vom Typ SlideId. Irgendetwas scheint an dem Vergleich nicht zu stimmen.

Eigentlich sollte Where{} nur die Objekte liefern, die vom Typ „SlideId“ sind. Da es aber auch ein Objekt gibt, das vom Typ „SlideIdList“ ist, funktioniert der Vergleich mit dem eq-Operator nicht und es werden beim Vergleich mit „SlideId“ keine Objekte zurückgegeben.

Kurios ist, dass der folgende Vergleich funktioniert:

Diese Abfrage gibt für jeden Typnamen = „SlideId“ mit dem eq-Operator ein $true zurück. Es muss also „irgendwie“ mit dem Umstand zu tun haben, dass das erste Objekt vom Typ SlideIdList ist, die übrigen vom Typ SlideId und mit dem, was durch die Array-Konvertierung der Rückgabe von Descendants() (eine generische Collection) entstanden ist.

Da ich keine Lust habe, einen schönen Sonntagnachmittag mit der Lösung zu verbringen, gebe ich an dieser Stelle auf und bin gespannt, wann ich eine Erklärung für dieses (im Grunde natürlich vollkommen unwichtige) Phänom erhalte. Apropos unwichtige Kleinigkeit: Hätte sich Clifford Stoll irgendwann Ende der 80er Jahre nicht über eine Abrechnungsdifferenz von wenigen Cents gewundert, wäre er nicht einem der größten Hacker-Einbrüche in den USA auf die Spur gekommen.

PowerShell 7.0 unter Ubuntu – dank snap alles ganz einfach

Die PowerShell unter Linux zu installieren, ist grundsätzlich kein Problem, auf der Downloadseite ist die Installation bestens für alle Versionen und Varianten dokumentiert und gelingt daher auch Unix-NOOBS wie mir (wobei ich Unix seit den 80er Jahren kenne und eine noch ausgepackte Box mit Linux Suse 4.2 bei mir im Regal steht – das Interesse war also immer da;).

Dank snap gelingt die Installation inzwischen mit einem Aufruf – es muss kein Repo mehr registriert werden.

Der Aufruf sieht wie folgt aus:

sudo snap install powerShell --classic

Die Angabe „–classic“ ist aus für mich nicht mehr nachvollziehbaren Gründen offenbar erforderlich. Anschließend kann die PowerShell 7.0 (bzw. eine aktuellere Version) durch Eingabe von pwsh gestartet werden.

Auch bei der neuen PowerShell muss die Hilfe per Update-Help aktualisiert werden. Hier kommt es aber offenbar auf den Parameter -UICulture mit en-us als Parameterwert an. Anschließend zeigt die Hilfe dann auch die Beispiele an und alles sieht richtig gut aus. Auch wenn ich natürlich etwas voreingenommen bin, bin ich mir sicher, dass die PowerShell eine Bereicherung im (gigantischen) Linux-Ökosystem ist.

Praxistipp: Feststellen, ob alle Dateien mit einer Zahlenendung vorhanden sind

Die Anforderung: Ein Verzeichnis enthält viele Dateien, die zuvor (einzeln) aus dem Internet heruntergeladen wurde. Jede Datei endet mit einer aus drei Ziffern bestehenden Zahl, also z.B. Datei1.zip.001, Datei1.zip.002, Datei1.zip.003 usw. Da die Dateien alle per Mausklick aus einem per OneDrive Business (Sharepoint) freigegebenen Webverzeichnis heruntergeladen wurden, soll sichergestellt werden, dass tatsächlich alle Dateien heruntergeladen wurden. Dazu muss geprüft werden, ob alle Zahlen von 1..n am Ende des Dateinamens in der Zahlenfolge von 1..n enthalten sind.

Das ist natürlich eine ideale Gelegenheit, die eigenen PowerShell-Kenntnisse zu überprüfen.

Da in dem Befehl nicht nur eine Hashtable, sondern auch ein (Mini-) Regex vorkommen, ist die Schreibweise am Anfang vielleicht etwas speziell, aber wie immer ist sie eigentlich ganz einfach.

Das folgende Beispiel geht davon aus, dass sich im Verzeichnis F:\VIS 2020 Dateien mit den Endungen .001 bis .242 befinden. Sollte eine Datei fehlen, wird sie ausgegeben.

In der ersten Version meiner „genialen Idee“ hatte ich noch einen Denkfehler eingebaut. Jetzt sollte das Ergebnis aber stimmen und es werden nur die Nummern ausgegeben, für die es keine Datei gibt.

PowerShell 7-0 und SSH

PowerShell Remoting per SSH ist seit der ersten Version der PowerShell Core möglich und damit nichts Neues mehr. Wirklich gebraucht habe ich es bislang aber noch nicht. Der Newsletter von PowerShell-Experte Adam Betram („Adam the Automator“) hat mich dazu veranlasst, es nach langer Zeit wieder einmal selber auszuprobieren:

https://adamtheautomator.com/ssh-with-powershell/

Das ganze Thema ist sehr gut dokumentiert. Voraussetzung auf der Server-Seite ist natürlich SSH. Bei Linux und MacOS ist SSH fest eingebaut (ich gehe einmal davon aus, dass dies für jede Linux-Distribution gilt), bei Windows 10/Windows Server muss es in Gestalt von Open SSH for Windows eventuell nachträglich hinzugefügt werden:

https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_server_configuration

Der SSH-Server läuft als Systemdienst mit dem Namen „Sshd“, so dass sich sein Status per Get-Service einfach abfragen lässt. Durch die Installation des SSH Servers wird automatisch eine Firewall-Regel für den Port 22 angelegt.

Der SSH-Client ist im Folgenden eine PowerShell 7.0, die unter Ubuntu 16.04 läuft. Hier muss nichts konfiguriert werden.

Damit eine Verbindung mit dem SSH-Server möglich ist, muss auf dem Computer ein Eintrag zur Konfigurationsdatei sshd_config (ohne Erweiterung) im Verzeichnis C:\ProgramData\ssh hinzugefügt werden.

In einer als Administrator gestarten PowerShell wird die Datei wie folgt geöffnet:

Der Eintrag wird unterhalb von „override default of no subsystems“ eingefügt:

Abb. 1: PowerShell wird als Subsystem zum SSH Server hinzugefügt

Mehr ist nicht zu tun. Jetzt kann von einem Remote-Computer per PowerShell eine Remote-Verbindung per SSH hergestellt werden:

Der Parameter SSHTransport sorgt dafür, dass anstelle von WsMan SSH verwendet wird. „minipc“ ist in diesem Beispiel der Name des Windows-PCs mit SSH Server. Auch eine SSH-Session wird per exit verlassen.

Insgesamt ist es erfreulich, wie einfach sich eine PowerShell-Remoting-Session per SSH anlegen lässt.

Generische Methoden aufrufen

Eine generische Methode ist eine Methode, der neben den (optionalen) Parameterwerten beim Aufruf auch ein Typ übergeben wird. Alle generischen Datentypen, die innerhalb der Methode verwendet werden inklusive dem Rückgabewert (sofern vorhanden), verwenden dann diesen Typ. Damit wird mehr Flexibilität erreicht, da die Methode nicht für jeden in Frage kommenden Datentyp überladen werden muss.

Das klassische Beispiel für eine generische Methode void swap<T>(T arg1, T arg2), welche zwei als Referenz übergebene Werte vertauscht. Mit ihr lassen sich Werte eines beliebigen Datentyps vertauschen, da dieser beim Aufruf der Methode ebenfalls angegeben wird:

In PowerShell kann man keine generischen Functions definieren und es würde auch keinen Sinn ergeben. Generische Methoden kommen immer dann ins Spiel, wenn eine Funktionalität einer externen Assembly-Bibliothek genutzt werden soll.

Dabei muss zwischen dem Aufruf einer statischen Methode und einer Instanzmethode unterschieden werden.

Es gibt im Internet (seit 2007) natürlich sehr viele Lösungen, von denen die meisten auch gut funktionieren. Eine sehr gute Lösung ist das Skript von David Wyatt, das dieser über die TechNet Script Gallery zur Verfügung stellt:

https://gallery.technet.microsoft.com/Invoke-Generic-Methods-bf7675af/view/Discussions#content

Mir ist die Lösung etwas überdimensioniert. Für einfache Anforderungen ist sehr viel weniger formaler Aufwand erforderlich. Der Aufruf einer generischen Methode läuft darauf, dass man zuerst die Methode per GetMethods() als Objekt holt, daraus per MakeGenericMethod() eine generische Methode macht, die dann per Invoke() aufgerufen wird. Eine Parameterübergabe ist natürlich kein Problem.

Die folgenden zwei Functions stelle ich ohne weitere Erläuterungen vor.

Aufruf einer generischen Instanzenmethode

Aufruf einer generischen statischen Methode