HOWTO · PowerShell
Come terminare uno script in Windows PowerShell
Scopri quando usare exit, return, throw, break, continue o Stop-Process per fermare l'ambito corretto e restituire un codice di uscita PowerShell affidabile.
In questa pagina
Terminare uno script PowerShell non è una singola operazione. Scegli il comando in base al confine che deve arrestarsi: l’intero processo dello script, la funzione o l’ambito dello script corrente, un’iterazione del ciclo oppure un altro processo del sistema operativo.
| Obiettivo | Usa |
|---|---|
| Terminare lo script di livello superiore e comunicare uno stato | exit <code> |
| Uscire da una funzione, uno script o uno scriptblock | return |
| Segnalare un errore che i chiamanti possono intercettare | throw |
Uscire da un ciclo o da switch |
break |
Passare all’elemento successivo del ciclo o di switch |
continue |
| Terminare un processo locale distinto | Stop-Process |
Terminare uno script PowerShell con exit
exit termina uno script o un’istanza di PowerShell. Un intero facoltativo diventa il codice di uscita del processo: per convenzione, 0 indica il successo e un valore diverso da zero indica un errore. Assegna a ogni codice diverso da zero un significato documentato, così le attività pianificate, i processi CI e i programmi wrapper possono reagire correttamente.
Salva questo modello di script di ingresso come Check-Config.ps1. Il blocco di codice cattura lo stato di errore invece di scrivere un errore o uscire con un valore diverso da zero, quindi rimane autonomo e termina correttamente quando viene eseguito senza un file di input. Nel vero punto di ingresso, sostituisci quella cattura con Write-Error e 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
}
Salva il codice seguente come Demo.ps1 ed eseguilo: cattura gli stati dei processi figli e li stampa.
$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
Esegui lo script salvato come processo figlio da un’altra sessione PowerShell, quindi controlla il suo stato. I comandi seguenti illustrano la sintassi per lo script di ingresso effettivo; non costituiscono una prova di esecuzione fornita con questo articolo:
powershell.exe -NoProfile -File .\Check-Config.ps1 -Path .\missing.json
$LASTEXITCODE
Un chiamante PowerShell legge lo stato del processo figlio tramite $LASTEXITCODE; un chiamante cmd.exe legge %ERRORLEVEL%. Mantieni exit al confine dello script di livello superiore. Se il codice riutilizzabile chiama exit, può chiudere una sessione interattiva, un test runner o un altro host che lo ha invocato. Le funzioni sono più facili da riutilizzare quando restituiscono dati o generano errori, lasciando allo script di ingresso il compito di tradurre il risultato in un codice di uscita.
Uscire dall’ambito corrente con return
return esce dalla funzione, dallo script o dallo scriptblock corrente. Non imposta automaticamente un codice di uscita del processo.
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 una normale funzione PowerShell, ogni valore non acquisito del flusso di successo diventa output, non soltanto l’espressione dopo return. Per le informazioni diagnostiche usa Write-Verbose o un altro flusso appropriato, invece di emettere stringhe aggiuntive con Write-Output.
Fermarsi con un errore intercettabile usando throw
throw crea per impostazione predefinita un errore che termina lo script e risale lo stack di chiamate finché un blocco catch o un’istruzione trap non lo gestisce. Usalo quando una funzione non può produrre un risultato valido e il chiamante deve decidere come recuperare.
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
Questo modello separa due responsabilità: la funzione descrive l’errore con throw, mentre lo script di ingresso lo intercetta e sceglie il codice di uscita pubblico. Il blocco di codice conserva quel codice invece di chiamare exit 2, quindi l’esempio stesso termina correttamente. Non presumere che ogni errore di un cmdlet raggiunga catch; un errore non terminante richiede -ErrorAction Stop quando deve essere gestito lì. trap è un gestore alternativo quando questa è la struttura richiesta dallo script circostante.
Usare break e continue solo nei blocchi di controllo
break esce dal ciclo o dallo switch più vicino. continue salta il resto dell’iterazione corrente e avvia quella successiva.
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
}
Questo ciclo è pensato per scrivere alpha e beta, quindi fermarsi quando raggiunge stop; non scrive omega. break e continue non sono comandi generici per terminare lo script. Usali solo dove il ciclo o lo switch è visibile. In altri contesti, PowerShell può cercare nello stack di chiamate un costrutto contenitore; in assenza di tale costrutto, il comportamento non deve essere usato come sostituto di un confine esplicito dello script.
Terminare un altro processo con Stop-Process
Stop-Process agisce su un processo distinto nel computer locale; non significa “fermare questo script”. Visualizza in anteprima il processo da colpire con -WhatIf prima di consentire l’arresto:
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
}
Il blocco di codice visualizza in anteprima il processo corrente e non lo arresta. Nel codice operativo, rimuovi -WhatIf solo dopo aver confermato la destinazione. Preferisci un oggetto processo noto o un PID quando un nome potrebbe corrispondere a più processi. Per arrestare un processo appartenente a un altro utente potrebbe essere necessaria una sessione PowerShell con privilegi elevati. Quando esiste, preferisci il normale meccanismo di arresto dell’applicazione; Stop-Process -Force può impedire al processo di pulire file o stato.
Conservare i codici di uscita dei programmi nativi
$? indica se l’ultimo comando PowerShell è riuscito, mentre $LASTEXITCODE contiene il codice di uscita dell’ultimo programma nativo. Salva subito lo stato nativo, perché il comando nativo successivo può sovrascriverlo.
Il modello seguente è descritto intenzionalmente in linea, non in un blocco di codice: il pacchetto non fornisce un eseguibile nativo portabile né prove di esecuzione per questo caso. Sostituisci native-command con il comando usato dal tuo ambiente, esegui native-command e assegna immediatamente $nativeCode = $LASTEXITCODE. Se $nativeCode -ne 0, segnala native-command failed with exit code $nativeCode e usa exit $nativeCode al confine di ingresso scelto.
Non usare $? come sostituto del valore numerico restituito dal programma nativo. Se un wrapper deve conservare il risultato nativo, cattura $LASTEXITCODE prima di eseguire un altro comando nativo e traducilo solo al confine di ingresso scelto.
Preservare la pulizia e distinguere l’annullamento
Mantieni la pulizia in finally, non dopo una riga che può interrompere l’elaborazione. È il punto consueto in cui rilasciare uno stream, un blocco o un file temporaneo quando il controllo esce dal costrutto try a causa di un errore gestito o del completamento normale. Non considerarlo una prova che la pulizia venga eseguita per ogni interruzione a livello di host o terminazione del processo; quando la distinzione è importante, verifica il comportamento dell’invocazione effettiva.
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
}
}
Lascia che il codice riutilizzabile usi throw o return verso uno script di ingresso invece di chiamare exit nel mezzo di questo lavoro. Lo script di ingresso può quindi eseguire la pulizia, scrivere una sola informazione diagnostica e scegliere lo stato del processo in un unico punto visibile. Ctrl+C, un job arrestato e un input non valido sono eventi diversi: decidi e documenta se ciascuno è un annullamento, un errore di convalida o un altro stato a cui un chiamante o un’attività pianificata deve reagire. Non ridurli silenziosamente a un unico errore generico. Arrestare un altro processo è ancora diverso; quando esiste, preferisci il normale meccanismo di arresto dell’applicazione.
Mantenere script figli e host nell’ambito previsto
L’operatore di chiamata esegue uno script figlio nel proprio ambito di script, mentre il dot-sourcing esegue uno script nell’ambito corrente e importa funzioni e variabili. Usa il dot-sourcing solo quando importare definizioni è intenzionale.
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
}
Un exit nel codice riutilizzabile è più forte di return: può terminare la sessione interattiva, il test runner o l’altro host che lo ha invocato. L’effetto visibile dipende inoltre dall’host. Un processo figlio avviato con powershell.exe -File termina e restituisce il controllo al chiamante, mentre una console interattiva può chiudere la sessione corrente. Runspace incorporati ed editor possono comportarsi diversamente. Verifica separatamente la riga di comando di produzione esatta, inclusi -File rispetto a -Command, profili, virgolette, output e stato di uscita; gli esempi qui riportati non affermano che tali controlli siano stati eseguiti.
Raccomandazione
Usa exit <code> solo al confine di livello superiore scelto intenzionalmente, return per l’ambito corrente e throw per un errore che il chiamante può gestire. Usa break e continue dove il relativo ciclo o switch è visibile, e usa Stop-Process solo per un processo locale distinto. Mantenere la logica di business in funzioni che restituiscono valori o generano errori la rende riutilizzabile; un piccolo script di ingresso può quindi tradurre il risultato finale in uno stato stabile per una shell, un’attività pianificata, un job CI o un programma wrapper.