Blue Day
Blue Day

Reputation: 21

Powershell ReadKey() case sensitive

In my script I need to get 1 character from user and process it immediately whithout waiting for an Enter. Additionally, I want to treat the character case-sensitive.

write-host("Please press a or A:")
$choice = ($host.UI.RawUI.ReadKey(('NoEcho,IncludeKeyUp,IncludeKeyDown'))).character

if($choice -ceq "a")
{
    write-host("You pressed a");
}
elseif($choice -ceq "A")
{
    write-host("You pressed A");
}
else
{
    Write-Host("You pressed neither a nor A")
}
Pause

The issue of this code is when I try to press "A", it shows "You pressed neither a nor A". The reason is to type "A" I have to press Shift first, Powershell detects Shift pressed and it process immediately without waiting for an A.

Anyone has idea how to solve this?

Upvotes: 1

Views: 1022

Answers (3)

mklement0
mklement0

Reputation: 439737

The simplest solution is to only react to keypresses that result in a printable character, and then evaluate which character was pressed via a switch statement.

# Wait for a printable character to be pressed.
while (($char=($host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')).Character) -eq 0) {}

# Evaluate it.
switch -CaseSensitive ($char) {
  'a' { 'You pressed a' }
  'A' { 'You pressed A' }
  default { 'You pressed neither a nor A' }
}

Note: While modifier keys Shift, Control, and Alt by themselves do not count as keypresses, combinations with a printable character do; therefore, for instance, Alt-a is treated the same as 'a', and Control-a as control character START OF HEADING, U+0001.

If you want to avoid that, use the following variation instead:

# Wait for a printable character to be pressed, but only if not combined
# with Ctrl or Alt.
while (($key=$host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')).Character -eq 0 -or 
       $key.ControlKeyState -notin 0, 'ShiftPressed') {}
$char = $key.Character

Note: This works on Windows only - on Unix-like platforms, the .ControlKeyState property is apparently always 0.

However, if you use [Console]::ReadKey() instead, you can make it work on Unix-like platforms too - which assumes that you're willing to assume that your script always runs in a console (terminal), and not other kinds of PowerShell hosts.

# Wait for a printable character to be pressed, but only if not combined
# with Ctrl or Alt.
while (($key = [Console]::ReadKey($true)).KeyChar -eq 0 -or 
       $key.Modifiers -notin 0, 'Shift') {}
$char = $key.KeyChar

Upvotes: 1

Karthick Ganesan
Karthick Ganesan

Reputation: 385

Does the below make up for what is expected,

($key = $Host.UI.RawUI.ReadKey()) | % { if ($_.VirtualKeyCode -eq '16') {
            $key = $Host.UI.RawUI.ReadKey()
        }
        $Choice = $key.Character
        if ($Choice -ceq "a"){
            "`rYou pressed 'a'"
        }
        elseif ($Choice -ceq "A"){
            "`rYou pressed 'A'"
        }
        else {
            "`rYou neither pressed 'a' nor 'A'"
        }
    }

Upvotes: 1

JosefZ
JosefZ

Reputation: 30183

Try the following code snippet:

if ( $Host.Name -eq 'ConsoleHost' ) {
    Write-Host "Please press a or A:"
    Do  {
            $choice = $host.UI.RawUI.ReadKey(14)
        } until ( $choice.VirtualKeyCode -in @( 48..90) )

    if ( $choice.Character -ceq "a") {
        Write-Host "You pressed a";
    }
    elseif ( $choice.Character -ceq "A") {
        Write-Host "You pressed A";
    }
    else {
        Write-Host "You pressed neither a nor A ($($choice.Character))";
    }
} else {
    # e.g. Windows PowerShell ISE Host:
    # the "ReadKey" method or operation is not implemented.
    Write-Host '$Host.Name -neq ConsoleHost' -ForegroundColor Magenta
}

As currently written, $choice.VirtualKeyCode -in @( 48..90) condition allows some (limited) subset of printable characters. Adjust it with respect to Keys Enum

Upvotes: 1

Related Questions