You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在PowerShell中检测全局鼠标按下事件并实现7×24小时运行?

全局鼠标按下事件检测:轮询 vs 钩子方案

我需要实现一个可7×24小时运行的PowerShell脚本,用于检测全局鼠标按下事件。目前我已编写了如下脚本:

$key  = 0x01
$Signature = @'
    [DllImport("user32.dll", CharSet=CharSet.Auto, ExactSpelling=true)] 
    public static extern short GetAsyncKeyState(int virtualKeyCode); 
'@
Add-Type -MemberDefinition $Signature -Name Keyboard -Namespace PsOneApi
$f=0
do
{   If( [bool]([PsOneApi.Keyboard]::GetAsyncKeyState($key) -eq -32767))
        { 
            $f++
            #Write-Host $f -ForegroundColor Green 
        }
     
      Start-Sleep -Milliseconds 10

} while($true)

但我不确定这是否为最优方案,因为无限循环可能会造成系统资源的低效占用。请问是否存在基于鼠标事件钩子的解决方案?该方案是否比轮询按键输入更具优势?

基于钩子的解决方案及优势

是的,存在基于全局鼠标钩子的实现方案,而且它比你当前的轮询方案优势明显:

  • 轮询方案需要定时调用GetAsyncKeyState,即使没有事件发生也会占用CPU周期(哪怕Start-Sleep降低了频率);钩子方案是事件驱动,只有当鼠标事件触发时才会执行回调逻辑,空闲时几乎不消耗系统资源,更适合7×24小时运行。
  • 轮询可能会漏掉短时间内的快速按键(取决于Start-Sleep的间隔),钩子则能精准捕获每一次事件。

钩子方案的PowerShell实现代码

以下是通过Windows API注册全局鼠标钩子的实现:

Add-Type @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace MouseHook
{
    public class GlobalMouseHook
    {
        private const int WH_MOUSE_LL = 14;
        private const int WM_LBUTTONDOWN = 0x0201;

        private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
        private LowLevelMouseProc _proc;
        private IntPtr _hookID = IntPtr.Zero;

        public event EventHandler LeftButtonDown;

        public GlobalMouseHook()
        {
            _proc = HookCallback;
        }

        public void Hook()
        {
            using (Process curProcess = Process.GetCurrentProcess())
            using (ProcessModule curModule = curProcess.MainModule)
            {
                _hookID = SetWindowsHookEx(WH_MOUSE_LL, _proc, GetModuleHandle(curModule.ModuleName), 0);
            }
        }

        public void Unhook()
        {
            UnhookWindowsHookEx(_hookID);
        }

        private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0 && wParam == (IntPtr)WM_LBUTTONDOWN)
            {
                LeftButtonDown?.Invoke(this, EventArgs.Empty);
            }
            return CallNextHookEx(_hookID, nCode, wParam, lParam);
        }

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);
    }
}
"@

# 初始化钩子并绑定事件
$mouseHook = New-Object MouseHook.GlobalMouseHook
$eventHandler = Register-ObjectEvent -InputObject $mouseHook -EventName LeftButtonDown -Action {
    # 这里写鼠标左键按下时的处理逻辑
    Write-Host "鼠标左键被按下" -ForegroundColor Green
}

# 启动钩子
$mouseHook.Hook()

# 保持脚本运行(7×24小时)
try {
    while ($true) {
        [System.Windows.Forms.Application]::DoEvents()
        Start-Sleep -Milliseconds 100
    }
}
finally {
    # 清理资源
    $mouseHook.Unhook()
    Unregister-Event -SourceIdentifier $eventHandler.Name
}

两种方案的核心差异对比

  • 资源占用:轮询方案会周期性唤醒线程,即使无事件也有CPU开销;钩子仅在事件触发时执行逻辑,空闲时资源占用极低。
  • 事件准确性:轮询间隔如果设置过大,可能漏掉快速触发的事件;钩子能100%捕获符合条件的事件。
  • 复杂度:轮询实现简单;钩子需要处理API注册、事件回调和资源清理,但长期运行更可靠。

注意事项

  • 钩子方案依赖System.Windows.Forms,上述代码已通过Add-Type隐式处理依赖加载。
  • 长期运行时,必须在脚本退出时调用Unhook()释放钩子资源,避免内存泄漏。

内容的提问来源于stack exchange,提问作者Laura Bieber

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 01:52:08