当我按CTRL+C而不是直接关闭进程时,如何让PowerShell脚本给予我一个YES或NO选项?

yzuktlbb  于 2023-08-05  发布在  Shell
关注(0)|答案(1)|浏览(120)

我正在使用以下PowerShell脚本,以便能够在一定时间后关闭计算机。

# 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
}

字符串
剧本运行得相当好,除了几件事;但我有几样东西想要。

1)如果存在关机请求,当我们按Ctrl-C键时,脚本会自动取消该请求。但我不想他直接取消。在取消之前,我希望它给予我一个选项“你确定要取消现有的关机倒计时(Y/N)?:“.
**2)**在当前脚本中按Ctrl-C键后,关机请求被取消,并在终端中给出如下警告:

第一个月
选择并输入Y后,端子关闭。然而,在以相同的方式选择并输入N之后,终端也被关闭。
现在我需要这个。如果我选择N选项,终端就不会关闭;让我输入一个新的关机请求换句话说应该给予我一个选项,设置一个新的关机后,目前的关机被取消。
如果有人知道这个问题,我想让他知道我真诚地表达我的感激之情。

klr1opcd

klr1opcd1#

the answer that you took the code in the question from所示:

  • 在PowerShell中通过try / catch / finally statementfinally块处理Ctrl-C限制了您的 * 清理 * 操作-您的脚本将 * 总是 * 终止。
  • 如果您需要 * 拦截 *Ctrl-C,以便可以选择 * 防止 * 终止,则需要使用[Console]::TreatControlCAsInput= $true的自定义键盘轮询循环,如this answer所示,并在下面的代码上下文中说明。

这种方法还意味着您正在调用PowerShell脚本的批处理文件不会看到Ctrl-C键,因此不会显示可怕的Terminate batch job (Y/N)?提示符,该提示符无法被抑制(请参阅this answer)。
这里有一个简化的概念证明,展示了核心技术;它循环CA。10秒,在此期间检测Ctrl-C键:

try {
  # Make sure that [Console]::ReadKey() can read Ctrl-C as a regular keypress
  # instead of getting terminated by it.
  [Console]::TreatControlCAsInput = $true
  foreach ($i in 1..100) { # Sample loop that runs for ca. 10 secs.
    Write-Host -NoNewline . # Visualize the passage of time.
    # Check for any keyboard input.
    if ([Console]::KeyAvailable) {
      # Consume the key without displaying it.
      $key = [Console]::ReadKey($true)
      # Check if it represents Ctrl-C
      $ctrlCPressed = $key.Modifiers -eq 'Control' -and $key.Key -eq 'C'
      if ($ctrlCPressed) {
        Write-Verbose -Verbose 'Ctrl-C was pressed'
        # ... take action here.
      }
    }
    # Sleep a little, but not too long, so that 
    # keyboard input checking remains responsive.
    Start-Sleep -Milliseconds 100
  }
}
finally {
  # Restore normal Ctrl-C behavior.
  [Console]::TreatControlCAsInput = $false
}

字符串
您的代码上下文中的完整解决方案

  • 请注意,它包含一个R(恢复)choice.exe选项,可让您恢复当前的关机计划并关机。
# 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

# Loop for potential scheduling and rescheduling
do {
  
  if (-not $shutdownAlreadyPending -or $reschedule) {

    # Prompt the user for when (how many minutes / hours and minutes from now) to shut down.
    # Note: Pressing Ctrl-C at this prompt aborts the entire script instantly.
    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.'
      }
    }
    $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 for what time.
        Write-Warning "A shutdown is already pending. It will be canceled and rescheduled as reqeusted."
        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 the 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, with support for Ctrl-C in order to cancel.
  $ctrlCPressed = $reschedule = $canceled = $false
  $prevSecsRemaining = 0
  try {
    # Make sure that [Console]::ReadKey() can read Ctrl-C as a regular keypress
    # instead of getting terminated by it.
    [Console]::TreatControlCAsInput = $true
    [Console]::CursorVisible = $false
    do {
      
      $timespanRemaining = $shutdownTime - [datetime]::UtcNow
      if ([int] $timespanRemaining.TotalSeconds -ne $prevSecsRemaining) {
        # Update only if the seconds position changed.
        Write-Host -NoNewline ("`r" + 'SHUTTING DOWN in {0:hh\:mm\:ss}, at {1}. Press Ctrl-C to CANCEL.' -f $timespanRemaining, $shutdownTime.ToLocalTime())
        $prevSecsRemaining = [int] $timespanRemaining.TotalSeconds
      }
    
      # Check for Ctrl-C
      if ([Console]::KeyAvailable) {
        # A keypress is available, consume it without displaying it.
        $key = [Console]::ReadKey($true)
        # Check if it represents Ctrl-C.
        $ctrlCPressed = $key.Modifiers -eq 'Control' -and $key.Key -eq 'C'
        if ($ctrlCPressed) {
          # Use choice.exe to prompt for further action.
          choice.exe /m "`nAre you sure you want to cancel the existing countdown to shutdown? Press N to reschedule instead, or R to resume the current countdown." /c ynr
      
          # Evaluate the user's choice, which is reflected in the exit code
          # and therefore in the automatic $LASTEXITCODE variable.
          # 1 indicates the first choice ('y'), 2 the second ('n')
          switch ($LASTEXITCODE) {
            1 {
              # YES: Cancel the shutdown and exit
              Write-Host 'Canceling shutdown.'
              $canceled = $true
              break 
            }  
            2 {
              # NO: Cancel the shutdown, but schedule a new one.
              Write-Host 'Canceling current shutdown. Please schedule a new one now:';
              $reschedule = $true
            } 
            3 {
              # RESUME: keep the current schedule and keep counting down.
            }
            Default { 
              # With three choices, this would imply a value of 0, which would signal having pressed Ctrl-C again.
              # Due to [Console]::TreatControlCAsInput = $false, however, you won't get here.
            }
          }

          if ($reschedule -or $canceled) { break }

        }
      }
    
      # Sleep only for a short while, so that checking for keyboard input is responsive.
      Start-Sleep -Milliseconds 100
    
    } while ($timespanRemaining -gt 0)
  }
  finally {
    # Clean up / restore settings.
    [Console]::CursorVisible = $true
    [Console]::TreatControlCAsInput = $false
    # Clean up the file in which the last schedule attempt is persisted. 
    # This is appropriate even if the loop is exited due to having intercepted Ctrl-C
    Remove-Item -ErrorAction Ignore $lastScheduleFile
  }

  if ($reschedule -or $canceled) {

    # Cancel the pending shutdown 
    shutdown /a *>$null
    switch ($LASTEXITCODE) {
      0 { <# OK #> }
      1116 { <# Shutdown has unexpectedly already been canceled, but that is a benign condition #> }
      default { Write-Host "`nUNEXPECTED ERROR trying to cancel the pending shutdown."; exit $_ }
    }

  }

} while ($reschedule)

# Exit the script here: either the shutdown has begun, or it has been canceled without replacement.
exit

相关问题