HOWTO · PowerShell

Comment mettre fin à un script dans Windows PowerShell

Découvrez quand employer exit, return, throw, break, continue ou Stop-Process pour arrêter la bonne portée et renvoyer un code de sortie PowerShell fiable.

Sur cette page

Mettre fin à un script PowerShell ne correspond pas à une seule opération. Choisissez l’instruction en fonction de la limite qui doit s’arrêter : tout le processus du script, la fonction ou la portée de script actuelle, une itération de boucle ou un autre processus du système d’exploitation.

Objectif Instruction
Terminer le script de niveau supérieur et communiquer un état exit <code>
Quitter une fonction, un script ou un bloc de script return
Signaler un échec que l’appelant peut intercepter throw
Quitter une boucle ou un switch break
Passer à l’élément suivant d’une boucle ou d’un switch continue
Terminer un processus local distinct Stop-Process

Terminer un script PowerShell avec exit

exit termine un script ou une instance PowerShell. Un entier facultatif devient le code de sortie du processus : par convention, 0 indique une réussite et une valeur différente de zéro un échec. Donnez à chaque code différent de zéro une signification documentée afin que les planificateurs, les tâches CI et les programmes enveloppes puissent réagir correctement.

Enregistrez ce modèle de script d’entrée sous le nom Check-Config.ps1. Le bloc ci-dessous capture l’état d’échec au lieu d’écrire une erreur ou de sortir avec un code différent de zéro ; il reste donc autonome et réussi lorsqu’il est exécuté sans fichier d’entrée. Au véritable point d’entrée, remplacez cette capture par Write-Error et 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
}

Enregistrez ceci sous le nom Demo.ps1 et exécutez-le : il capture les états des processus enfants et les affiche :

$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

Exécutez le script enregistré comme processus enfant depuis une autre session PowerShell, puis examinez son état. Les commandes ci-dessous illustrent la syntaxe du véritable script d’entrée ; elles ne constituent pas une preuve d’exécution fournie avec cet article :

powershell.exe -NoProfile -File .\Check-Config.ps1 -Path .\missing.json

$LASTEXITCODE

Un appelant PowerShell lit l’état de l’enfant dans $LASTEXITCODE ; un appelant cmd.exe le lit dans %ERRORLEVEL%. Gardez exit à la limite du script de niveau supérieur. Si du code réutilisable appelle exit, il peut fermer la session interactive, l’exécuteur de tests ou un autre hôte qui l’a invoqué. Les fonctions sont plus faciles à réutiliser lorsqu’elles renvoient des données ou lèvent des erreurs, et que seul le script d’entrée transforme ensuite le résultat en code de sortie.

Quitter la portée actuelle avec return

return quitte la fonction, le script ou le bloc de script actuel. Il ne définit pas à lui seul un code de sortie du processus.

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
}

Dans une fonction PowerShell normale, toute valeur non capturée du flux de réussite devient une sortie, et pas seulement l’expression qui suit return. Pour les diagnostics, utilisez Write-Verbose ou un autre flux approprié plutôt que d’émettre des chaînes supplémentaires avec Write-Output.

S’arrêter sur une erreur interceptable avec throw

throw crée par défaut une erreur qui termine le script et remonte la pile d’appels jusqu’à ce qu’un bloc catch ou une instruction trap la traite. Utilisez-le lorsqu’une fonction ne peut pas produire un résultat valide et que son appelant doit décider de la récupération.

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

Ce modèle sépare deux responsabilités : la fonction décrit l’échec avec throw, tandis que le script d’entrée l’intercepte et choisit le code de sortie public. Le bloc conserve ce code au lieu d’appeler exit 2, si bien que l’exemple lui-même se termine correctement. Ne supposez pas que toute erreur d’une cmdlet atteint catch ; une erreur non bloquante nécessite -ErrorAction Stop lorsqu’elle doit y être traitée. trap constitue une autre possibilité lorsque la structure du script environnant l’exige.

Employer break et continue uniquement dans les blocs de contrôle

break quitte la boucle ou le switch le plus proche. continue ignore le reste de l’itération actuelle et commence la suivante.

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
}

Cette boucle est censée écrire alpha et beta, puis s’arrêter lorsqu’elle atteint stop ; elle n’écrit pas omega. break et continue ne sont pas des commandes générales de fin de script. Utilisez-les uniquement lorsque la boucle ou le switch est visible. Dans d’autres contextes, PowerShell peut rechercher une construction englobante dans la pile d’appels ; en l’absence d’une telle construction, ce comportement ne doit pas remplacer une limite de script explicite.

