I am using the following PowerShell Script to be able to shut down the computer after a certain time.
# Determine the path to a file in which information about a pending
# shutdown is persisted by this script.
$lastScheduleFile = Join-Path $env:TEMP ('~{0}_Schedule.txt' -f [IO.Path]::GetFileNameWithoutExtension($PSCommandPath))
[datetime] $shutdownTime = 0
# First, see if this script previously scheduled a shutdown.
try {
$shutdownTime = ([datetime] (Get-Content -ErrorAction Ignore $lastScheduleFile)).ToUniversalTime()
}
catch {}
# If the time is in the past, by definition it doesn't reflect the true pending shutdown time, so we ignore it.
if ($shutdownTime -lt [datetime]::UtcNow) {
$shutdownTime = 0
}
else {
# Warn that the retrieved shutdown time isn't *guaranteed* to be correct.
Write-Warning @'
The pending shutdown time is assumed to be what *this* script last requested,
which is not guaranteed to be the true time, nor is it guaranteed that a shutdown is even still pending.
'@
}
$shutdownAlreadyPending = $shutdownTime -ne 0
if (-not $shutdownAlreadyPending) {
# Prompt the user for when (how many minutes / hours and minutes from now) to shut down.
while ($true) {
try {
$secsFromNow = switch -Regex ((Read-Host 'Enter the timespan after which to shut down, either in minutes (e.g. 30) or hours and minutes (e.g. 1:15)').Trim()) {
'^[1-9]\d*$' { [int] $_ * 60; break }
'^\d+:\d+$' { ([timespan] $_).TotalSeconds; break }
default { throw }
}
break # input was valid; proceed below.
}
catch {
Write-Warning 'Invalid timespan entered; please try again.'
}
}
# Calculate the resulting shutdown time.
$shutdownTime = [datetime]::UtcNow.AddSeconds($secsFromNow)
# Schedule the shutdown via shutdown.exe
while ($true) {
# Note: Due to use of /t with a nonzero value, /f is implied,
# i.e. the shutdown will be forced at the implied time.
shutdown /s /t $secsFromNow
if ($LASTEXITCODE -eq 1190) {
# A shutdown/restart is already scheduled. We cannot know what its delay is.
Write-Warning "A shutdown is already pending. It will be canceled and rescheduled as requsted."
shutdown /a # Abort the pending shutdown, so that the new one can be requested as scheduled.
continue
}
break
}
if ($LASTEXITCODE) {
# Unexpected error.
Write-Error 'Scheduling a shutdown failed unexpectedly.'
exit $LASTEXITCODE
}
# Persist the scheduled shutdown time in a file, so that
# if this script gets killed, we can resume the countdown on re-execution.
$shutdownTime.ToString('o') > $lastScheduleFile
}
# Show a countdown display or handle a preexisting shutdown request,
# with support for Ctrl-C in order to cancel.
$ctrlCPressed = $true
try {
[Console]::CursorVisible = $false
# Display a countdown to the shutdown.
do {
$secsRemaining = ($shutdownTime - [datetime]::UtcNow).TotalSeconds
$timespanRemaining = $shutdownTime - [datetime]::UtcNow
Write-Host -NoNewline ("`r" + 'SHUTTING DOWN in {0:hh\:mm\:ss}, at {1}. Press Ctrl-C to CANCEL.' -f $timespanRemaining, $shutdownTime.ToLocalTime())
Start-Sleep -Seconds 1
} while ($secsRemaining -gt 0)
# Getting here means that Ctrl-C was NOT pressed.
$ctrlCPressed = $false
}
finally {
# Note: Only Write-Host statements can be used in this block.
[Console]::CursorVisible = $true
if ($ctrlCPressed) {
# Abort the pending shutdown.
shutdown /a *>$null
switch ($LASTEXITCODE) {
0 { Write-Host "`nShutdown aborted by user request." }
1116 { Write-Host "`n(Shutdown has already been canceled.)" }
default { Write-Host "`nUNEXPECTED ERROR trying to cancel the pending shutdown."; exit $_ }
}
}
# Clean up the file in which the last schedule attempt is persisted.
Remove-Item -ErrorAction Ignore $lastScheduleFile
# Note: We consider this way of exiting successful.
# If the shutdown is allowed to take place, this script never returns to a caller.
# If it *does* return:
# * If it is due to a *failure to even schedule* the shutdown (see above), it will be nonzero.
# * 0 therefore implies having successfully aborted (canceled) the shutdown.
exit 0
}
The script works pretty well, except for a few things; but there are a few things i want.
1) If there is an existing shutdown request, the script automatically cancels the request when we press the Ctrl-C key. But I don't want him to cancel it directly. Before canceling I want it to give me an option "Are you sure you want to cancel the existing countdown to shutdown (Y/N)?:".
2) After pressing the Ctrl-C key in the current script, the shutdown request is canceled and it gives the following warning in the terminal:
Shutdown aborted by user request. Terminate batch job (Y/N)?
After selecting and entering Y, the terminal is closed. However, the terminal is also closed after selecting and entering N in the same way.
At this point I need this. If I select the N option, the terminal will not be closed; and ask me to enter a new shutdown request. In other words; should give me the option to set a new shutdown after the current shutdown is cancelled.
If there is someone who has knowledge on this subject, I would like him to know that I am expressing my gratitude with all my sincerity.
As noted in the answer that you took the code in the question from:
Handling Ctrl-C via the
finallyblock of atry/catch/finallystatement in PowerShell restricts to you to cleanup operations - your script will invariably terminate.If you need to intercept Ctrl-C so that you can opt to prevent termination, you'll need a custom keyboard-polling loop with
[Console]::TreatControlCAsInput= $true, as shown in this answer, and as spelled out in the context of your code below.This approach also means that the batch file that you're calling the PowerShell script from won't see the Ctrl-C keypress, and therefore won't show the dreaded
Terminate batch job (Y/N)?prompt, which cannot be suppressed (see this answer).Here's a simplified proof of concept that shows the core technique; it loops for ca. 10 seconds, during which it detects Ctrl-C keypresses:
The full solution in the context of your code:
R(Resume)choice.exeoption that lets you resume the current shutdown schedule and shutdown.