Windows タスクスケジューラではまった
タスクスケジューラから起動するプロセスのユーザがログインユーザと違うと
- MainWindowTitleが取得できません
- MouseMoveやMouseClickが動きません
でも
- プロセス一覧は取れます
- ファイル作成はできます
そんなことをタスクでやりたい時はユーザをちゃんと揃えるか、BUILTIN\USERSにしましょう。
上記のように部分的に動かなかったので切り分けに時間がかかりました。
「できない」部分はちゃんと成功して終わっているようで、エラーも起きず追いづらかったです。
恐らくMainWindowTitleとかMouse関係はユーザごと固有に割り当てられていて、
タスクのユーザがログインユーザと違ったので存在しないものにアクセスしてしまったのかな??という印象です。
推測ですが画面表示に関わること、画面からの入力に関わることがほぼアクセスできないのではないのでしょうか。
経緯
- とある装置を起動した時、ある部分をクリックしたかった
- とある装置はWindows Embedded 7で動いていた
- 特権モードで結構色々いじれた
よって
- タスクスケジューラに自動クリックスクリプトを登録して起動後自動クリック
なんてことをしたかったんですが、肝心のクリックが上手く動かなくてはまりました。
部分的に動くタスクの訳が分からずカット&トライの嵐となりました・・・。
この装置は特権モード起動と通常モード起動があり、通常モードでクリックをしたかったわけです。
通常モードではWindowsをとことん隠蔽されてしまうのでログインユーザが不明でした。
まぁ名前的にこれだろうな、という思い込みが外れてはまってただけ、という悲しい結末でした。
自動クリックスクリプト
ここのスクリプトを参考に以下の様に変更。
# HACK:クリック位置を外から設定できるようにするべきか???
$clickXpos = 300
$clickYpos = 200
# クリックの為Win32API をいじるC#クラス
# C#のソースを変数に格納
$source = @"
///
///クリックの為user32.dllを操作するクラス
///
using System;
using System.Runtime.InteropServices; // for DllImport, Marshal
using System.Windows.Forms;
public class MouseController {
// マウス関連の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 (この値は固定)
// 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]));
}
}
"@
try{
#自作クリッククラスの取り込み
Add-Type -Language CSharp -TypeDefinition $source -ReferencedAssemblies System.Windows.Forms
#クリック発火
[MouseController]::LeftClick($clickXpos, $clickYpos)
}
catch
{
$error[0] | Format-List -force | Out-File "error.log"
}
タスクに登録したのは違うスクリプト
実はタスクで起動したらコマンドプロンプトの画面をクリックしてしまいました。
クリックしたいアプリケーションがどうやら最背面指定がされているようでuser32.dllのSetForeGroundをしても前に出てくれませんでした。
よってコマンドプロンプトを表示させないでPowershellスクリプトを実行する方法を模索しました。
この解決方法が良さそうだったので採用しました。
起動までに若干時間がかかりますが、今回のケースでは問題ありません。