Read individual key presses in PowerShell

22,054

Solution 1

Perhaps you could clarify a bit - but ReadKey returns a KeyInfo object not a string. KeyInfo contains VirtualKeyCode, Character, ControlKeyState and KeyDown members - all fields in your output string in that order. In fact, it looks like PowerShell has just called the .ToString() method in your output example. You will probably want to look at the Character property to find your desired character. Consider the following example where I press 1:

$key = $Host.UI.RawUI.ReadKey()
if ($key.Character -eq '1') {
  "Pressed 1"
}

Solution 2

This will definitely do what you want.

This accepts a single character as input and has validation.

  • I use a menu system to run programs and utilities using PowerShell.
  • When I hit a character, it goes right to the option and runs without hitting enter

I have extracted out the essentials of the input and menu system and gave a few example menu items.

Please note: There are 2 modes for this to run in

MODE 1: Uses Read-Host, requires Enter, Runs in ISE. Use this for troubleshooting/building

MODE 2: Uses ReadKey, no Enter required, Does NOT run in ISE... You will need to run this in a PowerShell command line. The code below is currently in Mode 2.

Comment/UnComment the lines as needed to change the mode

##Formatting Variables
$fgc1 = 'cyan'
$fgc2 = 'white'
$indent = '  '

Function MainMenu {
    CLS
    Write-Host "###############"
    Write-Host "## Main Menu ##"
    Write-Host "###############"
    Write-Host -NoNewLine "$indent" "A " -ForegroundColor 'red'; Write-Host "== Options A" -ForegroundColor $fgc2
    Write-Host -NoNewLine "$indent" "B " -ForegroundColor 'red'; Write-Host "== Options B" -ForegroundColor $fgc2
    Write-Host -NoNewLine "$indent" "C " -ForegroundColor 'red'; Write-Host "== Options C" -ForegroundColor $fgc2
    Write-Host -NoNewLine "$indent" "D " -ForegroundColor 'red'; Write-Host "== Options D" -ForegroundColor $fgc2
    Write-Host -NoNewLine "$indent" "E " -ForegroundColor 'red'; Write-Host "== Options E" -ForegroundColor $fgc2
    Write-Host -NoNewLine "$indent" "F " -ForegroundColor 'red'; Write-Host "== Options F" -ForegroundColor $fgc2
    Write-Host -NoNewLine "$indent" "G " -ForegroundColor 'red'; Write-Host "== Options G" -ForegroundColor $fgc2
    Write-Host ""

    #This gives you a way to set the current function as a variable.  The Script: is there because the variable has to
    #be available OUTSIDE the function.  This way you can make it back to the menu that you came from as long as all 
    #of your menus are in functions!
    $Script:SourceMenu = $MyInvocation.MyCommand.Name

    # Mode 1#
    #Use this for troubleshooting so that you can stay in ISE
    # Uncomment the 2 lines below to use Read-Host.  This will necessitate an ENTER Key. BUT, it WILL work in ISE
    #$K = Read-Host - "Which option?"
    #MenuActions

    # Mode 2#
    #Uncomment the line below to use ReadKey.  This will NOT necessitate an ENTER Key. BUT, it ## will NOT work ## in ISE
    ReadKey
}

Function ReadKey {
    Write-Host "Please make your choice..."
    Write-Host ""
    Write-Host "Press Q to quit"
    $KeyPress = [System.Console]::ReadKey()

    #This gets the keypress to a common variable so that both modes work (Read-Host and KeyPress)
    $K = $KeyPress.Key

    #Jumps you down to the MenuActions function to take the keypress and "Switch" to it
    MenuActions
}

Function MenuActions {
    Switch ($K) {
        A {CLS;Write-Host "You Pressed A";Write-Host "Going to pause now... ";&pause}
        B {CLS;Write-Host "You pressed B";Write-Host "Going to pause now... ";&pause}
        C {CLS;Write-Host "You pressed C";Write-Host "Going to pause now... ";&pause}
        D {CLS;Write-Host "You pressed D";Write-Host "Going to pause now... ";&pause}
        E {CLS;Write-Host "You pressed E";Write-Host "Going to pause now... ";&pause}
        F {CLS;Write-Host "You pressed F";Write-Host "Going to pause now... ";&pause}
        G {CLS;Write-Host "You pressed G";Write-Host "Going to pause now... ";&pause}

        #This is a little strange of a process to exit out, but I like to use an existing mechanism to exit out
        #It sets the $SourceMenu to a process that will exit out.
        #I use this same process to jump to a different menu/sub-menu
        Q {$SourceMenu = "Exit-PSHostProcess";CLS;Write-Host "Exited Program"}
}
        #This next command will loop back to the menu you came from.  This, in essence, provides a validation that one of the 
        #"Switch ($X.key)" options were pressed.  This is also a good way to always find your way back to 
        #the menu you came from.  See "$Script:SourceMenu = $MyInvocation.MyCommand.Name" above.
        #
        #This is also the way that the Menu Item for Q exits out
    & $SourceMenu
}

