Archiv der Kategorie: Tipps

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: 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

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)