问题描述
在PowerShell脚本编写的时候,我们会使用Read-Host
函数来接收用户的输入,但是很多时候我们想要在这个函数上附加一个超时效果,即等待用户在一段时间输入内容,如果超过时间脚本便不再等待。
解决方案概述
由于PowerShell可以调用C#代码,所以如果有C#的大佬的话,可能会有一些其他的实现思路,同时也欢迎分享。由于我对于C#了解不多,所以还是从PowerShell的角度来实现。我的实现方式我自认为具有以下特点:
- 支持Window PowerShell和.Net PowerShell
PowerShell有Window PowerShell
版本,也就是Window自带的PowerShell。还有一种可以跨平台的.Net Core
版本,官方仓库在这里。Window PowerShell
基本上都没有怎么维护了,所以现在的Window 10/11自带的都是Window PowerShell
的5.1版本。开发时使用的是Widnow PowerShell
版本,然后写好之后对于.Net Core
版本做了一个适配。 - 性能不是很好
本函数为了做到超时终端读取效果,会开启一个进程来读取用户输入,超时再杀死进程,所以性能不高。之前我也试过一些底层的控制IO输入输入的C++接口,使用下来有一些Bug,所以最终才使用这种方式实现。 - 效果完美(自认为)
用户如果在限定时间输入,结果就会返回用户输入,如果超时会有提示,同时返回NULL
。这个逻辑在调试的时候依旧不存在任何异常情况(这里直接指出是因为之前用了一个C#的方案,在调试的时候会出现一个Bug)。当然,你也可以在理解原理之后自行根据需求修改
源码及分析
PowerShell生态本身就不是很好,如果你只是想要在PowerShell中实现这个效果而已,那你完全可以拷贝这个函数直接用即可,不用花时间分析原理。
FunctionRead-HostWithTimeout{param([string]$Prompt="请输入内容: ",[System.ConsoleColor]$PromptBackGroundColor=$Host.UI.RawUI.BackgroundColor,[System.ConsoleColor]$PromptForeGroundColor=$Host.UI.RawUI.ForegroundColor,[int]$Timeout=5000,[string]$TimeoutHint="输入超时",[System.ConsoleColor]$HintBackgroundColor=$Host.UI.RawUI.BackgroundColor,[System.ConsoleColor]$HintForeGroundColor=[System.ConsoleColor]::Yellow)# 在这里调用输出提示的内容而不是在新进程里面提示是因为新进程提示的内容会被计入返回值,这里就不会Write-Host$Prompt-ForegroundColor$PromptForeGroundColor-BackgroundColor$PromptBackGroundColor-NoNewline$res=try{# Windows PowerShell会在超时结束抛出异常,所以这里需要定义异常可捕捉$ErrorActionPreference='Stop'# 新开线程执行超时读取的逻辑,注意,这里跟了一个timeout的参数,这个参数内部可以读取到powershell.exe-Command{param($Timeout)# 在这个进程里面新开一个Powershell线程,线程之间共享控制台对象$InitialSessionState=[initialsessionstate]::CreateDefault()$InitialSessionState.Variables.Add([System.Management.Automation.Runspaces.SessionStateVariableEntry]::new("ThreadContext",@{Host=$Host},"share host between threads"))$PSThread=[powershell]::Create($InitialSessionState)$null=$PSThread.AddScript{$ThreadContext.Host.UI.ReadLine()# 执行读取控制台操作}# 开始异步执行创建好的对象,并等待timeout秒无结果之后杀死本进程$Job=$PSThread.BeginInvoke()if(-not$Job.AsyncWaitHandle.WaitOne($Timeout)){Get-Process-Id$PID|Stop-Process}else{return$PSThread.EndInvoke($Job)}}-args$Timeout}catch{# Windows PowerShell超时会执行这里的代码Write-Host$TimeoutHint-ForegroundColor$HintForeGroundColor-BackgroundColor$HintBackGroundColor}# .Net Core PowerShell超时会执行这里的代码if($null-eq$res-and$PSVersionTable.PSEdition-eq"Core"){Write-Host$TimeoutHint-ForegroundColor$HintForeGroundColor-BackgroundColor$HintBackGroundColor}return$res}
调用示例
$res = Read-HostWithTimeout -Timeout 3000
# 超时$res的值就为null
# 有输入$res的值就为用户输入
Write-Host $res