Windows 10 環境で「SendKeys を呼んだのに相手アプリに何も入力されない」という相談はとても多いです。原因の大半はフォーカス(前面アクティブ化)とハンドルの取り違え、さらに UAC をはじめとする OS 側の制限にあります。本稿は C#/Windows Forms から別プロセス(例:メモ帳)へ確実に文字列を送るための実践ガイドです。基本解決策から高信頼な代替手段まで、現場で迷わない手順に落とし込んで解説します。
症状と背景:なぜ Windows 10 で SendKeys が無反応になるのか
SendKeys は「現在アクティブなウィンドウ」に対してキー入力を模倣する仕組みです。つまり、ターゲットのウィンドウが手前に出てフォーカスを持っていないと、送られたキーはどこにも届きません。さらに Windows 10 では、ユーザーの意図しないフォーカス奪取を抑制するために、SetForegroundWindow が常に成功するとは限らないという仕様があります。加えて、以下の要因が重なると失敗しやすくなります。
- ウィンドウ ハンドルではなく「プロセス ハンドル」を使っている(代表的なミス)。
- アプリの権限レベルが異なる(送信側が通常権限、相手が管理者権限 など)。
- ターゲットが最小化・隠しウィンドウ・応答停止中。
- タイミングの競合(ウィンドウを前面に出す直後に SendKeys して見失う)。
- プロジェクト設定不備(.NET 8+ の Windows Forms 有効化や参照不足)。
まず最初に通すチェックリスト
| 確認項目 | OK 条件 | ポイント |
|---|---|---|
| ハンドルの種類 | Process.MainWindowHandle を使う | SetForegroundWindow に プロセス ハンドルを渡すのは誤り。必ず ウィンドウ ハンドル。 |
| 権限レベル | 送信側と受信側を同じ権限(通常:非管理者)で起動 | 権限差(UIPI)で入力やメッセージがブロックされることがある。 |
| ウィンドウの状態 | 最小化解除/表示/応答あり | ShowWindowAsync(SW_RESTORE) で復元し、応答待ちを入れる。 |
| フォーカス制御 | 確実にアクティブ化 | SwitchToThisWindow、SetForegroundWindow、AttachThreadInput 併用で成功率UP。 |
| タイミング | SendKeys.SendWait を優先 | フォーカス確立を待ってから送る。待機ループや WaitForInputIdle も有効。 |
| プロジェクト設定 | <TargetFramework>net8.0-windows</TargetFramework>、<UseWindowsForms>true</UseWindowsForms> | Windows 専用 API と Windows Forms を明示。using System.Windows.Forms; を忘れずに。 |
最短ルートの基本解決策:正しいハンドル + フォーカス付与 + SendKeys
以下の流れがもっともシンプルで効果的です。
- 目的プロセスを取得し、
MainWindowHandleから「ウィンドウ ハンドル」を得る。 SwitchToThisWindow(hWnd, true)またはShowWindowAsync(SW_RESTORE)+SetForegroundWindowでアクティブ化。SendKeys.SendWait("This is a test.")で文字列を送る。
サンプル(簡潔版)
using System;
using System.Diagnostics;
using System.Linq; // ← Process 検索で FirstOrDefault を使うため
using System.Runtime.InteropServices;
using System.Windows.Forms;
public partial class Form1 : Form
{
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);
```
private void button1_Click(object sender, EventArgs e)
{
var p = Process.GetProcessesByName("notepad").FirstOrDefault();
if (p == null) { MessageBox.Show("Notepad が見つかりません"); return; }
IntPtr hWnd = p.MainWindowHandle; // 必ずウィンドウ ハンドル
if (hWnd == IntPtr.Zero) { MessageBox.Show("MainWindowHandle が取得できません"); return; }
SwitchToThisWindow(hWnd, true); // 可能ならこれで復元+アクティブ化
SendKeys.SendWait("This is a test."); // SendWait を推奨
}
```
}
「SwitchToThisWindow が環境によって効かない」「Windows の仕様変更が心配」という場合は、下の安定版ユーティリティを使ってください。
安定版ユーティリティ(フォーカス取得の総当たり)
using System;
using System.Runtime.InteropServices;
public static class ForegroundHelper
{
private const int SW_RESTORE = 9;
```
[DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")] private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("kernel32.dll")] private static extern uint GetCurrentThreadId();
[DllImport("user32.dll", SetLastError = true)] private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);
public static bool ActivateWindow(IntPtr hWnd)
{
if (hWnd == IntPtr.Zero) return false;
try
{
// 1) 復元
ShowWindowAsync(hWnd, SW_RESTORE);
// 2) まずは素直に SetForegroundWindow
if (SetForegroundWindow(hWnd)) return true;
// 3) SwitchToThisWindow を試す(存在すれば)
try { SwitchToThisWindow(hWnd, true); } catch { /* 環境により未対応 */ }
if (GetForegroundWindow() == hWnd) return true;
// 4) スレッドを一時的にアタッチして前面化を強制
var fg = GetForegroundWindow();
uint dummy;
uint targetThread = GetWindowThreadProcessId(hWnd, out dummy);
uint foregroundThread = GetWindowThreadProcessId(fg, out dummy);
uint currentThread = GetCurrentThreadId();
bool attached1 = false, attached2 = false;
try
{
if (foregroundThread != 0 && currentThread != foregroundThread)
{
attached1 = AttachThreadInput(currentThread, foregroundThread, true);
}
if (targetThread != 0 && currentThread != targetThread)
{
attached2 = AttachThreadInput(currentThread, targetThread, true);
}
if (SetForegroundWindow(hWnd)) return true;
}
finally
{
if (attached1) AttachThreadInput(currentThread, foregroundThread, false);
if (attached2) AttachThreadInput(currentThread, targetThread, false);
}
}
catch { /* 飲み込み: 後段で UIA/WM_SETTEXT にフォールバック */ }
return GetForegroundWindow() == hWnd;
}
```
}
利用例(安定版ユーティリティ + SendKeys)
var p = Process.GetProcessesByName("notepad").FirstOrDefault();
if (p == null || p.MainWindowHandle == IntPtr.Zero) return;
if (ForegroundHelper.ActivateWindow(p.MainWindowHandle))
{
SendKeys.SendWait("This is a test.");
}
else
{
MessageBox.Show("前面化に失敗しました。別案を検討してください。");
}
高信頼の代替手段①:UI Automation(アクセシビリティ経由で直接書き込む)
UI Automation (UIA) は、入力ブロックや IME の状態に左右されにくく、ターゲットのテキスト コントロールへ値を直接設定できます。SendKeys がセキュリティ設定で制限される環境でも動作しやすく、RPA 的な用途に適します。
前提
- 参照:
System.Windows.Automation - ターゲットが UIA の
ValuePatternをサポートしていること(Windows 10 のメモ帳のエディットは概ね OK) - 権限レベルは基本的に同等であること
サンプル:Notepad の編集ボックスに文字列を直接設定
using System;
using System.Diagnostics;
using System.Linq;
using System.Windows.Automation;
public static class UiaWriter
{
public static bool TrySetTextToNotepad(string text)
{
var p = Process.GetProcessesByName("notepad").FirstOrDefault();
if (p == null || p.MainWindowHandle == IntPtr.Zero) return false;
```
var root = AutomationElement.FromHandle(p.MainWindowHandle);
if (root == null) return false;
// クラス名 "Edit" の要素(メモ帳のテキスト領域)を取得
var edit = root.FindFirst(TreeScope.Subtree,
new AndCondition(
new PropertyCondition(AutomationElement.ClassNameProperty, "Edit"),
new PropertyCondition(AutomationElement.IsEnabledProperty, true)
)
);
if (edit == null) return false;
if (edit.TryGetCurrentPattern(ValuePattern.Pattern, out object patternObj)
&& patternObj is ValuePattern value)
{
value.SetValue(text);
return true;
}
// ValuePattern 非対応ならフォールバックでフォーカスして SendKeys
edit.SetFocus();
System.Windows.Forms.SendKeys.SendWait(text);
return true;
}
```
}
UIA はコントロール単位の操作であるため、アプリのレイアウトが変わっても比較的壊れにくいという利点があります。また、IME が ON の状態でも確実に値を挿入できます。
高信頼の代替手段②:WM_SETTEXT(低レベルメッセージで直接流し込む)
Win32 メッセージ WM_SETTEXT を使えば、相手のエディット コントロールへ文字列を直接書き込めます。速度は最速級ですが、コントロールによっては未対応・制約がある点に注意してください。
サンプル:WM_SETTEXT を使う
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
public static class Win32TextWriter
{
private const uint WM_SETTEXT = 0x000C;
```
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, string lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string lpszClass, string lpszWindow);
public static bool TrySetTextToNotepad(string text)
{
var p = Process.GetProcessesByName("notepad").FirstOrDefault();
if (p == null || p.MainWindowHandle == IntPtr.Zero) return false;
// メモ帳の子にある "Edit" コントロール(標準エディット)
var editHwnd = FindWindowEx(p.MainWindowHandle, IntPtr.Zero, "Edit", null);
if (editHwnd == IntPtr.Zero) return false;
SendMessage(editHwnd, WM_SETTEXT, IntPtr.Zero, text);
return true;
}
```
}
Unicode を使うため、CharSet = CharSet.Unicode を明示しています。古い ANSI API 宣言のままだと多言語文字で問題が出ます。Notepad 以外のアプリでは、子コントロールのクラス名が異なることがあるため、対象アプリのコントロール マップをツール(例:Spy++、Inspect.exe)で確認しましょう。
キーイベントを「確実に」送るコツ
SendKeys.Send()よりSendKeys.SendWait()を優先:フォーカス確立や IME 切替のラグを吸収できる。- 小分け送信:長文を一気に送らず、数百文字ごとに送ると取りこぼしを防げる。
- 短い待機をはさむ:アクティブ化→送信の間に
Task.Delay(50~150ms)程度の待ちを入れると安定するケースが多い。 - IME 状態に依存しない入力:確実性重視なら UIA / WM_SETTEXT を検討。
プロジェクト設定とビルド構成の落とし穴
.NET 8 以降の SDK スタイル プロジェクトで Windows Forms を使うなら、次の 2 行は必須です。
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
また、コード側で using System.Windows.Forms; を忘れると SendKeys が参照できません。Any CPU ビルドでも動作しますが、対象アプリが 64bit 前提の場合は x64 でビルドして検証するとトラブル切り分けが容易です。
ケース別:原因と対策を一望
| 状況 | 主因 | 対策 | 補足 |
|---|---|---|---|
| SetForegroundWindow を呼んでも前面化しない | フォーカス盗み防止/別スレッド | SwitchToThisWindow、AttachThreadInput、ShowWindowAsync の併用 | 少し待機を入れると成功率が上がる。 |
| SendKeys が全く届かない | ウィンドウがアクティブでない | 前面化の成否を確認し、SendKeys.SendWait に切り替え | ステータス バーなどにフォーカスがあると入力先が違うことがある。 |
| 管理者で起動したアプリに送れない | UIPI によるブロック | 送信側も管理者で起動する/UIA での制御を検討 | 原則は同一権限で合わせる。 |
| 最小化から復帰すると失敗する | 編集コントロールが再生成されている | フォーカス再取得 or UIA/WM_SETTEXT を再探索して書き込む | ハンドルが変わることを前提に作る。 |
| マルチモニタ/DPI 高い環境で不安定 | 描画負荷とタイミング | 短い遅延をはさむ/UIA を使う | SendKeys は画面状態に影響を受けやすい。 |
| サービスや非対話セッションから実行 | デスクトップ未接続 | 対話ユーザーのセッションで実行する(タスク スケジューラの「ユーザーがログオンしている場合のみ」) | 非対話セッションでは SendKeys は届かない。 |
堅牢な実装例:ターゲット検出・前面化・送信までを一本化
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Automation;
using System.Windows.Forms;
public static class CrossProcessTyper
{
public static async Task TryTypeTo(string processName, string text)
{
var p = Process.GetProcessesByName(processName).FirstOrDefault();
if (p == null) return false;
```
// MainWindowHandle がまだ 0 の場合に備えて待つ
for (int i = 0; i < 20 && p.MainWindowHandle == IntPtr.Zero; i++)
{
await Task.Delay(50);
p.Refresh();
}
if (p.MainWindowHandle == IntPtr.Zero) return false;
// 1) UIA で直接書き込みを試す(高信頼)
try
{
var root = AutomationElement.FromHandle(p.MainWindowHandle);
var edit = root?.FindFirst(TreeScope.Subtree,
new PropertyCondition(AutomationElement.ClassNameProperty, "Edit"));
if (edit != null && edit.TryGetCurrentPattern(ValuePattern.Pattern, out var pat))
{
((ValuePattern)pat).SetValue(text);
return true;
}
}
catch { /* UIA 非対応なら後段へ */ }
// 2) 前面化して SendKeys
if (ForegroundHelper.ActivateWindow(p.MainWindowHandle))
{
await Task.Delay(120); // アクティブ化の安定待ち
SendKeys.SendWait(text);
return true;
}
// 3) WM_SETTEXT フォールバック
try
{
return Win32TextWriter.TrySetTextToNotepad(text); // Notepad 固有例
}
catch { return false; }
}
```
}
テストの仕方:現象再現と切り分け
- Notepad を通常権限で起動し、空白のドキュメントを開く。
- 自作アプリ側で「前面化 → 入力」を実行する。
- 届かないときは、権限を合わせる(両方非管理者/両方管理者)→ 成否を比較。
- ウィンドウが最小化状態から復帰するケースを再現し、アクティブ化待機(50〜150ms)を入れる。
- それでもダメなら UIA と WM_SETTEXT のパスを試す。
デバッグのヒント:どこで失敗しているかを特定する
- ハンドル確認:
MainWindowHandleが 0 でないか。 - フォーカス確認:
GetForegroundWindow()がターゲットか。 - スレッド関係:
GetWindowThreadProcessIdで前面ウィンドウのスレッド ID を確認。 - UAC 確認:アイコンの盾マークや「管理者として実行」の有無。
- コントロール探索:UIA の Inspect や Spy++ で
ClassNameを調べる。
セキュリティと運用上の注意
- 権限の原則:原則、送信側/受信側は同レベル権限で実行する。高権限から低権限への操作はできても、低権限→高権限は多くがブロックされる。
- 機密文字列の送信:パスワード等を SendKeys で送るのは監査の観点で推奨されません。UIA の
SetValueなどで最小権限・最小公開を心がける。 - 非対話セッション:サービスやリモートの切断セッションでは入力が届かない。対話セッションでだけ使う設計にする。
- 将来互換性:
SwitchToThisWindowは環境依存な面があり、ShowWindowAsync + SetForegroundWindow + AttachThreadInput の組み合わせを用意しておくと安心。
典型的 Q&A
Q. Notepad の前面化はできたのに、入力先がタイトルバーやメニューになります。
A. エディット コントロールがフォーカスを持っていません。UIA で編集領域を特定して SetFocus() を呼ぶか、メニュー閉じを意識してから SendKeys.SendWait を使います。
Q. 複数の Notepad が開いている場合は?
A. タイトルやウィンドウ テキストで対象を絞り込み、該当プロセスの MainWindowHandle を使ってください。Win32 の FindWindow / FindWindowEx を併用すると正確に狙えます。
Q. 日本語の長文が化けます。
A. WM 系 API を使うときは CharSet.Unicode を明示してください。SendKeys 側の IME 状態にも影響されるため、確実性が必要なら UIA/WM_SETTEXT で直接流し込みましょう。
Q. どうしても SetForegroundWindow が失敗します。
A. OS の前面化ルールにひっかかっています。短い待機・ユーザー操作(クリック)を誘導する・AttachThreadInput を使う、の順に試してみてください。
まとめ:実装の指針
- 失敗の主因は「ウィンドウ ハンドルではなくプロセス ハンドルを渡している」ことと「フォーカスが取れていない」こと。
- 基本解決策は「正しいハンドルで前面化 →
SendKeys.SendWait」。 - 高信頼が必要なら UI Automation の
ValuePattern.SetValueを第一候補に。 - 速度優先なら
WM_SETTEXT(ただしコントロール依存)。 - .NET 8+ ではプロジェクトの
<UseWindowsForms>を忘れずに。using System.Windows.Forms;も必須。 - UAC、最小化、タイミング、セッション ーー これらの「環境要因」を潰せば、Windows 10 でも SendKeys は十分実用になります。
付録:この記事内に登場した主要 API/クラス一覧
| 名称 | 用途 | 備考 |
|---|---|---|
Process.MainWindowHandle | トップレベル ウィンドウのハンドルを取得 | 0 の場合はまだ生成されていない可能性あり。再試行と待機。 |
SwitchToThisWindow | 復元+アクティブ化(最小化でも戻せる) | 環境依存。効かない場合は他 API と併用。 |
ShowWindowAsync | 最小化から復元 | SW_RESTORE を使用。 |
SetForegroundWindow | 前面化 | OS ルールで失敗しうる。AttachThreadInput 併用で補強。 |
SendKeys.SendWait | キー送信(待機あり) | 非同期ズレを抑える。 |
AutomationElement / ValuePattern | UIA での直接値設定 | 高信頼・IME 非依存・アクセシビリティに優しい。 |
WM_SETTEXT | Win32 メッセージでテキスト設定 | 最速だがコントロール依存。 |
実務での “勝ち筋” レシピ
- ターゲットが明確なら UI Automation で値を直書き(第一候補)。
- UIA が難しい or 既存のキーシーケンスを活かすなら、安定版前面化 →
SendKeys.SendWait。 - 対象が標準 EDIT コントロールなら、
WM_SETTEXTで一撃。 - 権限差・セッション差を作らない(両者の起動方法を合わせる)。
- 長文・ループでは「小分け + 短待機」で落ち着かせる。
以上の手順と実装を押さえておけば、「タイマーで前面に出した時だけ動く」といった気まぐれな動作から卒業できます。SendKeys でつまずいたら、まずはハンドル・フォーカス・権限・タイミングの 4 点を疑い、必要に応じて UIA/WM_SETTEXT に切り替える――それが Windows 10 時代の確実なアプローチです。

コメント