HOWTO · PowerShell
How to Terminate a Script in Windows PowerShell
Learn when to use exit, return, throw, break, continue, or Stop-Process to end the correct scope and report a reliable PowerShell exit code.
On this page
Stopping a PowerShell script is not a single operation. The correct command depends on what must stop: the whole script process, the current function, a loop iteration, or another operating-system process.
| Goal | Use |
|---|---|
| Finish the top-level script and report a status | exit <code> |
| Leave a function, script, or scriptblock | return |
| Report a failure that callers can catch | throw |
Leave a loop or switch |
break |
Skip to the next loop or switch item |
continue |
| Terminate a separate local process | Stop-Process |
Terminate a PowerShell Script With exit
exit ends a script or PowerShell instance. An optional integer becomes the process exit code: conventionally, 0 means success and a nonzero value means failure. Give each nonzero code a documented meaning so schedulers, CI jobs, and wrapper programs can respond correctly.
Save this entry script as Check-Config.ps1:
param([Parameter(Mandatory)][string]$Path)
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
Write-Error "Configuration file not found: $Path"
exit 2
}
Write-Output 'Configuration file found.'
exit 0
Run it as a child process from another PowerShell session, then inspect the status:
powershell.exe -NoProfile -File .\Check-Config.ps1 -Path .\missing.json
$LASTEXITCODE
The last command prints 2. With PowerShell 7, use pwsh instead of powershell.exe. The caller can read the child process status through $LASTEXITCODE; a cmd.exe caller reads %ERRORLEVEL%.
Keep exit at the top-level script boundary. If a reusable function calls exit, it can close an interactive session, test runner, or other host that invoked it. Such code is easier to reuse when functions return data or throw errors and only the entry script translates the result into an exit code.
Leave the Current Scope With return
return leaves the current function, script, or scriptblock. It does not inherently set a process exit code.
function Get-ConfigText {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
return $null
}
Get-Content -LiteralPath $Path -Raw
}
In a normal PowerShell function, every uncaptured success-stream value becomes output—not only the expression after return. Use Write-Verbose or another appropriate stream for diagnostics instead of emitting extra strings with Write-Output.
Stop With a Catchable Error Using throw
throw creates a script-terminating error by default and unwinds the call stack until a catch block or trap handles it. Use it when a function cannot produce a valid result and its caller should decide how to recover.
function Get-RequiredConfig {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
throw "Configuration file not found: $Path"
}
Get-Content -LiteralPath $Path -Raw
}
try {
$config = Get-RequiredConfig -Path '.\settings.json'
}
catch {
Write-Error $_
exit 2
}
This pattern separates two responsibilities: the function explains the failure with throw, while the entry script catches it and chooses the public exit code. Do not assume that every cmdlet error reaches catch; a non-terminating error needs -ErrorAction Stop when it must be handled there.
Use break and continue Only for Control Blocks
break exits the nearest loop or switch. continue skips the rest of the current iteration and starts the next one.
foreach ($name in 'alpha', '', 'beta', 'stop', 'omega') {
if ([string]::IsNullOrWhiteSpace($name)) {
continue
}
if ($name -eq 'stop') {
break
}
Write-Output $name
}
Output:
alpha
beta
They are not general script-termination commands. Outside a loop, switch, or trap, PowerShell can search the call stack for an enclosing construct and may terminate the current runspace if none exists.
Terminate Another Process With Stop-Process
Stop-Process targets a separate process on the local computer; it does not mean “stop this script.” Preview a broad match with -WhatIf:
Get-Process -Name notepad -ErrorAction SilentlyContinue |
Stop-Process -WhatIf
Remove -WhatIf only after confirming the targets. Prefer a known process object or PID when a name could match several processes. Stopping a process owned by another user may also require an elevated PowerShell session.
Preserve Native Program Exit Codes
$? describes whether the last PowerShell command succeeded, whereas $LASTEXITCODE stores the latest native program’s exit code. Save a native status immediately because the next native command can overwrite it.
git status --porcelain
$gitCode = $LASTEXITCODE
if ($gitCode -ne 0) {
Write-Error "git failed with exit code $gitCode"
exit $gitCode
}
Preserve Cleanup and Separate Cancellation
Keep cleanup in finally, not after a line that may end processing. According to the PowerShell language-keyword documentation, finally runs whether the try work succeeds, an error reaches catch, exit is called, or Ctrl+C interrupts the script. It is therefore the right place to dispose a stream, release a lock, or remove a temporary file.
$stream = $null
try {
$stream = [System.IO.File]::OpenRead($Path)
# Process the stream.
}
finally {
if ($null -ne $stream) {
$stream.Dispose()
}
}
Let reusable code throw or return to an entry script instead of calling exit in the middle of this work. The entry script can then perform cleanup, write one diagnostic, and choose the process status in one visible place. Ctrl+C, a stopped job, and invalid input are different events: decide and document whether each is cancellation, a validation failure, or another status that a caller or scheduler must act on. Do not silently collapse them into one generic failure. Stopping another process is different again; prefer an application’s normal shutdown mechanism when it has one. Stop-Process -Force can prevent the target from cleaning up files or state, so use it only when that consequence is understood; it is not a general replacement for return, throw, or exit.
Keep Child Scripts and Hosts in the Intended Scope
The call operator runs a child script in its own script scope, while dot-sourcing runs a script in the current scope and imports its functions and variables. Use dot-sourcing only when importing definitions is intentional.
# Run the child in its own script scope.
& .\Child.ps1
# Import definitions into the current scope.
. .\Functions.ps1
An exit in reusable code is stronger than return: it can end the interactive session, test runner, or other host that invoked it. The visible effect also depends on the host. A child process launched with powershell.exe -File or pwsh -File ends and returns control to its caller, while an interactive console can close its current session. Embedded runspaces and editors can behave differently. Test the exact production command line, including -File versus -Command, profiles, quoting, output, and exit status separately.
Recommendation
Use exit <code> only at the deliberate top-level boundary, return for the current scope, and throw for a failure a caller can handle. Use break and continue where their loop or switch is visible, and use Stop-Process only for a separate local process. Keeping business logic in functions that return or throw makes it reusable; a small entry script can then translate the final result into a stable status for a shell, scheduler, CI job, or wrapper program.