Windows PowerShellでSendKeys+マウスクリック

Windows PowerShell初めて使ってみたのでメモ。
やりたかったのは他のアプリケーションに対するキー操作とマウスによるクリック。
で、ポイントは

  • 他アプリケーションに対するマウス操作はWin32APIを使う。
  • Windows PowerShell ではC#がインラインコンパイル可能→.NETで出来ることは大体出来る。

ってトコ。
以下は

  1. 入力欄に数字を入力
  2. Enterキー押下
  3. ポップアップでダイアログ表示が表示されるので、ボタンが表示されている座標でクリック
  4. Windowが閉じていたら数字をコンソールに出力する

を行うスクリプトです。0-9でどれが求められている入力かを試行する感じです。

# Win32API をいじるC#クラス
# C#のソースを変数に格納
$source = @"
using System;
using System.Runtime.InteropServices;  // for DllImport, Marshal
using System.Windows.Forms;

public class Win32SendKey {
  // マウス関連のWin32API
  [DllImport("user32.dll")]
  extern static uint SendInput(
    uint       nInputs,   // INPUT 構造体の数(イベント数)
    INPUT[]    pInputs,   // INPUT 構造体
    int        cbSize     // INPUT 構造体のサイズ
  );
  [StructLayout(LayoutKind.Sequential)]
  struct INPUT
  { 
    public int        type; // 0 = INPUT_MOUSE(デフォルト),
                            // 1 = INPUT_KEYBOARD
    public MOUSEINPUT mi;
  }
  [StructLayout(LayoutKind.Sequential)]
  struct MOUSEINPUT
  {
    public int    dx ;
    public int    dy ;
    public int    mouseData ;  // amount of wheel movement
    public int    dwFlags;
    public int    time;        // time stamp for the event
    public IntPtr dwExtraInfo;
  }
  const int MOUSEEVENTF_MOVED      = 0x0001 ;
  const int MOUSEEVENTF_LEFTDOWN   = 0x0002 ;  // 左ボタン Down
  const int MOUSEEVENTF_LEFTUP     = 0x0004 ;  // 左ボタン Up
  const int MOUSEEVENTF_RIGHTDOWN  = 0x0008 ;  // 右ボタン Down
  const int MOUSEEVENTF_RIGHTUP    = 0x0010 ;  // 右ボタン Up
  const int MOUSEEVENTF_MIDDLEDOWN = 0x0020 ;  // 中ボタン Down
  const int MOUSEEVENTF_MIDDLEUP   = 0x0040 ;  // 中ボタン Up
  const int MOUSEEVENTF_WHEEL      = 0x0080 ;
  const int MOUSEEVENTF_XDOWN      = 0x0100 ;
  const int MOUSEEVENTF_XUP        = 0x0200 ;
  const int MOUSEEVENTF_ABSOLUTE   = 0x8000 ;

  const int screen_length = 0x10000 ;  // for MOUSEEVENTF_ABSOLUTE (この値は固定)

  // WindowActivate関連のWin32API
  [System.Runtime.InteropServices.DllImport(
    "user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
  static extern IntPtr FindWindow(
    string lpClassName,
    string lpWindowName);

  [System.Runtime.InteropServices.DllImport("user32.dll")]
  static extern bool SetForegroundWindow(IntPtr hWnd);

  // PowerShellから呼び出されるメソッド
  public static void LeftClick(int x, int y) { // 指定した座標を左クリックするメソッド
    INPUT[] input = new INPUT[3];
    // MOUSEEVENTF_ABSOLUTEの場合、画面サイズは 65535 で考えるので
    // 自分の解像度に合わせて修正すること(この場合 1024*768)
    // マウスに対する一連の動作の配列。1回目は移動。2回目は左ボタン押下。3回目は左ボタン開放。
    input[0].mi.dx = x * (65535 / 1024);
    input[0].mi.dy = y * (65535 / 768);
    input[0].mi.dwFlags = MOUSEEVENTF_MOVED | MOUSEEVENTF_ABSOLUTE;
    input[1].mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
    input[2].mi.dwFlags = MOUSEEVENTF_LEFTUP;
    SendInput(3, input, Marshal.SizeOf(input[0]));
  }

  public static bool ActivateWindow(string winTitle) { // 指定したWindowをアクティブにするメソッド
    IntPtr hWnd = FindWindow(null, winTitle);
    if (hWnd != IntPtr.Zero) {
      SetForegroundWindow(hWnd);
      return true;
    }
    else {
      return false;
    }
  }

  public static void SendKey(string key) { // 指定したキーを送信するメソッド
    SendKeys.SendWait(key);
  }
}
"@
# 規定では.NET FrameworkのSystem.Windows.Formsアセンブリ読み込まれないので追加する
Add-Type -Language CSharp -TypeDefinition $source -ReferencedAssemblies System.Windows.Forms 

$window_name = "操作したいWindow名"  # 操作したいWindow名を設定
for ($i = 0; $i -lt 10; $i++) { # 10回繰り返す
  if ([Win32SendKey]::ActivateWindow($window_name)) { # Windowがあったらアクティブにして以下の処理を行う
    [Win32SendKey]::SendKey($i)                       # ループの添え字を送信
    [Win32SendKey]::SendKey("{ENTER}")                # ENTERキーを入力
    Start-Sleep -m 100                                # 100msec = 0.1sec 待つ  
    [Win32SendKey]::LeftClick(500, 600)               # (500, 600)の座標にあるダイアログのOKボタンをクリック
    Start-Sleep -m 100                                # 100msec = 0.1sec 待つ  
  }
  else {                                              # Windowがない場合は終了
    Write-Host($i - 1)                                # ループの添え字-1(試行がうまくいった数字)を出力
    break
  }
}

ということで原型はこんな感じで、後はテキトーに。