HOWTO · PowerShell

Como encerrar um script no Windows PowerShell

Saiba quando usar exit, return, throw, break, continue ou Stop-Process para encerrar o escopo correto e informar um código de saída confiável do PowerShell.

Nesta página

Parar um script do PowerShell não é uma única operação. Escolha o comando de acordo com o limite que precisa ser interrompido: todo o processo do script, a função ou o escopo de script atual, uma iteração de loop ou outro processo do sistema operacional.

Objetivo Usar
Finalizar o script de nível superior e informar um status exit <code>
Sair de uma função, script ou bloco de script return
Informar uma falha que os chamadores possam capturar throw
Sair de um loop ou switch break
Avançar para o próximo item do loop ou switch continue
Encerrar um processo local separado Stop-Process

Encerrar um script do PowerShell com exit

exit encerra um script ou uma instância do PowerShell. Um inteiro opcional se torna o código de saída do processo: por convenção, 0 significa sucesso e um valor diferente de zero significa falha. Dê a cada código diferente de zero um significado documentado para que agendadores, trabalhos de CI e programas wrapper possam reagir corretamente.

Salve este padrão de script de entrada como Check-Config.ps1. O bloco de código de teste captura o status de falha em vez de escrever um erro ou sair com um valor diferente de zero, por isso continua autocontido e é concluído com sucesso quando executado sem um arquivo de entrada. No ponto de entrada real, substitua essa captura por 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
}

Salve isto como Demo.ps1 e execute-o; ele captura os status dos processos filhos e os imprime:

$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

Execute o script salvo como um processo filho de outra sessão do PowerShell e inspecione o status. Os comandos abaixo são uma ilustração de sintaxe para o script de entrada real, não uma evidência de execução fornecida com este artigo:

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

$LASTEXITCODE

Um chamador PowerShell lê o status do filho por meio de $LASTEXITCODE; um chamador cmd.exe lê %ERRORLEVEL%. Mantenha exit no limite do script de nível superior. Se código reutilizável chamar exit, ele poderá fechar uma sessão interativa, um executor de testes ou outro host que o invocou. Funções são mais fáceis de reutilizar quando retornam dados ou lançam erros, e somente o script de entrada traduz o resultado em um código de saída.

Sair do escopo atual com return

return sai da função, do script ou do bloco de script atual. Ele não define inerentemente um código de saída do 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
}

Em uma função normal do PowerShell, todo valor não capturado do fluxo de sucesso se torna saída, e não apenas a expressão depois de return. Use Write-Verbose ou outro fluxo apropriado para diagnósticos em vez de emitir strings extras com Write-Output.

Parar com um erro capturável usando throw

throw cria por padrão um erro que encerra o script e desenrola a pilha de chamadas até que um bloco catch ou um trap o trate. Use-o quando uma função não puder produzir um resultado válido e o chamador precisar decidir como se recuperar.

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

Este padrão separa duas responsabilidades: a função explica a falha com throw, enquanto o script de entrada a captura e escolhe o código de saída público. O bloco de código de teste armazena esse código em vez de chamar exit 2, portanto o próprio exemplo termina com sucesso. Não presuma que todo erro de cmdlet chega a catch; um erro não terminante precisa de -ErrorAction Stop quando deve ser tratado ali. Um trap é um manipulador alternativo quando essa é a estrutura exigida pelo script ao redor.

Usar break e continue somente em blocos de controle

break sai do loop ou switch mais próximo. continue ignora o restante da iteração atual e inicia a próxima.

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
}

Este loop deve escrever alpha e beta e parar ao chegar a stop; ele não escreve omega. break e continue não são comandos gerais para encerrar scripts. Use-os somente quando o loop ou switch estiver visível. Em outros contextos, o PowerShell pode procurar na pilha de chamadas uma construção envolvente, e o comportamento sem uma construção desse tipo não deve substituir um limite explícito do script.

Encerrar outro processo com Stop-Process

