The pattern
terminal / powershell
$cancel = $false
[Console]::TreatControlCAsInput = $false # keep standard behavior as backstop
foreach ($file in $files) {
if ($Host.UI.RawUI.KeyAvailable) {
$k = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
if ($k.Key -eq 'Escape') { $cancel = $true; break }
}
# work…
}
if ($cancel) { Write-Host "Cancelled after $completed items; partial results kept." }Semantically important details
- Escape not Ctrl-C: Ctrl-C kills pipelines destructively; a Esc-key check in the iteration loop means the current file completes, partial state consistent, the rest never starts.
- Report partials: “cancelled after 2,118 checked” is a result; silent abort is an ambiguity.
- Don't hold locks across the check: file handles, registry hive handles, transactions should live per-iteration only.
For library work, CancellationTokenSource + cooperative ThrowIfCancellationRequested is the same pattern one level up — see how the preview's storage scan exposes a Cancel button exactly this way.