HOWTO · PowerShell
Wie man ein Skript in Windows PowerShell beendet
Erfahren Sie, wann exit, return, throw, break, continue oder Stop-Process den richtigen Bereich beendet und einen verlässlichen PowerShell-Exitcode liefert.
Auf dieser Seite
Das Beenden eines PowerShell-Skripts ist nicht immer derselbe Vorgang. Wählen Sie die Anweisung anhand der Grenze, die beendet werden soll: den gesamten Skriptprozess, den aktuellen Funktions- oder Skriptbereich, eine Schleifeniteration oder einen anderen Betriebssystemprozess.
| Ziel | Verwenden Sie |
|---|---|
| Das Hauptskript beenden und einen Status melden | exit <code> |
| Eine Funktion, ein Skript oder einen Skriptblock verlassen | return |
| Einen Fehler melden, den Aufrufer abfangen können | throw |
Eine Schleife oder switch verlassen |
break |
Zum nächsten Element einer Schleife oder von switch springen |
continue |
| Einen separaten lokalen Prozess beenden | Stop-Process |
Ein PowerShell-Skript mit exit beenden
exit beendet ein Skript oder eine PowerShell-Instanz. Eine optionale Ganzzahl wird zum Prozess-Exitcode: Üblicherweise bedeutet 0 Erfolg und ein Wert ungleich null einen Fehler. Geben Sie jedem Wert ungleich null eine dokumentierte Bedeutung, damit Scheduler, CI-Aufträge und Wrapper-Programme korrekt reagieren können.
Speichern Sie dieses Muster für ein Einstiegsskript als Check-Config.ps1. Das Beispiel erfasst den Fehlerstatus, statt einen Fehler zu schreiben oder mit einem Wert ungleich null zu beenden. Dadurch bleibt es beim Ausführen ohne Eingabedatei selbstständig und erfolgreich. Ersetzen Sie am tatsächlichen Einstiegspunkt diese Erfassung durch Write-Error und exit $exitCode.
param([string]$Path = '')
try {
$configurationExists =
-not [string]::IsNullOrWhiteSpace($Path) -and
(Test-Path -LiteralPath $Path -PathType Leaf -ErrorAction Stop)
if ($configurationExists) {
$message = 'Configuration file found.'
$exitCode = 0
}
else {
$message = "Configuration file not found: $Path"
$exitCode = 2
}
$message, $exitCode | Out-Null
}
catch {
$errorMessage = 'configuration check failed'
$errorMessage | Out-Null
}
Speichern Sie dies als Demo.ps1 und führen Sie es aus; das Skript erfasst die Statuscodes der Kindprozesse und gibt sie aus:
$successOutput = & pwsh -NoProfile -Command 'Write-Output success'
$successCode = $LASTEXITCODE
$failureOutput = & pwsh -NoProfile -Command 'Write-Output failure; exit 2'
$failureCode = $LASTEXITCODE
"success=$successCode"
"failure=$failureCode"
success=0
failure=2
Führen Sie das gespeicherte Skript aus einer anderen PowerShell-Sitzung als Kindprozess aus und prüfen Sie anschließend seinen Status. Die folgenden Befehle veranschaulichen die Syntax für das tatsächliche Einstiegsskript; sie sind keine mit diesem Artikel gelieferten Ausführungsnachweise:
powershell.exe -NoProfile -File .\Check-Config.ps1 -Path .\missing.json
$LASTEXITCODE
Ein PowerShell-Aufrufer liest den Status des Kindprozesses über $LASTEXITCODE; ein cmd.exe-Aufrufer liest %ERRORLEVEL%. Verwenden Sie exit an der Grenze des Hauptskripts. Wenn wiederverwendbarer Code exit aufruft, kann er eine interaktive Sitzung, einen Test-Runner oder einen anderen aufrufenden Host schließen. Funktionen lassen sich leichter wiederverwenden, wenn sie Daten zurückgeben oder Fehler auslösen und erst das Einstiegsskript das Ergebnis in einen Exitcode übersetzt.
Den aktuellen Bereich mit return verlassen
return verlässt die aktuelle Funktion, das aktuelle Skript oder den aktuellen Skriptblock. Die Anweisung setzt nicht automatisch einen Prozess-Exitcode.
param([string]$Content = 'enabled=true')
$path = Join-Path ([System.IO.Path]::GetTempPath()) ("config-" + [guid]::NewGuid() + ".txt")
try {
[System.IO.File]::WriteAllText($path, $Content)
function Get-ConfigText {
param([string]$Path = '')
if (-not (Test-Path -LiteralPath $Path -PathType Leaf -ErrorAction Stop)) {
return $null
}
Get-Content -LiteralPath $Path -Raw -ErrorAction Stop
}
$content = Get-ConfigText -Path $path
$missing = Get-ConfigText -Path ($path + '.missing')
$content, $missing | Out-Null
}
catch {
$errorMessage = $_.Exception.Message
$errorMessage | Out-Null
}
finally {
Remove-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue
}
In einer normalen PowerShell-Funktion wird jeder nicht abgefangene Wert des Erfolgsstreams zur Ausgabe — nicht nur der Ausdruck hinter return. Verwenden Sie für Diagnosemeldungen Write-Verbose oder einen anderen passenden Stream, statt zusätzliche Zeichenfolgen mit Write-Output auszugeben.
Mit throw einen abfangbaren Fehler auslösen
throw erzeugt standardmäßig einen skriptbeendenden Fehler und wickelt den Aufrufstack ab, bis ein catch-Block oder trap ihn behandelt. Verwenden Sie throw, wenn eine Funktion kein gültiges Ergebnis liefern kann und ihr Aufrufer entscheiden soll, wie die Verarbeitung fortgesetzt wird.
param([string]$MissingName = 'missing-settings.json')
function Get-RequiredConfig {
param([string]$Path = '')
if (-not (Test-Path -LiteralPath $Path -PathType Leaf -ErrorAction Stop)) {
throw "Configuration file not found: $Path"
}
Get-Content -LiteralPath $Path -Raw -ErrorAction Stop
}
$exitCode = 0
try {
$config = Get-RequiredConfig -Path (Join-Path ([System.IO.Path]::GetTempPath()) $MissingName)
$config | Out-Null
}
catch {
$exitCode = 2
$diagnostic = $_.Exception.Message
$diagnostic | Out-Null
}
$exitCode | Out-Null
Dieses Muster trennt zwei Verantwortlichkeiten: Die Funktion beschreibt den Fehler mit throw, während das Einstiegsskript ihn abfängt und den öffentlichen Exitcode auswählt. Das Beispiel speichert diesen Code, statt exit 2 aufzurufen, sodass es selbst erfolgreich abgeschlossen wird. Gehen Sie nicht davon aus, dass jeder Cmdlet-Fehler catch erreicht; ein nicht skriptbeendender Fehler benötigt -ErrorAction Stop, wenn er dort behandelt werden muss. trap ist eine alternative Behandlungsmöglichkeit, wenn die umgebende Skriptstruktur dies erfordert.
break und continue nur in Kontrollblöcken verwenden
break verlässt die nächste Schleife oder switch-Anweisung. continue überspringt den Rest der aktuellen Iteration und beginnt die nächste.
param([string]$StopAt = 'stop')
try {
foreach ($name in 'alpha', '', 'beta', 'stop', 'omega') {
if ([string]::IsNullOrWhiteSpace($name)) {
continue
}
if ($name -eq $StopAt) {
break
}
Write-Output $name
}
}
catch {
$errorMessage = $_.Exception.Message
$errorMessage | Out-Null
}
Diese Schleife soll alpha und beta ausgeben und anhalten, sobald sie stop erreicht; omega wird nicht ausgegeben. break und continue sind keine allgemeinen Befehle zum Beenden eines Skripts. Verwenden Sie sie nur dort, wo die Schleife oder switch sichtbar ist. In anderen Kontexten kann PowerShell den Aufrufstack nach einem umgebenden Konstrukt durchsuchen; ein Verhalten ohne ein solches Konstrukt sollte nicht als Ersatz für eine ausdrückliche Skriptgrenze dienen.
Einen anderen Prozess mit Stop-Process beenden
Stop-Process zielt auf einen separaten Prozess auf dem lokalen Computer; es bedeutet nicht „dieses Skript beenden“. Zeigen Sie ein Ziel mit -WhatIf in der Vorschau an, bevor Sie das Beenden zulassen:
param([int]$ProcessId = 0)
try {
$targetId = if ($ProcessId -gt 0) { $ProcessId } else { $PID }
$target = Get-Process -Id $targetId -ErrorAction Stop
$preview = $target | Stop-Process -WhatIf
$preview | Out-Null
}
catch {
$errorMessage = $_.Exception.Message
$errorMessage | Out-Null
}
Das Fixture zeigt den aktuellen Prozess in der Vorschau an und beendet ihn nicht. Entfernen Sie -WhatIf in operativem Code erst, nachdem Sie das Ziel bestätigt haben. Bevorzugen Sie ein bekanntes Prozessobjekt oder eine PID, wenn ein Name mehreren Prozessen entsprechen kann. Das Beenden eines Prozesses, der einem anderen Benutzer gehört, kann eine PowerShell-Sitzung mit erhöhten Rechten erfordern. Bevorzugen Sie den normalen Beendigungsmechanismus der Anwendung, sofern einer vorhanden ist; Stop-Process -Force kann verhindern, dass das Ziel Dateien oder Zustand bereinigt.
Exitcodes nativer Programme bewahren
$? beschreibt, ob der letzte PowerShell-Befehl erfolgreich war, während $LASTEXITCODE den Exitcode des zuletzt ausgeführten nativen Programms speichert. Speichern Sie einen nativen Status sofort, da der nächste native Befehl ihn überschreiben kann.
Das folgende Muster wird absichtlich als Text und nicht in einem Codeblock beschrieben: Das Paket enthält keine portierbare native ausführbare Datei und keinen Ausführungsnachweis dafür. Ersetzen Sie native-command durch den in Ihrer Umgebung verwendeten Befehl, führen Sie native-command aus und weisen Sie $nativeCode = $LASTEXITCODE sofort zu. Wenn $nativeCode -ne 0 gilt, melden Sie native-command failed with exit code $nativeCode und verwenden Sie exit $nativeCode an der bewussten Einstiegspunktgrenze.
Verwenden Sie $? nicht als Ersatz für den numerischen Status des nativen Programms. Wenn ein Wrapper das native Ergebnis bewahren muss, erfassen Sie $LASTEXITCODE, bevor Sie einen weiteren nativen Befehl ausführen, und übersetzen Sie es erst an der bewussten Einstiegspunktgrenze.
Aufräumen sicherstellen und Abbruch unterscheiden
Bewahren Sie Aufräumarbeiten in finally auf und nicht hinter einer Zeile, die die Verarbeitung beenden kann. Wenn die Steuerung den try-Block durch einen behandelten Fehler oder den normalen Abschluss verlässt, ist dies der übliche Ort, um einen Stream freizugeben, eine Sperre zu lösen oder eine temporäre Datei zu entfernen. Betrachten Sie dies nicht als Beleg dafür, dass die Bereinigung bei jeder Unterbrechung auf Hostebene oder Prozessbeendigung ausgeführt wird; prüfen Sie das Verhalten des tatsächlichen Aufrufs, wenn diese Unterscheidung wichtig ist.
param([string]$Content = 'temporary data')
$path = $null
$stream = $null
try {
$path = Join-Path ([System.IO.Path]::GetTempPath()) ("stream-" + [guid]::NewGuid() + ".txt")
[System.IO.File]::WriteAllText($path, $Content)
$stream = [System.IO.File]::OpenRead($path)
$stream.Length | Out-Null
}
catch {
$errorMessage = $_.Exception.Message
$errorMessage | Out-Null
}
finally {
if ($null -ne $stream) {
$stream.Dispose()
}
if ($null -ne $path) {
Remove-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue
}
}
Lassen Sie wiederverwendbaren Code mit throw oder return zu einem Einstiegsskript zurückkehren, statt mitten in dieser Arbeit exit aufzurufen. Das Einstiegsskript kann dann an einer sichtbaren Stelle aufräumen, eine Diagnose schreiben und den Prozessstatus auswählen. Strg+C, ein angehaltener Job und ungültige Eingaben sind unterschiedliche Ereignisse: Entscheiden und dokumentieren Sie, ob jedes davon ein Abbruch, ein Validierungsfehler oder ein anderer Status ist, auf den ein Aufrufer oder Scheduler reagieren muss. Fassen Sie sie nicht stillschweigend zu einem allgemeinen Fehler zusammen. Einen anderen Prozess zu beenden ist wiederum etwas anderes; bevorzugen Sie den normalen Beendigungsmechanismus der Anwendung, sofern sie einen besitzt.
Kindskripte und Hosts im vorgesehenen Bereich halten
Der Aufrufoperator führt ein Kindskript in seinem eigenen Skriptbereich aus, während Dot-Sourcing ein Skript im aktuellen Bereich ausführt und dessen Funktionen und Variablen importiert. Verwenden Sie Dot-Sourcing nur, wenn der Import von Definitionen beabsichtigt ist.
param([string]$FunctionName = 'Get-DemoValue')
$root = Join-Path ([System.IO.Path]::GetTempPath()) ("scope-" + [guid]::NewGuid())
try {
New-Item -ItemType Directory -Path $root -ErrorAction Stop | Out-Null
$child = Join-Path $root 'Child.ps1'
$functions = Join-Path $root 'Functions.ps1'
Set-Content -LiteralPath $child -Value 'Set-Variable -Name ChildValue -Value child' -ErrorAction Stop
Set-Content -LiteralPath $functions -Value "function $FunctionName { return 'loaded' }" -ErrorAction Stop
& $child
. $functions
& $FunctionName | Out-Null
}
catch {
$errorMessage = $_.Exception.Message
$errorMessage | Out-Null
}
finally {
Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue
}
Ein exit in wiederverwendbarem Code ist stärker als return: Es kann die interaktive Sitzung, den Test-Runner oder einen anderen Host beenden, der den Code aufgerufen hat. Die sichtbare Wirkung hängt außerdem vom Host ab. Ein mit powershell.exe -File gestarteter Kindprozess endet und gibt die Steuerung an seinen Aufrufer zurück, während eine interaktive Konsole ihre aktuelle Sitzung schließen kann. Eingebettete Runspaces und Editoren können sich anders verhalten. Testen Sie die genaue Produktionskommandozeile getrennt, einschließlich -File gegenüber -Command, Profilen, Quoting, Ausgabe und Exitstatus; die Beispiele hier behaupten nicht, dass diese hostabhängigen Prüfungen ausgeführt wurden.
Empfehlung
Verwenden Sie exit <code> nur an der bewussten Grenze auf der obersten Skriptebene, return für den aktuellen Bereich und throw für einen Fehler, den ein Aufrufer behandeln kann. Verwenden Sie break und continue, wenn die zugehörige Schleife oder switch sichtbar ist, und Stop-Process nur für einen separaten lokalen Prozess. Wenn die Geschäftslogik in Funktionen bleibt, die Werte zurückgeben oder Fehler auslösen, bleibt sie wiederverwendbar; ein kleines Einstiegsskript kann das Endergebnis anschließend in einen stabilen Status für eine Shell, einen Scheduler, einen CI-Auftrag oder ein Wrapper-Programm übersetzen.