PSReadLine
PSReadLine copied to clipboard
Windows: $host.UI.RawUI.KeyAvailable has false positives.
Environment data
PS version: 7.0.0-preview.1
PSReadline version: 2.0.0-beta4
os: 10.0.18362.1 (WinBuild.160101.0800)
PS file version: 7.0.0.0
BufferWidth: 180
BufferHeight: 9999
Steps to reproduce or exception report
Note: The problem surfaces on Windows only.
Run the following code with and without PSReadLine imported:
# Show a spinner until the user presses a key.
$i=0; while (-not $host.UI.RawUI.KeyAvailable) { Write-Host -NoNewline ("`r{0}" -f '/―\|'[($i++ % 4)]); start-sleep -ms 200 }
Expected: A spinner should appear and keep spinning until the user presses a key.
Actual, with PSReadLine imported: The while loop is either never entered (1st invocation immediately after start of the PS session) or stops after one iteration even if no key is pressed (subsequent invocations).
Once you run Remove-Module PSReadLine, the behavior is as expected in regular console windows; interestingly, the problem persists in a Windows Terminal window.
For consoles / terminals only, using [Console]::KeyAvailable in lieu of $host.UI.RawUI.KeyAvailable is an effective workaround.
Duplicate of #901 and #291.
Quoted from https://github.com/PowerShell/PSReadLine/issues/291#issuecomment-342685388
It seems reasonable for PSReadLine to consume the final
KeyUpbefore returning to the caller.
First, this is a Windows only issue because on Linux/macOS, the KeyAvailable and ReadKey implementation in ConsoleHost use Console.ReadKey directly.
I looked into consuming the final KeyUp events before returning from PSConsoleReadLine.ReadLine using the Win32 APIs GetNumberOfConsoleInputEvents, PeekConsoleInput and GetConsoleInput. The problem is, after Console.ReadKey(), there is a delay till the unread KeyUp events can be queried by those APIs.
On my dev machine, the delay is about 100 ms. With Thread.Sleep(100) after Console.ReadKey(), the APIs GetNumberOfConsoleInputEvents, PeekConsoleInput and GetConsoleInput can return the unread KeyUp keys, while without Thread.Sleep or sleeping for a shorter time span, those 3 Win32 APIs shows 0 event read. This explains the reported symptom: The while loop is either never entered or stops after one iteration even if no key is pressed. It stops after one iteration because the first time $host.UI.RawUI.KeyAvailable is call, the unread KeyUp events haven't shown up to the Win32 APIs yet, and the second call to the property, they show up.
Given the delay, I don't think there is a reliable way to consume the KeyUp events before returning to the caller. @lzybkr any suggestion?
@daxian-dbw 100ms? Maybe @DHowett-MSFT has an idea why that might be.
Previous alternatives I've considered - but not really good options:
- Change .Net to return on key up.
- Use P/Invoke directly on Windows.
And a new idea based your investigation:
- Consume the keyboard event in the background. This is might be a horrible idea - it would introduce a race condition unless you can lock the correct handle (CONIO$)?
Is there a work around for this? $Host.UI.RawUI.KeyAvailable always returns true and if I ReadKey after this it breaks in my script. Is there anyway to defect a key press other than KeyAvailable?
We have https://github.com/PowerShell/PowerShell/issues/7971 - Read-Host is slow on Linux. Perhaps it is related to this.
Here is my workaround for this. Apparently FlushInputBuffer() is not quick enough so I need to throw in a quick sleep at the beginning. I have found 100ms does the trick reliably, I was able to go down as low as 75ms but it was a hit or miss. Tested in PowerShell 5.1 and 7.0.3.
start-sleep -milliseconds 100;
$host.ui.RawUI.FlushInputBuffer();
$i=0; while(-not $host.UI.RawUI.KeyAvailable) { Write-Host -NoNewline ("`r{0}" -f '/?\|'[($i++ % 4)]); start-sleep -Milliseconds 200 }
Using [Console]::KeyAvailable worked for me
Interestingly, in Windows Terminal (but not in regular console windows) the problem also surfaces when PSReadLine is not loaded.