Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2016年10月24日] [更新 2021年11月22日] Delphi サンプルプログラム集
508_コマンドプロンプトへコマンドを送信
動作確認等 Windows 7 U64(SP1) + DelUphi XE(UP1) Pro
後半は Windows 10 ビルド 19042 Pro 64 bit + Delphi XE5(UP2) Pro VCL-32
508_SendCmdPrompt.zip [4.8 MB] 2021年11月22日版 (EXE 同梱)







自アプリから起動中のコマンドプロンプトへコマンドを送信


本ページのサンプルは、既に起動中のコマンドプロンプトにコマンドを送り実行します。
コマンドは文字例です。したがって、コマンドを送るということは、起動中のコマンドプロンプトに文字列を送信することと同じですが、コマンドプロンプトの画面には、[他のアプリへ文字列送信と取得] のサンプルのような、SendMessage を使用する方法では文字列を送信できません。

自アプリからコマンドプロンプトを起動するサンプルは以下の記事にあります。






01_コマンドプロンプトへコマンドを送信


AttachConsole 関数で起動中のコマンドプロンプトにアタッチして、その標準入力に対してコマンドの文字列を送信して実行するサンプルです。
WriteConsoleInput 関数を使用しています。WriteConsoleInput は 1 文字単位での送信です。

WriteConsoleOutputCharacter という行単位で文字列を送信する関数があります。これはコンソールアプリのコンソール画面の任意の位置に文字列をセットする関数です。



図1
実行例
  • DIR コマンドを実行
図2
Windows 10 の PowerShell での実行結果例

リスト1
コマンドプロンプトへコマンドを送信
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

function AttachConsole(dwProcess: DWORD): BOOL; stdcall; external 'kernel32.dll';

//-----------------------------------------------------------------------------
//  標準入力にキー入力する
//-----------------------------------------------------------------------------
procedure SendCommandPrompt(ACmdWnd: HWND; AText: string);
var
  LhStdInput : THandle;
  LInputRec  : TInputRecord;
  LEventNum  : DWORD;
  LIndex     : Integer;