Terminer un autre processus avec Stop-Process

Stop-Process cible un processus distinct sur l’ordinateur local ; cela ne signifie pas « arrêter ce script ». Prévisualisez une cible avec -WhatIf avant d’autoriser l’arrêt :

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
}

Le bloc prévisualise le processus actuel sans l’arrêter. Dans le code opérationnel, retirez -WhatIf uniquement après avoir confirmé la cible. Préférez un objet processus connu ou un PID lorsqu’un nom peut correspondre à plusieurs processus. Arrêter un processus appartenant à un autre utilisateur peut nécessiter une session PowerShell avec élévation de privilèges. Préférez le mécanisme d’arrêt normal de l’application lorsqu’il existe ; Stop-Process -Force peut empêcher la cible de nettoyer ses fichiers ou son état.

Conserver le code de sortie d’un programme natif

$? indique si la dernière commande PowerShell a réussi, tandis que $LASTEXITCODE contient le code de sortie du dernier programme natif. Enregistrez immédiatement l’état natif, car la commande native suivante peut l’écraser.

Le modèle suivant est décrit directement dans le texte plutôt que dans un bloc de code : le paquet ne fournit ni exécutable natif portable ni preuve d’exécution pour celui-ci. Remplacez native-command par la commande utilisée dans votre environnement, exécutez native-command, puis affectez immédiatement $nativeCode = $LASTEXITCODE. Si $nativeCode -ne 0, signalez native-command failed with exit code $nativeCode et utilisez exit $nativeCode à la limite d’entrée prévue.

N’utilisez pas $? à la place du code numérique du programme natif. Si un wrapper doit conserver le résultat natif, capturez $LASTEXITCODE avant d’exécuter une autre commande native et ne le traduisez qu’à la limite d’entrée prévue.

Préserver le nettoyage et distinguer l’annulation

Conservez le nettoyage dans finally, et non après une ligne susceptible de mettre fin au traitement. C’est l’emplacement normal pour libérer un flux, un verrou ou un fichier temporaire lorsque le contrôle quitte la construction try après une erreur traitée ou une fin normale. Ne considérez pas cela comme la preuve que le nettoyage s’exécute lors de toute interruption au niveau de l’hôte ou de toute terminaison de processus ; vérifiez le comportement de l’appel réel lorsque cette distinction est importante.

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

Laissez le code réutilisable faire throw ou return vers un script d’entrée au lieu d’appeler exit au milieu de ce traitement. Le script d’entrée peut alors effectuer le nettoyage, écrire un seul diagnostic et choisir l’état du processus à un endroit visible. Ctrl+C, un travail arrêté et une entrée non valide sont des événements différents : décidez et documentez si chacun correspond à une annulation, à un échec de validation ou à un autre état auquel un appelant ou un planificateur doit réagir. Ne les réduisez pas silencieusement à un échec générique. Arrêter un autre processus est encore différent ; préférez le mécanisme d’arrêt normal de l’application lorsqu’il existe.

Garder les scripts enfants et les hôtes dans la portée prévue

L’opérateur d’appel exécute un script enfant dans sa propre portée de script, tandis que le dot-sourcing exécute un script dans la portée actuelle et importe ses fonctions et ses variables. N’utilisez le dot-sourcing que lorsque l’importation de définitions est intentionnelle.

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 dans du code réutilisable est plus fort que return : il peut mettre fin à la session interactive, à l’exécuteur de tests ou à un autre hôte qui l’a invoqué. L’effet visible dépend aussi de l’hôte. Un processus enfant lancé avec powershell.exe -File se termine et rend le contrôle à son appelant, tandis qu’une console interactive peut fermer sa session actuelle. Les runspaces incorporés et les éditeurs peuvent se comporter différemment. Testez séparément la ligne de commande de production exacte, notamment -File ou -Command, les profils, les guillemets, la sortie et le code de sortie ; les exemples de cet article ne prétendent pas que ces vérifications dépendantes de l’hôte ont été exécutées.

Recommandation

Utilisez exit <code> uniquement à la limite de niveau supérieur prévue, return pour la portée actuelle et throw pour un échec qu’un appelant peut traiter. Utilisez break et continue là où leur boucle ou leur switch est visible, et Stop-Process uniquement pour un processus local distinct. En gardant la logique métier dans des fonctions qui renvoient des données ou lèvent des erreurs, vous la rendez réutilisable ; un petit script d’entrée peut ensuite traduire le résultat final en un état stable pour un shell, un planificateur, une tâche CI ou un programme enveloppe.