Link to home
Start Free TrialLog in
Avatar of smad007
smad007

asked on

Paul Kimmel's Ctrl+Alt+Del Trapping Code Does Not Work for VB.NET Newbie

I want to trap Ctrl+Alt+Del and Ctrl+Esc and related keyboard sequences from my VB.NET app. In researching this I came across Paul Kimmel's very informitive and well-written article Managing Low-Level Keyboard Hooks in VB .NET at http://www.developer.com/net/vb/article.php/2193301 and I thought all of my problems were solved.

Unfortunately, the code that he provided in his article did not work for me and I'm trying to figure out why. I'm using .NET beta 2. As Mr. Kimmel suggested I copied and pasted his code into a new module in my solution called Keyboard, then from the click of a button on my main form I call Keyboard.HookKeyboard() and nothing happens.

In stepping through the code I can see that the code:

KeyboardHandle = SetWindowsHookEx( _
          WH_KEYBOARD_LL, callback, _
          Marshal.GetHINSTANCE( _
          [Assembly].GetExecutingAssembly.GetModules()(0)).ToInt32, 0)

in sub HookKeyboard is not working. After executing this line the value of KeyboardHandle = 0 and the value of Err.LastDLLError = 127. This question is probably not very difficult as I'm pretty sure Mr Kimmel's code would work if I were using it properly. But I'm a newbie with VB.NET and I think I'm doing something wrong. If anyone has any idea please let me know.

Thanks
Avatar of smad007
smad007

ASKER

I posted this question several hours ago and haven't got a single response. I'm thinking either it's not worth enough points, or people don't want to read the article.

