Kapitel 1. Die interaktive PowerShell-Shell

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

1.0 Einleitung

Das Design der PowerShell legt den Schwerpunkt auf die Verwendung als effiziente und leistungsstarke interaktive Shell. Sogar die Skriptsprache spielt dabei eine wichtige Rolle, da auch sie die interaktive Nutzung stark begünstigt.

Was die meisten Leute überrascht, wenn sie die PowerShell zum ersten Mal starten, ist ihre Ähnlichkeit mit der Eingabeaufforderung, die schon lange Teil von Windows ist. Vertraute Tools werden weiterhin ausgeführt. Vertraute Befehle werden weiterhin ausgeführt. Sogar die vertrauten Hotkeys sind dieselben. Hinter dieser vertrauten Benutzeroberfläche verbirgt sich jedoch eine leistungsstarke Engine, mit der du einst mühsame Verwaltungs- und Skriptaufgaben mit Leichtigkeit erledigen kannst.

Dieses Kapitel stellt die PowerShell aus der Perspektive ihrer interaktiven Shell vor.

1.1 PowerShell Core installieren

Problem

Du möchtest die neueste Version der PowerShell auf deinem Windows-, Mac- oder Linux-System installieren.

Lösung

Auf der Microsoft-Website findest du die Installationsanweisungen für das Betriebssystem und die Plattform, auf der du die Software installieren möchtest. Für die gängigsten:

Windows

Installiere PowerShell von Microsoft über die Microsoft Store-Anwendung im Startmenü. Installiere dann Windows Terminal von Microsoft über die Microsoft Store-Anwendung im Startmenü.

Mac

Installiere PowerShell aus Homebrew:

brew install --cask powershell

Linux

Die Installationsanweisungen variieren von Linux-Distribution zu Linux-Distribution, aber die häufigste Distribution unter PowerShell Core-Nutzern ist Ubuntu:

# Update the list of packages
sudo apt-get update

# Install pre-requisite packages.
sudo apt-get install -y wget apt-transport-https software-properties-common

# Download the Microsoft repository GPG keys
wget -q https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb

# Register the Microsoft repository GPG keys
sudo dpkg -i packages-microsoft-prod.deb

# Update the list of packages after we added packages.microsoft.com
sudo apt-get update

# Install PowerShell
sudo apt-get install -y powershell

Diskussion

PowerShell hat bereits ein langes und aufregendes Leben hinter sich. In den ersten 15 Jahren ihres Bestehens war sie als "Windows PowerShell" bekannt: eine fantastische objektbasierte Verwaltungsshell und Plattform, die es Administratoren, Entwicklern und Power-Usern leicht machte, ihre Aufgaben zu erledigen.

In der Anfangsphase war diese Unterstützung Teil des "Windows Management Framework": ein eigenständiger Download, der diese dringend benötigte Funktionalität für Windows bereitstellte. Die Windows PowerShell wurde schließlich ein Teil von Windows selbst und ist seit Windows 7 ein Kernbestandteil des Betriebssystems.

2016 hat die PowerShell einen gewaltigen Wandel vollzogen, als sie ankündigte,die PowerShell auf mehreren Betriebssystemplattformen auszuliefern - und ganz nebenbei das gesamte Projekt gleichzeitig Open Source zu machen! Mit der neuen Zukunft bekam die Windows PowerShell einen neuen Namen: einfach PowerShell. Diese wichtige Änderung öffnete die Türen für wesentlich schnellere Innovationen, die Beteiligung der Community und die Verfügbarkeit auf Wegen, die vorher nicht möglich gewesen wären. Die klassische Windows PowerShell ist zwar immer noch standardmäßig im Betriebssystem enthalten, aber sie wird nicht mehr aktualisiert und sollte daher gemieden werden.

Installieren und Ausführen der PowerShell unter Windows

Wie in der Lösung erwähnt, ist der beste Weg, PowerShell über den Microsoft Store zu installieren. So ist sie einfach zu installieren und zu aktualisieren. Nach der Installation findest du PowerShell wie jede andereAnwendung im Startmenü.

Hinweis

Wenn du eine systemweite Version der PowerShell für die Automatisierung und andere Verwaltungsaufgaben installieren möchtest, wirst du wahrscheinlich den MSI-basierten Installationsmechanismus bevorzugen. Weitere Informationen findest du auf der Microsoft-Website.

Während du die PowerShell aus dem Microsoft Store installierst, ist jetzt ein guter Zeitpunkt, um die Windows Terminal-Anwendung aus dem Microsoft Store zu installieren. Die traditionelle Konsolenoberfläche (das Fenster, in dem die PowerShell läuft), die in Windows enthalten ist, hat so viele Tools und Anwendungen, die von ihren genauen Eigenheiten abhängen, dass es fast unmöglich ist, sie sinnvoll zu ändern. Deshalb ist die Windows Terminal-Anwendung aus dem Microsoft Store, wie in Abbildung 1-1 gezeigt, die Lösung. Wie auch die PowerShell ist sie Open Source, ein Fokus auf schnelle Innovation und eine enorme Verbesserung gegenüber dem, was standardmäßig in Windows enthalten ist.

wps4 0101
Abbildung 1-1. Windows Terminal mit PowerShell, Bash und sogar Azure Cloud Shell!

Du kannst viele Shells innerhalb von Tabs in Windows Terminal ausführen: PowerShell, Windows PowerShell, cmd.exe, Bash (wenn du das Windows Subsystem für Linux aktiviert hast) und sogar eine Verbindung zu Azure Cloud Shell. Windows Terminal verwendet standardmäßig die PowerShell, wenn du sie installiert hast.

PowerShell auf dem Windows Terminal anpassen

Es gibt zwei Änderungen an der Standardinstallation von Windows Terminal + PowerShell, die das Erlebnis wirklich verbessern: die Anheftung an die Taskleiste und die Themen.

Anheften der Taskleiste

Wenn du Windows Terminal startest, klicke mit der rechten Maustaste auf das Symbol in der Taskleiste. Wähle "An die Taskleiste anheften" und ziehe das Symbol dann ganz nach links in die Taskleiste. Wenn du von nun an die Windows-Taste und 1 gleichzeitig drückst, werden Windows Terminal und PowerShell entweder gestartet (wenn sie nicht bereits geöffnet sind) oder aktiviert. Auf diese Weise hast du deine Lieblingsshell immer griffbereit.

Themen

Windows PowerShell hat ein wunderschönes Noble Blue Theme. Es ist schön anzusehen, schnell zu erkennen und hebt sich von den Dutzenden anderer Shells ab. PowerShell Core hat dieses Farbschema nicht standardmäßig mitgenommen, aber es ist trotzdem möglich, deine Installation anzupassen. Drücke im Windows Terminal die Tastenkombination Strg+Komma oder klicke auf den Abwärtspfeil auf der rechten Seite des Tabstrips, um den Einstellungsdialog des Windows Terminals zu öffnen. Die Datei, die diese Einstellungen enthält, wird in deinem Standardtexteditor geöffnet. Suche unter Profiles das Element mit Windows.Terminal.​Power⁠shellCore als Quelle und füge Campbell Powershell als colorScheme hinzu. Das Ergebnis sollte so aussehen:

{
    "guid": ...
    "hidden": false,
    "name": "PowerShell",
    "colorScheme": "Campbell Powershell",
    "source": "Windows.Terminal.PowershellCore"
},

Achte auf Groß- und Kleinschreibung, Anführungszeichen, Doppelpunkte und Kommas, und deine PowerShell-Sitzungen sollten im Handumdrehen wieder edel aussehen!

Installieren und Ausführen der PowerShell auf Mac und Linux

Im Großen und Ganzen folgt die Installation der PowerShell auf Mac und Linux den Mustern, mit denen du wahrscheinlich schon vertraut bist.

Auf dem Mac ist die empfohlene Installationsmethode der beliebte Homebrew-Paketmanager. Homebrew ist unter macOS nicht standardmäßig installiert, aber die Installation ist ganz einfach. Wenn du Homebrew noch nicht installiert hast, findest du eine Anleitung auf der offiziellen Homebrew-Seite.

Unter Linux variieren die Installationsmethoden je nach Distribution, an der du interessiert bist. In den meisten Fällen ist die Installation so einfach wie das Registrieren des Microsoft-Repositorys für den Paketmanager deiner Distribution und die anschließende Installation der PowerShell. Die Lösung enthält ein Beispiel für Ubuntu 20.04, aber auf der Microsoft-Website findest du spezifische Anweisungen für deine Distribution und deine Version.

1.2 Programme, Skripte und vorhandene Tools ausführen

Problem

Du verlässt dich auf eine Menge Arbeit, die du in deine aktuellen Tools investiert hast. Du hast herkömmliche ausführbare Dateien, Perl-Skripte, VBScript und natürlich ein Legacy-Build-System, das organisch zu einem Wirrwarr von Batch-Dateien gewachsen ist. Du willst die PowerShell nutzen, aber du willst nicht alles aufgeben, was du bereits hast.

Lösung

Um ein Programm, Skript, eine Batch-Datei oder einen anderen ausführbaren Befehl im Systempfad auszuführen, gibst du den Dateinamen ein. Bei diesen ausführbaren Dateien ist die Erweiterung optional:

Program.exe arguments
ScriptName.ps1 arguments
BatchFile.cmd arguments

