After a lot of troubleshooting, I think I got something that is working.
The importing of the HotKey class did work for me but it took me a bit to realize it was actually failing to register on the keys you specified (CTRL-SHIFT K). To catch this I changed:
# original
[void][Hotkey]::RegisterHotKey($window.Handle, 1, $MOD_CONTROL -bor $MOD_SHIFT, $VK_K)
# to this
$registered = [bool][Hotkey]::RegisterHotKey($window.Handle, 1,$MOD_CONTROL -bor $MOD_SHIFT, $VK_K)
$errorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
if ($registered -eq $false){
Write-Output "Failed to register HotKey. Error code $errorCode"
exit
}
Since the RegisterHotKey function returns a bool, we save the return value and if it's false a problem has occurred in registering the hotkey and we exit. I also save the error code which is a win32 error code. Once I saved the error code I discovered that it was failing for me and returning code 1409 which is ERROR_HOTKEY_ALREADY_REGISTERED. I'm not sure if it'll be the same for your system but the hotkey was already registered on mine and was causing it to fail.
Changing the $VK_K value to the value for L fixed it, I also tested M (key code values).
I tried using Add_KeyDown but had issues getting it to work from there.
The last part of the code was causing your error that you posted object reference not set to an instance of an object."
[System.Windows.Forms.Application]::Run($syncHash.Window)
$synch.Window is still null at this point. I'm not exactly sure why the hashtable was added but it works for me just passing the original object $window to the run function.
--
From my understanding after some research, the key to capturing hotkeys is overriding the WndProc function. WndProc ("... is a callback function, which you define in your application, that processes messages sent to a window"). So basically, we override that function and just wait for the WM_HOTKEY system message to be passed and do an action based on that. I used this answer to help in defining the Form class with the override function.
The following is working for me and is set for Control + Shift + L:
# Define constants
# https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-hotkey
$MOD_CONTROL = [uint16]0x0002
$MOD_SHIFT = [UInt16]0x0004
# Control + SHIFT + K - Didn't work for me. Looks like it's already a registed hotkey at least on my system.
# Not sure if it'll be the same for others.
# Produced an error code 1409 when trying to register. (ERROR_HOTKEY_ALREADY_REGISTERED)
# https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--1300-1699-
# $VK_K = [uint16]0x4B (letter "K")
# "L" key - worked for me. Tested "M" as well.
# https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
$VK_K = [uint16]0x4C # (Letter "L")
# Load necessary types
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public static class Hotkey {
[DllImport("user32.dll", SetLastError = true)]
public static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool UnregisterHotKey(IntPtr hWnd, int id);
}
"@ -Language CSharp
# used this as reference for overriding the WndProc function for the form
# to capture hotkeys: https://stackoverflow.com/a/59043639/20473839
# Create the C# derived Form
$assemblies = "System.Windows.Forms", "System.Drawing"
$code = @'
using System;
using System.Windows.Forms;
public class MyForm:Form
{
// https://wiki.winehq.org/List_Of_Windows_Messages
private static int WM_HOTKEY = 0x0312;
protected override void WndProc(ref System.Windows.Forms.Message msg)
{
// if hotkey has been used do desired action
if (msg.Msg == WM_HOTKEY){
hotKeyAction();
}
base.WndProc(ref msg);
}
public void hotKeyAction(){
Console.WriteLine("HOTKEY PRESSED");
}
}
'@
Add-Type -ReferencedAssemblies $assemblies -TypeDefinition $code -Language CSharp
# Create a hidden window for message processing
$window = [Myform] @{}
$window.ShowInTaskbar = $false
$window.WindowState = [System.Windows.Forms.FormWindowState]::Minimized
$window.MinimizeBox = $false
$window.MaximizeBox = $false
$window.Visible = $false
# Register the hotkey
# RegisterHotKey will return false on a failed registration
$registered = [bool][Hotkey]::RegisterHotKey($window.Handle, 1,$MOD_CONTROL -bor $MOD_SHIFT, $VK_K)
# if hot key registration fails, it creates a win32 error.
# retrieving error reference. https://stackoverflow.com/a/73395877/20473839
$errorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
if ($registered -eq $false){
Write-Output "Failed to register HotKey. Error code $errorCode"
exit
}
# Unregister the hotkey when the window is closed
$window.Add_FormClosed({
[void][Hotkey]::UnregisterHotKey($window.Handle, 1)
})
$window.Add_Load({
$window.Activate()
})
# Run
[System.Windows.Forms.Application]::Run($window)
Some other references I used (one,two, three, four) that I found interesting.
I'm not sure that this is the best way to go about what you want but it does work and is a good enough starting point to improve upon.
$syncHash.Windowis null when you are passing it to the function at the end. You did not add anything to the hashtable before that function runs as far as I can see. Try adding$syncHash.Add("Window",$window)to add the $window object to the hashtable before the last line[System.Windows.Forms.Application]::Run($syncHash.Window). That seems to get it to launch for me. However, there seems to be some other issues with it fully recognizing the hotkey presses when I'm running it.