I'm trying to use a powershell script (using C#) to listen to key strokes. It works a short time before erroring out. What's interesting is after I added some console writes, it seems the error is occurring external to my code.
Here's the powreshell script:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace KeyLogger {
public static class Program {
private const int WH_KEYBOARD_LL = 13;
private static IntPtr hookId = IntPtr.Zero;
public static void Begin() {
hookId = SetHook();
Application.Run();
UnhookWindowsHookEx(hookId);
}
private static IntPtr SetHook() {
IntPtr moduleHandle = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
return SetWindowsHookEx(WH_KEYBOARD_LL, HookCallback, moduleHandle, 0);
}
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) {
Console.WriteLine('0');
if (nCode == 0) {
Console.WriteLine('1');
Console.WriteLine(Marshal.ReadInt32(lParam) + " " + wParam + " ");
}
Console.WriteLine('2');
IntPtr x = CallNextHookEx(hookId, nCode, wParam, lParam);
Console.WriteLine('3');
return x;
}
private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll")]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll")]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll")]
private static extern IntPtr GetModuleHandle(string lpModuleName);
}
}
"@ -ReferencedAssemblies System.Windows.Forms
[KeyLogger.Program]::Begin();
The error is:
An error has occurred that was not properly handled. Additional information is shown below. The Windows PowerShell process will exit. Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object.
Notice the Console.WriteLine
's in the HookCallback
method. But the last debug line printed is 3
, indicating the error occurs outside of HookCallback
. What's even more interesting, if I have multiple instances of the script running, they don't all error simultaneously; one may crash, but the remaining continue on for a bit more.
I'm not too familiar with powershell or C#, so am wondering how to get more debug information? I would expect a line number or file or library name where the error occurred, but am not sure how to get that information.
Disclaimer, I took the script from here https://blogs.msdn.microsoft.com/toub/2006/05/03/low-level-keyboard-hook-in-c/ (modified slightly).
Edit: I've accepted Stuartd's answer as it was very helpful, but as I said in the comments, it required small modifications to work. For ease to future readers, here are the modifications that worked:
private static HookProc callback;
...
private static IntPtr SetHook() {
callback = HookCallback;
IntPtr moduleHandle = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
return SetWindowsHookEx(WH_KEYBOARD_LL, callback, moduleHandle, 0);
}
...
I've done more searching and reading up on C#, and it seems the cause was that SetWindowsHookEx
does not manage / keep alive it's hookProc delegate parameter, and passing a static method directly creates a local (local to the method) delegate that is ready for deletion once the SetHook
method returns.
This might do it: create a field that refers to HookProc
, and tell GC to keep it alive:
public static class Program
{
private const int WH_KEYBOARD_LL = 13;
private static IntPtr hookId = IntPtr.Zero;
private static HookProc proc;
public static void Begin()
{
proc = HookCallback;
hookId = SetHook();
GC.KeepAlive(proc);
Application.Run();
UnhookWindowsHookEx(hookId);
}
… etc