Um einen Befehl auszuführen, der ein Leerzeichen in seinem Namen enthält, schließe den Dateinamen in einfache Anführungszeichen ein (') und stelle dem Befehl ein kaufmännisches Und (&) voran, was in der PowerShell als Invoke-Operator bekannt ist:

& 'C:\Program Files\Program\Program.exe' arguments

Um einen Befehl im aktuellen Verzeichnis auszuführen, stellst du .\ vor den Dateinamen:

.\Program.exe arguments

Um einen Befehl mit Leerzeichen in seinem Namen aus dem aktuellen Verzeichnis heraus auszuführen, stellst du ihm ein kaufmännisches Und und .\ voran:

& '.\Program With Spaces.exe' arguments

Diskussion

In diesem Fall besteht die Lösung hauptsächlich darin, dass du deine aktuellen Tools wie bisher verwendest. Der einzige Unterschied ist, dass du sie in der interaktiven PowerShell und nicht in cmd.exe ausführst.

Angeben des Befehlsnamens

Die letzten drei Tipps in der Lösung verdienen besondere Aufmerksamkeit. Es handelt sich dabei um die Funktionen der PowerShell, über die viele neue Benutzer stolpern, wenn es um das Ausführen von Programmen geht. Der erste betrifft die Ausführung von Befehlen, die Leerzeichen enthalten. In cmd.exe wird ein Befehl mit Leerzeichen ausgeführt, indem er in Anführungszeichen gesetzt wird:

"C:\Program Files\Program\Program.exe"

In der PowerShell ist das Setzen von Text in Anführungszeichen jedoch Teil einer Funktion, mit der du komplexe Ausdrücke an der Eingabeaufforderung auswerten kannst, wie in Beispiel 1-1 gezeigt.

Beispiel 1-1. Ausdrücke an der Eingabeaufforderung der PowerShell auswerten
PS > 1 + 1
2
PS > 26 * 1.15
29.9
PS > "Hello" + " World"
Hello World
PS > "Hello World"
Hello World
PS > "C:\Program Files\Program\Program.exe"
C:\Program Files\Program\Program.exe
PS >

Ein Programmname in Anführungszeichen unterscheidet sich also nicht von jeder anderen Zeichenkette in Anführungszeichen. Er ist einfach ein Ausdruck. Wie bereits gezeigt, kannst du einen Befehl in einer Zeichenkette ausführen, indem du dieser Zeichenkette den Operator invoke (&) voranstellst. Wenn es sich bei dem auszuführenden Befehl um eine Batch-Datei handelt, die ihre Umgebung verändert, siehe Rezept 3.5.

Hinweis

Standardmäßig verhindern die Sicherheitsrichtlinien der PowerShell, dass Skripte ausgeführt werden. Sobald du jedoch anfängst, Skripte zu schreiben oder zu verwenden, solltest du diese Richtlinie so konfigurieren, dass sie weniger restriktiv ist. Informationen darüber, wie du deine Ausführungsrichtlinie konfigurierst, findest du in Rezept 18.1.

Der zweite Befehl, über den neue Benutzer (und erfahrene Veteranen vor dem Kaffee!) manchmal stolpern, ist das Ausführen von Befehlen aus dem aktuellen Verzeichnis. In cmd.exe wird das aktuelle Verzeichnis als Teil des Pfades betrachtet: die Liste der Verzeichnisse, die Windows durchsucht, um den von dir eingegebenen Programmnamen zu finden. Wenn du dich im Verzeichnis C:\Programme befindest, sucht cmd.exe (unter anderem) in C:\Programme nach Anwendungen, die ausgeführt werden sollen.

Wie die meisten Unix-Shells verlangt auch die PowerShell, dass du explizit angibst, dass du ein Programm aus dem aktuellen Verzeichnis ausführen möchtest. Dazu verwendest du die .\Program.exe Syntax, wie oben gezeigt. Dadurch wird verhindert, dass böswillige Benutzer deine Festplatte mit bösen Programmen übersäen, die ähnliche (oder gleiche) Namen wie die Befehle haben, die du beim Besuch des Verzeichnisses ausführen könntest.

Um sich die Eingabe des Speicherorts häufig verwendeter Skripte und Programme zu ersparen, legen viele Benutzer häufig verwendete Dienstprogramme zusammen mit ihren PowerShell-Skripten in einem "Tools"-Verzeichnis ab, das sie dem Pfad ihres Systems hinzufügen. Wenn PowerShell ein Skript oder Dienstprogramm im Systempfad finden kann, musst du seinen Speicherort nicht explizit angeben.

Wenn du möchtest, dass PowerShell automatisch in deinem aktuellen Arbeitsverzeichnis nach Skripten sucht, kannst du einen Punkt (.) an deine PATH-Umgebungsvariable anhängen.

Weitere Informationen zum Aktualisieren deines Systempfads findest du in Rezept 16.2.

Wenn du die Ausgabe eines Befehls festhalten willst, kannst du die Ergebnisse entweder in einer Variablen oder in einer Datei speichern. Um die Ergebnisse in einer Variablen zu speichern, siehe Rezept 3.3. Um die Ergebnisse in einer Datei zu speichern, siehe Rezept 9.2.

Angeben von Befehlsargumenten

Um Argumente für einen Befehl anzugeben, kannst du sie genau wie in anderen Shells eingeben. Um zum Beispiel eine bestimmte Datei schreibgeschützt zu machen (zwei Argumente für attrib.exe), gibst du einfach ein:

attrib +R c:\path\to\file.txt

Wenn es um Befehlsargumente geht, werden viele Skripter in die Irre geführt, wenn es darum geht, wie du sie in deinen Skripten ändern kannst. Wie bekommst du zum Beispiel den Dateinamen aus einer PowerShell-Variablen? Die Antwort ist, eine Variable zu definieren, die den Wert des Arguments enthält, und diese an der Stelle zu verwenden, an der du das Befehlsargument geschrieben hast:

$filename = "c:\path\to\other\file.txt"
attrib +R $filename

Du kannst die gleiche Technik verwenden, wenn du ein PowerShell-Cmdlet, -Skript oder eineFunktion aufrufst:

$filename = "c:\path\to\other\file.txt"
Get-Acl -Path $filename

Wenn du unter eine Lösung siehst, die das Cmdlet Invoke-Expression zum Zusammenstellen von Befehlsargumenten verwendet, ist sie mit großer Wahrscheinlichkeit falsch. Das Cmdlet Invoke-Expression nimmt die Zeichenfolge, die du ihm gibst, und behandelt sie wie ein vollständiges PowerShell-Skript. ist nur ein Beispiel für die Probleme, die dies verursachen kann: Dateinamen dürfen das Zeichen Semikolon (;) enthalten, aber wenn Invoke-Expression ein Semikolon sieht, nimmt es an, dass es sich um eine neue Zeile eines PowerShell-Skripts handelt. Probiere zum Beispiel Folgendes aus:

$filename = "c:\file.txt; Write-Warning 'This could be bad'"
Invoke-Expression "Get-Acl -Path $filename"

Da diese dynamischen Argumente oft aus Benutzereingaben stammen, kann die Verwendung von Invoke-Expression zum Zusammenstellen von Befehlen (im besten Fall) zu unvorhersehbaren Skriptergebnissen führen. Schlimmer noch, es könnte zu Schäden an deinem System oder zu einer Sicherheitslücke führen.

In der PowerShell kannst du nicht nur einzelne Argumente in Form von Variablen übergeben, sondern auch mehrere auf einmal, und zwar mit einer Technik, die als Splatting bekannt ist. Weitere Informationen zum Splatting findest du in Rezept 11.14.

1.3 Einen PowerShell-Befehl ausführen

Problem

Du möchtest einen PowerShell-Befehl ausführen.

Lösung

Um einen PowerShell-Befehl auszuführen, gibst du den Namen des Befehls in die Eingabeaufforderung ein. Zum Beispiel:

PS > Get-Process

 NPM(K)    PM(M)      WS(M)     CPU(s)      Id  SI ProcessName
 ------    -----      -----     ------      --  -- -----------
     14     3.47      10.55       0.00    6476   0 AGMService
     14     3.16      10.57       0.00    3704   0 AGSService
     37    40.12      40.51       2.06   17676   1 ApplicationFrameHost

Diskussion

Der Befehl Get-Process ist ein Beispiel für einen nativen PowerShell-Befehl, ein sogenanntes Cmdlet. Im Vergleich zu herkömmlichen Befehlen bieten Cmdlets sowohl für Administratoren als auch für Entwickler erhebliche Vorteile:

  • Sie haben eine gemeinsame und regelmäßige Kommandozeilen-Syntax.

  • Sie unterstützen umfangreiche Pipeline-Szenarien (Verwendung der Ausgabe eines Befehls als Eingabe für einen anderen).

  • Sie erzeugen eine einfach zu handhabende objektbasierte Ausgabe anstelle einer fehleranfälligen Klartextausgabe.

Da das Cmdlet Get-Process eine umfangreiche objektbasierte Ausgabe erzeugt, kannst du die Ausgabe für viele prozessbezogene Aufgaben verwenden.

Bei jedem PowerShell-Befehl kannst du über die Parameter Eingaben für den Befehl machen. Weitere Informationen zur Eingabe von Befehlen findest du unter "Befehle ausführen".

Das Cmdlet Get-Process ist nur einer von vielen, die PowerShell unterstützt. In Rezept 1.12 erfährst du, wie du weitere von der PowerShell unterstützte Befehle finden kannst.

Weitere Informationen zur Arbeit mit Klassen aus dem .NET Framework findest du in Rezept 3.8.

1.4 Fehler beim Aufrufen nativer Ausführungsprogramme beheben

Problem

Du hast eine Befehlszeile, die über cmd.exe funktioniert, und möchtest Fehler beheben, die beim Ausführen dieses Befehls in der PowerShell auftreten.

Lösung

Schließe alle betroffenen Befehlsargumente in einfache Anführungszeichen ein, um zu verhindern, dass sie von der PowerShell interpretiert werden, und ersetze alle einfachen Anführungszeichen im Befehl durch zwei einfache Anführungszeichen:

PS > cmd /c echo '!"#$%&''()*+,-./09:;<=>?@AZ[\]^_`az{|}~'
!"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~

Für komplizierte Befehle, bei denen das nicht funktioniert, verwende die Syntax für wörtliche Argumente(--% ):

PS > cmd /c echo 'quotes' "and" $variables @{ etc = $true }
quotes and System.Collections.Hashtable

PS > cmd --% /c echo 'quotes' "and" $variables @{ etc = $true }
'quotes' "and" $variables @{ etc = $true }

Diskussion

Eines der wichtigsten Ziele der PowerShell war schon immer die Konsistenz der Befehle. Aus diesem Grund sind Cmdlets sehr regelmäßig in der Art und Weise, wie sie Parameter akzeptieren. Native Cmdlets schreiben ihr eigenes Parameter-Parsing, sodass du nie weißt, was dich bei der Arbeit mit ihnen erwartet. Außerdem bietet die PowerShell viele Funktionen, die deine Arbeit in der Befehlszeile effizienter machen: Befehlssubstitution, Variablenexpansion und vieles mehr. Da viele native ausführbare Dateien vor der Entwicklung der PowerShell geschrieben wurden, verwenden sie möglicherweise Sonderzeichen, die mit diesen Funktionen in Konflikt stehen.

Als Beispiel verwendet der Befehl in der Lösung alle Sonderzeichen, die auf einer typischen Tastatur verfügbar sind. Ohne die Anführungszeichen behandelt PowerShell einige von ihnen als Sprachmerkmale, wie in Tabelle 1-1 gezeigt.

Tabelle 1-1. Beispiele für Sonderzeichen
Sonderzeichen Bedeutung

"

Der Anfang (oder das Ende) des zitierten Textes

#

Der Anfang eines Kommentars

$

Der Anfang einer Variable

&

Der Pipeline-Betreiber im Hintergrund

( )

Klammern werden für Unterausdrücke verwendet

;

Statement Separator

{ }

Skript-Block

|

Rohrleitungsabscheider

`

Escape-Zeichen

Wenn sie von einfachen Anführungszeichen umgeben sind, akzeptiert PowerShell diese Zeichen wie geschrieben, ohne die besondere Bedeutung.

Trotz dieser Vorsichtsmaßnahmen kann es vorkommen, dass du auf einen Befehl stößt, der nicht funktioniert, wenn er von der PowerShell aufgerufen wird. In den meisten Fällen kannst du diese Probleme mit dem Verbatim-Argument-Marker (--%) lösen, der verhindert, dass PowerShell die restlichen Zeichen in der Zeile interpretiert. Du kannst diese Markierung an einer beliebigen Stelle in den Befehlsargumenten platzieren, damit du die PowerShell-Konstrukte nutzen kannst, wo es sinnvoll ist. Im folgenden Beispiel wird für einige der Befehlsargumente eine PowerShell-Variable verwendet, für den Rest jedoch die wörtlichen Argumente:

PS > $username = "Lee"
PS > cmd /c echo Hello $username with 'quotes' "and" $variables @{ etc = $true }
Hello Lee with quotes and System.Collections.Hashtable
PS > cmd /c echo Hello $username `
     --% with 'quotes' "and" $variables @{ etc = $true }
Hello Lee with 'quotes' "and" $variables @{ etc = $true }

In diesem Modus akzeptiert die PowerShell auch Umgebungsvariablen im Stil von cmd.exe, da diese häufig in Befehlen verwendet werden, die "früher einfach funktioniert haben":

PS > $env:host = "localhost"
PS > ping %host%
Ping request could not find host %host%. Please check the name and try again.
PS > ping --% %host%

Pinging localhost [127.0.1.1] with 32 bytes of data:
(...)

1.5 Standardwerte für Parameter bereitstellen

Problem

Du möchtest einen Standardwert für einen Parameter in einem PowerShell-Befehl festlegen.

Lösung

Füge einen Eintrag in die Hashtabelle PSDefaultParameterValues hinzu:

PS > Get-Process

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    150      13     9692       9612    39    21.43    996 audiodg
   1013      84    45572      42716   315     1.67   4596 WWAHost
(...)

PS > $PSDefaultParameterValues["Get-Process:ID"] = $pid
PS > Get-Process

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    584      62   132776     157940   985    13.15   9104 powershell

PS > Get-Process -Id 0

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
      0       0        0         20     0               0 Idle

Diskussion

In der PowerShell haben viele Befehle (Cmdlets und erweiterte Funktionen) Parameter, mit denen du ihr Verhalten konfigurieren kannst. Eine vollständige Beschreibung der Eingabe von Befehlen findest du unter "Befehle ausführen". Manchmal ist es jedoch umständlich oder wiederholend, die Werte für diese Parameter bei jedem Aufruf einzugeben.

In frühen Versionen der PowerShell war es die Aufgabe der Cmdlet-Autoren, umständliche oder sich wiederholende Konfigurationseigenschaften zu erkennen und Unterstützung für "Einstellungsvariablen" in das Cmdlet selbst einzubauen. Das Cmdlet Send-MailMessage sucht zum Beispiel nach der Variable $PSEmailServer, wenn du keinen Wert für seinen Parameter angibst.-SmtpServer Parameter angibst.

Um diese Unterstützung einheitlicher und konfigurierbar zu machen, unterstützt die PowerShell die Einstellungsvariable PSDefaultParameterValues. Diese Einstellungsvariable ist eine Hashtabelle. Wie alle anderen PowerShell-Hashtables bestehen die Einträge aus zwei Teilen: dem Schlüssel und dem Wert.

Die Schlüssel in der Hashtabelle PSDefaultParameterValues müssen dem Muster cmdlet:parameter-das heißt, ein Cmdlet-Name und ein Parametername, getrennt durch einen Doppelpunkt. Für jeden (oder beide) können Platzhalter verwendet werden, und Leerzeichen zwischen Befehlsname, Doppelpunkt und Parameter werden ignoriert.

Die Werte für die cmdlet/Parameterpaare können entweder ein einfacher Parameterwert (eine Zeichenkette, ein boolescher Wert, eine ganze Zahl usw.) oder ein Skriptblock sein. Die einfachen Parameterwerte wirst du am häufigsten verwenden.

Wenn du möchtest, dass sich der Standardwert dynamisch ändert, je nachdem, welche Parameterwerte bisher angegeben wurden, kannst du einen Skriptblock als Standardwert verwenden. Wenn du das tust, wertet die PowerShell den Skriptblock aus und verwendet sein Ergebnis als Standardwert. Wenn dein Skriptblock kein Ergebnis zurückgibt, wendet PowerShell keinen Standardwert an.

Wenn PowerShell deinen Skriptblock aufruft, enthält $args[0] Informationen über alle bisher gebundenen Parameter: BoundDefaultParameters, BoundParameters, und BoundPositionalParameters. Ein Beispiel hierfür ist die Angabe von Standardwerten für den Parameter -Credential auf der Grundlage des Computers, mit dem eine Verbindung besteht. Hier ist eine Funktion, die einfach die verwendeten Anmeldeinformationen ausgibt:

function RemoteConnector
{
    param(
        [Parameter()]
        $ComputerName,

        [Parameter(Mandatory = $true)]
        $Credential)

    "Connecting as " + $Credential.UserName
}

Jetzt kannst du eine Credential Map definieren:

PS > $credmap = @{}
PS > $credmap["RemoteComputer1"] = Get-Credential
PS > $credmap["RemoteComputer2"] = Get-Credential

Erstelle dann eine Parametervorgabe für alle Credential Parameter, die sich auf denComputerName gebundenen Parameter:

$PSDefaultParameterValues["*:Credential"] = {
    if($args[0].BoundParameters -contains "ComputerName")
    {
        $cred = $credmap[$PSBoundParameters["ComputerName"]]
        if($cred) { $cred }
    }
}

Hier ist ein Beispiel dafür, wie das funktioniert:

PS > RemoteConnector -ComputerName RemoteComputer1
Connecting as UserForRemoteComputer1
PS > RemoteConnector -ComputerName RemoteComputer2
Connecting as UserForRemoteComputer2
PS > RemoteConnector -ComputerName RemoteComputer3

cmdlet RemoteConnector at command pipeline position 1
Supply values for the following parameters:
Credential: (...)

Weitere Informationen zur Arbeit mit Hashtables in der PowerShell findest du unter "Hashtables (Assoziative Arrays)".

1.6 Aufrufen eines lang laufenden oder im Hintergrund laufenden Befehls

Problem

Du willst einen langlaufenden Befehl auf einem lokalen oder entfernten Computer aufrufen.

Lösung

Invoke den Befehl als Job, damit die PowerShell ihn im Hintergrund ausführt:

PS > Start-Job { while($true) { Get-Random; Start-Sleep 5 } } -Name Sleeper

Id              Name            State      HasMoreData     Location
--              ----            -----      -----------     --------
1               Sleeper         Running    True            localhost

PS > Receive-Job Sleeper
671032665
1862308704
PS > Stop-Job Sleeper

Oder, wenn Ihr Befehl eine einzelne Pipeline ist, setzen Sie ein & Zeichen am Ende der Zeile ein, um diese Pipeline im Hintergrund auszuführen:

PS > dir c:\windows\system32 -recurse &

Id     Name            PSJobTypeName   State         HasMore
                                                     Data
--     ----            -------------   -----         -------
1      Job1            BackgroundJob   Running       True

PS > 1+1
2

PS > Receive-Job -id 1 | Select -First 5

    Directory: C:\Windows\System32

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----           12/7/2019  1:50 AM                0409
d----           11/5/2020  7:09 AM                1028
d----           11/5/2020  7:09 AM                1029
d----           11/5/2020  7:09 AM                1031
d----           11/5/2020  7:09 AM                1033

Diskussion

Die Auftrags-Cmdlets der PowerShell bieten eine einheitliche Methode zur Erstellung und Interaktion mit Hintergrundaufgaben. In der Lösung verwenden wir das Cmdlet Start-Job, um einen Hintergrundauftrag auf dem lokalen Computer zu starten. Wir geben ihm den Namen Sleeper, aber ansonsten passen wir die Ausführungsumgebung kaum an.

Unter kannst du nicht nur den Auftragsnamen anpassen, sondern mit dem Cmdlet Start-Job auch den Auftrag unter anderen Benutzeranmeldeinformationen oder als 32-Bit-Prozess starten (wenn er ursprünglich von einem 64-Bit-Prozess ausgeführt wurde).

Als Alternative zum cmdlet Start-Job kannst du auch das cmdlet Start-ThreadJob verwenden. Das Cmdlet Start-ThreadJob ist etwas schneller beim Starten von Hintergrundaufträgen und ermöglicht es dir außerdem, Live-Objekte in den von dir erstellten Aufträgen bereitzustellen und mit ihnen zu interagieren. Es verbraucht jedoch Ressourcen deines aktuellen PowerShell-Prozesses und ermöglicht es dir nicht, deinen Auftrag unter alternativen Benutzeranmeldeinformationen auszuführen.

Sobald du einen Auftrag gestartet hast, kannst du die anderen Cmdlets von Job verwenden, um mit ihm zu interagieren:

Get-Job

Ruft alle Aufträge ab, die mit der aktuellen Sitzung verbunden sind. Außerdem kannst du mit den Parametern -Before,-After, -Newest und -State kannst du die Aufträge nach ihrem Status oder ihrer Abschlusszeit filtern.

Wait-Job

Wartet auf auf einen Auftrag, bis die Ausgabe zum Abruf bereit ist.

Receive-Job

Ruft alle Ausgaben ab, die der Auftrag seit dem letzten Aufruf von Receive-Job erzeugt hat.

Stop-Job

Stoppt einen Auftrag.

Remove-Job

Entfernt einen Auftrag aus der Liste der aktiven Aufträge.

Hinweis

In kannst du neben dem Cmdlet Start-Job auch den Parameter -AsJob in vielen Cmdlets verwenden, damit diese ihre Aufgaben im Hintergrund ausführen. Zwei der nützlichsten Beispiele sind das Cmdlet Invoke-Command (für den Einsatz auf entfernten Computern) und das Cmdlet ForEach-Object.

Wenn dein Auftrag einen Fehler erzeugt, zeigt dir das Cmdlet Receive-Job diesen an, wenn du die Ergebnisse erhältst, wie in Beispiel 1-2 gezeigt. Wenn du diese Fehler genauer untersuchen möchtest, zeigt das von Get-Job zurückgegebene Objekt sie über die Eigenschaft Error an.

Beispiel 1-2. Abrufen von Fehlern aus einem Auftrag
PS > Start-Job -Name ErrorJob { Write-Error Error! }

Id              Name            State      HasMoreData     Location
--              ----            -----      -----------     --------
1               ErrorJob        Running    True            localhost


PS > Receive-Job ErrorJob
Write-Error: Error!

PS > $job = Get-Job ErrorJob
PS > $job | Format-List *

State         : Completed
HasMoreData   : False
StatusMessage :
Location      : localhost
Command       :  Write-Error Error!
JobStateInfo  : Completed
Finished      : System.Threading.ManualResetEvent
InstanceId    : 801e932c-5580-4c8b-af06-ddd1024840b7
Id            : 1
Name          : ErrorJob
ChildJobs     : {Job2}
Output        : {}
Error         : {}
Progress      : {}
Verbose       : {}
Debug         : {}
Warning       : {}

PS > $job.ChildJobs[0] | Format-List *
State         : Completed
StatusMessage :
HasMoreData   : False
Location      : localhost
Runspace      : System.Management.Automation.RemoteRunspace
Command       :  Write-Error Error!
JobStateInfo  : Completed
Finished      : System.Threading.ManualResetEvent
InstanceId    : 60fa85da-448b-49ff-8116-6eae6c3f5006
Id            : 2
Name          : Job2
ChildJobs     : {}
Output        : {}
Error         : {Microsoft.PowerShell.Commands.WriteErrorException,Microso
                ft.PowerShell.Commands.WriteErrorCommand}
Progress      : {}
Verbose       : {}
Debug         : {}
Warning       : {}


PS > $job.ChildJobs[0].Error
Write-Error: Error!

PS >

Wie dieses Beispiel zeigt, sind Aufträge manchmal Container für andere Aufträge, so genannte Unteraufträge. Aufträge, die mit dem Cmdlet Start-Job erstellt werden, sind immer Unteraufträge, die an einen generischen Container angehängt sind. Um auf die Fehler zuzugreifen, die von diesen Aufträgen zurückgegeben werden, greifst du stattdessen auf die Fehler im ersten Unterauftrag zu (Unterauftrag Nummer Null).

Neben lang laufenden Aufträgen, die unter der Kontrolle der aktuellen PowerShell-Sitzung ausgeführt werden, möchtest du vielleicht auch Aufträge registrieren und steuern, die nach einem Zeitplan oder unabhängig von der aktuellen PowerShell-Sitzung ausgeführt werden. Die PowerShell verfügt über eine Handvoll Befehle, mit denen du mit solchen geplanten Aufträgen arbeiten kannst; weitere Informationen findest du in Rezept 27.14.

1.7 Programm: Einen Befehl auf Änderungen überwachen

So aufregend unser Leben auch sein mag, manche Tage bestehen darin, einen Befehl immer und immer wieder auszuführen. Wurden die Dateien schon fertig kopiert? Ist der Bau abgeschlossen? Ist die Website noch online?

Normalerweise erhältst du die Antwort auf diese Fragen, indem du einen Befehl ausführst, dir seine Ausgabe ansiehst und dann entscheidest, ob er deine Kriterien erfüllt. In der Regel bedeutet das, dass du einfach darauf wartest, dass sich die Ausgabe ändert, dass ein Text erscheint oder dass ein Text verschwindet.

Zum Glück automatisiert Beispiel 1-3 diesen mühsamen Prozess für dich.

Beispiel 1-3. Watch-Befehl.ps1
##############################################################################
##
## Watch-Command
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################

<#

.SYNOPSIS

Watches the result of a command invocation, alerting you when the output
either matches a specified string, lacks a specified string, or has simply
changed.

.EXAMPLE

PS > Watch-Command { Get-Process -Name Notepad | Measure } -UntilChanged
Monitors Notepad processes until you start or stop one.

.EXAMPLE

PS > Watch-Command { Get-Process -Name Notepad | Measure } -Until "Count    : 1"
Monitors Notepad processes until there is exactly one open.

.EXAMPLE

PS > Watch-Command {
     Get-Process -Name Notepad | Measure } -While 'Count    : \d\s*\n'
Monitors Notepad processes while there are between 0 and 9 open
(once number after the colon).

#>

[CmdletBinding(DefaultParameterSetName = "Forever")]
param(
    ## The script block to invoke while monitoring
    [Parameter(Mandatory = $true, Position = 0)]
    [ScriptBlock] $ScriptBlock,

    ## The delay, in seconds, between monitoring attempts
    [Parameter()]
    [Double] $DelaySeconds = 1,

    ## Specifies that the alert sound should not be played
    [Parameter()]
    [Switch] $Quiet,

    ## Monitoring continues only while the output of the
    ## command remains the same.
    [Parameter(ParameterSetName = "UntilChanged", Mandatory = $false)]
    [Switch] $UntilChanged,

    ## The regular expression to search for. Monitoring continues
    ## until this expression is found.
    [Parameter(ParameterSetName = "Until", Mandatory = $false)]
    [String] $Until,

    ## The regular expression to search for. Monitoring continues
    ## until this expression is not found.
    [Parameter(ParameterSetName = "While", Mandatory = $false)]
    [String] $While
)

Set-StrictMode -Version 3

$initialOutput = ""
$lastCursorTop = 0
Clear-Host

## Start a continuous loop
while($true)
{
    ## Run the provided script block
    $r = & $ScriptBlock

    ## Clear the screen and display the results
    $buffer = $ScriptBlock.ToString().Trim() + "`r`n"
    $buffer += "`r`n"
    $textOutput = $r | Out-String
    $buffer += $textOutput

    [Console]::SetCursorPosition(0, 0)
    [Console]::Write($buffer)

    $currentCursorTop = [Console]::CursorTop
    $linesToClear = $lastCursorTop - $currentCursorTop
    if($linesToClear -gt 0)
    {
        [Console]::Write((" " * [Console]::WindowWidth * $linesToClear))
    }

    $lastCursorTop = [Console]::CursorTop
    [Console]::SetCursorPosition(0, 0)

    ## Remember the initial output, if we haven't
    ## stored it yet
    if(-not $initialOutput)
    {
        $initialOutput = $textOutput
    }

    ## If we are just looking for any change,
    ## see if the text has changed.
    if($UntilChanged)
    {
        if($initialOutput -ne $textOutput)
        {
            break
        }
    }

    ## If we need to ensure some text is found,
    ## break if we didn't find it.
    if($While)
    {
        if($textOutput -notmatch $While)
        {
            break
        }
    }

    ## If we need to wait for some text to be found,
    ## break if we find it.
    if($Until)
    {
        if($textOutput -match $Until)
        {
            break
        }
    }

    ## Delay
    Start-Sleep -Seconds $DelaySeconds
}

## Notify the user
if(-not $Quiet)
{
    [Console]::Beep(1000, 1000)
}

Weitere Informationen zum Ausführen von Skripten findest du in Rezept 1.2.

1.8 Benachrichtige dich selbst über die Fertigstellung des Auftrags

Problem

Du willst dich benachrichtigen lassen, wenn ein lang laufender Auftrag abgeschlossen ist.

Lösung

Verwende den Befehl Register-TemporaryEvent aus Rezept 31.3, um dich für das Ereignis StateChanged zu registrieren:

PS > $job = Start-Job -Name TenSecondSleep { Start-Sleep 10 }
PS > Register-TemporaryEvent $job StateChanged -Action {
     [Console]::Beep(100,100)
     Write-Host "Job #$($sender.Id) ($($sender.Name)) complete."
}

PS > Job #6 (TenSecondSleep) complete.
PS >

Diskussion

Wenn ein Auftrag abgeschlossen ist, wird ein Ereignis StateChanged ausgelöst, um die Abonnenten zu benachrichtigen, dass sich sein Status geändert hat. Wir können die PowerShell-Cmdlets zur Ereignisbehandlung verwenden, um uns für Benachrichtigungen über dieses Ereignis zu registrieren, aber sie sind nicht auf diese Art der einmaligen Ereignisbehandlung ausgerichtet. Um das Problem zu lösen, verwenden wir den Befehl Register-TemporaryEvent aus Rezept 31.3.

In unserem Beispiel-Aktionsblock in der Lösung geben wir einfach einen Piepton aus und schreiben eine Nachricht, dass der Auftrag abgeschlossen ist.

Du kannst auch deine prompt Funktion aktualisieren, um Aufträge zu markieren, die abgeschlossen sind, aber noch nicht verarbeitet wurden:

$psJobs = @(Get-Job -State Completed | ? { $_.HasMoreData })
if($psJobs.Count -gt 0) {
    ($psJobs | Out-String).Trim() | Write-Host -Fore Yellow }

Weitere Informationen über Ereignisse und diese Art der automatischen Ereignisbehandlung findest du in Kapitel 31.

1.9 Anpassen von Shell, Profil und Eingabeaufforderung

Problem

Du möchtest die interaktive Erfahrung der PowerShell mit einer personalisierten Eingabeaufforderung, Aliasen und mehr anpassen.

Lösung

Wenn du Aspekte der PowerShell anpassen möchtest, platzierst du diese Anpassungen in deinem persönlichen Profilskript. Die PowerShell ermöglicht einen einfachen Zugriff auf dieses Profilskript, indem sie seinen Speicherort in der Variablen $profile speichert.

Hinweis

Standardmäßig verhindern die Sicherheitsrichtlinien der PowerShell, dass Skripte (einschließlich deines Profils) ausgeführt werden. Sobald du jedoch anfängst, Skripte zu schreiben, solltest du diese Richtlinie so konfigurieren, dass sie weniger restriktiv ist. Informationen darüber, wie du deine Ausführungsrichtlinie konfigurierst, findest du in Rezept 18.1.

So erstellst du ein neues Profil (und überschreibst eines, wenn es bereits existiert):

New-Item -type file -force $profile

Um dein Profil zu bearbeiten (in Visual Studio Code, wenn du es installiert hast):

code $profile

Um deine Profildatei zu sehen:

Get-ChildItem $profile

Sobald du ein Profilskript erstellt hast, kannst du eine Funktion namens prompt hinzufügen, die eine Zeichenkette zurückgibt. Die PowerShell zeigt die Ausgabe dieser Funktion als Eingabeaufforderung in der Befehlszeile an:

function prompt
{
    "PS [$env:COMPUTERNAME] >"
}

Diese Eingabeaufforderung zeigt den Namen deines Computers an und sieht aus wie PS [LEE-DESK] >.

Vielleicht findest du es auch hilfreich, deinem Profil Aliase hinzuzufügen. Mit Aliasen kannst du häufige Befehle mit einem von dir gewählten Namen bezeichnen. Mit persönlichen Profilskripten kannst du automatisch Aliase, Funktionen, Variablen oder andere Anpassungen definieren, die du interaktiv über die Eingabeaufforderung der PowerShell festlegen kannst. Aliase gehören zu den gängigsten Anpassungen, da sie es dir ermöglichen, PowerShell-Befehle (und deine eigenen Skripte) mit einem Namen zu versehen, der sich leichter eingeben lässt.

Hinweis

Wenn du einen Alias für einen Befehl definieren willst, aber auch die Parameter dieses Befehls ändern musst, dann definiere stattdessen eine Funktion. Weitere Informationen findest du in Rezept 11.14.

Zum Beispiel:

Set-Alias new New-Object
Set-Alias browse 'C:\Users\lee\AppData\Local\Microsoft\*\MicrosoftEdge.exe'

Deine Änderungen werden wirksam, sobald du dein Profil speicherst und die PowerShell neu startest. Alternativ kannst du dein Profil auch sofort neu laden, indem du diesen Befehl ausführst:

. $profile

Funktionen sind ebenfalls sehr häufig anzutreffende Anpassungen, wobei die beliebteste die Funktion prompt ist.

Diskussion

In der Lösung werden drei Techniken vorgestellt, mit denen du deine PowerShell-Umgebung sinnvoll anpassen kannst: Aliase, Funktionen und eine handschriftlich angepasste Eingabeaufforderung. Du kannst diese Techniken jederzeit während deiner PowerShell-Sitzung anwenden (und wirst es auch oft tun), aber dein Profilskript ist der Standardort für Anpassungen, die du in jeder Sitzung anwenden möchtest.

Hinweis

Um einen Alias oder eine Funktion zu entfernen, verwendest du das Cmdlet Remove-Item:

Remove-Item function:\MyCustomFunction
Remove-Item alias:\new

Obwohl die Funktion prompt eine einfache Zeichenkette zurückgibt, kannst du die Funktion auch für komplexere Aufgaben verwenden. Viele Benutzer aktualisieren zum Beispiel den Titel ihres Konsolenfensters (indem sie die Variable $host.UI.RawUI.WindowTitle ändern) oder verwenden das Cmdlet Write-Host , um die Eingabeaufforderung in Farbe auszugeben. Wenn deine Eingabeaufforderung die Bildschirmausgabe selbst übernimmt, muss sie trotzdem eine Zeichenkette zurückgeben (z. B. ein einzelnes Leerzeichen), damit PowerShell nicht die Standardeinstellung verwendet. Wenn du nicht möchtest, dass dieses zusätzliche Leerzeichen in deiner Eingabeaufforderung erscheint, füge ein zusätzliches Leerzeichen am Ende deines Write-Host Befehls ein und gib das Backspace ("`b") Zeichen zurück, wie in Beispiel 1-4 gezeigt.

Beispiel 1-4. Ein Beispiel für eine PowerShell Eingabeaufforderung
##############################################################################
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################

Set-StrictMode -Version 3

function prompt
{
    $id = 1
    $historyItem = Get-History -Count 1
    if($historyItem)
    {
        $id = $historyItem.Id + 1
    }

    Write-Host -ForegroundColor DarkGray "`n[$(Get-Location)]"
    Write-Host -NoNewLine "PS:$id > "
    $host.UI.RawUI.WindowTitle = "$(Get-Location)"

    "`b"
}

Unter zeigt diese Eingabeaufforderung nicht nur die aktuelle Position, sondern auch die ID des Befehls in deinem Verlauf an. So kannst du frühere Befehle relativ einfach finden und aufrufen:

[C:\]
PS:73 >5 * 5
25

[C:\]
PS:74 >1 + 1
2

[C:\]
PS:75 >Invoke-History 73
5 * 5
25

[C:\]
PS:76 >

Obwohl das Profil, auf das $profile verweist, dasjenige ist, das du fast immer verwenden wirst, unterstützt die PowerShell eigentlich vier verschiedene Profilskripte. Weitere Einzelheiten zu diesen Skripten (und anderen Optionen zur Anpassung der Shell) findest du unter "Allgemeine Anpassungspunkte".

1.10 Anpassen des Benutzereingabeverhaltens der PowerShell

Problem

Du möchtest die Art und Weise, wie die PowerShell Eingaben an der Eingabeaufforderung liest und verarbeitet, außer Kraft setzen.

Lösung

Verwende das Set-PSReadLineOption cmdlet, um Eigenschaften wie EditMode (Windows, VI, Emacs) und die Verlaufsverwaltung zu konfigurieren. Zum Beispiel, um die Fortsetzungszeile für unvollständige Eingaben etwas roter als sonst zu machen:

Set-PSReadLineOption -Colors @{ ContinuationPrompt = "#663333" }

Verwende den Befehl Set-PSReadLineKeyHandler, um zu konfigurieren, wie PSReadLine auf deine aktuellen Tastendrücke reagiert. So kannst du zum Beispiel die Vorwärts- und Rückwärtsnavigation im Verzeichnisverlauf für Alt+Comma und Alt+Period hinzufügen:

Set-PSReadLineKeyHandler -Chord 'Alt+,' -ScriptBlock {
    Set-Location -
    [Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
    [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()
}

Set-PSReadLineKeyHandler -Chord 'Alt+.' -ScriptBlock {
    Set-Location +
    [Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
    [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()
}

Diskussion

Als die PowerShell zum ersten Mal auftauchte, waren die Unixer unter den ersten, die es bemerkten. Sie hatten schon seit Jahren eine leistungsfähige Shell und ein starkes Erbe der Automatisierung - und "wenn ich gezwungen bin, Windows zu benutzen, ist die PowerShell der Hammer", ist ein Satz, den wir oft gehört haben.

Diese natürliche Übernahme war kein Fehler. Viele im Team kommen aus dem Unix-Umfeld, und die Ähnlichkeiten mit traditionellen Unix-Shells waren beabsichtigt. Trotzdem hören wir von Leuten, die von anderen Shells kommen, immer noch gelegentlich, dass sich die eine oder andere Funktion seltsam anfühlt. Alt+P startet nicht das integrierte Auslagerungsprogramm? Strg+XX bewegt sich nicht zwischen dem Anfang der Zeile und der aktuellen Cursorposition? Abscheulich!

In den frühen Versionen der PowerShell gab es nichts, was du sinnvollerweise tun konntest, um dieses Problem zu lösen. In diesen Versionen las die PowerShell ihre Eingaben von der Konsole im so genannten Cooked Mode, in demdas Windows-Konsolen-Subsystem alle Tastendrücke, ausgefallene F7-Menüs und mehr verarbeitet. Wenn du die Eingabetaste oder die Tabulatortaste drückst, erhält die PowerShell den Text, den du bisher eingegeben hast, aber das war's auch schon. Sie weiß nicht, dass du die (Unix-ähnlichen) Tasten Strg+R, Strg+A, Strg+E oder eine andere Taste gedrückt hast.

In späteren Versionen der PowerShell sind die meisten dieser Fragen mit der Einführung des fantastischen PSReadLine-Moduls, das die PowerShell für die Befehlszeileneingabe verwendet, verschwunden. PSReadLine bietet umfangreiche Syntaxhervorhebung, Tabulatorvervollständigung, Verlaufsnavigation und mehr.

Mit dem Modul PSReadLine kannst du es in einem unglaublichen Ausmaß konfigurieren. DasSet-PSReadLineOption cmdlet unterstützt Optionen für die Benutzeroberfläche, den Eingabemodus, die Verlaufsverarbeitung und vieles mehr:

EditMode                       BellStyle
ContinuationPrompt             CompletionQueryItems
HistoryNoDuplicates            WordDelimiters
AddToHistoryHandler            HistorySearchCaseSensitive
CommandValidationHandler       HistorySaveStyle
HistorySearchCursorMovesToEnd  HistorySavePath
MaximumHistoryCount            AnsiEscapeTimeout
MaximumKillRingCount           PromptText
ShowToolTips                   ViModeIndicator
ExtraPromptLineCount           ViModeChangeHandler
DingTone                       PredictionSource
DingDuration                   Colors

In kannst du nicht nur das Laufzeitverhalten konfigurieren, sondern auch die Reaktion auf deine Tastendrücke festlegen. Um alle Verhaltensweisen zu sehen, die du dem Tastendruck zuordnen kannst, rufe Get-PSReadLineKeyHandler auf. PSReadLine bietet seitenweise Optionen - viele davon sind derzeit keinem Tastendruck zugeordnet:

PS > Get-PSReadLineKeyHandler

Basic editing functions
=======================

Key              Function           Description
---              --------           -----------
Enter            AcceptLine         Accept the input or move to the next line if
                                    input is missing a closing token.
Shift+Enter      AddLine            Move the cursor to the next line without
                                    attempting to execute the input
Backspace        BackwardDeleteChar Delete the character before the cursor
Ctrl+h           BackwardDeleteChar Delete the character before the cursor
Ctrl+Home        BackwardDeleteLine Delete text from the cursor to the start of
                                    the line
Ctrl+Backspace   BackwardKillWord   Move the text from the start of the current
                                    or previous word to the cursor to the kill
                                    ring
Ctrl+w           BackwardKillWord   Move the text from the start of the current
                                    or previous word to the cursor to...
(...)

Um für eine dieser Funktionen zu konfigurieren, verwende den Befehl Set-PSReadLineKeyHandler. Um zum Beispiel Ctrl+Shift+C so einzustellen, dass farbige Bereiche des Puffers in die Zwischenablage aufgenommen werden, führe den Befehl aus:

Set-PSReadLineKeyHandler -Chord Ctrl+Shift+C -Function CaptureScreen

Wenn es unter keine vordefinierte Funktion gibt, die das tut, was du willst, kannst du mit dem-ScriptBlock Parameter verwenden, damit PSReadLine einen beliebigen Code deiner Wahl ausführt, wenn du eine Taste oder Tastenkombination drückst. Das Beispiel in der Lösung demonstriert dies, indem es die Navigation im Verzeichnisverlauf vorwärts und rückwärts ermöglicht.

Damit diese Änderungen bestehen bleiben, fügst du einfach diese Befehle zu deinem PowerShell-Profil hinzu.

Obwohl eigentlich nur für extrem fortgeschrittene Szenarien gedacht ist, da PSReadLine fast alles abdeckt, was du jemals brauchen könntest, kannst du diese Funktionalität mit der Funktion PSConsoleHostReadLine noch weiter anpassen oder erweitern. Wenn du diese Methode im PowerShell-Konsolenhost definierst, ruft die PowerShell diese Funktion anstelle der standardmäßigen Cooked Mode-Eingabefunktion von Windows auf. Die Standardversion dieser Funktion ruft den PSReadLine ReadLine input handler auf. Wenn du diese Funktion jedoch komplett neu definieren möchtest, ist das alles - der Rest ist dir überlassen. Wenn du eine benutzerdefinierte Eingabemethode implementieren möchtest, liegt die Freiheit (und die Verantwortung) ganz bei dir.

Wenn du diese Funktion definierst, muss sie die Benutzereingabe verarbeiten und den resultierenden Befehl zurückgeben. Beispiel 1-5 implementiert einen etwas lächerlichen Notepad-basierten Benutzereingabemechanismus:

Beispiel 1-5. Ein Notepad-basierter Benutzereingabemechanismus
function PSConsoleHostReadLine
{
    $inputFile = Join-Path $env:TEMP PSConsoleHostReadLine
    Set-Content $inputFile "PS > "

    ## Notepad opens. Enter your command in it, save the file,
    ## and then exit.
    notepad $inputFile | Out-Null
    $userInput = Get-Content $inputFile
    $resultingCommand = $userInput.Replace("PS >", "")
    $resultingCommand
}

Unter findest du weitere Informationen zum Umgang mit Tastatureingaben und anderen Formen der Benutzereingabe, siehe Kapitel 13.

1.11 Anpassen des Befehlsauflösungsverhaltens der PowerShell

Problem

Du möchtest den Befehl, den die PowerShell aufruft, außer Kraft setzen oder anpassen, bevor er aufgerufen wird.

Lösung

Weisen Sie einen Skriptblock einer oder allen PreCommandLookupAction, PostCommand​Loo⁠kupAction oder CommandNotFoundAction Eigenschaften von $executionContext.SessionState.InvokeCommand zu. Beispiel 1-6 ermöglicht eine einfache Navigation im übergeordneten Verzeichnis, wenn Sie mehrere Punkte eingeben.

Beispiel 1-6. Einfache Navigation im übergeordneten Pfad durch CommandNotFoundAction ermöglichen
##############################################################################
##
## Add-RelativePathCapture
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################

<#

.SYNOPSIS

Adds a new CommandNotFound handler that captures relative path
navigation without having to explicitly call 'Set-Location'

.EXAMPLE

PS C:\Users\Lee\Documents>..
PS C:\Users\Lee>...
PS C:\>

#>

Set-StrictMode -Version 3

$executionContext.SessionState.InvokeCommand.CommandNotFoundAction = {
    param($CommandName, $CommandLookupEventArgs)

    ## If the command is only dots
    if($CommandName -match '^\.+$')
    {
        ## Associate a new command that should be invoked instead
        $CommandLookupEventArgs.CommandScriptBlock = {

            ## Count the number of dots, and run "Set-Location .." one
            ## less time.
            for($counter = 0; $counter -lt $CommandName.Length - 1; $counter++)
            {
                Set-Location ..
            }

        ## We call GetNewClosure() so that the reference to $CommandName can
        ## be used in the new command.
        }.GetNewClosure()

        ## Stop going through the command resolution process. This isn't
        ## strictly required in the CommandNotFoundAction.
        $CommandLookupEventArgs.StopSearch = $true
    }
}

Diskussion

Wenn du einen Befehl in der PowerShell aufrufst, durchläuft die Engine drei verschiedene Phasen:

  1. Rufe den Text des Befehls ab.

  2. Finde den Befehl für diesen Text.

  3. Rufe den Befehl auf, der gefunden wurde.

In der PowerShell kannst du mit der Eigenschaft $executionContext.SessionState.InvokeCommand jede dieser Phasen mit Skriptblöcken überschreiben, um eine oder alle Phasen abzufangen.PreCommandLookupAction, PostCommandLookupAction oder CommandNotFoundAction abzufangen.

Jeder Skriptblock erhält zwei Parameter: den Befehlsnamen und ein Objekt(CommandLookupEventArgs ), das das Verhalten der Befehlssuche steuert. Wenn dein Handler einen Skriptblock der Eigenschaft CommandScriptBlock des Command​Looku⁠pEventArgs zuweist oder ein CommandInfo der Eigenschaft Command des CommandLookupEventArgs zuweist, verwendet PowerShell diesen Skriptblock bzw. diesen Befehl. Wenn dein Skriptblock die Eigenschaft StopSearch auf true setzt, führt PowerShell keine weitere Befehlsauflösung durch.

PowerShell ruft den Skriptblock PreCommandLookupAction auf, wenn sie den Namen eines Befehls kennt (z. B. Get-Process), aber noch nicht nach dem Befehl selbst gesucht hat. Du kannst diese Aktion außer Kraft setzen, wenn du in erster Linie auf den Text des Befehlsnamens reagieren oder die reguläre Befehls- oder Aliasauflösung der PowerShell umgehen möchtest. Beispiel 1-7 demonstriert eine PreCommandLookupAction, die nach Befehlen mit einem Sternchen vor dem Namen sucht. Wenn er einen findet, aktiviert er den-Verbose Parameter.

Beispiel 1-7. Anpassen der PreCommandLookupAction
$executionContext.SessionState.InvokeCommand.PreCommandLookupAction = {
    param($CommandName, $CommandLookupEventArgs)

    ## If the command name starts with an asterisk, then
    ## enable its Verbose parameter
    if($CommandName -match "\*")
    {
        ## Remove the leading asterisk
        $NewCommandName = $CommandName -replace '\*',''

        ## Create a new script block that invokes the actual command,
        ## passes along all original arguments, and adds in the -Verbose
        ## parameter
        $CommandLookupEventArgs.CommandScriptBlock = {
            & $NewCommandName @args -Verbose

        ## We call GetNewClosure() so that the reference to $NewCommandName
        ## can be used in the new command.
        }.GetNewClosure()
    }
}

PS > dir > 1.txt
PS > dir > 2.txt
PS > del 1.txt
PS > *del 2.txt
VERBOSE: Performing operation "Remove file" on Target "C:\temp\tempfolder\2.txt".

Nachdem die PowerShell den PreCommandLookupAction ausgeführt hat (falls er existiert und keinen Befehl zurückgibt), durchläuft sie ihre reguläre Befehlsauflösung. Wenn sie einen Befehl findet, ruft sie den Skriptblock auf, der mit PostCommandLookupAction verknüpft ist. Du kannst diese Aktion außer Kraft setzen, wenn du in erster Linie auf einen Befehl reagieren willst, der gerade aufgerufen werden soll. Beispiel 1-8 zeigt ein PostCommandLookupAction, das die von dir am häufigsten verwendeten Befehle auflistet.

Beispiel 1-8. Anpassen der PostCommandLookupAction
$executionContext.SessionState.InvokeCommand.PostCommandLookupAction = {
    param($CommandName, $CommandLookupEventArgs)

    ## Stores a hashtable of the commands we use most frequently
    if(-not (Test-Path variable:\CommandCount))
    {
        $global:CommandCount = @{}
    }

    ## If it was launched by us (rather than as an internal helper
    ## command), record its invocation.
    if($CommandLookupEventArgs.CommandOrigin -eq "Runspace")
    {
        $commandCount[$CommandName] = 1 + $commandCount[$CommandName]
    }
}

PS > Get-Variable commandCount
PS > Get-Process -id $pid
PS > Get-Process -id $pid
PS > $commandCount

Name                           Value
----                           -----
Out-Default                    4
Get-Variable                   1
prompt                         4
Get-Process                    2

Wenn die Befehlsauflösung nicht erfolgreich ist, ruft die PowerShell den Skriptblock CommandNotFound​Ac⁠tion auf, sofern ein solcher vorhanden ist. Im einfachsten Fall kannst du diese Aktion außer Kraft setzen, wenn du das Fehlerverhalten der PowerShell, wenn sie einenBefehl nicht finden kann, aufheben oder überschreiben möchtest.

Als fortgeschrittene Anwendung kannst du mit CommandNotFoundAction PowerShell-Erweiterungen schreiben, die ihr Verhalten anhand der Form des Namens und nicht anhand der übergebenen Argumente ändern. Du könntest zum Beispiel URLs automatisch starten, wenn du sie eingibst, oder zwischen Anbietern navigieren, indem du relative Pfadangaben eingibst.

Die Lösung enthält ein Beispiel für die Implementierung dieser Art von Handler. Die dynamische relative Pfadnavigation ist zwar keine eingebaute Funktion der PowerShell, aber es ist möglich, eine sehr vernünftige Alternative zu erhalten, indem man die CommandNotFoundAction abfängt. Wenn wir einen fehlenden Befehl sehen, der ein Muster hat, das wir behandeln wollen (eine Reihe von Punkten), geben wir einen Skriptblock zurück, der die entsprechende relative Pfadnavigation durchführt.

1.12 Einen Befehl finden, um eine Aufgabe zu erledigen

Problem

Du möchtest eine Aufgabe in der PowerShell erledigen, kennst aber nicht den Befehl oder das Cmdlet, um diese Aufgabe zu erledigen.

Lösung

Verwende das Cmdlet Get-Command, um nach Befehlen zu suchen und sie zu untersuchen.

Um die Zusammenfassung eines bestimmten Befehls zu erhalten, gibst du den Befehlsnamen als Argument an:

Get-Command CommandName

Um detaillierte Informationen über einen bestimmten Befehl zu erhalten, leiten Sie die Ausgabe vonGet-Command an das Cmdlet Format-List weiter:

Get-Command CommandName | Format-List

Um nach allen Befehlen zu suchen, deren Name Text enthält, umrandest du den Text mit Sternchen:

Get-Command *text*

Um nach allen Befehlen zu suchen, die das Verb Get verwenden, gibst du Get an den-Verb Parameter an:

Get-Command -Verb Get

Um nach allen Befehlen zu suchen, die auf einen Dienst wirken, verwende Service als Wert für den Parameter-Noun:

Get-Command -Noun Service

Diskussion

Einer der Vorteile, die PowerShell Administratoren bietet, ist die Konsistenz der Befehlsnamen. Alle PowerShell-Befehle ( Cmdlets genannt) folgen einem regelmäßigen Verb-Nomen-Muster - zum Beispiel Get-Process, Get-Service und Set-Location. Die Verben stammen aus einer relativ kleinen Gruppe von Standardverben (siehe Anhang J) und beschreiben, welche Aktion das Cmdlet ausführt. Die Substantive sind spezifisch für das Cmdlet und beschreiben, worauf das Cmdlet wirkt.

Wenn du diese Philosophie kennst, kannst du leicht lernen, mit Gruppen von Cmdlets zu arbeiten. Wenn du einen Dienst auf dem lokalen Rechner starten willst, ist das Standardverb dafür Start. Am besten versuchst du es zuerst mit Start-Service (was in diesem Fall richtig wäre), aber auch die Eingabe von Get-Command -Verb Start ist ein guter Weg, um zu sehen, welche Dinge du starten kannst. Umgekehrt kannst du sehen, welche Aktionen von Diensten unterstützt werden, indem du Get-Command -Noun Service eingibst.

Wenn du das Cmdlet Get-Command verwendest, gibt die PowerShell Ergebnisse aus der Liste aller auf deinem System verfügbaren Befehle zurück. Wenn du stattdessen nur nach Befehlen aus Modulen suchen möchtest, die du entweder explizit oder durch automatisches Laden geladen hast, verwende den-ListImported Parameter. Weitere Informationen über das automatische Laden von Befehlen in der PowerShell findest du in Rezept 1.28.

In Rezept 1.13 findest du eine Liste aller Befehle und eine kurze Beschreibung ihrer Funktionen.

Das Cmdlet Get-Command ist einer der drei Befehle, die du am häufigsten verwenden wirst, wenn du die PowerShell kennenlernst. Die anderen beiden Befehle sind Get-Help und Get-Member.

gibt es einen wichtigen Punkt, den du beachten musst, wenn du nach einem PowerShell-Befehl suchst, um eine bestimmte Aufgabe zu erledigen. Oft gibt es diesen PowerShell-Befehl gar nicht, weil die Aufgabe am besten auf dieselbe Art und Weise wie bisher erledigt wird- zum Beispiel ipconfig.exe, um IP-Konfigurationsinformationen abzurufen, netstat.exe, um Protokollstatistiken und aktuelle TCP/IP-Netzwerkverbindungen aufzulisten, und viele mehr.

Weitere Informationen über das Cmdlet Get-Command findest du, indem du Get-Help Get-Command.

Siehe auch

Rezept 1.13

1.13 Hilfe zu einem Befehl erhalten

Problem

Du willst lernen, wie ein bestimmter Befehl funktioniert und wie man ihn benutzt.

Lösung

Der Befehl, der Hilfe und Nutzungsinformationen zu einem Befehl liefert, heißt Get-Help. Er unterstützt verschiedene Ansichten der Hilfeinformationen, je nach deinen Bedürfnissen.

Um eine Zusammenfassung der Hilfeinformationen für einen bestimmten Befehl zu erhalten, gib den Namen des Befehls als Argument für das Cmdlet Get-Help an. Dazu gehören vor allem die Zusammenfassung, die Syntax und die detaillierte Beschreibung:

Get-Help CommandName

oder:

CommandName -?

Um die detaillierten Hilfeinformationen für einen bestimmten Befehl zu erhalten, gibst du das Flag -Detailed an das Cmdlet Get-Help an. Zusätzlich zur Übersichtsansicht werden auch die Parameterbeschreibungen und Beispiele angezeigt:

Get-Help CommandName -Detailed

Um die vollständigen Hilfeinformationen für einen bestimmten Befehl zu erhalten, gibst du dem Cmdlet Get-Help das Flag -Full mit. Neben der detaillierten Ansicht enthält dies auch die vollständigen Parameterbeschreibungen und zusätzliche Hinweise:

Get-Help CommandName -Full

Um nur die Beispiele für einen bestimmten Befehl zu erhalten, gibst du das Flag -Examples an das Cmdlet Get-Help an:

Get-Help CommandName -Examples

Um die aktuellste Online-Version des Hilfethemas eines Befehls abzurufen, gibst du dem Cmdlet Get-Help das Flag -Online mit:

Get-Help CommandName -Online

Um eine durchsuchbare, grafische Ansicht eines Hilfethemas anzuzeigen, verwende den Parameter -ShowWindow:

Get-Help CommandName -ShowWindow

Um alle Hilfethemen zu finden, die ein bestimmtes Schlüsselwort enthalten, gibst du dieses Schlüsselwort als Argument an das Cmdlet Get-Help an. Wenn das Schlüsselwort nicht auch der Name eines bestimmten Hilfethemas ist, werden alle Hilfethemen zurückgegeben, die das Schlüsselwort enthalten, einschließlich des Namens, der Kategorie und der Synopsis:

Get-Help Keyword

Diskussion

Das Cmdlet Get-Help ist die wichtigste Methode zur Interaktion mit dem Hilfesystem in der PowerShell. Wie das Cmdlet Get-Command unterstützt auch das Cmdlet Get-Help Wildcards. Wenn du alle Befehle auflisten möchtest, deren Hilfeinhalt mit einem bestimmten Muster übereinstimmt (z. B. process), kannst du einfach Folgendes eingeben:

Get-Help *process*

Wenn das Muster nur auf einen einzigen Befehl zutrifft, zeigt PowerShell die Hilfe für diesen Befehl an. Obwohl die Suche mit Befehlsplatzhaltern und Schlüsselwörtern eine hilfreiche Methode ist, die PowerShell-Hilfe zu durchsuchen, findest du in Rezept 1.15 ein Skript, mit dem du den Inhalt der Hilfe nach einem bestimmten Muster durchsuchen kannst.

Während dir Tausende von Seiten benutzerdefinierter Hilfeinhalte zur Verfügung stehen, enthält die PowerShell standardmäßig nur Informationen, die sie automatisch aus den in den Befehlen selbst enthaltenen Informationen generieren kann: Namen, Parameter, Syntax und Parametervorgaben. Du musst deinen Hilfeinhalt aktualisieren, um den Rest zu erhalten. Wenn du Get-Help für einen Befehl aufrufst, für den du keinen Hilfeinhalt heruntergeladen hast, siehst du die folgenden Hinweise als Teil der Hilfe:

REMARKS
    Get-Help cannot find the Help files for this cmdlet on this computer.
    It is displaying only partial help.
        -- To download and install Help files for the module that includes
    this cmdlet, use Update-Help.
        -- To view the Help topic for this cmdlet online, type: "Get-Help
    Get-Process -Online" or
           go to https://go.microsoft.com/fwlink/?LinkID=2096814.

Führe das Cmdlet Update-Help aus, und PowerShell lädt automatisch die neuesten Hilfeinhalte für alle Module auf deinem System herunter und installiert sie. Weitere Informationen zur aktualisierbaren Hilfe findest du in Rezept 1.14.

Wenn du eine Liste aller Cmdlets und Aliase (zusammen mit einer kurzen Zusammenfassung) erstellen möchtest, führe den folgenden Befehl aus:

Get-Help * -Category Cmdlet | Select-Object Name,Synopsis | Format-Table -Auto

Neben der konsolenbasierten Hilfe bietet die PowerShell auch einen Online-Zugang zu ihren Hilfeinhalten. Die Lösung zeigt, wie du schnell auf Online-Hilfeinhalte zugreifen kannst.

Das Cmdlet Get-Help ist einer der drei Befehle, die du am häufigsten verwenden wirst, wenn du die PowerShell kennenlernst. Die anderen beiden Befehle sind Get-Command und Get-Member.

Weitere Informationen über das Cmdlet Get-Help findest du, indem du Get-Help Get-Help.

1.14 Inhalt der Systemhilfe aktualisieren

Problem

Du möchtest die Hilfe deines Systems auf den neuesten Stand bringen.

Lösung

Führe den Befehl Update-Help aus. Um die Hilfe von einem lokalen Pfad abzurufen, verwende den-SourcePath Cmdlet-Parameter:

Update-Help

oder:

Update-Help -SourcePath \\helpserver\help

Diskussion

Eine der größten Stärken der PowerShell ist der unglaublich detaillierte Inhalt der Hilfe. Zählt man nur den Hilfeinhalt und die about_* Themen, die die Kernfunktionen beschreiben, umfasst die PowerShell-Hilfe etwa eine halbe Million Wörter und würde in gedruckter Form 1.200 Seiten umfassen.

Die Herausforderung, mit der jede Version der PowerShell zu kämpfen hat, besteht darin, dass diese Hilfeinhalte zur gleichen Zeit wie die PowerShell selbst geschrieben werden. Da es das Ziel ist, dem Benutzer zu helfen, ist der Inhalt, der zum Zeitpunkt der Veröffentlichung einer PowerShell-Version fertig ist, eine bestmögliche Schätzung dessen, wozu die Benutzer Hilfe benötigen werden.

Sobald die Benutzer die PowerShell in die Hände bekommen, haben sie Fragen. Einige davon werden in den Hilfethemen beantwortet, andere wiederum nicht. Manchmal ist die Hilfe aufgrund einer Produktänderung während der Veröffentlichung einfach falsch. Deshalb unterstützt die PowerShell die aktualisierbare Hilfe.

Es ist nicht nur möglich, die Hilfe zu aktualisieren, sondern der Befehl Update-Help ist sogar die einzige Möglichkeit, Hilfe zu deinem System zu erhalten. Die PowerShell bietet von Haus aus nur das, was in den Befehlen selbst enthalten ist: Name, Syntax, Parameter und Standardwerte.

Get-Help Wenn du für einen Befehl aufrufst, für den du keinen Hilfeinhalt heruntergeladen hast, siehst du die folgenden Hinweise als Teil der Hilfe:

REMARKS
    Get-Help cannot find the Help files for this cmdlet on this computer.
    It is displaying only partial help.
        -- To download and install Help files for the module that includes
    this cmdlet, use Update-Help.
        -- To view the Help topic for this cmdlet online, type: "Get-Help
    Get-Process -Online" or
           go to https://go.microsoft.com/fwlink/?LinkID=2096814.

Wenn du das Cmdlet Update-Help ausführst, lädt PowerShell automatisch die neuesten Hilfeinhalte für alle Module auf deinem System herunter und installiert sie.

Wenn du Update-Help aufrufst, prüft PowerShell jedes Modul auf deinem System und vergleicht die Hilfe, die du für dieses Modul hast, mit der neuesten Online-Version. Für Inbox-Module verwendet die PowerShell download.microsoft.com, um aktualisierte Hilfeinhalte abzurufen. Andere Module, die du aus dem Internet herunterlädst, können den Modulschlüssel HelpInfoUri verwenden, um ihre eigene aktualisierbare Hilfe zu unterstützen.

PowerShell speichert diesen Inhalt im Verzeichnis PowerShell\Help in deinen Benutzerdokumenten oder deinem Home-Verzeichnis.

Standardmäßig bezieht der Befehl Update-Help seinen Inhalt aus dem Internet. Wenn du die Hilfe auf einem Rechner aktualisieren möchtest, der nicht mit dem Internet verbunden ist, kannst du den-SourcePath Parameter des Cmdlets Update-Help verwenden. Dieser Pfad stellt ein Verzeichnis oder einen UNC-Pfad dar, in dem PowerShell nach aktualisierten Hilfeinhalten suchen soll. Um diesen Inhalt aufzufüllen, lädst du die Dateien zunächst mit dem Cmdlet Save-Help herunter und kopierst sie dann an den Quellort.

Weitere Informationen zur PowerShell-Hilfe findest du in Rezept 1.13.

1.15 Programm: Suchhilfe für Text

Mit den beiden Cmdlets , Get-Command und Get-Help kannst du nach Befehlsnamen suchen, die einem bestimmten Muster entsprechen. Wenn du jedoch nicht genau weißt, nach welchen Teilen eines Befehlsnamens du suchst, wirst du häufiger Erfolg haben, wenn du den Inhalt der Hilfe nach einer Antwort durchsuchst. Auf Unix-Systemen heißt dieser Befehl Apropos.

Das Cmdlet Get-Help sucht automatisch in der Hilfedatenbank nach Schlüsselwortreferenzen, wenn es kein Hilfethema für das von dir angegebene Argument finden kann. Du kannst die Suche aber auch noch weiter ausdehnen, um nach Textmustern oder sogar nach Hilfethemen zu suchen, die sich auf bestehende Hilfethemen beziehen. Die PowerShell-Hilfe unterstützt eine Version der Suche mit Platzhaltern, aber keine vollständigen regulären Ausdrücke.

Das muss uns aber nicht aufhalten, denn wir können die Funktionalität selbst schreiben.

Um dieses Programm auszuführen, gibst du einen Suchstring in das Skript Search-Help ein (siehe Beispiel 1-9). Der Suchstring kann entweder einfacher Text oder ein regulärer Ausdruck sein. Das Skript zeigt dann den Namen und die Zusammenfassung aller Hilfethemen an, die übereinstimmen. Um den Inhalt der Hilfe zu diesem Thema zu sehen, verwende das Cmdlet Get-Help.

Beispiel 1-9. Search-Help.ps1
##############################################################################
##
## Search-Help
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################

<#

.SYNOPSIS

Search the PowerShell help documentation for a given keyword or regular
expression. For simple keyword searches in PowerShell version two or three,
simply use "Get-Help <keyword>"

.EXAMPLE

PS > Search-Help hashtable
Searches help for the term 'hashtable'

.EXAMPLE

PS > Search-Help "(datetime|ticks)"
Searches help for the term datetime or ticks, using the regular expression
syntax.

#>

param(
    ## The pattern to search for
    [Parameter(Mandatory = $true)]
    $Pattern
)

$helpNames = $(Get-Help * | Where-Object { $_.Category -ne "Alias" })

## Go through all of the help topics
foreach($helpTopic in $helpNames)
{
    ## Get their text content, and
    $content = Get-Help -Full $helpTopic.Name | Out-String
    if($content -match "(.{0,30}$pattern.{0,30})")
    {
        $helpTopic | Add-Member NoteProperty Match $matches[0].Trim()
        $helpTopic | Select-Object Name,Match
    }
}

Weitere Informationen zum Ausführen von Skripten findest du in Rezept 1.2.

1.16 PowerShell an einem bestimmten Ort starten

Problem

Du möchtest eine PowerShell-Sitzung an einem bestimmten Ort starten.

Lösung

Sowohl Windows als auch die PowerShell bieten mehrere Möglichkeiten, die PowerShell an einem bestimmten Ort zu starten:

  • Die Adressleiste des Explorers

  • Die Befehlszeilenargumente der PowerShell

  • Windows Terminal "In Windows Terminal öffnen" Shell-Erweiterung

Diskussion

Wenn du das Dateisystem mit dem Windows Explorer durchsuchst, gibst du pwsh.exe oderpowershell.exe in die Adressleiste ein, um die PowerShell an dieser Stelle zu starten (siehe Abbildung 1-2).

wps4 0102
Abbildung 1-2. Starten der PowerShell aus dem Windows Explorer

Beachte, dass deine Eingaben mit der Erweiterung .exe enden müssen, sonst öffnet der Explorer in der Regel deinen PowerShell Dokumentenordner. Außerdem kannst du die Windows PowerShell direkt über das Menü Datei öffnen, wie in Abbildung 1-3 gezeigt.

Unter findest du eine weitere Möglichkeit, die PowerShell aus dem Windows Explorer zu starten: Windows Terminal (wenn du es installiert hast) fügt die Option "In Windows Terminal öffnen" hinzu, wenn du mit der rechten Maustaste auf einen Ordner im Windows Explorer klickst.

Wenn du den gewünschten Ordner nicht mit dem Windows Explorer durchsuchst, kannst du die PowerShell über Start→Ausführen (oder eine andere Möglichkeit, eine Anwendung zu starten) an einem bestimmten Ort starten. Verwende dazu den -NoExit Parameter der PowerShell zusammen mit dem-Command Parameter. Im Parameter -Command rufst du das Cmdlet Set-Location auf, um zunächst den gewünschten Speicherort aufzurufen.

pwsh -NoExit -Command Set-Location 'C:\Program Files'
wps4 0103
Abbildung 1-3. Starten der PowerShell aus dem Explorer

1.17 Aufrufen eines PowerShell-Befehls oder -Skripts von außerhalb der PowerShell

Problem

Du möchtest einen PowerShell-Befehl oder ein Skript aus einer Batch-Datei, einem Anmeldeskript, einer geplanten Aufgabe oder einer anderen Nicht-PowerShell-Anwendung heraus aufrufen.

Lösung

Um einen PowerShell-Befehl aufzurufen, verwendest du den Parameter -Command:

pwsh -Command Get-Process; Read-Host

Um ein PowerShell-Skript zu starten, verwendest du den Parameter -File:

pwsh -File 'full path to script' arguments

Zum Beispiel:

pwsh -File 'c:\shared scripts\Get-Report.ps1' Hello World

Diskussion

In der Voreinstellung werden alle Argumente an pwsh.exe als auszuführendes Skript interpretiert. Wenn du den Parameter -Command verwendest, führt PowerShell den Befehl so aus, als hättest du ihn in der interaktiven Shell eingegeben, und beendet sich dann. Du kannst dieses Verhalten anpassen, indem du andere Parameter an pwsh.exe übergibst, z. B. -NoExit, -NoProfile, und andere.

Hinweis

Wenn du der Autor eines Programms bist, das PowerShell-Skripte oder -Befehle ausführen muss, kannst du diese Skripte und Befehle mit der PowerShell viel einfacher aufrufen als mit der Befehlszeilenschnittstelle. Weitere Informationen zu diesem Ansatz findest du in Rezept 17.10.

Da das Starten eines Skripts so häufig vorkommt, bietet PowerShell den Parameter -File an, um die Komplexität zu beseitigen, die entsteht, wenn man ein Skript über den-Command Parameter. Mit dieser Technik kannst du ein PowerShell-Skript als Ziel eines Anmeldeskripts, einer erweiterten Dateiverknüpfung, einer geplanten Aufgabe und vielem mehr aufrufen.

Hinweis

Wenn die PowerShell feststellt, dass ihre Eingabe- oder Ausgabeströme umgeleitet wurden, unterdrückt sie alle Eingabeaufforderungen, die sie normalerweise anzeigen würde. Wenn du eine interaktive Eingabeaufforderung der PowerShell in einer anderen Anwendung (z. B. Emacs) bereitstellen möchtest, verwende - als Argument für den Parameter -File. In der PowerShell (wie auch in traditionellen Unix-Shells) bedeutet dies "von der Standardeingabe übernommen".

pwsh -File -

Wenn das Skript für die Hintergrundautomatisierung oder eine geplante Aufgabe ist, können diese Skripte manchmal die Umgebung des Benutzers stören (oder von ihr beeinflusst werden). Für diese Situationen sind drei Parameter nützlich:

-NoProfile

Führt den Befehl oder das Skript aus, ohne Benutzerprofilskripte zu laden. Dadurch wird das Skript schneller gestartet, aber vor allem wird verhindert, dass Benutzereinstellungen (z. B. Aliase und Einstellungsvariablen) die Arbeitsumgebung des Skripts beeinträchtigen.

-WindowStyle

Führt den Befehl oder das Skript mit dem angegebenen Fenstermodell aus - in der Regel Hidden. Wenn es mit dem Fenstermodell Hidden ausgeführt wird, blendet PowerShell das Hauptfenster sofort aus. Weitere Möglichkeiten zur Steuerung des Fensterstils in der PowerShell findest du in Rezept 24.3.

-ExecutionPolicy

Führt den Befehl oder das Skript mit einer bestimmten Ausführungsrichtlinie aus, die nur für diese Instanz von PowerShell gilt. So kannst du PowerShell-Skripte zur Verwaltung eines Systems schreiben, ohne die systemweite Ausführungsrichtlinie ändern zu müssen. Weitere Informationen über Ausführungsrichtlinien finden Sie in Rezept 18.1.

Wenn die Argumente für den Parameter -Command komplex werden, kann die Sonderzeichenbehandlung in der Anwendung, die die PowerShell aufruft (z. B. cmd.exe), den Befehl, den du an die PowerShell senden willst, beeinträchtigen. Für diese Situation unterstützt PowerShell einen EncodedCommand Parameter: eine Base64-kodierte Darstellung der Unicode-Zeichenfolge, die du ausführen möchtest. Beispiel 1-10 zeigt wie du einen String mit PowerShell-Befehlen in eine Base64-kodierte Form umwandelst.

Beispiel 1-10. PowerShell-Befehle in eine Base64-kodierte Form umwandeln
$commands = '1..10 | % { "PowerShell Rocks" }'
$bytes = [System.Text.Encoding]::Unicode.GetBytes($commands)
$encodedString = [Convert]::ToBase64String($bytes)

Sobald du die verschlüsselte Zeichenkette hast, kannst du sie als Wert für den Parameter EncodedCommand verwenden, wie in Beispiel 1-11 gezeigt.

Beispiel 1-11. Starten der PowerShell mit einem verschlüsselten Befehl aus cmd.exe
Microsoft Windows [Version 10.0.19041.685]
(c) 2020 Microsoft Corporation. All rights reserved.

C:\Users\Lee>PowerShell -EncodedCommand MQAuAC4AMQAwACAAfAAgACUAIAB7ACAAIgBQAG8A
   dwBlAHIAUwBoAGUAbABsACAAUgBvAGMAawBzACIAIAB9AA==
PowerShell Rocks
PowerShell Rocks
PowerShell Rocks
PowerShell Rocks
PowerShell Rocks
PowerShell Rocks
PowerShell Rocks
PowerShell Rocks
PowerShell Rocks
PowerShell Rocks

Unter findest du weitere Informationen zum Ausführen von Skripten, siehe Rezept 1.2.

1.18 Verstehen und Anpassen der Registerkartenvervollständigung der PowerShell

Problem

Du möchtest anpassen, wie die PowerShell auf das Drücken der Tabulatortaste und Strg+Leertaste reagiert.

Lösung

Erstelle eine benutzerdefinierte Funktion namens TabExpansion2. PowerShell ruft diese Funktion auf, wenn du in der Konsole oder in Visual Studio Code die Tabulatortaste oder Strg+Leertaste drückst.

Diskussion

Wenn du die Tabulatortaste drückst, ruft die PowerShell eine Funktion auf, die als Tabulator-Erweiterung bekannt ist: Sie ersetzt das, was du bisher eingegeben hast, durch eine erweiterte Version davon (falls zutreffend). Wenn du zum Beispiel Set-Location C:\ eingibst und dann die Tabulatortaste drückst, beginnt die PowerShell, die Verzeichnisse unter C:\ zu durchlaufen, in die du navigieren kannst.

Die Funktionen der integrierten PowerShell-Tab-Erweiterung sind sehr umfangreich, wie in Tabelle 1-2 gezeigt wird.

Tabelle 1-2. Tab-Erweiterungsfunktionen in der PowerShell
Beschreibung Beispiel

Befehlsvervollständigung. Vervollständigt Befehlsnamen, wenn der aktuelle Text einen Befehlsaufruf darstellt.

Get-Ch <Tab>

Parameterabschluss. Vervollständigt die Befehlsparameter für den aktuellen Befehl.

Get-ChildItem -Pat <Tab>

Argumentvervollständigung. Vervollständigt die Befehlsargumente für den aktuellen Befehlsparameter. Dies gilt für jedes Befehlsargument, das einen festen Satz von Werten annimmt (Aufzählungen oder Parameter, die ein ValidateSet Attribut definieren). Darüber hinaus enthält die PowerShell eine erweiterte Argumentvervollständigung für Modulnamen, Hilfethemen, CIM-/WMI-Klassen, Ereignisprotokollnamen, Auftrags-IDs und -Namen, Prozess-IDs und -Namen, Anbieternamen, Laufwerksnamen, Dienstnamen und Anzeigenamen sowie Trace-Quellennamen.

Set-ExecutionPolicy -ExecutionPolicy <Tab>

Vervollständigung des Textes in der Historie. Ersetzt die aktuelle Eingabe durch Elemente aus der Befehlshistorie, die mit dem Text nach dem Zeichen # übereinstimmen.

#Process <Tab>

Abschluss der History ID. Ersetzt die aktuelle Eingabe durch die Befehlszeile aus dem Eintrag Nummer ID in deinem Befehlsverlauf.

#12 <Tab>

Dateinamenvervollständigung. Ersetzt den aktuellen Parameterwert durch Dateinamen, die mit dem übereinstimmen, was du bisher eingegeben hast. Wenn du das Cmdlet Set-Location anwendest, filtert die PowerShell die Ergebnisse zusätzlich auf Verzeichnisse.

Set-Location C:\Windows\S <Tab>

Operator-Vervollständigung. Ersetzt den aktuellen Text durch einen passenden Operator. Dies gilt auch für Flags, die der Anweisung switch übergeben werden.

"Hello World" -rep<Tab>

switch - c <Tab>

Variablenvervollständigung. Ersetzt den aktuellen Text durch verfügbare PowerShell-Variablen. PowerShell bezieht sogar Variablen aus Skriptinhalten ein, die noch nie aufgerufen wurden.

$myGreeting = "Hello World"; $myGr <Tab>

Member Ergänzung. Ersetzt die Membernamen für die aktuell referenzierte Variable oder den Typ. Wenn PowerShell die Member aus vorherigen Befehlen in der Pipeline ableiten kann, unterstützt sie sogar die Membervervollständigung innerhalb von Skriptblöcken.

[Console]::Ba <Tab>

Get-Process | Where-Object { $_.Ha <Tab>

Typvervollständigung. Ersetzt abgekürzte Typennamen durch ihren Namespace-qualifizierten Namen.

[PSSer <Tab>

$l = New-Object List[Stri <Tab>

Wenn du die PowerShell-Funktionen zur Tabulator-Erweiterung erweitern möchtest, definiere eine Funktion namens TabExpansion2. Du kannst diese Funktion direkt zu deinem PowerShell-Profil hinzufügen oder sie per Dot-Source aus deinem Profil beziehen. Beispiel 1-12 zeigt ein Beispiel für eine benutzerdefinierte Tabulator-Erweiterungsfunktion, die die bereits in der PowerShell integrierten Funktionen erweitert.

Beispiel 1-12. Eine Beispielimplementierung von TabExpansion2
##############################################################################
##
## TabExpansion2
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################

function TabExpansion2
{
    [CmdletBinding(DefaultParameterSetName = 'ScriptInputSet')]
    Param(
        [Parameter(ParameterSetName = 'ScriptInputSet', Mandatory = $true, Position = 0)]
        [string] $inputScript,

        [Parameter(ParameterSetName = 'ScriptInputSet', Mandatory = $true, Position = 1)]
        [int] $cursorColumn,

        [Parameter(ParameterSetName = 'AstInputSet', Mandatory = $true, Position = 0)]
        [System.Management.Automation.Language.Ast] $ast,

        [Parameter(ParameterSetName = 'AstInputSet', Mandatory = $true, Position = 1)]
        [System.Management.Automation.Language.Token[]] $tokens,

        [Parameter(ParameterSetName = 'AstInputSet', Mandatory = $true, Position = 2)]
        [System.Management.Automation.Language.IScriptPosition] $positionOfCursor,

        [Parameter(ParameterSetName = 'ScriptInputSet', Position = 2)]
        [Parameter(ParameterSetName = 'AstInputSet', Position = 3)]
        [Hashtable] $options = $null
    )

    End
    {
        ## Create a new 'Options' hashtable if one has not been supplied.
        ## In this hashtable, you can add keys for the following options, using
        ## $true or $false for their values:
        ##
        ## IgnoreHiddenShares - Ignore hidden UNC shares (such as \\COMPUTER\ADMIN$)
        ## RelativePaths - When expanding filenames and paths, $true forces PowerShell
        ##     to replace paths with relative paths. When $false, forces PowerShell to
        ##     replace them with absolute paths. By default, PowerShell makes this
        ##     decision based on what you had typed so far before invoking tab completion.
        ## LiteralPaths - Prevents PowerShell from replacing special file characters
        ##     (such as square brackets and back-ticks) with their escaped equivalent.
        if(-not $options) { $options = @{} }

        ## Demonstrate some custom tab expansion completers for parameters.
        ## This is a hash table of parameter names (and optionally cmdlet names)
        ## that we add to the $options hashtable.
        ##
        ## When PowerShell evaluates the script block, $args gets the
        ## following: command name, parameter, word being completed,
        ## AST of the command being completed, and currently-bound arguments.
        $options["CustomArgumentCompleters"] = @{
            "Get-ChildItem:Filter" = { "*.ps1","*.txt","*.doc" }
            "ComputerName" = { "ComputerName1","ComputerName2","ComputerName3" }
        }

        ## Also define a completer for a native executable.
        ## When PowerShell evaluates the script block, $args gets the
        ## word being completed, and AST of the command being completed.
        $options["NativeArgumentCompleters"] = @{
            "attrib" = { "+R","+H","+S" }
        }

        ## Define a "quick completions" list that we'll cycle through
        ## when the user types '!!' followed by TAB.
        $quickCompletions = @(
            'Get-Process -Name PowerShell | ? Id -ne $pid | Stop-Process',
            'Set-Location $pshome',
            ('$errors = $error | % { $_.InvocationInfo.Line }; Get-History | ' +
                ' ? { $_.CommandLine -notin $errors }')
        )

        ## First, check the built-in tab completion results
        $result = $null
        if ($psCmdlet.ParameterSetName -eq 'ScriptInputSet')
        {
            $result = [System.Management.Automation.CommandCompletion]::CompleteInput(
                <#inputScript#>  $inputScript,
                <#cursorColumn#> $cursorColumn,
                <#options#>      $options)
        }
        else
        {
            $result = [System.Management.Automation.CommandCompletion]::CompleteInput(
                <#ast#>              $ast,
                <#tokens#>           $tokens,
                <#positionOfCursor#> $positionOfCursor,
                <#options#>          $options)
        }

        ## If we didn't get a result
        if($result.CompletionMatches.Count -eq 0)
        {
            ## If this was done at the command-line or in a remote session,
            ## create an AST out of the input
            if ($psCmdlet.ParameterSetName -eq 'ScriptInputSet')
            {
                $ast = [System.Management.Automation.Language.Parser]::ParseInput(
                    $inputScript, [ref]$tokens, [ref]$null)
            }

            ## In this simple example, look at the text being supplied.
            ## We could do advanced analysis of the AST here if we wanted,
            ## but in this case just use its text. We use a regular expression
            ## to check if the text started with two exclamations, and then
            ## use a match group to retain the rest.
            $text = $ast.Extent.Text
            if($text -match '^!!(.*)')
            {
                ## Extract the rest of the text from the regular expression
                ## match group.
                $currentCompletionText = $matches[1].Trim()

                ## Go through each of our quick completions and add them to
                ## our completion results. The arguments to the completion results
                ## are the text to be used in tab completion, a potentially shorter
                ## version to use for display (i.e.: intellisense in the ISE),
                ## the type of match, and a potentially more verbose description to
                ## be used as a tool tip.
                $quickCompletions | Where-Object { $_ -match $currentCompletionText } |
                    Foreach-Object { $result.CompletionMatches.Add(
                        (New-Object Management.Automation.CompletionResult $_,$_,
                            "Text",$_) )
                }
            }
        }

        return $result
    }
}

1.19 Programm: Aliase für gängige Befehle lernen

Bei der interaktiven Verwendung von sind vollständige Cmdlet-Namen (wie Get-ChildItem) umständlich und langsam in der Eingabe. Obwohl Aliase viel effizienter sind, dauert es eine Weile, sie zu entdecken. Um Aliase leichter zu erlernen, kannst du deine Eingabeaufforderung so ändern, dass sie dich an die kürzere Version der von dir verwendeten Aliase erinnert.

Dies umfasst zwei Schritte:

  1. Füge das in Beispiel 1-13 gezeigte Programm Get-AliasSuggestion.ps1 zu deinem Tools-Verzeichnis oder einem anderen Verzeichnis hinzu.

    Beispiel 1-13. Get-AliasSuggestion.ps1
    ##############################################################################
    ##
    ## Get-AliasSuggestion
    ##
    ## From PowerShell Cookbook (O'Reilly)
    ## by Lee Holmes (http://www.leeholmes.com/guide)
    ##
    ##############################################################################
    
    <#
    
    .SYNOPSIS
    
    Get an alias suggestion from the full text of the last command. Intended to
    be added to your prompt function to help learn aliases for commands.
    
    .EXAMPLE
    
    PS > Get-AliasSuggestion Remove-ItemProperty
    Suggestion: An alias for Remove-ItemProperty is rp
    
    #>
    
    param(
        ## The full text of the last command
        $LastCommand
    )
    
    Set-StrictMode -Version 3
    
    $helpMatches = @()
    
    ## Find all of the commands in their last input
    $tokens = [Management.Automation.PSParser]::Tokenize(
        $lastCommand, [ref] $null)
    $commands = $tokens | Where-Object { $_.Type -eq "Command" }
    
    ## Go through each command
    foreach($command in $commands)
    {
        ## Get the alias suggestions
        foreach($alias in Get-Alias -Definition $command.Content)
        {
            $helpMatches += "Suggestion: An alias for " +
                "$($alias.Definition) is $($alias.Name)"
        }
    }
    
    $helpMatches
  2. Füge den Text aus Beispiel 1-14 zu der Funktion Prompt in deinem Profil hinzu. Wenn du noch keine Prompt Funktion hast, erfährst du in Rezept 1.9, wie du eine hinzufügen kannst.

    Beispiel 1-14. Eine nützliche Eingabeaufforderung, um dir Aliase für gängige Befehle beizubringen
    function prompt
    {
        ## Get the last item from the history
        $historyItem = Get-History -Count 1
    
        ## If there were any history items
        if($historyItem)
        {
            ## Get the training suggestion for that item
            $suggestions = @(Get-AliasSuggestion $historyItem.CommandLine)
            ## If there were any suggestions
            if($suggestions)
            {
                ## For each suggestion, write it to the screen
                foreach($aliasSuggestion in $suggestions)
                {
                    Write-Host "$aliasSuggestion"
                }
                Write-Host ""
    
            }
        }
    
        ## Rest of prompt goes here
        "PS [$env:COMPUTERNAME] >"
    }

Weitere Informationen zum Ausführen von Skripten findest du in Rezept 1.2.

1.20 Programm: Aliase für gängige Parameter lernen

Problem

Du möchtest Aliase lernen, die für Befehlsparameter definiert wurden.

Lösung

Verwende das Skript Get-ParameterAlias, wie in Beispiel 1-15 gezeigt, um alle Aliase für Parameter, die vom vorherigen Befehl verwendet wurden, in deinem Sitzungsverlauf anzuzeigen.

Beispiel 1-15. Get-ParameterAlias.ps1
##############################################################################
##
## Get-ParameterAlias
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################

<#

.SYNOPSIS

Looks in the session history, and returns any aliases that apply to
parameters of commands that were used.

.EXAMPLE

PS > dir -ErrorAction SilentlyContinue
PS > Get-ParameterAlias
An alias for the 'ErrorAction' parameter of 'dir' is ea

#>

Set-StrictMode -Version 3

## Get the last item from their session history
$history = Get-History -Count 1
if(-not $history)
{
    return
}

## And extract the actual command line they typed
$lastCommand = $history.CommandLine

## Use the Tokenizer API to determine which portions represent
## commands and parameters to those commands
$tokens = [System.Management.Automation.PsParser]::Tokenize(
    $lastCommand, [ref] $null)
$currentCommand = $null

## Now go through each resulting token
foreach($token in $tokens)
{
    ## If we've found a new command, store that.
    if($token.Type -eq "Command")
    {
        $currentCommand = $token.Content
    }

    ## If we've found a command parameter, start looking for aliases
    if(($token.Type -eq "CommandParameter") -and ($currentCommand))
    {
        ## Remove the leading "-" from the parameter
        $currentParameter = $token.Content.TrimStart("-")

        ## Determine all of the parameters for the current command.
        (Get-Command $currentCommand).Parameters.GetEnumerator() |

            ## For parameters that start with the current parameter name,
            Where-Object { $_.Key -like "$currentParameter*" } |

            ## return all of the aliases that apply. We use "starts with"
            ## because the user might have typed a shortened form of
            ## the parameter name.
            Foreach-Object {
                $_.Value.Aliases | Foreach-Object {
                    "Suggestion: An alias for the '$currentParameter' " +
                    "parameter of '$currentCommand' is '$_'"
                }
            }
    }
}

Diskussion

Um die Eingabe von Befehlsparametern zu erleichtern, lässt dich die PowerShell nur so viel von dem Befehlsparameter eingeben, wie nötig ist, um ihn von anderen Parametern des Befehls zu unterscheiden. Zusätzlich zu den von der Shell implizit unterstützten Abkürzungen können Cmdlet-Autoren auch explizite Aliase für ihre Parameter definieren - zum Beispiel CN als Kurzform für ComputerName.

Diese Aliasnamen sind zwar hilfreich, aber schwer zu entdecken.

Wenn du die Aliase für einen bestimmten Befehl sehen willst, kannst du seine Parameters Sammlung aufrufen:

PS > (Get-Command New-TimeSpan).Parameters.Values | Select Name,Aliases

Name                Aliases
----                -------
Start               {LastWriteTime}
End                 {}
Days                {}
Hours               {}
Minutes             {}
Seconds             {}
Verbose             {vb}
Debug               {db}
ErrorAction         {ea}
WarningAction       {wa}
InformationAction   {infa}
ErrorVariable       {ev}
WarningVariable     {wv}
InformationVariable {iv}
OutVariable         {ov}
OutBuffer           {ob}
PipelineVariable    {pv}

Wenn du alle Aliase für die Parameter deines vorherigen Befehls lernen willst, rufe einfach Get-ParameterAlias.ps1 auf. Damit die PowerShell dies automatisch tut, füge in deiner Eingabeaufforderung einen Aufruf an Get-ParameterAlias.ps1 hinzu.

Dieses Skript baut auf zwei Hauptfunktionen auf: Die Tokenizer-API der PowerShell und die umfangreichen Informationen, die das Cmdlet Get-Command zurückgibt. Die Tokenizer-API der PowerShell untersucht die Eingabe und gibt die Interpretation der Eingabe durch die PowerShell zurück: Befehle, Parameter, Parameterwerte, Operatoren und mehr. Wie die umfangreichen Ausgaben der meisten PowerShell-Befehle gibt auch Get-Command Informationen über die Parameter eines Befehls, die Parametersätze, den Ausgabetyp (falls angegeben) und vieles mehr zurück.

Weitere Informationen über die Tokenizer-API findest du in Rezept 10.10.

1.21 Zugriff und Verwaltung des Konsolenverlaufs

Problem

Nachdem du eine Weile in der Shell gearbeitet hast, möchtest du Befehle aus deinem Verlauf aufrufen, deinen Befehlsverlauf einsehen und deinen Befehlsverlauf speichern.

Lösung

Mit den in Rezept 1.9 beschriebenen Tastenkombinationen kannst du deinen Verlauf verwalten, aber die PowerShell bietet mehrere Funktionen, mit denen du noch detaillierter mit deiner Konsole arbeiten kannst.

Um die neuesten Befehle aus deiner Sitzung abzurufen, verwende das Cmdlet Get-History (oder seinen Alias h):

Get-History

Um einen bestimmten Befehl aus deinem Sitzungsverlauf erneut auszuführen, gibst du seine ID an das Cmdlet Invoke-History (oder seinen Alias ihy):

Invoke-History ID

Um die Anzahl der in deinem Sitzungsverlauf gespeicherten Befehle zu erhöhen (oder zu begrenzen), weise der Variablen $MaximumHistoryCount einen neuen Wert zu:

$MaximumHistoryCount = Count

Um deinen Befehlsverlauf in einer Datei zu speichern, leite die Ausgabe von Get-History an das Cmdlet Export-CliXml weiter:

Get-History | Export-CliXml Filename

Um einen zuvor gespeicherten Befehlsverlauf zu deinem aktuellen Sitzungsverlauf hinzuzufügen, rufst du das Cmdlet Import-CliXml auf und leitest die Ausgabe dann an das Cmdlet Add-History weiter:

Import-CliXml Filename | Add-History

Um alle Befehle aus deinem Sitzungsverlauf zu löschen, verwende das cmdlet Clear-History:

Clear-History

Diskussion

Im Gegensatz zu den in Rezept 1.9 besprochenen Hotkeys für den Konsolenverlauf erzeugt das Cmdlet Get-History umfangreiche Objekte, die Informationen über die Elemente in deinem Verlauf darstellen. Jedes Objekt enthält die ID des Eintrags, die Befehlszeile, die Zeit des Ausführungsbeginns und die Zeit des Ausführungsendes.

Sobald du die ID eines Eintrags aus der Vergangenheit kennst (wie in der Ausgabe von Get-History), kannst du sie an Invoke-History übergeben, um den Befehl erneut auszuführen. Die in Rezept 1.9 gezeigte Beispiel-Eingabeaufforderung macht die Arbeit mit früheren History-Einträgen einfach, da die Eingabeaufforderung für jeden Befehl die History-ID enthält, die ihn repräsentiert.

Hinweis

Du kannst leicht erkennen, wie lange der Aufruf einer Reihe von Befehlen gedauert hat, indem du dir die Eigenschaft Duration ansiehst. Das ist eine gute Möglichkeit, um herauszufinden, wie wenig Zeit du für die Befehle gebraucht hast, die dir stundenlange manuelle Arbeit erspart haben:

PS:29 > Get-History 27,28 | Format-Table *

Id CommandLine             StartExecutionTime   Duration
-- -----------             ------------------   --------
27 dir                     2/15/2021 5:12:49 PM 00:00:00.0319401
28 Start-Sleep -Seconds 45 2/15/2021 5:12:53 PM 00:00:45.0073792

Die vom Cmdlet Get-History bereitgestellten IDs unterscheiden sich von den IDs, die von den allgemeinen Verlaufs-Hotkeys der Windows-Konsole (z. B. F7) bereitgestellt werden, da sich ihre Verlaufsverwaltungstechniken unterscheiden.

Standardmäßig speichert die PowerShell die letzten 4.096 Einträge deines Befehlsverlaufs. Wenn du diese Zahl erhöhen oder verringern möchtest, setze die Variable $MaximumHistoryCount auf die gewünschte Größe. Um diese Änderung dauerhaft zu machen, setze die Variable in deinem PowerShell-Profilskript.

Die bei weitem nützlichste Funktion des PowerShell-Befehlsverlaufs besteht darin, Ad-hoc-Experimente zu überprüfen und in einem Skript festzuhalten, das du dann immer wieder verwenden kannst. Einen Überblick über diesen Prozess (und ein Skript, mit dem du ihn automatisieren kannst) findest du in Rezept 1.22.

1.22 Programm: Skripte aus deinem Sitzungsverlauf erstellen

Nachdem du eine Weile interaktiv an der Kommandozeile experimentiert hast, um eine mehrstufige Aufgabe zu lösen, möchtest du oft die genauen Schritte, mit denen du das Problem schließlich gelöst hast, behalten oder weitergeben. Das Skript lächelt dich aus deinem History-Puffer an, aber leider ist es von vielen weiteren Befehlen umgeben, die du nicht behalten willst.

Hinweis

Ein Beispiel für die Verwendung des Cmdlets Out-GridView, um dies grafisch umzusetzen, findest du in Rezept 2.4.

Um dieses Problem zu lösen, verwende das Cmdlet Get-History, um die zuletzt eingegebenen Befehle anzuzeigen. Dann rufst du Copy-History mit den IDs der Befehle auf, die du behalten willst, wie in Beispiel 1-16 gezeigt.

Beispiel 1-16. Copy-History.ps1
##############################################################################
##
## Copy-History
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################

<#

.SYNOPSIS

Copy selected commands from the history buffer into the clipboard as a script.

.EXAMPLE

PS > Copy-History
Copies the entire contents of the history buffer into the clipboard.

.EXAMPLE

PS > Copy-History -5
Copies the last five commands into the clipboard.

.EXAMPLE

PS > Copy-History 2,5,8,4
Copies commands 2,5,8, and 4.

.EXAMPLE

PS > Copy-History (1..10+5+6)
Copies commands 1 through 10, then 5, then 6, using PowerShell's array
slicing syntax.

#>

[CmdletBinding()]
param(
    ## The range of history IDs to copy
    [Alias("Id")]
    [int[]] $Range
)

Set-StrictMode -Version 3

$history = @()

## If they haven't specified a range, assume it's everything
if((-not $range) -or ($range.Count -eq 0))
{
    $history = @(Get-History -Count ([Int16]::MaxValue))
}
## If it's a negative number, copy only that many
elseif(($range.Count -eq 1) -and ($range[0] -lt 0))
{
    $count = [Math]::Abs($range[0])
    $history = (Get-History -Count $count)
}
## Otherwise, go through each history ID in the given range
## and add it to our history list.
else
{
    foreach($commandId in $range)
    {
        if($commandId -eq -1) { $history += Get-History -Count 1 }
        else { $history += Get-History -Id $commandId }
    }
}

## Finally, export the history to the clipboard.
$history | Foreach-Object { $_.CommandLine } | clip.exe

Weitere Informationen zum Ausführen von Skripten findest du in Rezept 1.2.

1.23 Einen Befehl aus deinem Sitzungsverlauf aufrufen

Problem

Du möchtest einen Befehl aus dem Verlauf deiner aktuellen Sitzung ausführen.

Lösung

Verwenden Sie das Cmdlet Invoke-History (oder seinen Alias ihy ), um einen bestimmten Befehl über seine ID:

Invoke-History ID

Um deinen Verlauf nach einem Befehl zu durchsuchen, der text:

PS > #text<Tab>

Um deinen Befehl mit dem Text eines vorherigen Befehls zu füllen, indem du seine ID:

PS > #ID<Tab>

Diskussion

Wenn du deine Shell eine Zeit lang geöffnet hast, füllt sich dein Verlaufspuffer schnell mit nützlichen Befehlen. Die in Rezept 1.9 beschriebenen Hotkeys für die Verlaufsverwaltung zeigen eine Möglichkeit, wie du in deinem Verlauf navigieren kannst, aber diese Art der Verlaufsnavigation funktioniert nur für Befehlszeilen, die du in der jeweiligen Sitzung eingegeben hast. Wenn du einen dauerhaften Befehlsverlauf hast (wie in Rezept 1.31 beschrieben), gelten diese Tastenkombinationen nicht.

Das Cmdlet Invoke-History veranschaulicht das einfachste Beispiel für die Arbeit mit deinem Befehlsverlauf. Wenn du eine bestimmte Verlaufs-ID angibst (die du vielleicht in deiner Eingabeaufforderung angegeben hast), führt der Aufruf von Invoke-History mit dieser ID diesen Befehl erneut aus. Weitere Informationen zu dieser Technik findest du in Rezept 1.9.

Als Teil der Unterstützung für die Tabulatorvervollständigung bietet die PowerShell dir auch einen einfachen Zugriff auf vorherige Befehle. Wenn du deinem Befehl das Zeichen # voranstellst, hat die Tabulatorvervollständigung eine von zwei Möglichkeiten:

ID-Vervollständigung

Wenn du eine Zahl eingibst, findet die Tabulatorvervollständigung den Eintrag in deinem Befehlsverlauf mit dieser ID und ersetzt dann deine Befehlszeile durch den Text dieses Verlaufseintrags. Das ist besonders nützlich, wenn du einen früheren Verlaufseintrag geringfügig ändern möchtest, denn Invoke-History selbst unterstützt das nicht.

Mustervervollständigung

Wenn du etwas anderes eingibst, sucht die Tabulatorvervollständigung nach Einträgen in deinem Befehlsverlauf, die diesen Text enthalten. PowerShell verwendet den Operator -like, um deine Befehlseingaben abzugleichen. Du kannst also alle Platzhalterzeichen verwenden, die dieser Operator unterstützt. Weitere Informationen zum Durchsuchen von Text nach Mustern findest du in Rezept 5.7.

Die PowerShell-Registerkartenvervollständigung wird größtenteils durch die vollständig anpassbare Funktion Tab​Expansion2 gesteuert. Du kannst diese Funktion leicht ändern, um erweiterte Funktionen einzubinden oder auch nur bestimmte Verhaltensweisen an deine persönlichen Vorlieben anzupassen. Weitere Informationen findest du in Rezept 1.18.

1.24 Programm: Formatierte Ausgabe nach einem Muster durchsuchen

Die eingebauten Filterfunktionen der PowerShell sind zwar unglaublich flexibel (z. B. das Cmdlet Where-Object ), aber sie arbeiten in der Regel mit bestimmten Eigenschaften des eingehenden Objekts. Wenn du nach Text in der formatierten Ausgabe des Objekts suchst oder nicht weißt, welche Eigenschaft den gesuchten Text enthält, ist eine einfache textbasierte Filterung manchmal hilfreich.

Um dieses Problem zu lösen, kannst du die Ausgabe in das Cmdlet Out-String leiten, bevor du sie an das Cmdlet Select-String weitergibst:

Get-Service | Out-String -Stream | Select-String audio

Oder du verwendest integrierte Aliasnamen:

Get-Service | oss | sls audio

In der Skriptform macht Select-TextOutput (siehe Beispiel 1-17) genau das und lässt dich nach einem Muster in der visuellen Darstellung der Befehlsausgabe suchen.

Beispiel 1-17. Select-TextOutput.ps1
##############################################################################
##
## Select-TextOutput
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################

<#

.SYNOPSIS

Searches the textual output of a command for a pattern.

.EXAMPLE

PS > Get-Service | Select-TextOutput audio
Finds all references to "Audio" in the output of Get-Service

#>

param(
    ## The pattern to search for
    $Pattern
)

Set-StrictMode -Version 3
$input | Out-String -Stream | Select-String $pattern

Weitere Informationen zum Ausführen von Skripten findest du in Rezept 1.2.

1.25 Befehlsausgaben interaktiv anzeigen und verarbeiten

Problem

Du möchtest die Ausgabe eines Befehls grafisch untersuchen und analysieren.

Lösung

Verwende das cmdlet Out-GridView, um die Ausgabe eines Befehls interaktiv zu untersuchen.

Diskussion

Das Cmdlet Out-GridView ist eines der wenigen PowerShell-Cmdlets, das eine grafische Benutzeroberfläche anzeigt. Während die Cmdlets Where-Object und Sort-Object die gängigste Methode zum Sortieren und Filtern von Listen sind, ist das Cmdlet Out-GridView sehr effektiv bei der wiederholten Verfeinerung, die dir manchmal bei der Entwicklung komplexer Abfragen hilft. Abbildung 1-4 zeigt das Cmdlet Out-GridView in Aktion.

wps4 0104
Abbildung 1-4. Out-GridView, bereit zum Filtern

Out-GridView kannst du deine Befehlsausgabe vor allem auf zwei Arten filtern: mit einem Schnellfilterausdruck und einem Kriterienfilter.

Schnellfilter sind ziemlich einfach. Wenn du einen Text in das oberste "Filter"-Fenster eingibst, filtert Out-GridView die Liste so, dass sie nur Einträge enthält, die diesem Text entsprechen. Wenn du diese Textfilterung auf bestimmte Spalten beschränken möchtest, gibst du einfach einen Spaltennamen vor deinem Suchbegriff ein und trennst die beiden mit einem Doppelpunkt. Du kannst auch mehrere Suchbegriffe angeben. In diesem Fall gibt Out-GridView nur die Zeilen zurück, die mit allen erforderlichen Suchbegriffen übereinstimmen.

Hinweis

Anders als die meisten Filter-Cmdlets in der PowerShell unterstützen die Schnellfilter im Cmdlet Out-GridView keine Platzhalter oder regulären Ausdrücke. Für diese Art der erweiterten Abfrage kann eine kriterienbasierte Filterung hilfreich sein.

MitKriterienfiltern kann die Filterung des Cmdlets Out-GridView genau steuern. Um einen Kriterienfilter anzuwenden, klickst du auf die Schaltfläche "Kriterien hinzufügen" und wählst eine Eigenschaft aus, nach der gefiltert werden soll. Out-GridView fügt eine Zeile unter dem Schnellfilterfeld hinzu und lässt dich eine von mehreren Operationen auswählen, die auf diese Eigenschaft angewendet werden sollen:

  • Weniger als oder gleich

  • Größer als oder gleich

  • Zwischen

  • Entspricht

  • Ist nicht gleich

  • Enthält

  • Enthält nicht

Zusätzlich zu diesen Filteroptionen kannst du auf Out-GridView auch die Kopfspalten anklicken und neu anordnen, um nach ihnen zu sortieren.

Verarbeitung der Ausgabe

Wenn du deine Befehlsausgabe in Scheiben und Würfel geschnitten hast, kannst du alle Zeilen markieren, die du behalten möchtest, und Strg+C drücken, um sie in die Zwischenablage zu kopieren. Out-GridView kopiert die Elemente als tabulatorgetrennte Daten in die Zwischenablage, so dass du die Informationen leicht in eine Tabellenkalkulation oder eine andere Datei zur weiteren Bearbeitung einfügen kannst.

Das Cmdlet Out-GridView unterstützt nicht nur die Ausgabe über die Zwischenablage, sondern auch das Filtern von Objekten, wenn du den Parameter -PassThru verwendest. Ein Beispiel für diese Full-Fidelity-Filterung findest du in Rezept 2.4.

1.26 Programm: Interaktives Betrachten und Erforschen von Objekten

Wenn du mit unbekannten Objekten in der PowerShell arbeitest, verbringst du einen Großteil deiner Zeit mit den Befehlen Get-Member und Format-List - du navigierst durch Eigenschaften, überprüfst Mitglieder und mehr.

Für Ad-hoc-Untersuchungen ist eine grafische Oberfläche oft nützlich.

Um dieses Problem zu lösen, bietet Beispiel 1-18 eine interaktive Baumansicht, mit der du Objekte erkunden und navigieren kannst. So kannst du zum Beispiel die Struktur eines Skripts aus der Sicht der PowerShell untersuchen (den abstrakten Syntaxbaum):

$ps = { Get-Process -ID $pid }.Ast
Show-Object $ps

Weitere Informationen zum Parsen und Analysieren der Struktur von PowerShell-Skripten findest du in Rezept 10.10.

Beispiel 1-18. Show-Object.ps1
#############################################################################
##
## Show-Object
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################

<#

.SYNOPSIS

Provides a graphical interface to let you explore and navigate an object.


.EXAMPLE

PS > $ps = { Get-Process -ID $pid }.Ast
PS > Show-Object $ps

#>

param(
    ## The object to examine
    [Parameter(ValueFromPipeline = $true)]
    $InputObject
)

Set-StrictMode -Version 3

Add-Type -Assembly System.Windows.Forms

## Figure out the variable name to use when displaying the
## object navigation syntax. To do this, we look through all
## of the variables for the one with the same object identifier.
$rootVariableName = dir variable:\* -Exclude InputObject,Args |
    Where-Object {
        $_.Value -and
        ($_.Value.GetType() -eq $InputObject.GetType()) -and
        ($_.Value.GetHashCode() -eq $InputObject.GetHashCode())
}

## If we got multiple, pick the first
$rootVariableName = $rootVariableName| % Name | Select -First 1

## If we didn't find one, use a default name
if(-not $rootVariableName)
{
    $rootVariableName = "InputObject"
}

## A function to add an object to the display tree
function PopulateNode($node, $object)
{
    ## If we've been asked to add a NULL object, just return
    if(-not $object) { return }

    ## If the object is a collection, then we need to add multiple
    ## children to the node
    if([System.Management.Automation.LanguagePrimitives]::GetEnumerator($object))
    {
        ## Some very rare collections don't support indexing (i.e.: $foo[0]).
        ## In this situation, PowerShell returns the parent object back when you
        ## try to access the [0] property.
        $isOnlyEnumerable = $object.GetHashCode() -eq $object[0].GetHashCode()

        ## Go through all the items
        $count = 0
        foreach($childObjectValue in $object)
        {
            ## Create the new node to add, with the node text of the item and
            ## value, along with its type
            $newChildNode = New-Object Windows.Forms.TreeNode
            $newChildNode.Text = "$($node.Name)[$count] = $childObjectValue"
            $newChildNode.ToolTipText = $childObjectValue.GetType()

            ## Use the node name to keep track of the actual property name
            ## and syntax to access that property.
            ## If we can't use the index operator to access children, add
            ## a special tag that we'll handle specially when displaying
            ## the node names.
            if($isOnlyEnumerable)
            {
                $newChildNode.Name = "@"
            }

            $newChildNode.Name += "[$count]"
            $null = $node.Nodes.Add($newChildNode)

            ## If this node has children or properties, add a placeholder
            ## node underneath so that the node shows a '+' sign to be
            ## expanded.
            AddPlaceholderIfRequired $newChildNode $childObjectValue

            $count++
        }
    }
    else
    {
        ## If the item was not a collection, then go through its
        ## properties
        foreach($child in $object.PSObject.Properties)
        {
            ## Figure out the value of the property, along with
            ## its type.
            $childObject = $child.Value
            $childObjectType = $null
            if($childObject)
            {
                $childObjectType = $childObject.GetType()
            }

            ## Create the new node to add, with the node text of the item and
            ## value, along with its type
            $childNode = New-Object Windows.Forms.TreeNode
            $childNode.Text = $child.Name + " = $childObject"
            $childNode.ToolTipText = $childObjectType
            if([Management.Automation.LanguagePrimitives]::GetEnumerator($childObject))
            {
                $childNode.ToolTipText += "[]"
            }

            $childNode.Name = $child.Name
            $null = $node.Nodes.Add($childNode)

            ## If this node has children or properties, add a placeholder
            ## node underneath so that the node shows a '+' sign to be
            ## expanded.
            AddPlaceholderIfRequired $childNode $childObject
        }
    }
}

## A function to add a placeholder if required to a node.
## If there are any properties or children for this object, make a temporary
## node with the text "..." so that the node shows a '+' sign to be
## expanded.
function AddPlaceholderIfRequired($node, $object)
{
    if(-not $object) { return }

    if([System.Management.Automation.LanguagePrimitives]::GetEnumerator($object) -or
        @($object.PSObject.Properties))
    {
        $null = $node.Nodes.Add( (New-Object Windows.Forms.TreeNode "...") )
    }
}

## A function invoked when a node is selected.
function OnAfterSelect
{
    param($Sender, $TreeViewEventArgs)

    ## Determine the selected node
    $nodeSelected = $Sender.SelectedNode

    ## Walk through its parents, creating the virtual
    ## PowerShell syntax to access this property.
    $nodePath = GetPathForNode $nodeSelected

    ## Now, invoke that PowerShell syntax to retrieve
    ## the value of the property.
    $resultObject = Invoke-Expression $nodePath
    $outputPane.Text = $nodePath

    ## If we got some output, put the object's member
    ## information in the text box.
    if($resultObject)
    {
        $members = Get-Member -InputObject $resultObject | Out-String
        $outputPane.Text += "`n" + $members
    }
}

## A function invoked when the user is about to expand a node
function OnBeforeExpand
{
    param($Sender, $TreeViewCancelEventArgs)

    ## Determine the selected node
    $selectedNode = $TreeViewCancelEventArgs.Node

    ## If it has a child node that is the placeholder, clear
    ## the placeholder node.
    if($selectedNode.FirstNode -and
        ($selectedNode.FirstNode.Text -eq "..."))
    {
        $selectedNode.Nodes.Clear()
    }
    else
    {
        return
    }

    ## Walk through its parents, creating the virtual
    ## PowerShell syntax to access this property.
    $nodePath = GetPathForNode $selectedNode

    ## Now, invoke that PowerShell syntax to retrieve
    ## the value of the property.
    Invoke-Expression "`$resultObject = $nodePath"

    ## And populate the node with the result object.
    PopulateNode $selectedNode $resultObject
}

## A function to handle key presses on the tree view.
## In this case, we capture ^C to copy the path of
## the object property that we're currently viewing.
function OnTreeViewKeyPress
{
    param($Sender, $KeyPressEventArgs)

    ## [Char] 3 = Control-C
    if($KeyPressEventArgs.KeyChar -eq 3)
    {
        $KeyPressEventArgs.Handled = $true

        ## Get the object path, and set it on the clipboard
        $node = $Sender.SelectedNode
        $nodePath = GetPathForNode $node
        [System.Windows.Forms.Clipboard]::SetText($nodePath)

        $form.Close()
    }
    elseif([System.Windows.Forms.Control]::ModifierKeys -eq "Control")
    {
        if($KeyPressEventArgs.KeyChar -eq '+')
        {
            $SCRIPT:currentFontSize++
            UpdateFonts $SCRIPT:currentFontSize

            $KeyPressEventArgs.Handled = $true
        }
        elseif($KeyPressEventArgs.KeyChar -eq '-')
        {
            $SCRIPT:currentFontSize--
            if($SCRIPT:currentFontSize -lt 1) { $SCRIPT:currentFontSize = 1 }
            UpdateFonts $SCRIPT:currentFontSize

            $KeyPressEventArgs.Handled = $true
        }
    }
}

## A function to handle key presses on the form.
## In this case, we handle Ctrl-Plus and Ctrl-Minus
## to adjust font size.
function OnKeyUp
{
    param($Sender, $KeyUpEventArgs)

    if([System.Windows.Forms.Control]::ModifierKeys -eq "Control")
    {
        if($KeyUpEventArgs.KeyCode -in 'Add','OemPlus')
        {
            $SCRIPT:currentFontSize++
            UpdateFonts $SCRIPT:currentFontSize

            $KeyUpEventArgs.Handled = $true
        }
        elseif($KeyUpEventArgs.KeyCode -in 'Subtract','OemMinus')
        {
            $SCRIPT:currentFontSize--
            if($SCRIPT:currentFontSize -lt 1) { $SCRIPT:currentFontSize = 1 }
            UpdateFonts $SCRIPT:currentFontSize

            $KeyUpEventArgs.Handled = $true
        }
        elseif($KeyUpEventArgs.KeyCode -eq 'D0')
        {
            $SCRIPT:currentFontSize = 12
            UpdateFonts $SCRIPT:currentFontSize

            $KeyUpEventArgs.Handled = $true
        }
    }
}

## A function to handle mouse wheel scrolling.
## In this case, we translate Ctrl-Wheel to zoom.
function OnMouseWheel
{
    param($Sender, $MouseEventArgs)

    if(
        ([System.Windows.Forms.Control]::ModifierKeys -eq "Control") -and
        ($MouseEventArgs.Delta -ne 0))
    {
        $SCRIPT:currentFontSize += ($MouseEventArgs.Delta / 120)
        if($SCRIPT:currentFontSize -lt 1) { $SCRIPT:currentFontSize = 1 }

        UpdateFonts $SCRIPT:currentFontSize
        $MouseEventArgs.Handled = $true
    }
}

## A function to walk through the parents of a node,
## creating virtual PowerShell syntax to access this property.
function GetPathForNode
{
    param($Node)

    $nodeElements = @()

    ## Go through all the parents, adding them so that
    ## $nodeElements is in order.
    while($Node)
    {
        $nodeElements = ,$Node + $nodeElements
        $Node = $Node.Parent
    }

    ## Now go through the node elements
    $nodePath = ""
    foreach($Node in $nodeElements)
    {
        $nodeName = $Node.Name

        ## If it was a node that PowerShell is able to enumerate
        ## (but not index), wrap it in the array cast operator.
        if($nodeName.StartsWith('@'))
        {
            $nodeName = $nodeName.Substring(1)
            $nodePath = "@(" + $nodePath + ")"
        }
        elseif($nodeName.StartsWith('['))
        {
            ## If it's a child index, we don't need to
            ## add the dot for property access
        }
        elseif($nodePath)
        {
            ## Otherwise, we're accessing a property. Add a dot.
            $nodePath += "."
        }

        ## Append the node name to the path
        $tempNodePath = $nodePath + $nodeName
        if($nodeName -notmatch '^[$\[\]a-zA-Z0-9]+$')
        {
            $nodePath += "'" + $nodeName + "'"
        }
        else
        {
            $nodePath = $tempNodePath
        }
    }

    ## And return the result
    $nodePath
}

function UpdateFonts
{
    param($fontSize)

    $treeView.Font = New-Object System.Drawing.Font "Consolas",$fontSize
    $outputPane.Font = New-Object System.Drawing.Font "Consolas",$fontSize
}

$SCRIPT:currentFontSize = 12

## Create the TreeView, which will hold our object navigation
## area.
$treeView = New-Object Windows.Forms.TreeView
$treeView.Dock = "Top"
$treeView.Height = 500
$treeView.PathSeparator = "."
$treeView.ShowNodeToolTips = $true
$treeView.Add_AfterSelect( { OnAfterSelect @args } )
$treeView.Add_BeforeExpand( { OnBeforeExpand @args } )
$treeView.Add_KeyPress( { OnTreeViewKeyPress @args } )

## Create the output pane, which will hold our object
## member information.
$outputPane = New-Object System.Windows.Forms.TextBox
$outputPane.Multiline = $true
$outputPane.WordWrap = $false
$outputPane.ScrollBars = "Both"
$outputPane.Dock = "Fill"

## Create the root node, which represents the object
## we are trying to show.
$root = New-Object Windows.Forms.TreeNode
$root.ToolTipText = $InputObject.GetType()
$root.Text = $InputObject
$root.Name = '$' + $rootVariableName
$root.Expand()
$null = $treeView.Nodes.Add($root)

UpdateFonts $currentFontSize

## And populate the initial information into the tree
## view.
PopulateNode $root $InputObject

## Finally, create the main form and show it.
$form = New-Object Windows.Forms.Form
$form.Text = "Browsing " + $root.Text
$form.Width = 1000
$form.Height = 800
$form.Controls.Add($outputPane)
$form.Controls.Add($treeView)
$form.Add_MouseWheel( { OnMouseWheel @args } )
$treeView.Add_KeyUp( { OnKeyUp @args } )
$treeView.Select()
$null = $form.ShowDialog()
$form.Dispose()

Weitere Informationen zum Ausführen von Skripten findest du in Rezept 1.2.

1.27 Zeichne eine Abschrift deiner Shell-Sitzung auf

Problem

Du möchtest ein Protokoll oder eine Abschrift deiner Shell-Sitzung aufzeichnen.

Lösung

Um eine Abschrift deiner Shell-Sitzung aufzuzeichnen, führst du den Befehl Start-Transcript aus. Er hat einen optionalen Parameter -Path, der standardmäßig einen Dateinamen angibt, der auf der aktuellen Systemzeit basiert. Standardmäßig legt die PowerShell diese Datei im Verzeichnis Eigene Dateien ab. Um die Aufzeichnung der Mitschrift deines Shell-Systems zu beenden, führe den Befehl Stop-Transcript aus.

Diskussion

Obwohl das Cmdlet Get-History hilfreich ist, zeichnet es die während deiner PowerShell-Sitzung erzeugten Ausgaben nicht auf. Dafür kannst du das Cmdlet Start-Transcript verwenden. Zusätzlich zu dem zuvor beschriebenen Parameter Path unterstützt das Cmdlet Start-Transcript auch Parameter, mit denen du steuern kannst, wie PowerShell mit der Ausgabedatei interagiert.

Wenn du keinen -Path Parameter angibst, generiert PowerShell einen zufälligen Dateinamen für dich. Wenn du diese Datei nach dem Anhalten des Transkripts verarbeiten möchtest, fügt PowerShell diesen Namen als Eigenschaftsname zur Ausgabe von Start-Transcript oder Stop-Transcript hinzu:

PS > $myTranscript = Start-Transcript
PS > Stop-Transcript
Transcript stopped, output file is D:\Lee\PowerShell_transcript...
PS > $myTranscript | fl * -force

Path   : D:\Lee\PowerShell_transcript.LEE-DESKTOP.kg_Vsm_o.20201217195052.txt
Length : 104

PS > $myTranscript.Path
D:\Lee\PowerShell_transcript.LEE-DESKTOP.kg_Vsm_o.20201217195052.txt

PowerShell Transkripte beginnen mit einem Standard-Dateikopf, der die Zeit, den Benutzer, den Hostnamen und einige andere nützliche Angaben enthält. Wenn du den-IncludeInvocationHeader Parameter entweder interaktiv oder über eine systemweite Richtlinie angibst, fügt PowerShell auch ein Trennzeichen zwischen den Befehlen ein, um die automatische Analyse zu unterstützen.

**********************
PowerShell transcript start
Start time: 20201217190500
Username: ubuntu-20-04\lee
Machine: ubuntu-20-04 (Unix 4.19.128.0)
Host Application: /opt/microsoft/powershell/7/pwsh.dll
Process ID: 1925
OS: Linux 4.19.128-microsoft-standard #1 SMP Tue Jun 23 12:58:10 UTC 2020
(...)
**********************

**********************
Command start time: 20201217190502
**********************
PS /mnt/c/Users/lee> Get-Process

 NPM(K)    PM(M)      WS(M)     CPU(s)      Id  SI ProcessName
 ------    -----      -----     ------      --  -- -----------
      0     0.00       5.26       0.16     984 984 bash
      0     0.00       0.53       0.02       1   0 init
      0     0.00       0.07       0.00     982 982 init
      0     0.00       0.08       0.32     983 982 init
      0     0.00      96.52       0.64    1925 984 pwsh
      0     0.00       3.25       0.00    1873 …73 rsyslogd

**********************
Command start time: 20201217190504
**********************
PS /mnt/c/Users/lee> cat /var/log/powershell.log
(...)

Mit der PowerShell kannst du Abschriften nicht nur manuell aufzeichnen, sondern auch eine Systemrichtlinie einrichten, um sie automatisch aufzuzeichnen. Weitere Informationen dazu, wie du das einrichtest, findest du in Rezept 18.2.

1.28 Erweitere deine Shell mit zusätzlichen Befehlen

Problem

Du möchtest PowerShell Cmdlets, Provider oder skriptbasierte Erweiterungen verwenden, die von einem Drittanbieter geschrieben wurden.

Lösung

Wenn das Modul Teil des standardmäßigen PowerShell-Modulpfads ist, führst du einfach den gewünschten Befehl aus:

Invoke-NewCommand

Wenn es nicht ist, verwende den Befehl Import-Module, um Befehle von Drittanbietern in deine PowerShell-Sitzung zu importieren.

So importierst du ein Modul aus einem bestimmten Verzeichnis:

Import-Module c:\path\to\module

So importierst du ein Modul aus einer bestimmten Datei (Modul, Skript oder Assembly):

Import-Module c:\path\to\module\file.ext

Diskussion

PowerShell unterstützt zwei Befehlssätze, die zusätzliche Cmdlets und Provider ermöglichen: *-Module und *-PsSnapin. Snapins waren die Pakete für Erweiterungen in Version 1 der PowerShell und werden nur noch selten verwendet. Snapins unterstützten nur kompilierte Erweiterungen und hatten aufwändige Installationsanforderungen.

Mit Version 2 der PowerShell wurden Module eingeführt, die alles unterstützen, was Snapins unterstützen (und mehr), ohne dass die Installation mühsam ist. Allerdings musste man sich bei PowerShell Version 2 auch daran erinnern, welche Module welche Befehle enthielten, und diese Module manuell laden, bevor man sie verwenden konnte. In Windows gibt es jetzt Tausende von Befehlen in Hunderten von Modulen, so dass es nicht mehr ausreicht, sich auf den eigenen Speicher zu verlassen.

Jede neuere Version der PowerShell verbessert die Situation erheblich, indem sie Module automatisch für dich lädt. Intern verwaltet die PowerShell eine Zuordnung von Befehlsnamen zu dem Modul, das sie enthält. Wenn du einen Befehl verwendest (den du mit dem Cmdlet Get-Command herausfinden kannst), lädt die PowerShell automatisch das entsprechende Modul. Wenn du dieses automatische Ladeverhalten anpassen möchtest, kannst du die Einstellungsvariable $PSModuleAutoLoadingPreference verwenden.

Wenn die PowerShell ein Modul mit einem bestimmten Namen importiert, durchsucht sie alle Verzeichnisse, die in der Umgebungsvariablen PSModulePath aufgeführt sind, und sucht nach dem ersten Modul, das die Unterverzeichnisse enthält, die mit dem von dir angegebenen Namen übereinstimmen. Innerhalb dieser Verzeichnisse sucht sie nach dem Modul (*.psd1, *.psm1 und *.dll) mit demselben Namen und lädt es.

Wenn du ein Modul auf deinem eigenen System installierst, befindet es sich in der Regel im Verzeichnis PowerShell\Modules in deinem Verzeichnis Eigene Dateien. In der Windows PowerShell lautet dieser Ort WindowsPowerShell\Modules. Damit die PowerShell in einem anderen Verzeichnis nach Modulen sucht, fügst du es zu deiner persönlichen PSModulePath Umgebungsvariable hinzu, so wie du auch ein Tools-Verzeichnis zu deinem persönlichen Pfad hinzufügen würdest.

Weitere Informationen zur Verwaltung von Systempfaden findest du in Rezept 16.2.

Wenn du ein Modul aus einem Verzeichnis laden willst, das nicht in PSModulePath enthalten ist, kannst du den gesamten Verzeichnis- und Modulnamen an den Befehl Import-Module übergeben. Für ein Modul mit dem Namen Test verwendest du zum Beispiel Import-Module c:\path\to\Test. Wie beim Laden von Modulen nach Namen sucht die PowerShell in c:\temp\path\to nach einem Modul(*.psd1, *.psm1, oder *.dll) mit dem Namen Test und lädt es.

Wenn du die spezifische Moduldatei kennst, die du laden willst, kannst du auch den vollständigen Pfad zu diesem Modul angeben.

Wenn du weitere Befehle finden willst, siehe Rezept 1.29.

1.30 Befehle aus angepassten Shells verwenden

Problem

Du möchtest die Befehle von einem PowerShell-basierten Produkt verwenden, das eine angepasste Version der PowerShell-Konsole startet, aber in einer regulären PowerShell-Sitzung.

Lösung

Starte die angepasste Version der PowerShell-Konsole und verwende dann die Befehle Get-Module und Get-PsSnapin , um zu sehen, welche zusätzlichen Module und/oder Snapins geladen wurden.

Diskussion

Wie in Rezept 1.28 beschrieben, sind PowerShell-Module und Snapins die beiden Möglichkeiten, mit denen Drittanbieter zusätzliche PowerShell-Befehle verbreiten und hinzufügen können. Produkte, die angepasste Versionen der PowerShell-Konsole anbieten, tun dies, indem sie die PowerShell mit einem von drei Parametern starten:

  • -PSConsoleFile, um eine Konsolendatei zu laden, die eine Liste der zu ladenden Snapins enthält.

  • -Commandum einen anfänglichen Startbefehl anzugeben (der dann ein Snapin oder Modul lädt)

  • -Fileum ein anfängliches Startskript anzugeben (das dann ein Snapin oder Modul lädt)

Unabhängig davon, welche Methode du verwendest, kannst du den resultierenden Satz an geladenen Erweiterungen untersuchen, um zu sehen, welche du in deine anderen PowerShell-Sitzungen importieren kannst.

Erkennen von geladenen Snapins

Der Befehl Get-PsSnapin gibt alle Snapins zurück, die in der aktuellen Sitzung geladen sind. Er gibt immer den Satz der PowerShell-Kern-Snapins zurück, aber auch alle zusätzlichen Snapins, die von der angepassten Umgebung geladen wurden, werden angezeigt. Wenn zum Beispiel der Name eines Snapins, das du kennst, lautet Product.Feature.Commandslautet, kannst du es in zukünftige PowerShell-Sitzungen laden, indem du eintippst Add-PsSnapin Product.Feature.Commands. Um dies zu automatisieren, füge den Befehl in dein PowerShell-Profil ein.

Wenn du dir nicht sicher bist, welches Snapin du laden sollst, kannst du auch den Befehl Get-Command verwenden, um herauszufinden, welches Snapin einen bestimmten Befehl definiert:

PS > Get-Command Get-Counter | Select PsSnapin

PSSnapIn
--------
Microsoft.PowerShell.Diagnostics

Erkennen von geladenen Modulen

Wie der Befehl Get-PsSnapin gibt auch der Befehl Get-Module alle in der aktuellen Sitzung geladenen Module zurück. Er gibt alle Module zurück, die du bisher in der Sitzung hinzugefügt hast, aber auch alle zusätzlichen Module, die von der angepassten Umgebung geladen wurden. Wenn der Name eines Moduls, das du kennst, zum Beispiel ProductModulelautet, kannst du es in zukünftige PowerShell-Sitzungen laden, indem du eintippst Import-Module ProductModule. Um dies zu automatisieren, füge den Befehl in dein PowerShell-Profil ein.

Wenn du dir nicht sicher bist, welches Modul du laden sollst, kannst du auch den Befehl Get-Command verwenden, um herauszufinden, welches Modul einen bestimmten Befehl definiert:

PS > Get-Command Start-BitsTransfer | Select Module

Module
------
BitsTransfer

1.31 Status zwischen Sitzungen speichern

Problem

Du möchtest den Status oder den Verlauf zwischen PowerShell-Sitzungen speichern.

Lösung

Abonniere das PowerShell.Exiting Engine-Ereignis, damit PowerShell ein Skript oder einen Skriptblock aufruft, das/der jeden benötigten Status speichert.

Diskussion

PowerShell bietet einen einfachen skriptbasierten Zugriff auf eine Vielzahl von System-, Engine- und anderen Ereignissen. Du kannst dich für die Benachrichtigung über diese Ereignisse registrieren und sogar jedes dieser Ereignisse automatisch verarbeiten. Im folgenden Beispiel melden wir uns für das Ereignis mit dem Namen PowerShell.Exiting an. PowerShell erzeugt dieses Ereignis, wenn du eine Sitzung schließt.

Du kannst dieses Ereignis nutzen, um deinen Status, deine Variablen und alles andere, was du brauchst, zu speichern und wiederherzustellen. Das Modul PSReadLine speichert deinen Befehlsverlauf bereits automatisch zwischen den Sitzungen. Zu Demonstrationszwecken können wir eine ähnliche Funktion über das Ereignis PowerShell.Exiting implementieren. Dazu musst du in deinem Profil einen Aufruf an Enable-HistoryPersistence platzieren(Beispiel 1-19).

Beispiel 1-19. Enable-HistoryPersistence.ps1
##############################################################################
##
## Enable-HistoryPersistence
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################

<#

.SYNOPSIS

Reloads any previously saved command history, and registers for the
PowerShell.Exiting engine event to save new history when the shell
exits.

#>

Set-StrictMode -Version 3

## Load our previous history
$GLOBAL:maximumHistoryCount = 32767
$historyFile = (Join-Path (Split-Path $profile) "commandHistory.clixml")
if(Test-Path $historyFile)
{
    Import-CliXml $historyFile | Add-History
}

## Register for the engine shutdown event
$null = Register-EngineEvent -SourceIdentifier `
    ([System.Management.Automation.PsEngineEvent]::Exiting) -Action {

    ## Save our history
    $historyFile = (Join-Path (Split-Path $profile) "commandHistory.clixml")
    $maximumHistoryCount = 1kb

    ## Get the previous history items
    $oldEntries = @()
    if(Test-Path $historyFile)
    {
        $oldEntries = Import-CliXml $historyFile -ErrorAction SilentlyContinue
    }

    ## And merge them with our changes
    $currentEntries = Get-History -Count $maximumHistoryCount
    $additions = Compare-Object $oldEntries $currentEntries `
        -Property CommandLine | Where-Object { $_.SideIndicator -eq "=>" } |
        Foreach-Object { $_.CommandLine }

    $newEntries = $currentEntries | ? { $additions -contains $_.CommandLine }

    ## Keep only unique command lines. First sort by CommandLine in
    ## descending order (so that we keep the newest entries,) and then
    ## re-sort by StartExecutionTime.
    $history = @($oldEntries + $newEntries) |
        Sort -Unique -Descending CommandLine | Sort StartExecutionTime

    ## Finally, keep the last 100
    Remove-Item $historyFile
    $history | Select -Last 100 | Export-CliXml $historyFile
}

Dieses Skript könnte alles Mögliche tun, aber in diesem Beispiel speichern wir unseren Befehlsverlauf und stellen ihn wieder her, wenn wir die PowerShell starten. Warum sollten wir das tun? Nun, mit einem umfangreichen Verlaufspuffer können wir Befehle, die wir zuvor ausgeführt haben, leichter wiederfinden und wiederverwenden. Zwei Beispiele für diese Vorgehensweise findest du in den Rezepten 1.21 und 1.23.

Enable-HistoryPersistence führt zwei Hauptaktionen durch. Zuerst laden wir unseren gespeicherten Befehlsverlauf (falls vorhanden). Dann registrieren wir eine automatische Aktion, die immer dann ausgeführt wird, wenn die Engine das Ereignis PowerShell.Exiting erzeugt. Die Aktion selbst ist relativ einfach, aber das Exportieren unseres neuen Verlaufs erfordert ein wenig Fingerspitzengefühl. Wenn du mehrere Sitzungen gleichzeitig geöffnet hast, aktualisiert jede Sitzung die gespeicherte Verlaufsdatei, wenn sie beendet wird. Da wir den von den anderen Shells gespeicherten Verlauf nicht überschreiben wollen, laden wir ihn zunächst von der Festplatte neu und kombinieren ihn mit dem Verlauf der aktuellen Shell.

Sobald wir die kombinierte Liste der Befehlszeilen haben, sortieren wir sie und wählen die eindeutigen Zeilen aus, bevor wir sie wieder in der Datei speichern.

Unter findest du weitere Informationen über die Arbeit mit PowerShell-Engine-Ereignissen, siehe Rezept 31.2.

Get PowerShell Kochbuch, 4. Auflage now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.