PowerShellとC#でグローバルホットキーを登録する

[C#|グローバルホットキーを登録する](http://anis774.net/codevault/hotkey.html)に感銘を受けたので、一部アレンジして、PowerShellから使用できるようにしました。

# 概要
ホットキーを登録し、登録したキーを押下するとPowerShellコマンドが実行できるようにします。

# 実装
クラス定義（C#）

```csharp:hotkeyClass.cs
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;

//クラス（ホットキー登録・解除用フォーム）
public class HotKeyForm : Form
{
    [DllImport("user32.dll")]
    extern static int RegisterHotKey(IntPtr HWnd, int ID, int MOD_KEY, Keys KEY);

    [DllImport("user32.dll")]
    extern static int UnregisterHotKey(IntPtr HWnd, int ID);

    const int WM_HOTKEY = 0x0312;
    public readonly int modKey;
    public readonly Keys key;
    public readonly int id;
    public readonly ThreadStart proc;

    //ホットキー登録
    public HotKeyForm(int modKey, Keys key, ThreadStart proc)
    {
        for (int i = 0x0000; i <= 0xbfff; i++)
        {
            if (RegisterHotKey(this.Handle, i, modKey, key) != 0)
            {
                this.modKey = modKey;
                this.key = key;
                this.id = i;
                this.proc = proc;
                break;
            }
        }
    }

    //ウィンドウメッセージ監視
    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);

        //ホットキーが押された時の処理
        if (m.Msg == WM_HOTKEY)
        {
            if ((int)m.WParam == this.id)
            {
                this.proc();
            }
        }
    }

    //ホットキー解除
    protected override void Dispose(bool disposing)
    {
        UnregisterHotKey(this.Handle, this.id);
        base.Dispose(disposing);
    }
}

//クラス（ホットキー制御用フォーム）
public class HotKeyController : Form
{
    public List<HotKeyForm> HotKeyFormList;

    //ホットキー登録・解除用フォームを表示すると、ウィンドウメッセージの監視が開始される
    private void HotKeyController_Load(object sender,EventArgs e)
    {
        foreach (HotKeyForm hotKeyForm in this.HotKeyFormList)
        {
            hotKeyForm.WindowState = System.Windows.Forms.FormWindowState.Minimized;
            hotKeyForm.Show();
            hotKeyForm.Hide();
        }
    }

    private void HotKeyController_FormClosing(object sender,EventArgs e)
    {
        foreach (HotKeyForm hotKeyForm in this.HotKeyFormList)
        {
            if (!hotKeyForm.IsDisposed)
            {
                hotKeyForm.Close();
            }
        }
    }

    public HotKeyController()
    {
        this.HotKeyFormList = new List<HotKeyForm>();

        this.WindowState = System.Windows.Forms.FormWindowState.Minimized;
        this.ShowInTaskbar = false;

        this.Load += HotKeyController_Load;
        this.FormClosing += HotKeyController_FormClosing;
    }
}
```

モジュール定義（PowerShell）

```ps1:hotkeyModule.ps1
$here = Split-Path $MyInvocation.MyCommand.Path -Parent

New-Module -ArgumentList $here {
  param($here)

  # --------------------
  # 非公開定義
  # --------------------
  # ホットキー登録・監視用クラス（C#）
  Add-Type `
    -ReferencedAssemblies 'System.Windows.Forms' `
    -Path (Join-Path $here 'hotkeyClass.cs')

  # 修飾キー
  $MODKEY_LIST = @{
    ALT   = 0x0001;
    CTRL  = 0x0002;
    SHIFT = 0x0004;
    _     = 0x0000;
  }

  # ハッシュテーブルを検索
  filter Select-HashTable{
    param(
      [Alias('with')][hashtable]$HashTable,
      $DefaultKey = '_'
    )

    if($HashTable.Contains($_)){
      return $HashTable[$_]
    } else {
      return $HashTable[$DefaultKey]
    }
  }
  Set-Alias match Select-HashTable

  # キー文字列を修飾キーとキーコードへ変換
  function ConvertTo-HotKeyArguments($KeyString){
    # キー文字列をリスト化
    # 例）Ctrl + A -> CTRL,A
    $KeyList = $KeyString.ToUpper().Split('+') | foreach{$_.Trim()}

    # 修飾キー（全て取得）
    $modkey = ($KeyList | match -with $MODKEY_LIST) -join ' -bor ' | iex

    # キーコード（初めの１つだけ取得）
    $key = ($KeyList | where{-not ($MODKEY_LIST.Contains($_))} | foreach{[Windows.Forms.Keys]::"$_"})[0]

    return $modkey,$key
  }
  Set-Alias hkargs ConvertTo-HotKeyArguments

  # --------------------
  # 公開定義
  # --------------------
  # ホットキーを登録し、ホットキーが押下された時に指定のコマンドを実行
  function Watch-HotKey($HotKeyList,$CloseKey){
    $ctl = New-Object HotKeyController

    # 各種ホットキーの登録
    $HotKeyList | foreach{
      $modKey,$key = hkargs $_.KeyString
      $proc        = $_.Procedure
      $ctl.HotKeyFormList.Add((New-Object HotKeyForm($modKey,$key,$proc)))
    }

    # 終了用ホットキーの登録
    $modKey,$key = hkargs $CloseKey
    $proc        = {$ctl.Close()}
    $ctl.HotKeyFormList.Add((New-Object HotKeyForm($modKey,$key,$proc)))    

    # ホットキー監視（終了用ホットキーが押下されるまで持続）
    [void]$ctl.ShowDialog()
  }

  Export-ModuleMember -Function Watch-HotKey
}
```

# 使用方法
上記のhotkeyModule.ps1を実行しモジュールを登録した後、Watch-Hotkey関数を呼び出してホットキーの登録及び監視を行います。

```ps1:hotkeyModuleTest.ps1
# モジュール登録
$here = Split-Path $MyInvocation.MyCommand.Path -Parent
& (Join-Path $here 'hotkeyModule.ps1') | Out-Null

# ホットキーの登録及び監視
Watch-HotKey @(
  @{KeyString='Ctrl+Shift+L';Procedure={Write-Host '処理１'}},
  @{KeyString='Ctrl+Shift+M';Procedure={Write-Host '処理２'}}
) -CloseKey 'Ctrl+W'
```

# まとめ
少しコマンドを実行する程度なら、AutoHotkeyなどのフリーソフトを使うより、PowerShellで組んでしまった方が、（Windows7以降だと）別途インストールが不要な分手軽で良いのかもしれません。

活用例としては、各種アプリケーションの起動、アクティブなプロセスに応じたコマンド実行（Excelマクロ実行など）、マウスカーソルがある座標の色情報取得・・・といった辺りが思いつきます。工夫次第では他にも色々使えそうです。
類似としては、WndProcを使うという点ではクリップボード監視ツールも同じような方法で実装できそうです。

余裕があればチャレンジしたいと思います。