How to configure a timeout for Read-Host in PowerShell
Asked Answered
F

4

5

Like I said, this code works in PowerShell version 2, but not in PowerShell version 5.

function wait
      {
       $compte = 0
        Write-Host  "To continue installation and ignore configuration warnings      type [y], type any key to abort"
          While(-not $Host.UI.RawUI.KeyAvailable -and ($compte -le 20))
         {
          $compte++
          Start-Sleep -s 1
          }
           if ($compte -ge 20)
           {
        Write-Host "Installation aborted..."
           break
           }
        else
            {
        $key = $host.ui.rawui.readkey("NoEcho,IncludeKeyup")
            }
         if ($key.character -eq "y")
            {Write-Host "Ignoring configuration warnings..."}
         else 
            {Write-Host "Installation aborted..." 
            }}
Flighty answered 2/5, 2017 at 8:8 Comment(1)
i need something like this: echo -n "To continue installation and ignore configuration warnings type [yes], type any key to abort (installation will automatically abort after 20s timeout): " read -t 20 respFlighty
T
5

The official documentation or Read-Host -? will tell that it's not possible to use Read-Host in that manner. There is no possible parameter to tell it to run with some kind of timeout.

But there are various other questions detailing how to do this in PowerShell (usually utilizing C#).

The idea seems to be to check whenever the user pressed a key using $Host.UI.RawUI.KeyAvailable and check that for the duration of your timeout.

A simple working example could be the following:

$secondsRunning = 0;
Write-Output "Press any key to abort the following wait time."
while( (-not $Host.UI.RawUI.KeyAvailable) -and ($secondsRunning -lt 5) ){
    Write-Host ("Waiting for: " + (5-$secondsRunning))
    Start-Sleep -Seconds 1
    $secondsRunning++
}

You could use $host.UI.RawUI.ReadKey to get the key that was pressed. This solution probably would not be acceptable if you need more complex input than a simple button press. See also:

Tribulation answered 2/5, 2017 at 8:52 Comment(6)
$compte = 0 Write-Host "To continue installation and ignore configuration warnings type [y], type any key to abort" While((-not $Host.UI.RawUI.KeyAvailable) -and ($compte -le 20)) { $compte++ Start-Sleep -Seconds 1 } i use the command to make a timeout but it doesn't workFlighty
Have a look at the example. It might help you understand what actually happens. I don't really get what you mean by "it doesn't work" as that has little information.Tribulation
thanks for your answer . this script work fine with powershell v2.0 but when i try it with powershell 5.1 i don't have the same resultFlighty
in powershell v2.0 i have this : Press any key to abort the following wait time. Waiting for: 5 Waiting for: 4 Waiting for: 3 Waiting for: 2 but in powershell version 5.1 i have this : Press any key to abort the following wait time. Waiting for: 5 Installation aborted...Flighty
$Host.UI.RawUI.KeyAvailable has a bug and sometimes returns true for no reason github.com/PowerShell/PSReadLine/issues/959Shinar
@Alex from Jitbit The alternative command [console]::KeyAvailable works in PS 5.1. Example: While ( !([Console]::KeyAvailable) -And ($i -le 10)) {sleep 1; Write-Host “$i..” -NoNewLine; $i++Discriminator
F
5

Seth, thank you for your solution. I expanded on the example you provided and wanted to give that back to the community.

The use case is a bit different here - I have a loop checking if an array of VMs can be migrated and if there are any failures to that check the operator can either remediate those until the checks clear or they can opt to "GO" and have those failing VMs excluded from the operation. If something other than GO is typed state remains within the loop.

One downside to this is if the operator inadvertently presses a key the script will be blocked by Read-Host and may not be immediately noticed. If that's a problem for anyone I'm sure they can hack around that ;-)

Write-Host "Verifying all VMs have RelocateVM_Task enabled..."
Do {
    $vms_pivoting = $ph_vms | Where-Object{'RelocateVM_Task' -in $_.ExtensionData.DisabledMethod}
    if ($vms_pivoting){
        Write-Host -ForegroundColor:Red ("Some VMs in phase have method RelocateVM_Task disabled.")
        $vms_pivoting | Select-Object Name, PowerState | Format-Table -AutoSize
        Write-Host -ForegroundColor:Yellow "Waiting until this is resolved -or- type GO to continue without these VMs:" -NoNewline
        $secs = 0
        While ((-not $Host.UI.RawUI.KeyAvailable) -and ($secs -lt 15)){
            Start-Sleep -Seconds 1
            $secs++
        }
        if ($Host.UI.RawUI.KeyAvailable){
            $input = Read-Host
            Write-Host ""
            if ($input -eq 'GO'){ 
                Write-Host -ForegroundColor:Yellow "NOTICE: User prompted to continue migration without the blocked VM(s)"
                Write-Host -ForegroundColor:Yellow "Removing the following VMs from the migration list"
                $ph_vms = $ph_vms | ?{$_ -notin $vms_pivoting} | Sort-Object -Property Name
            }
        }
    } else {
        Write-Host -ForegroundColor:Green "Verified all VMs have RelocateVM_Task method enabled."
    }
} Until(($vms_pivoting).Count -eq 0)
Footwear answered 3/6, 2020 at 16:40 Comment(0)
M
1

I also have this problem before, now I have a perfect solution I think. Here is my PowerShell version info:

Name                           Value
----                           -----
PSVersion                      5.1.19041.2673
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.19041.2673
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

Here is my Read-Host with timeout implement code:

Function Read-HostWithTimeout {
    param(
        [string]$Prompt = "Input Text: ",
        [System.ConsoleColor]$PromptBackGroundColor = $Host.UI.RawUI.BackgroundColor,
        [System.ConsoleColor]$PromptForeGroundColor = $Host.UI.RawUI.ForegroundColor,
        [int]$Timeout = 5000,
        [string]$TimeoutHint = "Timeout",
        [System.ConsoleColor]$HintBackgroundColor = $Host.UI.RawUI.BackgroundColor,
        [System.ConsoleColor]$HintForeGroundColor = [System.ConsoleColor]::Yellow
    )

    Write-Host $Prompt -ForegroundColor $PromptForeGroundColor -BackgroundColor $PromptBackGroundColor -NoNewline

    $res = try {
        $ErrorActionPreference = 'Stop'
        powershell.exe -Command {
            param($Timeout)
            $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()
            }
            $Job = $PSThread.BeginInvoke()
            if (-not $Job.AsyncWaitHandle.WaitOne($Timeout)) {
                Get-Process -Id $PID | Stop-Process
            }
            else {
                return $PSThread.EndInvoke($Job)
            }
        } -args $Timeout
    }
    catch {
        Write-Host $TimeoutHint -ForegroundColor $HintForeGroundColor -BackgroundColor $HintBackGroundColor
    }
    return $res
}

you can use this function like this:

$UserInput = Read-HostWithTimeout -Timeout 1000
$UserInput // outuput input sting

If user input a string, $UserInput is what user input. If user input timeout, it will return $null, nothing output.

Mev answered 23/6, 2023 at 12:10 Comment(0)
B
0

Also note that all this $Host.UI stuff doesn't work from the Powershell ISE. To find out from within a script you could test for $Host.Name -eq "ConsoleHost". When true you can use the code from this topic. Otherwise you could use $Host.UI.PromptForChoice or any other way of showing a dialog box. With System.Windows.Forms.Timer you can then set a timer, and code to close the dialog box or form can be run when it expires.

Babbittry answered 2/2, 2023 at 7:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.