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..." 
            }}
Tamás Sengel
  • 55,884
  • 29
  • 169
  • 223
h.s
  • 115
  • 3
  • 11
  • 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 resp – h.s May 02 '17 at 08:21

4 Answers4

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:

Community
  • 1
  • 1
Seth
  • 1,215
  • 15
  • 35
  • $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 work – h.s May 02 '17 at 13:24
  • 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. – Seth May 02 '17 at 13:59
  • 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 result – h.s May 02 '17 at 14:47
  • 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... – h.s May 02 '17 at 14:49
  • 2
    `$Host.UI.RawUI.KeyAvailable` has a bug and sometimes returns `true` for no reason https://github.com/PowerShell/PSReadLine/issues/959 – Alex from Jitbit Jun 03 '21 at 18:21
  • 1
    @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++ – Blaisem Oct 22 '21 at 19:20
4

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)
meyeaard
  • 51
  • 1
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.

Ting
  • 21
  • 3
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.

AndrePKI
  • 31
  • 4