Well, I increased the points. As far as the article goes I highly recommend reading it (even if you don't want to post a reply to my question). It is very well-written, and very interesting even from a strictly academic point of view. I think this subject (hooking) is very interesting and I wish I understood it better. I found Paul Kimmel's article to be far and away the best explanation of hooking keystrokes that I've found on the web in over a day of searching.

I hope this thread gets some action soon...
I had been playing around with hooking up to the ape-grip (as we Germans call it :-) about a year ago.
Without having read the article by PK that you mentioned - all my experiments and research resulted in it just not being possible with i.e. Windows XP as it happens "beyond a trappable level". :-(

FWIW - here's an another article on hooking (MSDN-magazine): http://msdn.microsoft.com/msdnmag/issues/02/10/CuttingEdge/
Avatar of smad007

ASKER

To try and spark more interest in this thread I've included the code listing from the article mentioned above. Here it is:

'Listing 2: The complete revised listing for implementing low-level keyboard hooks.

Imports System.Runtime.InteropServices
Imports System.Reflection
Imports System.Drawing
Imports System.Threading

Module Keyboard
  Public Declare Function UnhookWindowsHookEx Lib "user32" _
    (ByVal hHook As Integer) As Integer

  Public Declare Function SetWindowsHookEx Lib "user32" _
    Alias "SetWindowsHookExA" (ByVal idHook As Integer, _
    ByVal lpfn As KeyboardHookDelegate, ByVal hmod As Integer, _
    ByVal dwThreadId As Integer) As Integer

  Private Declare Function GetAsyncKeyState Lib "user32" _
    (ByVal vKey As Integer) As Integer

  Private Declare Function CallNextHookEx Lib "user32" _
    (ByVal hHook As Integer, _
    ByVal nCode As Integer, _
    ByVal wParam As Integer, _
    ByVal lParam As KBDLLHOOKSTRUCT) As Integer

  Public Structure KBDLLHOOKSTRUCT
    Public vkCode As Integer
    Public scanCode As Integer
    Public flags As Integer
    Public time As Integer
    Public dwExtraInfo As Integer
  End Structure

  ' Low-Level Keyboard Constants
  Private Const HC_ACTION As Integer      = 0
  Private Const LLKHF_EXTENDED As Integer = &H1
  Private Const LLKHF_INJECTED As Integer = &H10
  Private Const LLKHF_ALTDOWN As Integer  = &H20
  Private Const LLKHF_UP As Integer       = &H80

  ' Virtual Keys
  Public Const VK_TAB     = &H9
  Public Const VK_CONTROL = &H11
  Public Const VK_ESCAPE  = &H1B
  Public Const VK_DELETE  = &H2E

  Private Const WH_KEYBOARD_LL As Integer = 13&
  Public KeyboardHandle As Integer


  ' Implement this function to block as many
  ' key combinations as you'd like
  Public Function IsHooked( _
    ByRef Hookstruct As KBDLLHOOKSTRUCT) As Boolean

    Debug.WriteLine("Hookstruct.vkCode: " & Hookstruct.vkCode)
    Debug.WriteLine(Hookstruct.vkCode = VK_ESCAPE)
    Debug.WriteLine(Hookstruct.vkCode = VK_TAB)

    If (Hookstruct.vkCode = VK_ESCAPE) And _
      CBool(GetAsyncKeyState(VK_CONTROL) _
      And &H8000) Then

      Call HookedState("Ctrl + Esc blocked")
      Return True
    End If

    If (Hookstruct.vkCode = VK_TAB) And _
      CBool(Hookstruct.flags And _
      LLKHF_ALTDOWN) Then

      Call HookedState("Alt + Tab blockd")
      Return True
    End If

    If (Hookstruct.vkCode = VK_ESCAPE) And _
      CBool(Hookstruct.flags And _
        LLKHF_ALTDOWN) Then

      Call HookedState("Alt + Escape blocked")
      Return True
    End If

    Return False
  End Function

  Private Sub HookedState(ByVal Text As String)
    Debug.WriteLine(Text)
  End Sub

  Public Function KeyboardCallback(ByVal Code As Integer, _
    ByVal wParam As Integer, _
    ByRef lParam As KBDLLHOOKSTRUCT) As Integer

    If (Code = HC_ACTION) Then
      Debug.WriteLine("Calling IsHooked")

      If (IsHooked(lParam)) Then
        Return 1
      End If

    End If

    Return CallNextHookEx(KeyboardHandle, _
      Code, wParam, lParam)

  End Function


  Public Delegate Function KeyboardHookDelegate( _
    ByVal Code As Integer, _
    ByVal wParam As Integer, ByRef lParam As KBDLLHOOKSTRUCT) _
                 As Integer

  <MarshalAs(UnmanagedType.FunctionPtr)> _
  Private callback As KeyboardHookDelegate

  Public Sub HookKeyboard()
    callback = New KeyboardHookDelegate(AddressOf KeyboardCallback)

    KeyboardHandle = SetWindowsHookEx( _
      WH_KEYBOARD_LL, callback, _
      Marshal.GetHINSTANCE( _
      [Assembly].GetExecutingAssembly.GetModules()(0)).ToInt32, 0)

    Call CheckHooked()
  End Sub

  Public Sub CheckHooked()
    If (Hooked()) Then
      Debug.WriteLine("Keyboard hooked")
    Else
      Debug.WriteLine("Keyboard hook failed: " & Err.LastDllError)
    End If
  End Sub

  Private Function Hooked()
    Hooked = KeyboardHandle <> 0
  End Function

  Public Sub UnhookKeyboard()
    If (Hooked()) Then
      Call UnhookWindowsHookEx(KeyboardHandle)
    End If
  End Sub

End Module

And here is Mr. Kimmel's directions for making use of this code:

"You can copy and paste the code in listing 2 directly into a module to experiment with it. Call HookKeyboard to begin intercepting the three defined key combinations and UnhookKeyboard to restore the old keyboard state."

As directed, I added a module to my solution and pasted this code into it. Then on my main form I have a button that when pressed calls HookKeyboard. Now in stepping through the code I see that when line 2 of HookKeyboard finishes executing the value of KeyboardHandle = 0. Of course the subsequent calls to CheckHooked() and then Hooked() show that the keyboard has not been hooked. Also, I have noticed that the value of Err.LastDLLError = 127.

Hopefully someone has some idea where I'm going wrong.

Thanks

Avatar of smad007

ASKER

Just wanted to say thanks to Olaf for the article link. I enjoyed reading it. This is a very interesting subject and any additional information is usefull and welcome.

So thanks Olaf.
Hi smad007,

you sure are welcome.

I tried your sample now on a WindowsXP SP2 machine (auto-updates on). Other than the author, my default setting is to have Option Explicit and Option Strict turned on. If you do so, you'll have to change the VK_*-constants to be of Integer-type and the Hooked-function will have to be setup to return a Boolean. Maybe that'll do the trick on your PC as well - I simply don't know what the compiler would do with the VK-constants, but they surely should be integers!
Anyway, with that, the module actually did get a handle and hence the hook worked. While running (I suggest not to set any breakpoints in the KeyboardCallback-function as that might end up in just about anything "unhealthy"), I can see that Alt-Tab actually is stopped.
However, I didn't manage to set the hook to fire on Ctrl-Alt-Del. In Paul's module, the Alt-key declared private but still being used in the flags-var within the KBDLLHOOKSTRUCT. Setting the flag to hex 20 at least didn't do the trick. Have you digged further? I'm a little short on time this week so, sorry, I just didn't have the time to spend more than some ten minutes with the code. :-( And it won't get better until the end of next week as I'm in a hurry of getting everything done before I get to Amsterdam for TechEd 05.

Cheers,
Olaf
Avatar of smad007

ASKER

Hi Olaf,

As you suggested I turned on option explicit and strict and added the "as integer" and "as boolean" where necessary, but it still does the same thing. The keyboard is not being hooked.

I'm running this code on Windows 2000 SP4 and VB.NET beta 2. Unfortunately I have not gotten any further with this issue, but I'll keep trying. Thanks again for your input.
Avatar of Fernando Soto
Hi smad007;

The Ctrl+Alt+Del key combination can not be trapped because it is a hard wired interrupt from the keyboard. But the Ctrl + Esc can be trapped.

Create a Module in the project you want to trap keyboard combinations from, call it Keyboard. Then copy this code into a module replacing the stub code that Microsoft create for you. Then in your main form load event call the the sub "HookKeyboard()" and in your form closing event call the sub "UnhookKeyboard()" very important.

This should work on Windows 2000 with no problem, not sure about VB .Net Beta 2

This code is basically the same as Olaf_Rabbachin had instructed you to make. I have also added some comments about removing some code to unblock key combination that the author had added but that you may not want in, it is in function IsHooked.

Good Luck

Imports System.Runtime.InteropServices
Imports System.Reflection
Imports System.Drawing
Imports System.Threading

Module Keyboard
  Public Declare Function UnhookWindowsHookEx Lib "user32" _
    (ByVal hHook As Integer) As Integer

  Public Declare Function SetWindowsHookEx Lib "user32" _
    Alias "SetWindowsHookExA" (ByVal idHook As Integer, _
    ByVal lpfn As KeyboardHookDelegate, ByVal hmod As Integer, _
    ByVal dwThreadId As Integer) As Integer

  Private Declare Function GetAsyncKeyState Lib "user32" _
    (ByVal vKey As Integer) As Integer

  Private Declare Function CallNextHookEx Lib "user32" _
    (ByVal hHook As Integer, _
    ByVal nCode As Integer, _
    ByVal wParam As Integer, _
    ByVal lParam As KBDLLHOOKSTRUCT) As Integer

  Public Structure KBDLLHOOKSTRUCT
    Public vkCode As Integer
    Public scanCode As Integer
    Public flags As Integer
    Public time As Integer
    Public dwExtraInfo As Integer
  End Structure

  ' Low-Level Keyboard Constants
  Private Const HC_ACTION As Integer = 0
  Private Const LLKHF_EXTENDED As Integer = &H1
  Private Const LLKHF_INJECTED As Integer = &H10
  Private Const LLKHF_ALTDOWN As Integer = &H20
  Private Const LLKHF_UP As Integer = &H80

  ' Virtual Keys
  Public Const VK_TAB As Integer = &H9
  Public Const VK_CONTROL As Integer = &H11
  Public Const VK_ESCAPE As Integer = &H1B
  Public Const VK_DELETE As Integer = &H2E

  Private Const WH_KEYBOARD_LL As Integer = 13&
  Public KeyboardHandle As Integer


  ' Implement this function to block as many
  ' key combinations as you'd like
  Public Function IsHooked( _
    ByRef Hookstruct As KBDLLHOOKSTRUCT) As Boolean

    Debug.WriteLine("Hookstruct.vkCode: " & Hookstruct.vkCode)
    Debug.WriteLine(Hookstruct.vkCode = VK_ESCAPE)
    Debug.WriteLine(Hookstruct.vkCode = VK_TAB)

    ' Remove any of the If statement for those key combination
    ' you do not want to block. Add an If statement for those
    ' key combination you do want to block.

    ' Blocks the Control + Escape keys combination
    If (Hookstruct.vkCode = VK_ESCAPE) And _
      CBool(GetAsyncKeyState(VK_CONTROL) _
      And &H8000) Then

      Call HookedState("Ctrl + Esc blocked")
      Return True
    End If

    ' Blocks the Alt + Tab keys combination
    If (Hookstruct.vkCode = VK_TAB) And _
      CBool(Hookstruct.flags And _
      LLKHF_ALTDOWN) Then

      Call HookedState("Alt + Tab blockd")
      Return True
    End If

    ' Blocks the Alt + Escape keys combination
    If (Hookstruct.vkCode = VK_ESCAPE) And _
      CBool(Hookstruct.flags And _
        LLKHF_ALTDOWN) Then

      Call HookedState("Alt + Escape blocked")
      Return True

    End If

    Return False
  End Function

  Private Sub HookedState(ByVal Text As String)
    Debug.WriteLine(Text)
  End Sub

  Public Function KeyboardCallback(ByVal Code As Integer, _
    ByVal wParam As Integer, _
    ByRef lParam As KBDLLHOOKSTRUCT) As Integer

    If (Code = HC_ACTION) Then
      Debug.WriteLine("Calling IsHooked")

      If (IsHooked(lParam)) Then
        Return 1
      End If

    End If

    Return CallNextHookEx(KeyboardHandle, _
      Code, wParam, lParam)

  End Function


  Public Delegate Function KeyboardHookDelegate( _
    ByVal Code As Integer, _
    ByVal wParam As Integer, ByRef lParam As KBDLLHOOKSTRUCT) _
                 As Integer

  <MarshalAs(UnmanagedType.FunctionPtr)> _
  Private callback As KeyboardHookDelegate

  Public Sub HookKeyboard()
    callback = New KeyboardHookDelegate(AddressOf KeyboardCallback)

    KeyboardHandle = SetWindowsHookEx( _
      WH_KEYBOARD_LL, callback, _
      Marshal.GetHINSTANCE( _
      [Assembly].GetExecutingAssembly.GetModules()(0)).ToInt32, 0)

    Call CheckHooked()
  End Sub

  Public Sub CheckHooked()
    If (Hooked()) Then
      Debug.WriteLine("Keyboard hooked")
    Else
      Debug.WriteLine("Keyboard hook failed: " & Err.LastDllError)
    End If
  End Sub

  Private Function Hooked() As Boolean
    Hooked = KeyboardHandle <> 0
  End Function

  Public Sub UnhookKeyboard()
    If (Hooked()) Then
      Call UnhookWindowsHookEx(KeyboardHandle)
    End If
  End Sub

End Module
Avatar of smad007

ASKER

Hello Fernando,

I have followed your instructions but alas, it still does not work. Maybe this is a beta 2 thing as I have you, Olaf, and Paul Kimmel all saying this code works and I believe all three of you are reliable sources.

Using the code you posted above and calling HookKeyboard() from Form1_Load() I trace the execution and again I see that after the line:

KeyboardHandle = SetWindowsHookEx( _
          WH_KEYBOARD_LL, callback, _
          Marshal.GetHINSTANCE( _
          [Assembly].GetExecutingAssembly.GetModules()(0)).ToInt32, 0)

in HookKeyboard() finishes executing the value of KeyboardHandle == 0, which I interpret to mean the hook didn't work.

One slight difference though, now I can see the value of Err.LastDLLError == 0, Err.Number == 0, and
Err.Description == "". So it's like the hook does not work, but there is no error registering.

I am running on Windows 2000 Version 5, Service Pack 4 so I don't know why this code seems to work for everyone except me. It must be some sort of problem with beta 2 I guess?

Thanks

ASKER CERTIFIED SOLUTION
Avatar of Fernando Soto
Fernando Soto
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of smad007

ASKER

Fernando,

Again you have come to the rescue. The file from ForumHelp-Temp worked and from that I was able to get the keytrapping in my application. The code looks similar to what I had before, so I'm not sure what you did differently, but the important thing is it works now. I can now trap Ctrl + Esc, Ctrl + Shift + Esc, Alt + Tab, Alt + F4, Alt + Esc, and the left and right Windows keys.

As you suspected this method does not trap Ctrl + Alt + Del, but I have had some feedback that this is possible on Windows NT systems through GINA stubs and through the registry, so I will explore these avenues to try to trap that pesky key combination.

Thank you for your diligence,

Adam
Glad to help. Let me know if you find how to trap the Ctrl+Alt+Del I would like to know.

Good Luck;
Fernando
Avatar of smad007

ASKER

I did manage to find a way to trap Ctrl + Alt + Del. Fire up regedit and change the value of the key:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\AutoAdminLogon, from whatever it is (0 or 1), set it to 2.

This may seem very weird, here is an article which explains a little bit (but not really).

http://www.planetsourcecode.com/vb/scripts/ShowCode.asp?txtCodeId=35997&lngWId=-10

The important thing is...it works!
Great news - cheers!
Hi Adam;

Get to hear it and thanks for the link to the article.

Fernando