PowerShell,[Console]::KeyAvailable作为后台任务运行时生成错误

esbemjvw  于 2023-01-17  发布在  Shell
关注(0)|答案(2)|浏览(173)

与在脚本中使用pause不同,让脚本在设定的时间后自动退出对我来说很有用,这对我来说特别有用,因为这些脚本可以交互运行,或者作为Start-Process -WindowStyle Hidden的后台任务运行,pause意味着进程可能会永远挂起,但使用计时器,即使它在后台,它将超时。通过以下问题向我提供了此解决方案:PowerShell, break out of a timer with custom keypress

$t = 8; Write-Host "Exiting in $t seconds (press any key to exit now)" -NoNewLine
for ($i=0; $i -le $t; $i++) {
    Start-Sleep 1; Write-Host -NoNewLine "."
    if ([Console]::KeyAvailable) { 
        $key = [Console]::ReadKey($true).Key
        if ($key) { break }
    }
}

但是,如果我在后台使用Start-Process -WindowStyle Hidden运行此命令,PowerShell会由于[Console]::KeyAvailable而生成错误,因为在后台运行时没有可用的控制台。

Cannot see if a key has been pressed when either application does not have a 
console or when console input has been redirected from a file. Try 
Console.In.Peek.
At C:\Users\Boss\Install Chrome.ps1:36 char:78
+ ... ep 1;  Write-Host -NoNewLine "."; if ([Console]::KeyAvailable) { $key ...
+                                           ~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (:) [], InvalidOperationExcept 
   ion
    + FullyQualifiedErrorId : System.InvalidOperationException

·有没有什么方法可以调整我的代码,使它在后台运行时不会产生错误,同时在交互运行时仍然保留“按任意键立即退出”选项?
·Try Console.In.Peek是什么意思?

g2ieeal7

g2ieeal71#

  • 在Windows上启动 * 控制台应用程序 (如powershell.exe)时, 始终 * 分配控制台-即使它是 * 隐藏的 *(-WindowStyle Hidden
  • 除非使用-RedirectStandardInput,否则控制台的stdin流([Console]::In)* 不 * 重定向,如[Console]::IsInputRedirected返回$false所示。
  • 因此,您的代码可以工作:[Console]::KeyAvailable将始终返回$false,并且整个超时周期将过去。
  • 但是,**您 * 会 * 看到Start-Job**的问题:
  • 创建的后台job使用隐藏的PowerShell * 子进程 * 在后台运行代码,调用PowerShell示例与其通信的方式是 * 通过stdin *。
  • 因此,[Console]::KeyAvailable会导致您看到的语句终止错误,假定错误消息中提到的原因之一适用:* 标准输入被重定向 *(但控制台确实存在)。
    • 解决方法**:

只需在[Console]::IsInputRedirected返回$false的条件下使用[Console]::KeyAvailable

$t = 8; Write-Host "Exiting in $t seconds (press any key to exit now)" -NoNewLine
for ($i=0; $i -le $t; $i++) {
    Start-Sleep 1; Write-Host -NoNewLine "."
    # Note the use of -not [Console]::IsInputRedirected
    if (-not [Console]::IsInputRedirected -and [Console]::KeyAvailable) { 
        $key = [Console]::ReadKey($true).Key
        if ($key) { break }
    }
}
    • 或者,按原样使用您的代码,使用 * 线程 * 作业,使用Start-ThreadJob**,它 * 随PowerShell(Core)7 +* 一起提供,在 * Windows PowerShell * 中,可以 * 按需安装 *,例如,Install-Module ThreadJob -Scope CurrentUser
Start-ThreadJob { & 'C:\Users\Boss\Install Chrome.ps1' }

线程作业(如果可用)通常比常规的、基于子进程的后台作业更可取,因为线程作业更快、重量更轻。
由于 * stdin * 的使用仅适用于 * 进程间 * 通信,因此[Console]::KeyAvailable不会导致线程作业中的错误,并且-值得推荐的是-仅当手头的线程是 * 前台 * 线程时才注意击键。

rkue9o1l

rkue9o1l2#

使用Runspace而不是Start-Process。运行空间可以与PSHost相关联,因此您将看到两个好处,输出到控制台和按任意键取消脚本的能力。

try {
    $ttl = 20
    $iss = [initialsessionstate]::CreateDefault2()
    $rs  = [runspacefactory]::CreateRunspace($Host, $iss)
    $rs.Open()

    $ps  = [powershell]::Create().AddScript({
        param($ttl)

        Write-Host "Exiting in $ttl seconds (press any key to exit now)" -NoNewLine
        for ($i=0; $i -le $ttl; $i++) {
            Start-Sleep 1; Write-Host -NoNewLine "."
            if ([Console]::KeyAvailable) {
                $key = [Console]::ReadKey($true).Key
                if ($key) { break }
            }
        }
        Write-Host "Finished!" -ForegroundColor Green

    }).AddParameter('ttl', $ttl)

    $ps.Runspace = $rs
    $async       = $ps.BeginInvoke()

    do {
        $id = [System.Threading.WaitHandle]::WaitAny($async.AsyncWaitHandle, 200)
    }
    while($id -eq [System.Threading.WaitHandle]::WaitTimeout)

    $ps.Stop()
    $ps.EndInvoke($async)
}
finally {
    $ps, $rs | ForEach-Object Dispose
}

相关问题