Archiv des Autors: PemoAdmin

Unterschied zwischen PowerShell und Python – kurz, knapp und sehr eindrucksvoll

Der Vergleich zwischen Programmiersprache A und Programmiersprache B ist im Allgemeinen wie der sprichwörtliche Vergleich zwischen Äpfel und Birnen. Zwei Programmiersprachen lassen sich einfach nicht objektiv vergleichen. Der folgende „Vergleich“ zwischen PowerShell und Python ist daher auch nicht ganz ernst gemeint.

Eine Stärke von Python ist, dass es keine echte Limitierung für die Größe von ganzen Zahlen gibt.

Der folgende Ausdruck liefert in Python eine Zahl, die in einem typischen Konsolenfenster ganze 245 Zeilen (!) umfasst.

Bei der PowerShell besteht das Ergebnis dagegen aus nur einem einzigen Zeichen. Mathematisch korrekt sind beide;)

Die PowerShellv erwendete für sehr große Zahlen eine praktische Abkürzung;)

Tipp des Tages: Source-Abfrage bei Get-WinEvent

Bei PowerShell 7 gibt es bekanntlich kein Get-EventLog-Cmdlet mehr, statt dessen gibt es das universelle Get-WinEvent-Cmdlet, das es bereits bei der Windows PowerShell gab. Das Cmdlet ist zwar insgesamt leistungsfähiger, aber auch nicht ganz so pflegeleicht, da Filterparameter entweder über eine Hashtable oder einen XPath-Ausdruck angegeben werden müssen.

Ich habe ein wenig suchen müssen, um das Pendant für den Source-Parameter von Get-Eventlog für Get-WinEvent ausfindig zu machen. Es ist in beiden Fällen die Angabe „Providername“ innerhalb des Suchausdrucks.

Die beiden folgenden Befehle geben die letzten 10 Einträge aus dem System-Eventlog zurück, die vom Service Control Manager geschrieben wurden.


Get-WinEvent -FilterHashtable @{Logname="System";Providername='Service Control Manager'} -MaxEvents 10

und


Get-WinEvent -FilterXPath "Event/System/Provider[@Name='Service Control Manager']" -LogName System -MaxEvents 10

Pester 3.4.0 entfernen

So genial Pester auch ist, ein kleiner Makel ist der Umstand, dass Windows 10 und WIndows Server mit einer Version vorinstalliert werden (ich habe es bereits irgendwo erwähnt – Pester war das erste Open Source-Programm, das fester Bestandteil einer Windows-Installation ist), die inzwischen vollkommen veraltet ist. Seit der Version 4.0 (aktuell ist die Version 5.1.1 – Stand Feb 2021) hat es viele Änderungen gegeben, so dass man mit der Originalversion nicht mehr arbeiten sollte.

Das Löschen des Modulverzeichnisses scheitert aber zunächst an dem Umstand, dass es sich aufgrund fehlender Berechtigungen nicht löschen lässt. Der TrustedInstaller lässt es nicht zu.

Man muss daher zunächst den Ordner als Administrator in Besitz nehmen und die TrustedInstaller-Berechtigungen entfernen. Auch wenn das alles sowohl im Explorer als auch per PowerShell möglich ist, am einfachsten geht es mit Hilfe der guten, alten Kommandozeilentools takeown und icacls.

Die folgende Befehlsfolge zeigt, wie es für ein bestimmtes Verzeichnis unter einem 32- und 64-Bit-Windows funktioniert.


$PesterModulPfad = "C:\Program Files\WindowsPowerShell\Modules\Pester\3.4.0"
if (Test-Path -Path $PesterModulPfad)
{
    takeown /F $PesterModulPfad /A /R
    icacls $PesterModulPfad /reset
    icacls $pesterPath /grant "*S-1-5-32-544:F" /inheritance:d /T
    Remove-Item -Path $PesterModulPfad -Recurse -Force -Confirm:$false # -WhatIf
}
else
{
    Write-Warning "!!! $PesterModulPfad existiert nicht !!!"
}

Auch wenn jede Menge Syntaxhilfe ausgegeben wird (irgendetwas scheint mit den Aufrufparametern noch nicht zu stimmen), wird das Pester 3.4.0-Verzeichnis am Ende entfernt.

Eine ausführlichere Abhandlung der Thematik kann man in den GitHub-Gists von Jakub Jareš nachlesen (ein „Gist“ ist beim Microsoft-GitHub-Portal die Bezeichnung für einen Codeschnipsel bzw. eine einzelne Datei).

Remove built-in version of Pester 3 (or -All) from Windows 10 Program Files and Program Files (x86). (github.com)

PS: Das Pester-Projekt hat schon seit längerem eine eigene Projektwebseite: https://pester.dev/

Tipp des Tages: Die Befehlshistorie nach bereits eingegebenen Befehlen durchsuchen

Dieser Tipp ist so trivial, dass er eigentlich keiner Erwähnung wert ist, aber mir hat er geholfen, so dass sich davon ausgehe, dass er auch anderen Powershell-Anwendern die tägliche Arbeit etwas erleichtert.

Dank PsReadlLine wird die Befehlshistory in einer separaten Datei abgelegt, so dass sie immer zur Verfügung steht (allerdings nur über die Pfeiltasten und nicht per Get-History – dieses Command listet nur die interne Befehlshistorie auf, die mit dem Beenden der Sitzung verloren ist). Das hat zur Folge, dass die Befehlshistorie der PowerShell durchaus einen Zeitraum von mehreren Wochen oder gar Monaten umfasst.

