Can Powershell listen for and capture key presses?
Is it possible to write a PowerShell script that, like AutoHotkey, sits in tray and waits until you press a predefined keyboard key to start execution? And possibly not return but fire every time you press said key?
What I would like to achieve is - perform a predefined scripted action at the press of a button only AFTER starting the script, so putting it on the desktop and defining a shortcut key doesn't work.
For example:
I'd like the text "TEST" typed 3 times every time I press the "x" key but I would like this to happen only if the script that does this is running. So when the script is not running - pressing "x" would do nothing.
Basically, AutoHotkey can do exactly that but I'd like to do it in PowerShell, if possible, without writing huge amounts of C# code, because then I'd just write a tiny C# tray application for that.
Not in PowerShell directly maybe, but as you can run pretty much any C# code, here is a basic working example based on an excellent solution by Peter Hinchley:
Add-Type -TypeDefinition '
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);
}
}
}
' -ReferencedAssemblies System.Windows.Forms
while ($true) {
$key = [System.Windows.Forms.Keys][KeyLogger.Program]::WaitForKey()
if ($key -eq "X") {
Write-Host "Do something now."
}
}
Version 2
(using a callback):
Add-Type -TypeDefinition '
using System;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace PowerShell {
public static class KeyLogger {
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
private static Action<Keys> keyCallback;
private static IntPtr hookId = IntPtr.Zero;
[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 void Run(Action<Keys> callback) {
keyCallback = callback;
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) {
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN) {
var key = (Keys)Marshal.ReadInt32(lParam);
keyCallback(key);
}
return CallNextHookEx(hookId, nCode, wParam, lParam);
}
}
}
' -ReferencedAssemblies System.Windows.Forms
[PowerShell.KeyLogger]::Run({
param($key)
if ($key -eq "X") {
Write-Host "Do something now."
}
})