Stop-Process tem como alvo um processo separado no computador local; isso não significa “parar este script”. Faça uma prévia do alvo com -WhatIf antes de permitir a interrupção:

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
}

O bloco de código de teste faz uma prévia do processo atual e não o encerra. No código operacional, remova -WhatIf somente depois de confirmar o alvo. Prefira um objeto de processo ou PID conhecido quando um nome puder corresponder a vários processos. Encerrar um processo pertencente a outro usuário pode exigir uma sessão elevada do PowerShell. Prefira o mecanismo normal de desligamento da aplicação quando houver um; Stop-Process -Force pode impedir que o alvo limpe arquivos ou estado.

Preservar códigos de saída de programas nativos

$? descreve se o último comando do PowerShell foi bem-sucedido, enquanto $LASTEXITCODE armazena o código de saída do programa nativo mais recente. Salve um status nativo imediatamente, pois o próximo comando nativo pode sobrescrevê-lo.

O padrão a seguir é descrito intencionalmente em texto, e não em um bloco cercado: o pacote não fornece um executável nativo portátil nem evidência de execução para ele. Substitua native-command pelo comando usado no seu ambiente, execute native-command e atribua imediatamente $nativeCode = $LASTEXITCODE. Se $nativeCode -ne 0, informe native-command failed with exit code $nativeCode e use exit $nativeCode no limite de entrada deliberado.

Não use $? como substituto do status numérico do programa nativo. Se um wrapper precisar preservar o resultado nativo, capture $LASTEXITCODE antes de executar outro comando nativo e faça a tradução somente no limite de entrada deliberado.

Preservar a limpeza e distinguir o cancelamento

Mantenha a limpeza em finally, e não depois de uma linha que possa encerrar o processamento. Esse é o local normal para liberar um fluxo, desbloquear um recurso ou remover um arquivo temporário quando o controle sair da construção try por causa de um erro tratado ou de uma conclusão normal. Não trate isso como prova de que a limpeza será executada para toda interrupção no nível do host ou terminação de processo; verifique o comportamento da invocação real quando essa distinção for 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
    }
}

Deixe o código reutilizável usar throw ou return até um script de entrada, em vez de chamar exit no meio deste trabalho. O script de entrada poderá então executar a limpeza, escrever um diagnóstico e escolher o status do processo em um único local visível. Ctrl+C, um trabalho interrompido e uma entrada inválida são eventos diferentes: decida e documente se cada um é cancelamento, falha de validação ou outro status sobre o qual um chamador ou agendador deve agir. Não os reduza silenciosamente a uma única falha genérica. Encerrar outro processo é diferente novamente; prefira o mecanismo normal de desligamento da aplicação quando houver um.

Manter scripts filhos e hosts no escopo pretendido

O operador de chamada executa um script filho em seu próprio escopo de script, enquanto o dot-sourcing executa um script no escopo atual e importa suas funções e variáveis. Use dot-sourcing somente quando a importação de definições for intencional.

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
}

Um exit em código reutilizável é mais forte que return: ele pode encerrar a sessão interativa, o executor de testes ou outro host que o invocou. O efeito visível também depende do host. Um processo filho iniciado com powershell.exe -File termina e devolve o controle ao chamador, enquanto um console interativo pode fechar sua sessão atual. Runspaces incorporados e editores podem se comportar de modo diferente. Teste separadamente a linha de comando exata de produção, incluindo -File versus -Command, perfis, aspas, saída e status de saída; os exemplos aqui não afirmam que essas verificações dependentes do host foram executadas.

Recomendação

Use exit <code> somente no limite deliberado de nível superior, return para o escopo atual e throw para uma falha que o chamador possa tratar. Use break e continue onde o loop ou switch correspondente estiver visível, e use Stop-Process somente para um processo local separado. Manter a lógica de negócio em funções que retornam ou lançam erros torna o código reutilizável; um pequeno script de entrada pode então traduzir o resultado final em um status estável para um shell, agendador, trabalho de CI ou programa wrapper.