en-us/about_ConsoleBouncer.help.txt
|
about_ConsoleBouncer SHORT DESCRIPTION The ConsoleBouncer module implements a ctrl+c handler to ensure child processes do not linger (and mess up the console) when canceled. LONG DESCRIPTION Ctrl+c cancellation does not work really well on Windows. Each process attached to a console receives a ctrl+c signal (as opposed to just the active shell), and sometimes, when a shell has launched some large tree of child processes (imagine a build system, for example), some processes do not exit, leaving multiple processes all concurrently trying to consume console input, which Does Not Work Well(TM). It's usually not too bad when cmd.exe is your shell, because you can just keep mashing on ctrl+c and usually get back to a usable state. But it's considerably worse in PowerShell, due to how PSReadLine works, and can be completely unrecoverable. The ConsoleBouncer module implements a ctrl+c handler for PowerShell shell processes to mitigate this problem (it works in both legacy powershell.exe and pwsh.exe). The way it works is that when it is loaded, it takes a look at which PIDs are currently attached to the console (there could be multiple, for example, if you launched PowerShell from cmd.exe), and remembers those PIDs as the "allowed PIDs". Later, when a ctrl+c signal comes along, the ConsoleBouncer handler enumerates all PIDs attached to the console, and kills any which are not in the allow list (after a [configurable] grace period (default of 1 second)). Note that it only kills processes that are attached to the console, so if you launched some GUI processes after loading ConsoleBouncer (notepad, mspaint, etc.), they will not be touched. The ConsoleBouncer handles nested shell processes: only the ConsoleBouncer handler in the "leaf-most" PowerShell shell process will terminate stray processes; and non-leaf-most handlers will return TRUE from the handler, to signal that the event has been handled, and other handlers should not run (thus hiding the ctrl+c from non-leaf shells). There are a few configurable options (grace period, verbosity of informational messages, disabled/enabled), which can be configured or viewed with Get-ConsoleBouncerOption and Set-ConsoleBouncerOption. Settings are shared among a console group N.B. ConsoleBouncer settings are *shared* between all PowerShell shells (that have the ConsoleBouncer module loaded) that share a console. So for the following process tree: cmd.exe (PID 12) \ powershell.exe (PID 34) \ pwsh.exe (PID 56) If you change the grace period with "Set-ConsoleBouncerOption -GracePeriodMillis 2000") in pwsh.exe (PID 56), and then exit back to powershell.exe (PID 34), the grace period will still be set to 2000 milliseconds. Recommended use pattern If you are using ConsoleBouncer, you should load it from your $profile script, so that it is available in every (PowerShell) shell process. If you customize any settings (such as the grace period before termination) from your $profile script, use the -IfNotAlreadySet switch, so that launching a child shell will not overwrite a runtime customization of the setting in a parent shell. Example: In C:\users\$env:USERNAME\Documents\PowerShell\Microsoft.PowerShell_profile.ps1: Import-Module ConsoleBouncer Set-ConsoleBouncer -GracePeriodMillis 500 -IfNotAlreadySet # optional Note that legacy powershell.exe and modern pwsh.exe have different $profile script paths (legacy is under Documents\WindowsPowerShell); you would want to have the same code in both (or have both version-specific profile scripts dot-execute a common, shared script). Mitigation - Excluding processes Some programs may need to be excluded from the default "kill" behavior. For example, the console debugger cdb.exe relies on handling ctrl+c (to break into the target). By default, cdb.exe and kd.exe are not terminated by the ConsoleBouncer handler, and you can add more such processes via "Set-ConsoleBouncerOption -AllowedProcessname foo" (leave off the .exe extension). Mitigation - Temporarily disabling ConsoleBouncer If you run into some other situation where you need to temporariy disable ConsoleBouncer, simply unloading the ConsoleBouncer module is not a good solution, since it could be loaded in a parent shell process (and then your current shell, where you've just unloaded ConsoleBouncer, would be subject to termination when the user types ctrl+c). Instead, to temporarily disable ConsoleBouncer, you should run either: Set-ConsoleBouncerOption -Disarm or Set-ConsoleBouncerOption -Disable The first option is the "softer" / safer option: the handler will still run, and mask off ctrl+c signals in non-leaf shells; but it will not terminate stray processes, and ConsoleBouncer will be automatically re-armed when the shell that disarmed it exits. The second option is a bit stronger: it stays disabled (in all PowerShell shells attached to the current console) until reenabled, and though the handler runs, it just lets the ctrl+c signal pass through (as if it weren't there) in all processes that ConsoleBouncer is loaded in. Weaknesses 1. Reliance on PIDs. The GetConsoleProcessList API only reports process IDs (PIDs), but PIDs can be reused, so it's possible that in between querying GetConsoleProcessList and opening handles to the processes (which is done *before* waiting for the grace period), the process could go away, and a new one with the same PID starts up. This is a pretty small time window, though, and acceptable to live with in practice. 2. Only works in PowerShell shells. If, from a PowerShell shell with ConsoleBouncer loaded, you start a non-PowerShell child shell (such as cmd.exe), and then while using that child shell, you type ctrl+c, the leaf-most PowerShell shell will kill your current shell. To work around this problem, you can temporarily disable ConsoleBouncer by running "Set-ConsoleBouncerOption -Disarm". 3. Relies on getting a ctrl+c signal. Some child processes may disable ctrl+c handling (via SetConsoleMode) (they may then "manually" handle ctrl+c by handling ctrl-keydown followed by c-keydown). If this happens, there is nothing ConsoleBouncer can do--it is up to the app that has disabled ctrl+c handling to honor the user's desire to cancel or stop or whatever it is the app decides ctrl+c means. Note that in some cases observed in the wild, only a few random processes in a large tree of build processes do this, and simply mashing on ctrl+c is enough to eventually get a signal to ConsoleBouncer. |