I have a PowerShell script that listens for a key to be pressed before exiting. I accomplish this using a custom TypeDefinition
; here's the code:
param(
[string]$quitKey = "F16"
)
$keyLogger = @"
using System;
using System.IO;
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 const int WM_KEYDOWN = 0x0100;
private static HookProc hookProc = HookCallback;
private static IntPtr hookId = IntPtr.Zero;
private static int keyCode = 0;
[DllImport("user32.dll")]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll")]
private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("kernel32.dll")]
private static extern IntPtr GetModuleHandle(string lpModuleName);
public static int WaitForKey() {
hookId = SetHook(hookProc);
Application.Run();
UnhookWindowsHookEx(hookId);
return keyCode;
}
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) {
keyCode = Marshal.ReadInt32(lParam);
Application.Exit();
}
return CallNextHookEx(hookId, nCode, wParam, lParam);
}
}
}
"@
Add-Type -TypeDefinition $keyLogger -ReferencedAssemblies system.Windows.Forms
while ($keyPress -ne $quitKey) {
$keyPress = [System.Windows.Forms.Keys][KeyLogger.Program]::WaitForKey()
}
Write-Host "`nCaught $quitKey, ending recording..." -ForegroundColor Green
exit
The above executes flawlessly in PowerShell 5.1, the version pre-installed on Windows 11. However, I'm unable to get this script to run in PowerShell 7 (7.3.4 to be specific); I get this error:
InvalidOperation:
Line |
62 | $keyPress = [System.Windows.Forms.Keys][KeyLogger.Program]::WaitFor …
| ~~~~~~~~~~~~~~~~~~~
| Unable to find type [KeyLogger.Program].
Searched everywhere online for a similar issue and couldn't find anything. Add-Type
seems to work in my other scripts, even in PowerShell 7. But for some reason this specific custom TypeDefiniton
fails.
There is a long standing bug in newer versions of PowerShell where when -ReferenceAssemblies
is used then the standard assemblies are not included by default, see GitHub issue #9599.
You can use the same workaround I used in this answer, for your case, the code would look like this to make it compatible with both versions:
# Top of your code goes this:
Add-Type -AssemblyName System.Windows.Forms
$refAssemblies = @(
[System.Windows.Forms.Form].Assembly.Location
if ($IsCoreCLR) {
$pwshRefAssemblyPattern = [IO.Path]::Combine($PSHOME, 'ref', '*.dll')
Convert-Path $pwshRefAssemblyPattern
}
)
# Definition stays the same:
$keyLogger = @'
...
...
'@
# Here you include the assembly you want plus the default ones:
Add-Type -TypeDefinition $keyLogger -ReferencedAssemblies $refAssemblies