How To: Sending keys to a console in NT

Posted: (EET/GMT+2)

 

How To: Sending keys to a console in NT

Level: Win32, NT Advanced
See Also: Console applications with Delphi


Sometimes your nice GUI applications need to execute those ol' DOS programs, and sometimes you need to send keystrokes to them without user intervention. For example, I needed to execute PGP (Pretty Good Privacy) directly from my Windows-application, because I didn't have the time to find or create other encyption implementations. Thus, I came up with a solution described here. Please note that the code below has only been tested on a Finnish (Scandinavian) keyboard, so it might not work on yours. Also, the code only works on Windows NT, not on Windows 95. Tested version OS was 4.0/B1381/SP2.


Sending keys

Before we a ready to send keys to the console, we must obviously determine which application to run. I've used Borland Pascal 7.0 to create a very simple 16-bit program, which the "Sender" program executes.

Execution is done using the CreateProcess function, as is recommended for Win32 apps. In case of DOS and Win32 console processes, CreateProcess will automatically create a console (that black screen), but this automagical support isn't the right way to go for us. Instead, we must allocate our own console. This way, we can control the new console ourselves (ie. send keys).

Once the console is created and the DOS app running, we are ready to send some keys. The code we use is quite complicated:

    Function WriteStringToConsole(S : String) : Integer;
    Var
      I,J,K,StdOut : Integer;
      ShiftDown    : Boolean;
      IRB          : TInputRecord;
    
      Procedure PressShift(KeyDown : Boolean);
      Var IR : TInputRecord;
      Begin
        With IR,KeyEvent do Begin
          EventType := Key_Event;
          bKeyDown := KeyDown;
          wRepeatCount := 1;
          wVirtualKeyCode := vk_Shift;
          ASCIIChar := #0;
          wVirtualScanCode := 42;
          dwControlKeyState := 0;
        End;
        J := Integer(WriteConsoleInput(StdOut,IR,1,K));
        ShiftDown := KeyDown;
      End;
    
    Begin
      StdOut := GetStdHandle(Std_Input_Handle);
      ShiftDown := False;
      For I := 1 to Length(S) do Begin
        FillChar(IRB,SizeOf(IRB),0);
        With IRB,KeyEvent do Begin
          EventType := Key_Event;
          bKeyDown := True;
          wRepeatCount := 1;
          wVirtualKeyCode := VkKeyScan(S[I]);
          ASCIIChar := S[I];
          J := OemKeyScan(Ord(S[I]));
          If (J <> -1) Then Begin
            wVirtualScanCode := LoWord(J);
            J := HiWord(J);
            If ((J And 1) <> 0) Then Begin
              PressShift(True);
              dwControlKeyState := Shift_Pressed;
            End;
          End
          Else If (S[I] = #13) Then wVirtualScanCode := 28;
        End;
        J := Integer(WriteConsoleInput(StdOut,IRB,1,K));
        FillChar(IRB,SizeOf(IRB),0);
        With IRB,KeyEvent do Begin
          EventType := Key_Event;
          wRepeatCount := 1;
          wVirtualKeyCode := VkKeyScan(S[I]);
          ASCIIChar := S[I];
          J := OemKeyScan(Ord(S[I]));
          If (J <> -1) Then Begin
            wVirtualScanCode := LoWord(J);
            J := HiWord(J);
            If ((J And 1) <> 0) Then dwControlKeyState := Shift_Pressed;
          End
          Else If (S[I] = #13) Then wVirtualScanCode := 28;
        End;
        J := Integer(WriteConsoleInput(StdOut,IRB,1,K));
        If ShiftDown Then PressShift(False);
      End;
      Result := Length(S)*2;
    End;
    
Firstly, we obtain a handle to the console output using the GetStdHandle API function. This handle is used when we actually do our sending (ie. writing) of keys.

Next we start to loop thru the string we must send. For every character in the string, we must send two Input Records (IRs) to the console: one for pressing the key, and one for releasing it. Filling the IRB (IR buffer) requires some thought, because we must figure the ASCII value of the key, and also both the correct virtual and OEM scan code. The virtual key code is obtained using the VkKeyScan API call. Respectively, the OEM code can be obtained using the OEMKeyScan call.

However, the Enter key is special. OEMKeyScan returns an error value we trying to pass the Enter key to it. The Help says that this occurs if the keycode cannot be created using a single keystore. Honestly speaking, I don't understand this in case of Enter. Anyway, I've hardcoded the Enter key as scan code 28. This works with AT/102 keyboards here in Finland, but might fail elsewhere. If you find a way to send the Enter key without hardcoded values, I'd be glad to receive technically detailed mail!

Also, the Shift key poses problems similar to the Enter key. The Shift key must of course be pressed down if our function must write upper-case characters to the console. The Shift key used is the left one, with hardoced keycode of 42. Note that our Shift-press-release implementation quite stupidly releases the key for every uppercase character. If you need to send long strings with all CAPS letters, you could optimize the code some bit.

After the IRB for the key to write has been created, we simply call the WriteConsoleInput API function. Note that the assignment to the variable J is unecessary, but nice when debugging.

Conclusion

Having read this article, you should be able to send simple string to any application running in an NT console. This includes old DOS apps as well as Win32 console apps. However, I think that most often you will need to control DOS apps. Sending keys is closely related to redirecting input and output (I/O), but this is another story.