Möchte man einen Befehl ausführen, den man vor längerer Zeit bereits eingegeben und ausgeführt hatte, muss man lediglich per [F8] danach suchen. Die Suche besteht darin, die ersten zwei oder drei Zeichen des Befehls einzugeben und danach [F8] zu drücken. Die erste Befehlszeile, in der die Eingabe enthalten ist, wird in den Befehlspuffer geholt. Mit jedem [F8] wird der nächste Treffer geholt, per [Umschalt]+[F8] geht es wieder rückwärts.

Tipp des Tages: Hashtable-Property in Key-Value-Paare auflösen

Dieser Tipp klingt eventuell etwas speziell, ist aber für die Weiterverarbeitung bestimmter Rückgaben enorm praktisch. Einige Rückgaben von PowerShell-Commands bestehen aus einer einzigen Hashtable, erscheinen aber bei der Ausgabe wie viele Werte, da die Key-Value-Paare bei der Ausgabe automatisch aufgelöst werden.

Konkretes Beispiel: Auflisten der Umgebungsvariablen über [Environment]::GetEnvironmentVariables().

Auch wenn die Ausgabe nach vielen Werten aussieht, besteht sie nur aus einer Hashtable. Ein Sortieren nach dem Namen der Umgebungsvariablen ist daher nicht möglich.

Abhilfe schafft die universelle GetEnumerator()-Methode, welche die Elemente der Hashtable zurückgibt, und die einfach nur an die Ausgabe gehängt werden muss.

[Environment]::GetEnvironmentVariables().GetEnumerator() | Sort-Object Name

Ein Sortieren nach den Namen der Umgebungsvariablen ist damit möglich.

Ebenfalls sehr praktisch, das Auflisten aller dynamischen Parameter eines Cmdlets:

(Get-Command -Name Get-Content).Parameters.GetEnumerator() | Where-Object { $_.Value.IsDynmic}

Ohne GetEnumerator() wäre das deutlich aufwändiger, da man zuerst alle Keys durchlaufen müsste, um dann in der Wiederholung über den Key den Wert abzurufen. Dafür braucht man Zeit, Ausdauer und gute Nerven. Dank GetEnumerator() wird die Weiterverarbeitung sperriger Rückgaben powershell-typisch und damit einfach und konsistent.

Tipp des Tages: Ein Sql-Skript nach Tabellennamen gruppieren

Ausgangspunkt ist eine (sehr große) Sql-Skriptdatei, die mehrere Zehntausend Insert Into-Befehlszeilen enthält (es können auch ein paar mehr sein;). Insgesamt werden ca. ein Dutzend unterschiedliche Tabellen angesprochen.

Problem: Ich möchte die Namen aller Tabellen enthalten.

Lösung: Zerlegen der Datei per Select-String und einem einfachen Regex und gruppieren nach einer per Select-Object angelegten Property. Also, alles klassische PowerShell-Zutaten, die auch Anwendern geläufig sind, die die PowerShell nur sporadisch benutzen.

PS D:\Datenbanken\Select-String .\VGEFA.sql -Pattern "[dbo].[(\w+)]" | Select-Object @{n="TabName";e={$_.Matches[0].Groups[1].Value}} | Group-Object TabName

Tipp des Tages: Probleme mit der PowerShell Gallery – TLS 1.0 ist Schuld

Seit ein paar Monate habe ich „Probleme“ mit der PowerShell Gallery. Insbesondere ein Install-Module führte zu seltsamen Fehlermeldungen. Da ich eigentlich nur selten Module benötige, habe ich es nicht weiterverfolgt. Als dann aber die Installation des neuen PowerShellGet-Moduls einfach nicht möglich war und es so aussah, als wäre die PowerShell Gallery nicht nur sporadisch, sondern dauerhaft nicht mehr erreichbar, habe ich mich dann doch auf die Suche der Ursache begeben. die dann auch schnell gefunden war. Es lag offenbar wirklich daran, dass ich auf älteren Windows-Versionen immer noch TLS 1.0 verwende, der Microsoft-Server inzwischen aber TLS 1.2 erwartet.

Abhilfe schafft ein einfacher Aufruf, der als Standardprotokoll TLS 1.2 festlegt:

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

Danach funktionierte alles wie erwartet.

Fündig wurde ich in einem sehr guten Blockeintrag, bei dem sich der Autor deutlichmehr Mühe gemacht hatte als ich:

PowerShell-Remoting mit Localhost – Access Denied?

PowerShell Remoting ist alles andere als ein neues Thema und die Zeiten, in denen man die Troubleshooting-FAQs vorwärts und rückwärts lesen musste liegen schon ein paar Jahre zurück. Es funktioniert, sofern man es überhaupt benötigt.

Vor kurzem hatte ich wieder einen Fall, dass ein Invoke-Command mit Localhost auf einem Win10-PC einfach nicht funktionieren wollte (der TrustedHosts-Eintrag war natürlich gesetzt). Ich muss zugegeben, dass ich ohne Tante Google nicht so schnell (wenn überhaupt) darauf gekommen werden. Es lag wieder einmal an der LocalAccountTokenFilterPolicy, für die Eintrag in der Registry hinzugefügt werden muss:

reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v LocalAccountTokenFilterPolicy /t REG_DWORD /d 1 /f

Danach ging es auf einmal, ohne dass ich die PowerShell-Konsole neu starten musste. Interessant, dass der Blog-Eintrag, in dem ich die Lösung fand, aus dem Jahr 2020 datiert:

Enabling Powershell Remoting, Access is denied? – Fixya Cloud (wordpress.com)

Es scheint wohl, als ob das Thema immer noch aktuell ist.

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.