43

How do I catch and handle Ctrl+C in a PowerShell script? I understand that I can do this from a cmdlet in v2 by including an override for the Powershell.Stop() method, but I can't find an analog for use in scripts.

I'm currently performing cleanup via an end block, but I need to perform additional work when the script is canceled (as opposed to run to completion).

li ki
  • 342
  • 3
  • 11
Justin R.
  • 23,435
  • 23
  • 108
  • 157
  • Possible duplicate of [Is there a way to catch ctrl-c and ask the user to confirm?](https://stackoverflow.com/questions/10733718/is-there-a-way-to-catch-ctrl-c-and-ask-the-user-to-confirm) – jpaugh Aug 15 '19 at 19:31

4 Answers4

73

The documentation for try-catch-finally says:

A Finally block runs even if you use CTRL+C to stop the script. A Finally block also runs if an Exit keyword stops the script from within a Catch block.

See the following example. Run it and cancel it by pressing ctrl-c.

try
{
    while($true)
    {
        "Working.."
        Start-Sleep -Seconds 1
    }
}
finally
{
    write-host "Ended work."
}
zett42
  • 25,437
  • 3
  • 35
  • 72
stalskal
  • 1,181
  • 1
  • 8
  • 16
  • The is true, but Finally will run not only because Exit was called, but also if the Try block succeeds. As I indicated in my question, I need to perform additional work when the script is canceled (as opposed to run to completion). – Justin R. Apr 04 '13 at 00:18
  • 9
    @fatcat111 use a Boolean flag. For example, set `$didcomplete = $true` at the end of your `try` and check it with `if ($didcomplete) { Write-Host "Ended work." }` inside the `finally` – Jacob Krall Mar 15 '14 at 18:48
  • 2
    Unfortunately, the finally block is not executed if the PowerShell console window is closed. – AndreasHassing Dec 29 '17 at 10:11
  • 1
    The finally block is also called when stopping a remote PSSession invoke-command or Job! – Brain2000 Dec 20 '18 at 18:14
14

You could use the method described on here on PoshCode

Summary:

Set

[console]::TreatControlCAsInput = $true

then poll for user input using

if($Host.UI.RawUI.KeyAvailable -and (3 -eq  
    [int]$Host.UI.RawUI.ReadKey("AllowCtrlC,IncludeKeyUp,NoEcho").Character))
stalskal
  • 1,181
  • 1
  • 8
  • 16
FkYkko
  • 1,015
  • 1
  • 12
  • 18
2

There is also a Stopping property on $PSCmdlet that can be used for this.

  • `Stopping` is `false` even when stopping per [PowerShell/PowerShell#6322](https://github.com/PowerShell/PowerShell/issues/6322). – alx9r Mar 06 '18 at 17:46
2

Here is recent, working solution. I use the if part in a loop where I need to control the execution interruption (closing filehandles).

    [Console]::TreatControlCAsInput = $true # at beginning of script

    if ([Console]::KeyAvailable){

        $readkey = [Console]::ReadKey($true)

        if ($readkey.Modifiers -eq "Control" -and $readkey.Key -eq "C"){                
            # tasks before exit here...
            return
        }

    }

Also note that there is a bug which leads KeyAvailable to be true upon start of scripts. You can mitigate by read calling ReadKey once at start. Not needed for this approach, just worth knowing in this context.

aydunno
  • 91
  • 10