# This runs the MainMenu function.  It has to be after all the functions so that they are defined before being called
MainMenu

The main part of the whole thing is:

$KeyPress = [System.Console]::ReadKey()

#This gets the keypress to a common variable so that both modes work (Read-Host and KeyPress)
$K = $KeyPress.Key
#
#Then do something with $K

What about a system that accepts 2 key presses?

Just a little addition here. Since we are talking about a SINGLE key press... how about a double key press? Well, this will work fine. Just stack up the ReadKey commands and assign variables to each, then combine them:

Write-Host "Press the 2 character option you wish"
#Get KeyPress1 Variable
$KeyPress1 = [System.Console]::ReadKey()

#This gets the keypress to a common variable so that both modes work (Read-Host and KeyPress)
$K1 = $KeyPress1.Key

#Get KeyPress1 Variable
$KeyPress2 = [System.Console]::ReadKey()

#This gets the keypress to a common variable so that both modes work (Read-Host and KeyPress)
$K2 = $KeyPress2.Key

#This is just for troubleshooting to prove it works
CLS
Write-Host "This is the state of the variables right now"
Write-Host "Keypress1 is: $K1" -ForegroundColor Green
Write-Host "Keypress1 is: $K2" -ForegroundColor Green

$KEYS = "$K1"+"$K2"

Write-Host "The combined presses are: $KEYS" -ForegroundColor Red
pause

I look forward to questions or comments.

Solution 3

Regarding the use of $Host.UI.RawUI.ReadKey('IncludeKeyDown'); Goyuix answer should be marked as the right answer.

I found the $Host.UI.RawUI.ReadKey rather long and wanted to use [console].

But was a bad decision. This is a warning. Some might know how to use it right.

Key will give you for pressing some keys

  • 1 = D1
  • 1 on the NumPad = NumPad1
  • a = A (only capitalized letters)
  • - = OemMinus (yeah)

Additionally the similar looking [console]::ReadKey it will the Key Property.

$key = [console]::ReadKey()
if ($key.Key -eq 'D1') {
  "Pressed 1"
}
Share:
22,054
Stephen Craven
Author by

Stephen Craven

Updated on July 09, 2022

Comments

  • Stephen Craven
    Stephen Craven almost 2 years

    Using PowerShell, I would like to read the value of key presses as they occur, without the user having to press enter. For example, if the user presses '1' I would like the PowerShell script to react to the choice immediately without an 'enter'.

    My research has turned up ReadKey,

    $input = $Host.UI.RawUI.ReadKey('IncludeKeyDown');

    But ReadKey returns much more information in the string than I require:

    72,h,0,True

    While I can parse the key press from this string, I'd prefer a more direct option. Does one exist?

  • JeffR
    JeffR about 6 years
    Just a note if anyone else is trying this. ReadKey does not work in Powershell ISE.
  • I say Reinstate Monica
    I say Reinstate Monica over 5 years
    This doesn't work correctly for me on Windows 7, PS 5.1. For letters it works but for numbers and symbols it does not. For example, $key.key is equal to D1 when I press 1 in the number row above the keyboard, and equal to NumPad1 when I press 1 in the number pad.
  • I say Reinstate Monica
    I say Reinstate Monica over 5 years
    Be aware that this will treat modifier keys such as Shift as a key press, so, for example, responding with Shift+a is not possible.
  • JackGrinningCat
    JackGrinningCat over 5 years
    Thanks i rechecked what you say and it is the same for my environment. I added a warning for that solution, some might know the use-case for that function.
  • Eric Fossum
    Eric Fossum over 2 years
    I could not get this to work for me, but turns out [Console]::ReadKey() works just the same.