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



代替在脚本中使用pause,使脚本在设置时间后自动退出对我来说很有用。这对我来说特别有用,因为这些脚本可以交互运行,也可以作为Start-Process -WindowStyle Hidden的后台任务,其中pause意味着进程可以永远挂起,但有了计时器,即使它在后台,它也会超时。我通过这个问题得到了这个解决方案:PowerShell,打破一个定时器与自定义按键

$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:UsersBossInstall 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是什么意思?

  • 这是不是Start-Process的问题:

    • 控制台应用程序在Windows上启动时,例如powershell.exe,控制台总是分配-即使它是隐藏(-WindowStyle Hidden)

    • 除非您使用-RedirectStandardInput,否则该控制台的stdin流([Console]::In)是重定向的,正如[Console]::IsInputRedirected返回$false所反映的那样。

    • 因此,您的代码将工作:[Console]::KeyAvailable将始终返回$false,并且完整的超时时间将过去。

  • 然而,看到Start-Job的问题:

    • 创建的后台作业使用隐藏的PowerShell子进程在后台运行代码,调用PowerShell实例的方式是通过stdin与它通信。

    • 因此,[Console]::KeyAvailable会导致您看到的语句终止错误,如果错误消息中提到的原因之一适用:stdin被重定向(但确实存在控制台)。


工作区:

只要在[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:UsersBossInstall Chrome.ps1' }

线程作业,如果可用的话,通常比常规的、基于子进程的后台作业更可取,因为线程作业更快、更轻量。

由于stdin的使用仅适用于进程间通信,因此[Console]::KeyAvailable不会在线程作业中引起错误,并且-值得赞扬的是-仅在当前线程是前台线程时才注意按键。

使用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
}

最新更新