begin
  // 標準入力のハンドルを取得
  LhStdInput := GetStdHandle(STD_INPUT_HANDLE);

  FillChar(LInputRec, SizeOf(LInputRec), #0);
  LInputRec.EventType := KEY_EVENT;
  LInputRec.Event.KeyEvent.wRepeatCount := 1;
  LInputRec.Event.KeyEvent.bKeyDown     := True;
//
  // Delphi の String 型は最初の文字が入った要素番号は 1
  for LIndex := 1  To Length(AText) do begin
    LInputRec.Event.KeyEvent.UnicodeChar := AText[Lindex];
    WriteConsoleInput(LhStdInput, LInputRec, 1, LEventNum );
  end;
  Application.ProcessMessages;

  // [Enter] キーの送信
  PostMessage(ACmdWnd, WM_KEYDOWN, VK_RETURN, $001C0001);
  PostMessage(ACmdWnd, WM_KEYDOWN, VK_RETURN, $C01C0001);
end;

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit1.Text := 'dir';
  Button1.Default := True;
end;

//=============================================================================
//  起動中のコマンドプロンプトにコマンドを送信して実行
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LhCmdWindow : HWND;
  LProcessID  : HWND;
begin
  // コマンドプロンプトのウインドウハンドルを取得
  LhCmdWindow := FindWindow('ConsoleWindowClass', nil);

  // スレッドのプロセスIDを取得
  GetWindowThreadProcessId(LhCmdWindow, @LProcessID);

  // コンソールのスレッドにアタッチ
  if AttachConsole(LProcessID) then begin
    // コマンド実行
    SendCommandPrompt(LhCmdWindow, Edit1.Text);

    // FreeConsole でデアタッチしておく
    FreeConsole;
  end;
end;

end.




02_コマンドプロンプトへコマンドを送信 - クリップボードを使用


コマンドプロンプトには、SendMessage 等で文字列の送信はできません。コマンドプロンプトの編集機能を利用すれば、クリップボードを使用して、コマンドプロンプトにコマンドを貼り付けて実行することができます。

この [貼り付け] のコマンドの値が分かれば、WM_COMMAND メッセージで貼り付けの操作を自動で実行できます。しかし、コマンドプロンプトの編集操作は、グローバルフックでも捕捉できません。Spy++ でも捕捉できません。
このコマンドの値を力技で調べた方がいます。その値が mayu-ml にあります。その値を使用したサンプルです。

本サンプルは、TEdit に入力したコマンドの文字列をクリップボードにコピーし、それをコマンドプロンプトに自動で貼り付けて実行します。
管理者権限が必要なコマンドも実行できるように、アプリケーションマニュフェストを追加して、EXE を管理者権限で起動するようにしています。cmd.exe の内部コマンドの多くは、管理者として実行する必要はありません。



図3
実行例
  • DIR コマンドを実行
  • Button2 は改行文字だけの送信用
  • Button3 は EXIT コマンドの送信用

リスト2
コマンドプロンプトへコマンドを送信 - クリップボードを使用
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ShellAPI;
type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Button2: TButton;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Edit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  protected
    { Private 宣言 }
    FClipbrdText : String;
  public
    { Public 宣言 }
  end;
var
  Form1: TForm1;

implementation

uses
  Clipbrd;

{$R *.DFM}

const
  FWndClassName = 'ConsoleWindowClass';

//=============================================================================
//  フォーム生成時の処理
//  コマンドプロンプトを起動
//  ShellExecuteの使用にはusesにShellAPIが必要
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit1.Font.Charset := ANSI_CHARSET;
  Edit1.Font.Name    := 'Courier New';
  Edit1.Font.Size    := 9;

  Edit1.Text    := '';
  Edit1.Anchors := [akLeft, akTop, akRight];
  ActiveControl := Edit1;
end;

//=============================================================================
//  コマンドプロンプトに文字列を送る
//  クリップボードを利用
//  usesにClipbrdが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
const
  ID_Paste = $FFF1; //65521
var
  LCmdWnd  : HWND;
  LCmdText : String;
begin
  FClipbrdText := Clipboard.AsText;

  LCmdWnd := FindWindow(PChar(FWndClassName), nil);
  if LCmdWnd = 0 then begin
    MessageBox(Handle,
               'コマンドプロンプトが起動していません',
               '情報',
               MB_ICONINFORMATION);
    exit;
  end;

  //コマンドの文字列の末尾に改行文字を追加して、クリップボードにコピー
  LCmdText := Trim(Edit1.Text) + sLineBreak;
  Clipboard.AsText := LCmdText;

  //コマンドプロンプトのウィンドウに[貼り付け]のコマンドを送る
  SendMessage(LCmdWnd, WM_COMMAND, ID_Paste, 0);
  Edit1.SetFocus;

  if FClipbrdText <> '' then Clipboard.AsText := FClipbrdText;
end;

//=============================================================================
//  コマンドプロンプトに改行だけを送信
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
const
  ID_COPY   = $FFF0; //コピー
  ID_PASTE  = $FFF1; //貼り付け
  ID_RANGE  = $FFF2; //範囲指定
  ID_SCROLL = $FFF3; //スクロール
  ID_SEARCH = $FFF4; //検索
  ID_SELALL = $FFF5; //全て選択
var
  LCmdWnd : HWND;
begin
  FClipbrdText := Clipboard.AsText;

  LCmdWnd := FindWindow(PChar(FWndClassName), nil);
  if LCmdWnd = 0 then begin
    MessageBox(Handle,
               'コマンドプロンプトが起動していません',
               '情報',
               MB_ICONINFORMATION);
    exit;
  end;

  //改行文字をクリップボードにコピー
  Clipboard.AsText := sLineBreak;

  //コマンドプロンプトのウィンドウに[貼り付け]のコマンドを送る
  SendMessage(LCmdWnd, WM_COMMAND, ID_PASTE, 0);
  Edit1.SetFocus;

  if FClipbrdText <> '' then Clipboard.AsText := FClipbrdText;
end;

//=============================================================================
//  コマンドプロンプトにEXITコマンドを送信
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
const
  ID_Paste = $FFF1; //65521
var
  LCmdWnd : HWND;
begin
  FClipbrdText := Clipboard.AsText;

  LCmdWnd := FindWindow(PChar(FWndClassName), nil);
  if LCmdWnd = 0 then exit;

  //改行文字をクリップボードにコピー
  Clipboard.AsText := 'EXIT' + sLineBreak;

  //コマンドプロンプトのウィンドウに[貼り付け]のコマンドを送る
  SendMessage(LCmdWnd, WM_COMMAND, ID_Paste, 0);
  Edit1.SetFocus;

  if FClipbrdText <> '' then Clipboard.AsText := FClipbrdText;
end;

//=============================================================================
//  Edit1のOnKeyDownイベント処理
//  [Enter]キーでButton1クリックのコードを実行する
//=============================================================================
procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = VK_RETURN then begin
    Button1Click(nil);
  end;
end;

end.




03_コマンドの連続実行


コマンドを & で区切って複数指定すれば、それらのコマンドの連続実行となります。また、文字列関係の配列にコマンドを格納しておいて、それを連続して実行すれば連続実行となります。
コマンドプロンプトですから、バッチファイルを指定しての実行もできます。



図4
コマンドの連続実行例
図5
コマンドを & で結合して実行した例
  • 実行コマンドは上の図と同じ

Windows 10 の PowerShell での実行結果です。PowerShell のコマンドのデリミタ (区切り文字) は ; (セミコロン) です。



図6
Windows 10 の PowerShell での実行結果例

リスト3
コマンドプロンプトへコマンドを連続送信
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

function AttachConsole(dwProcess: DWORD): BOOL; stdcall; external 'kernel32.dll';

//-----------------------------------------------------------------------------
//  標準入力にキー入力する
//-----------------------------------------------------------------------------
procedure SendCommandPrompt(ACmdWnd: HWND; AText: string);
var
  LhStdInput : THandle;
  LInputRec  : TInputRecord;
  LEventNum  : DWORD;
  LIndex     : Integer;
begin
  // 標準入力のハンドルを取得
  LhStdInput := GetStdHandle(STD_INPUT_HANDLE);

  FillChar(LInputRec, SizeOf(LInputRec), #0);
  LInputRec.EventType := KEY_EVENT;
  LInputRec.Event.KeyEvent.wRepeatCount := 1;
  LInputRec.Event.KeyEvent.bKeyDown     := True;
//
  // Delphi の String 型は最初の文字が入った要素番号は 1
  for LIndex := 1  To Length(AText) do begin
    LInputRec.Event.KeyEvent.UnicodeChar := AText[Lindex];
    WriteConsoleInput(LhStdInput, LInputRec, 1, LEventNum );
  end;
  Application.ProcessMessages;

  // [Enter] キーの送信
  PostMessage(ACmdWnd, WM_KEYDOWN, VK_RETURN, $001C0001);
  PostMessage(ACmdWnd, WM_KEYDOWN, VK_RETURN, $C01C0001);
end;

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Memo1.Lines.Clear;
  Memo1.Lines.Add('cls');
  Memo1.Lines.Add('cd c:\Windows\System32');
  Memo1.Lines.Add('c:');
  Memo1.Lines.Add('dir');

  Button1.Default := True;
end;

//=============================================================================
//  起動中のコマンドプロンプトにコマンドを送信して実行
//  TMemoのテキストを順番に実行
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LhCmdWindow : HWND;
  LProcessID  : Cardinal;
  LCommand    : string;
  LIndex      : Integer;
begin
  // コマンドプロンプトのウインドウハンドルを取得
  LhCmdWindow := FindWindow('ConsoleWindowClass', nil);
  // スレッドのプロセスIDを取得
  GetWindowThreadProcessId(LhCmdWindow, @LProcessID);

  // コンソールのスレッドにアタッチ
  if AttachConsole(LProcessID) then begin
    // コマンド実行
    for LIndex := 0 to Memo1.Lines.Count - 1 do begin
      LCommand := Trim(Memo1.Lines[LIndex]);
      if LCommand <> '' then begin
        SendCommandPrompt(LhCmdWindow, LCommand);
        Application.ProcessMessages;
        // タイムラグがないとコマンドが完全に送信されないことがある
        Sleep(10);
      end;
    end;

    // FreeConsole でデアタッチしておく
    FreeConsole;
  end;
end;

end.




04_コマンドプロンプトへ Ctrl + C を送信


コマンドプロンプトの画面に [Ctrl] + [C] を送信するサンプルです。
Windows では通常 [Ctrl] + [C] は「コピー」の操作ですが、コマンドプロンプトでは、現在処理中のコマンドの実行を強制終了させるコマンドとなっています。

他のアプリにコマンドや文字列を送る場合、そのアプリのウィンドウではなく、対象のコントロールなりオブジェクトのウィンドウハンドルに対して送信します。
例えばメモ帳では、メモ帳のウィンドウハンドルではなく、メモ帳の編集ウィンドウに対して文字列等を送信します。しかし、コマンドプロンプトの場合、コマンドを入力する画面のウィンドウハンドルは取得できません。そこで、本サンプルでは、コマンドプロンプトをフォアグラウンドなウインドウにして、そこにキー操作をエミュレートします。
フォアグランドなウィンドウに対してキー操作を実行すると、そのウィンドウの、入力フォーカスのあるオブジェクトがキー入力を受け取ります。

Button1 をクリックすると、dir コマンドを実行して、そして、すぐに [Ctrl] + [C] を送信して処理を中止します。



[備考]
コマンドプロンプト自体を終了する、つまり閉じるコマンドは EXIT です。



図7
[Ctrl] + [C] で処理を強制終了

リスト4
コマンドプロンプトに [Ctrl] + [C] を送信してコマンドの実行を強制終了させる
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  protected
    { Private 宣言 }
    FClipbrdText : String;
    procedure SendCtrlKeys(hWindow: HWND; ACmdStr: string);
  public
    { Public 宣言 }
  end;
var
  Form1: TForm1;

implementation

uses
  Clipbrd;

{$R *.DFM}

const
  FWndClassName = 'ConsoleWindowClass';

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit1.Font.Charset := ANSI_CHARSET;
  Edit1.Font.Name    := 'Courier New';
  Edit1.Font.Size    := 9;

  Edit1.Text    := 'dir';
  Edit1.Anchors := [akLeft, akTop, akRight];
  ActiveControl := Edit1;
end;

//=============================================================================
//  コマンドプロンプトに文字列を送る
//  クリップボードを利用
//  usesにClipbrdが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
const
  ID_Paste = $FFF1; //65521
var
  LCmdWnd  : HWND;
  LCmdText : String;
begin
  FClipbrdText := Clipboard.AsText;

  LCmdWnd := FindWindow(PChar(FWndClassName), nil);
  if LCmdWnd = 0 then exit;


  //コマンドの文字列の末尾に改行文字を追加してクリップボードにコピー
  LCmdText := Trim(Edit1.Text) + sLineBreak;
  Clipboard.AsText := LCmdText;
  //コマンドプロンプトのウィンドウに[貼り付け]のコマンドを送る
  SendMessage(LCmdWnd, WM_COMMAND, ID_Paste, 0);

  //コマンドプロンプトに[Ctrl]+[C]を送信して処理を強制終了
  LCmdText := Chr(VK_CONTROL) + 'C';
  SendCtrlKeys(LCmdWnd, LCmdText);
  //これがないと[Ctrl]+[C]の操作が終了しないでフォーカス移動してしまう
  Application.ProcessMessages;

  SetForegroundWindow(Handle);
  Edit1.SetFocus;
  if FClipbrdText <> '' then Clipboard.AsText := FClipbrdText;
end;

//-----------------------------------------------------------------------------
//  コマンドプロンプトに制御文字としての文字を送信
//  他のアプリの編集ウィンドウへの送信にも利用可能なコード
//-----------------------------------------------------------------------------
procedure TForm1.SendCtrlKeys(hWindow: HWND; ACmdStr: string);
var
  LByte  : Byte;
  LIndex : Integer;
begin
  //hWindowは送信先ウィドウのウィンドウハンドル
  SetForegroundWindow(hWindow);
  for LIndex := 1 to Length(ACmdStr) do begin
    LByte := Ord(ACmdStr[LIndex]);
    keybd_event(LByte, MapVirtualKey(LByte, 0), 0, 0);
  end;
  for LIndex := Length(ACmdStr) downto 1 do begin
    LByte := Ord(ACmdStr[LIndex]);
    keybd_event(Ord(ACmdStr[LIndex]), MapVirtualKey(LByte, 0), KEYEVENTF_KEYUP, 0);
  end;
end;

end.




05_ショートカットキーとコマンドの送信テスト用プログラム


[Ctrl] + [C] は、初期の Windows 10 までは処理の中断のショートカットキーでした。現在では、コマンドプロンプトのテキストが選択状態の時はコピー、選択されていない状態では処理の中断となっています。この機能はプロパティのダイアログを表示して変更することができます。

これらの動作を確認するために、ショートカットの動作をテストするプログラムを作成してみました。
ショートカットキーの送信操作関係だけを別ユニットにています。添付の plSendShortCut.pas と plvkEnum.pas ファイルを、ライブラリパスの通ったフォルダ内に置き、uses に plSendShortCut を追加して使用してください。



図8
コマンドプロンプトのプロパティの [オプション] タブ

プログラムを起動して [送信 - TEdit] ボタンをクリックするとコマンドプロンプトが起動します。このコマンドプロンプトは、タイトルを変更しています。テストプログラムの終了時には、このタイトルのコマンドプロンプトも自動時に閉じるようにしています。


図9
ショートカットキーのテスト用プログラム
  • タイトルを変更している

次のショートカットキーを順番にコマンドプロンプトに送信すると、コマンドプロンプトで表示しているテキストを全てクリップボードにコピーします。
[Alt] + [Space] はメニュー表示のショートカットキーです。[E] と [Y] はメニューの項目を選択するアクセラレータキーです。


Ctrl+A
Alt+Space+E+Y


最近の Windows 10 では以下のショートカットキーでテキストのコピーになります。これは、Ctrl + A + C でも同じです。


Ctrl+A
Ctrl+C


以下のショートカットキーは、コマンドプロンプトのプロパティのダイアログを表示します。


Alt+Space+P


リスト5
ショートカットキーの送信と、コマンドプロンプトへのコマンド送信テスト用プログラム
unit SendShortCurKeyUnit;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Edit1: TEdit;
    Button2: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure Edit1KeyPress(Sender: TObject; var Key: Char);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

  function AttachConsole(dwProcess: DWORD): BOOL; stdcall; external 'kernel32.dll';

  // 単独で使用する場合を考慮して、クラスのメンバではなくここで宣言
  procedure SendKeysCmdMain(ACmdWnd: HWND; ATextKeysCmd: string);
  procedure SendCommandPrompt(ACmdWnd: HWND; ACmdLine: string);
  procedure SendYYesNo(ACmdWnd: HWND; AProcessID: Cardinal);

var
  Form1: TForm1;

implementation

uses
  Winapi.ShellAPI,
  System.StrUtils,
  plSendShortCut;

{$R *.dfm}

const
  FCmdTitle = 'コマンド送信テスト';

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Caption := 'コマンドプロンプトへの送信テスト';
  Memo1.Lines.Clear;
  Memo1.Lines.Add('Ctrl+A');
  Memo1.Lines.Add('Alt+Space+E+Y');

  Edit1.Text := 'cmd';
  Edit1.AutoSelect := False;
end;

//=============================================================================
//  フォームを閉じる時の処理
//  このプログラムから起動したコマンドプロンプトは閉じる
//  起動したコマンドプロンプトは、タイトルで判定する
//=============================================================================
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
var
  LTitle    : string;
  LhCmdWnd : HWND;
begin
  LhCmdWnd := FindWindow('ConsoleWindowClass', PChar(FCmdTitle));
  if LhCmdWnd = 0 then begin
    LTitle:= '管理者:  ' + FCmdTitle;
    LhCmdWnd := FindWindow('ConsoleWindowClass', PChar(LTitle));
  end;
  if LhCmdWnd <> 0 then begin
    PostMessage(LhCmdWnd, WM_CLOSE, 0, 0);
  end;
end;

//=============================================================================
//  TEdit のテキストをショートカットまたはコマンドとして実行
//  TEdit のテキストが cmd の時はコマンドプロンプトを起動
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LCmdWnd : HWND;
  LText   : string;
begin
  if Edit1.Text = 'cmd' then begin
    LCmdWnd := FindWindow('ConsoleWindowClass', nil);
    if LCmdWnd <> 0 then begin
      LText := 'コマンドプロンプトは起動しています。';
      MessageBox(Handle, PChar(lText), '起動中', MB_ICONINFORMATION);
    end else begin
      // 起動したコマンドプロンプトを見つけやすいように独自のタイトルを設定
      // このタイトルを使用して自分が起動したコマンドプロンプトを見つける
      LText := '/k title コマンド送信テスト';
      ShellExecute(Handle, '', 'cmd.exe', PChar(LText), nil, SW_SHOWNORMAL);
      Sleep(100);
      SetForegroundWindow(Handle);
    end;
    Edit1.Text := 'dir';
    Edit1.SetFocus;
    Exit;
  end;


  // コマンドプロンプトのウインドウハンドルを取得
  LCmdWnd := FindWindow('ConsoleWindowClass', nil);
  if LCmdWnd = 0 then begin
    LText := 'コマンドプロンプトが起動していません。';
    MessageBox(Handle, PChar(lText), '失敗', MB_ICONINFORMATION);
    Exit;
  end;


  LText := Edit1.Text;
  if Trim(LText) <> '' then begin
    SendkeysCmdMain(LCmdWnd, LText);
  end;
  SetForegroundWindow(Handle);
  Edit1.SetFocus;
end;

//=============================================================================
//  Tmemo のテキストを行単位でショートカットまたはコマンドとして実行
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LCmdWnd : HWND;
  LText   : string;
  LIndex  : Integer;
begin
  // コマンドプロンプトのウインドウハンドルを取得
  LCmdWnd := FindWindow('ConsoleWindowClass', nil);
  if LCmdWnd = 0 then begin
    LText := 'コマンドプロンプトが起動していません。';
    MessageBox(Handle, PChar(lText), '失敗', MB_ICONINFORMATION);
    Exit;
  end;


  for LIndex :=0 to Memo1.Lines.Count - 1 do begin
    LText := Memo1.Lines[LIndex];
    if Trim(LText) <> '' then begin
      SendkeysCmdMain(LCmdWnd, LText);
    end;
  end;
  SetForegroundWindow(Handle);
  Memo1.SetFocus;
end;

//=============================================================================
//  Edit1 の OnKeyPress イベント処理
//  [Enter] キーでコマンド実行
//=============================================================================
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
  if Key = Char(VK_RETURN) then begin
    Button1Click(nil);
  end;
end;

//-----------------------------------------------------------------------------
//  メインルーチン
//
//  ACmdWnd      : コマンドプロンプトのウィンドウハンドル または
//                 ショートカットキーを受信するウィンドウのハンドル
//  ATextKeysCmd : ショートカットキーまたはコマンドプロンプト用のコマンド
//
//  ショートカットの文字列は仮想キーコード名と英数字の組み合わせ
//  各キー名の間に + を挿入する。+ の両端に空白があってもよい
//  アクセレーターキーも使用可能
//  複数のショートカットキーは指定できない
//  例 : Alt+F+A (メモ帳の場合は [名前を付けて保存] のダイアログを表示)
//
//  コマンドプロント用のコマンドは & を使用して複数指定可能
//-----------------------------------------------------------------------------
procedure SendKeysCmdMain(ACmdWnd: HWND; ATextKeysCmd: string);
const
  ATTACH_PARENT_PROCESS = DWORD(-1);
var
  LProcessID : HWND;
  LSendText  : string;
begin

  GetWindowThreadProcessId(ACmdWnd, @LProcessID);
  LSendText := GetShorCutChars(ATextKeysCmd);

  if LSendText = '' then begin
    // コンソールのスレッドにアタッチ
    if AttachConsole(LProcessID) then begin
      // コマンド実行
      SendCommandPrompt(ACmdWnd, ATextKeysCmd);
      // FreeConsole でデアタッチ
      FreeConsole;
    end;
  end else begin
    // キー操作を送信
    SendShortCutKey(ACmdWnd, LSendText);

    if LSendText = #$11#$43 then begin
      // (y/n) のテキストが完全に表示されるまでは少しタイムラグがある
      // タイムラグは実行するコマンド (EXE, BAT 等)の処理内容による
      // どうせブレークなので多少長くても問題はないだろう
      Sleep(100);
      // (y/n) に y で自動応答する
      SendYYesNo(ACmdWnd, LProcessID);
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  標準入力 (コマンドプロンプト) にコマンドのテキストを送信して実行する
//  この手続きはコンソールのスレッドにアタッチしていないと機能しない
//
//  ACmdWnd    : 対象のコマンドプロンプトのウィンドウハンドル
//  ACmdLine   : そのコマンドプロンプトで実行するコマンドラインのテキスト
//               複数のコマンドを実行する場合は、コマンドを & で結合する
//-----------------------------------------------------------------------------
procedure SendCommandPrompt(ACmdWnd: HWND; ACmdLine: string);
var
  LhStdInput : THandle;
  LInputRec  : TInputRecord;
  LEventNum  : DWORD;
  LIndex     : Integer;
begin
  // 標準入力のハンドルを取得
  LhStdInput := GetStdHandle(STD_INPUT_HANDLE);

  FillChar(LInputRec, SizeOf(LInputRec), #0);
  LInputRec.EventType := KEY_EVENT;
  LInputRec.Event.KeyEvent.wRepeatCount := 1;
  LInputRec.Event.KeyEvent.bKeyDown     := True;

  // Delphi の String 型は最初の文字が入った要素番号は 1
  for LIndex := 1  To Length(ACmdLine) do begin
    LInputRec.Event.KeyEvent.UnicodeChar := ACmdLine[Lindex];
    WriteConsoleInput(LhStdInput, LInputRec, 1, LEventNum );
  end;
  Application.ProcessMessages;
  Sleep(10);

  // [Enter] キーの送信
  // KEYUP は LParam の値を調整して WM_KEYDOWN メッセージで処理
  PostMessage(ACmdWnd, WM_KEYDOWN, VK_RETURN, $001C0001);
  PostMessage(ACmdWnd, WM_KEYDOWN, VK_RETURN, $C01C0001);
end;

//-----------------------------------------------------------------------------
//  ブレーク処理の時の y/n の選択で自動的に y を送信する処理
//  コンソールに表示されているテキストの最後の行の文字列で判定する
//  uses に StrUtils が必要
//
//  ACmdWnd    : 対象のコマンドプロンプトのウィンドウハンドル
//  AProceddID : その ProcessID の値
//-----------------------------------------------------------------------------
procedure SendYYesNo(ACmdWnd: HWND; AProcessID: Cardinal);
var
  LhStdOutput : THandle;
  LBufferInfo : TConsoleScreenBufferInfo;
  LpCaracter  : PChar;
  LCurCoord   : TCoord;
  LTextLength : Cardinal;
  LReadCnt    : Cardinal;
  LLineText   : string;
begin
  LLineText := '';

  // コンソールのスレッドにアタッチして標準出力のハンドルを取得
  if AttachConsole(AProcessID) then begin
    LhStdOutput := GetStdHandle(STD_OUTPUT_HANDLE);

    // バッファの情報を取得
    GetConsoleScreenBufferInfo (LhStdOutput, LBufferInfo);

    LTextLength := LBufferInfo.dwSize.X;
    // メモリの確保(PChar型で受け取る時はメモリの確保が必要)
    GetMem(LpCaracter, LTextLength);
    try
      // バッファの値の読み取り位置
      // X 座標は左端
      // Y 座標はカーソルがある行 (最下行 - 先頭行は 1)
      LCurCoord.X := 0;
      LCurCoord.Y := LBufferInfo.dwCursorPosition.Y;
      // 標準出力バッファの内容を読み出す
      if ReadConsoleOutputCharacter(LhStdOutput,
                                    LpCaracter,
                                    LTextLength,
                                    LCurCoord,
                                    LReadCnt) then begin
        // 読み出した分だけにしないと後部にゴミが入ることがある
        LLineText := Copy(LpCaracter, 1, LReadCnt);
      end;
    finally
      FreeMem(LpCaracter);
    end;
  end;

  // 最後の行文字列に (y/n) の文字列があるかチェック (大文字小文字の区別なし)
  // SendCommandPrompt はコンソールのスレッドにアタッチしていないと機能しない
  if ContainsText(LLineText, '(y/n)') then begin
    SendCommandPrompt(ACmdWnd, 'Y');
  end;


  // FreeConsole でデアタッチ
  FreeConsole;
end;

end.




06_「終了しますか (Y/N)?」 に対して自動応答


コマンドプロンプトを起動して添付のバッチファイルを実行します。実行中に [Ctrl] + [C] を押下すると、次の表示が現れます。この時、N を入力してもバッチファイル内の処理に戻ることはありません。何も入力しないで、再度 [Ctrl] + [C] を押下すれば終了となります。


バッチ ジョブを終了しますか (Y/N)?

前項のショートカットキーのテスト用プログラムには、この (Y/N) の文字 Y をコマンドプロンプトに自動的に送信する機能を実装しています。
コマンドプロンプトに表示しているテキストを全て取得して、最後の行に (Y/N) の文字列があれば、文字 Y をコマンドプロンプトに送信します。下図はそのテストの実行画面です。[Ctrl] + [C] を送信すると、バッチファイルの処理が強制終了して、文字 Y が送信されます。
コマンドプロンプトのテキストが選択されている時は、[Ctrl] + [C] はテキストのコピーになります。もう一度 [Ctrl} + [C] を押下するとブレークになります。強制終了 (ブレーク) が目的であれば、[ESC] を送信してから [Ctrl] + [C] を送信する方法も考えられます。



図10
(y/n) に自動応答
  • [ESC] を送信してから [Ctrl} + [C] を送信




07_ショートカットキー送信操作用ユニット


これ以降に掲載していた記事とサンプルは、2021年11月22日、新規に作成した以下のページに移動しました。