'How to prevent input from displaying in console while script is running

I have a script that runs several loops of code and relies on specific input at various phases in order to advance. That functionality is working. My current issue revolves around extraneous input being supplied by the user displaying on screen in the console window wherever I have the cursor position currently aligned.

I have considered ignoring this issue since the functionality of the script is intact, however, I am striving for high standards with the console display of this script, and I would like to know a way to disable all user input period, unless prompted for. I imagine the answer has something to do with being able to command the Input Buffer to store 0 entries, or somehow disabling and then re-enabling the keyboard as needed.

I have tried using $HOST.UI.RawUI.Flushinputbuffer() at strategic locations in order to prevent characters from displaying, but I don't think there's anywhere I could put that in my loop that will perfectly block all input from displaying during code execution (it works great for making sure nothing gets passed when input is required, though). I've tried looking up the solution, but the only command I could find for manipulating the Input Buffer is the one above. I've also tried strategic implementation of the $host.UI.RawUI.KeyAvailable variable to detect keystrokes during execution, then $host.UI.RawUI.ReadKey() to determine if these keystrokes are unwanted and do nothing if they are, but the keystrokes still display in the console no matter what.

I am aware that this code is fairly broken as far as reading the key to escape the loop goes, but bear with me. I hashed up this example just so that you could see the issue I need help eliminating. If you hold down any letter key during this code's execution, you'll see unwanted input displaying.

$blinkPhase = 1

# Set Coordinates for cursor

$x = 106

$y = 16

$blinkTime = New-Object System.Diagnostics.Stopwatch

$blinkTime.Start()

$HOST.UI.RawUI.Flushinputbuffer()

do {

    # A fancy blinking ellipses I use to indicate when Enter should be pressed to advance.

    $HOST.UI.RawUI.Flushinputbuffer()

    while ($host.UI.RawUI.KeyAvailable -eq $false) {

        if ($blinkTime.Elapsed.Milliseconds -gt 400) {

            if ($blinkPhase -eq 1) {

                [console]::SetCursorPosition($x,$y)

                write-host ". . ." -ForegroundColor gray

                $blinkPhase = 2

                $blinkTime.Restart()

            } elseif ($blinkPhase -eq 2) {

                [console]::SetCursorPosition($x,$y)

                write-host "     "

                $blinkPhase = 1

                $blinkTime.Restart()

            }

        }

        start-sleep -m 10

    }

    # Reading for actual key to break the loop and advance the script.

    $key = $host.UI.RawUI.ReadKey()

} while ($key.key -ne "Enter")

The expected result is that holding down any character key will NOT display the input in the console window while the ellipses is blinking. The actual result, sans error message, is that a limited amount of unwanted/unnecessary input IS displaying in the console window, making the script look messy and also interfering with the blinking process.



Solution 1:[1]

What you're looking for is to not echo (print) the keys being pressed, and that can be done with:

$key = $host.UI.RawUI.ReadKey('IncludeKeyDown, NoEcho')

Also, your test for when Enter was pressed is flawed[1]; use the following instead:

# ...
} while ($key.Character -ne "`r")

Caveat: As of at least PSReadLine version 2.0.0-beta4, a bug causes $host.UI.RawUI.KeyAvailable to report false positives, so your code may not work as intended - see this GitHub issue.

Workaround: Use [console]::KeyAvailable instead, which is arguably the better choice anyway, given that you're explicitly targeting a console (terminal) environment with your cursor-positioning command.


As an aside: You can simplify and improve the efficiency of your solution by using a thread job to perform the UI updates in a background thread, while only polling for keystrokes in the foreground:

Note: Requires the ThreadJob module, which comes standard with PowerShell Core, and on Windows PowerShell can be installed with Install-Module ThreadJob -Scope CurrentUser, for instance.

Write-Host 'Press Enter to stop waiting...'

# Start the background thread job that updates the UI every 400 msecs.
# NOTE: for simplicity, I'm using a simple "spinner" here.
$jb = Start-ThreadJob { 
  $i=0
  while ($true) { 
    [Console]::Write("`r{0}" -f '/-\|'[($i++ % 4)])
    Start-Sleep -ms 400 
  }
}

# Start another thread job to do work in the background.
# ...

# In the foreground, poll for keystrokes in shorter intervals, so as 
# to be more responsive.
While (-not [console]::KeyAvailable -or ([Console]::ReadKey($true)).KeyChar -ne "`r" ) {
  Start-Sleep -Milliseconds 50
}

$jb | Remove-Job -Force # Stop and remove the background UI thread.

Note the use of [Console]::Write() in the thread job, because Write-Host output wouldn't actually be passed straight through to the console.


[1] You tried to access a .Key property, which only the [SystemConsoleKeyInfo] type returned by [console]::ReadKey() has; the approximate equivalent in the $host.UI.rawUI.ReadKey() return type, [System.Management.Automation.Host.KeyInfo], is .VirtualKeyCode, but its specific type differs, so you can't (directly) compare it to "Enter"; The latter type's .Character returns the actual [char] instance pressed, which is the CR character ("`r") in the case of Enter.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1