Delphi Programming / Object Pascal
[掲載 2005年02月15日] [更新 2016年10月24日] Delphi サンプルプログラム集
490_コマンドプロンプトを ShellExecute 関数で起動
動作確認等
Windows 7 U64(SP1) + Delphi XE(UP1) Pro, XE5(UP2) Pro
490_CmdPrompt1.zip [2,148 KB] 2016年10月24日版 (EXE 同梱)
2016年05月03日 (これより前の履歴は省略)
常駐アプリにコマンドプロンプトと同じクラス名が存在する場合の対応策として、ウィンドウの列挙関数のコールバック関数内で処理するようにした
2016年10月24日
終了時にコマンドプロンプトを閉じるコードは別サンプルとした
コマンドプロンプトにコマンドを送信するサンプルを別のページを作成して移動
コマンドプロンプトを ShellExecute 関数で起動
コマンドプロンプトは、Windows 9X 時代に DOS 窓 (DOS プロンプト) と呼ばれていたものに相当しますが、基本的に別物です。実装されている機能も違います。例えばコマンドプロンプトではエスケープシーケンスは利用できません。Windows 2000 以降には、DOS 窓 (DOS プロンプト、MS-DOS プロンプト) と呼ばれるプログラムは実装されていません。
本ページのサンプルは、ShellExecute 関数でコマンドプロンプトを起動します。ShellExecute 関数の単なる使用例とも言えます。ShellExecute 関数を使用した他のサンプルプログラムとの違いは、起動するアプリケーションがコマンドプロンプトであることです。
ShellExecute 関数では、システム的にパスが通っているプログラムは、ディレクトリ名を省略して実行できます。コマンドプロンプトの起動も、以下のように cmd.exe だけで起動できます。
ShellExecute 関数でコマンドプロンプトを起動する方法としてはこれだけです。
リスト1
ShellExecute 関数を使用して、コマンドプロンプトを起動
単純にコマンドプロンプトを起動するだけであれば、これで十分
cmd.exe の代わりに別の exe や bat ファイル名にすれば、それが実行される
copy code
// =============================================================================
// コマンドプロンプトを起動する基本コード
// Windows 2000以上で動作。Windows 9Xでは動作しない
//
// LFile コマンドプロセッサの実行ファイル
// LParams コマンドプロセッサの起動オプション
//
// ShellExecuteの使用にはusesにShellAPIが必要
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LFile : string ;
LParams : string ;
begin
LFile := 'cmd.exe' ;
LParams := '' ;
ShellExecute(Handle, 'open' , PChar(LFile), PChar(LParams), nil , SW_SHOWNORMAL);
end ;
上のコードでコマンドプロンプトを起動すると、
カレントディレクトリは、ShellExecute 関数を実行した EXE があるディスレクトりになります。
起動時のカレントディレクトリは、以下のように第 5 引数で指定します。このコードでコマンドプロンプトを起動すると、カレントディレクトリは D:\ になります。
リスト2
カレントディレクトリを指定してコマンドプロンプトを起動
copy code
procedure TForm1.Button1Click(Sender: TObject);
var
LFile : string ;
LParams : string ;
begin
LFile := 'cmd.exe' ;
LParams := '' ;
ShellExecute(Handle, 'open' , PChar(LFile), PChar(LParams), 'D:\' , SW_SHOWNORMAL);
end ;
コマンドプロンプトそのものは、32 ビット版と 64 ビット版がありますが、64 ビット版の Windows の場合、起動したコマンドプロンプトからコマンドを実行したり、システムディレクトリ内のプログラムを実行する場合は注意が必要です。
Telnet 等、64 ビット版はあるが、32 ビット版はないコマンド、アプリケーションがあります。
コマンドプロンプトにについては、以下のページにも参考記事やサンプルコードがあります。
上のリンクの記事以外にも、当サイトには、アプリの起動やコマンドプロンプトに関する記事やサンプルコードがあります。
[備考 1]
コマンドプロセッサで動作するアプリケーションには、cmd.exe の内部コマンドの他、コンソールアプリと DOS アプリがあります。本ページのサンプルはコマンドプロンプトというプログラムを起動するものです。
コンソールアプリ用のコンソール画面を起動する方法は、以下の記事にサンプルがあります。
[備考 2]
64 ビット版の Windows では、16 ビットのアプリと DOS アプリは起動できません。
DOS のプログラムは NTVDM という仮想 DOS マシン (Virtual DOS Machine) という、x86 CPU の持つ仮想 8086 モード上で実行されるようになっています。
CPU の 64 ビットモードでは、この仮想 86 モードがサポートされていません。したがって、64 ビット版 の Windows には、この NTVDM の機能が実装されていません。
01_ コマンドラインオプションとコマンドを指定して起動
コマンドラインのオプションと、コマンドを指定してコマンドプロンプトを起動します。
コマンドプロンプトの起動時に CD コマンドを実行し、カレントディレクトリを C:\ にします。起動後は、コマンドプロンプトも自アプリのどちらも操作可能な状態になります。
本ページのサンプルでは使用していませんが、ShellExecute 関数の第 5 引数の作業フォルダを指定すると、そのフォルダがカレントディレクトリとなります。指定しなければ現在のディレクトリがカレントディレクトリとなります。
[備考]
アプリ側で、自分自身に渡されたコマンドプラインのパラメータを処理するには、以下の記事のコマンドライン用のメンバを使用します。
図1
実行開始画面
図2
ボタンをクリック
コマンドプロンプトが起動する
ウィンドウのタイトルを [コマンドプロンプト] に変更
リスト3
ShellExecute 関数を使用して、コマンドプロンプトを起動
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ShellAPI;
type
TForm1 = class (TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
{$R *.DFM}
// =============================================================================
// カレントディレクトリをC:\としてコマンドプロンプトを起動
// Windows 2000以上で動作。Windows 9Xでは動作しない
//
// LFile コマンドプロセッサの実行ファイル
// LParams コマンドプロセッサの起動オプション
// LParmsを空にするとこのアプリをカレントディレクトリとして起動
//
// ShellExecuteの使用にはusesにShellAPIが必要
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
const
FWndClassName = 'ConsoleWindowClass' ;
var
LFile : string ;
LParams : string ;
LCmdWnd : HWND;
LhInstance : HWND;
LText : string ;
begin
LFile := 'cmd.exe' ;
// カレントディレクトリとドライブを変更
LParams := '/k cd /d c:\' ;
LhInstance := ShellExecute(Handle,
'open' ,
PChar(LFile),
PChar(LParams),
nil ,
SW_SHOWNORMAL);
if Pos('/K' , UpperCase(LParams)) > 0 then begin
if LhInstance > 32 then begin
// 起動に成功したらハンドルが有効になるまで少しタイムラグを持たせる
// ハンドルに対する操作が必要なければ不要
Sleep(100 );
LCmdWnd := FindWindow(PChar(FWndClassName), nil );
// コマンドプロンプトのウィンドウのタイトルの文字列を変更してしまう
LText := 'コマンドプロンプト' ;
SetWindowText(LCmdWnd, PChar(LText));
end ;
end ;
end ;
end .
cmd.exe のフルパスは環境変数 COMSPEC を指定して、GetEnvironmentVariable 関数で以下のように取得することができます。
LCmdexePath := GetEnvironmentVariable('COMSPEC') ;
32 ビットの EXE からコマンドプロンプトを起動すると、コマンドプロンプトのタイトルに System32 があります。これはリダイレクトされているためで、実際に起動しているのは、SysWOW64 ディレクトリ内にある 32 ビット版の cmd.exe です。
標準ユーザで起動した 32 ビットの EXE から、runas を指定して起動した場合は、該当ビットバージョンの cmd.exe が直接起動します。したがって、コマンドプロンプトのタイトルがその通りのパス名となります。
コマンドプロンプトを起動する時の起動オプションに /K を指定すると、コマンド実行後にコマンドプロンプトは閉じません。/C 指定すると、コマンド実行後にコマンドプロンプトを閉じます。デフォルトは /k ですが、必ずどちらかを指定した方がいいでしょう。
コマンドプロンプトを非表示にしてコマンドを実行する場合は注意が必要です。
コマンドプロンプトを /k オプションで起動したり、PAUSE コマンドがあったり、入力を促すコマンドがあると、コマンドプロンプトが閉じることができないで、メモリに残ったままになります。
ネット上にあるコマンドのサンプルは、コマンドプロンプトを表示して実行することを前提としている場合があります。
コマンドプロンプトの起動時に、複数のコマンドを実行するには、次のように、コマンドを & (アンパサンド)
文字で区切ります。
LFile := 'cmd.exe' ;
LParams := '/k cd /d c:\&dir' ;
あるいは、次の内容のテキストファイルを作成します。
このファイルの拡張子を .BAT にして、例えば CmdList.bat にして保存します。拡張子が .BAT のファイルはバッチファイルと呼ばれています。バッチファイルは実行可能ファイルの一種です。
そして、起動時のパラメータを次のようにしてコマンドプロンプトを起動します。
LFile := 'cmd.exe' ;
LParams := '/k CmdList.bat' ;
バッチファイルの場合は、cmd.exe を指定しなくても、次のように、起動するファイルをバッチファイルにすれば自動的にコマンドプロンプトが起動して、バッチファイルを実行します。この方法では、バッチファイルの実行が終了すると、コマンドプロンプトは自動的に閉じます。
サンプルプログラム集のバッチファイルの実行は、この方法でバッチファイルを実行しています。
この方法は、拡張子の関連付けで ShellExecute 関数を実行することになります。
拡張子が .doc の時はワードが起動し、拡張子が .xls の時はエクセルが起動しますが、.BAT の時はコマンドプロンプトが起動します。
// バッチファイルを実行
LFile := 'CmdList.bat' ;
LParams := '' ;
04_ 管理者権限でコマンドプロンプトや他のアプリを起動
Windows Vista 以降の環境で、コマンドプロンプトを管理者権限で起動するには、作成した EXE を管理者権限で実行します。アプリケーションマニフェストを使用すると、EXE 起動時に管理者への昇格ダイアログを表示するようにできます。
ShellExecute 関数では、第 2 引数の値を runas にすると、ShellExecute 関数で起動するアプリの実行時に「ユーザアカウント制御」ダイアログを表示して、管理者に昇格して実行できます。
Delphi の IDE から実行した場合はダイアログは表示しません。IDE の起動時に、すでに管理者の昇格ダイアログが表示されているはずです。
Windows Vista 以降では、管理者権限で起動しているアプリケーションの操作はできません。管理者権限で起動しているアプリを操作する側のアプリ自身も管理者権限で起動する必要があります。
これは、下からの命令、つまり標準ユーザからの指示や命令は受け付けないという、UIPI (ユーザインターフェイス特権の分離) の機能によるものです。
標準ユーザの権限で実行した EXE から、管理者権限で実行したプログラムを閉じる方法は、以下のサンプルのコードを参考にしてください。
図3
昇格ダイアログの表示
コマンドプロンプトの場合
コードサイニング証明書の署名があるアプリケーションの場合
図4
昇格ダイアログの表示
コードサイニング証明書の署名がないアプリケーションの場合
リスト4
ShellExecute 関数の第 2 の引数を runas にすると管理者権限で実行できる
copy code
LhInstance := ShellExecute(Handle,
'runas' ,
PChar(LFile),
PChar(LParams),
nil ,
SW_SHOW);
自分自身が管理者権限で実行しているかどうかは、IsUserAnAdmin 関数で確認できます。
リスト5
アプリケーションが管理者権限での実行かを判定
copy code
// IsUserAnAdminの使用には、usesにShlObjが必要
if IsUserAnAdmin then begin
// 管理者権限で実行中
end else begin
// 管理者権限以外で実行中
end ;
プログラムの終了時にコマンドプロンプトも同時に閉じるようにしたサンプルです。
コマンドプロンプトが起動しているかどうかのチェックは、EnumWindows 関数のコールバック関数内で行っています。常駐アプリの中には、コマンドプロンプトと同じクラス名のアプリがあります。そのため、環境によっては、FindWindows 関数だけでは、実際に起動または起動中のコマンドプロンプトを検出できない場合があります。そのため EnumWindows 関数を使用しています。
[備考 1]
当然ですが、表示中で可視状態のコマンドプロンプトであれば、Exit コマンドを送信すれば閉じることができます。
[備考 2]
ShellExecuteEx 関数あるいは CreateProcess 関数でアプリを起動した場合は、そのアプリのプロセス ID の値を取得できます。そのプロセス ID の値を使用して、
起動したアプリを終了、または強制終了させることができます。
[備考 3]
起動したコマンドプロンプトの画面が、アプリケーションが終了すると閉じてしまっては実用性がないという指摘がありました。本ページのサンプルのほとんどがそういう仕様になっていましたが、現在は、そのような意見を反映して修正しています。
この仕様は個人的には目的によるものと考えています。
リスト6
起動中のコマンドプロンプトを閉じるコードを追加したサンプル
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ShellAPI;
type
TForm1 = class (TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
{$R *.DFM}
const
FWndClassName = 'ConsoleWindowClass' ;
// -----------------------------------------------------------------------------
// ウィンドウハンドルからウィンドウクラス名を取得
// -----------------------------------------------------------------------------
function GetClassNameStr(hWindow: HWND): string ;
var
Buffer : array [0 ..MAX_PATH-1 ] of Char;
Len : Integer;
begin
FillChar(Buffer, SizeOf(Buffer), #0 );
Len := GetClassName(hWindow, Buffer, Length(Buffer));
if Len > 0 then Result := Buffer else Result := '' ;
end ;
// -----------------------------------------------------------------------------
// EnumWindowsのコールバック関数
// -----------------------------------------------------------------------------
function EnumWindowsProc(hWindow: hWnd; lPar: PString):
Boolean; Stdcall ;
var
LWndClass : string ;
begin
Result := True;
if IsWindowVisible(hWindow) then begin
// ウィンドウのクラス名が引数と同じだったら
LWndClass := GetClassNameStr(hWindow);
if LWndClass = lPar^ then begin
SendMessage(hWindow, WM_CLOSE, 0 , 0 );
end ;
end ;
end ;
// -----------------------------------------------------------------------------
// EnumWindowsのコールバック関数
// ウィンドウハンドル取得用
// -----------------------------------------------------------------------------
function EnumWindowsHandleProc(hWindow: hWnd; lPar: PNativeInt):
Boolean; Stdcall ;
var
LWndClass : string ;
begin
Result := True;
if IsWindowVisible(hWindow) then begin
// ウィンドウのクラス名が同じだったら
LWndClass := GetClassNameStr(hWindow);
if LWndClass = FWndClassName then begin
lPar^ := hWindow;
Result := False;
end ;
end ;
end ;
// =============================================================================
// カレントディレクトリをC:\としてコマンドプロンプトを起動
// Windows 2000以上で動作。Windows 9Xでは動作しない
//
// LFile コマンドプロセッサの実行ファイル
// LParams コマンドプロセッサの起動オプション
// LParmsを空にするとこのアプリをカレントディレクトリとして起動
//
// ShellExecuteの使用にはusesにShellAPIが必要
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LFile : string ;
LParams : string ;
LCmdWnd : HWND;
LhInstance : HWND;
LText : string ;
begin
LFile := 'cmd.exe' ;
// カレントディレクトリとドライブを変更
LParams := '/k cd /d c:\' ;
LhInstance := ShellExecute(Handle,
'open' ,
PChar(LFile),
PChar(LParams),
nil ,
SW_SHOWNORMAL);
if Pos('/K' , UpperCase(LParams)) > 0 then begin
if LhInstance > 32 then begin
// 起動に成功したらハンドルが有効になるまで少しタイムラグを持たせる
// ハンドルに対する操作が必要なければ不要
Sleep(100 );
LCmdWnd := 0 ;
EnumWindows(@EnumWindowsHandleProc, LPARAM(@LCmdWnd));
// コマンドプロンプトのウィンドウのタイトルの文字列を変更してしまう
LText := 'コマンドプロンプト' ;
SetWindowText(LCmdWnd, PChar(LText));
end ;
end ;
end ;
// =============================================================================
// フォーム破棄時にコマンドプロンプトも閉じる
// 複数起動している場合を考慮したコード
//
// 常駐アプリ等にはコマンドプロンプトと同じクラス名を持つものがある
// それらはWM_CLOSEでは終了できないのがある。また終了しては困る場合がある
// そこで起動中のプロセスを調べる
// 本プログラムでコマンドプロンプトを1つか起動しない場合でも、上記のような
// プログラムが起動している場合は、起動中のプロセスを調べるしかない
// =============================================================================
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
LClassStr : string ;
begin
LClassStr := 'ConsoleWindowClass' ;
EnumWindows(@EnumWindowsProc, LPARAM(@LClassStr));
end ;
end .
06_ コマンドプロンプトを非表示で起動してコマンドを実行
コマンドプロンプトを非表示で起動するには、ShellExecute 関数の最後の引数を SW_HIDE にします。
コマンド実行後にコマンドプロンプトを自動的に閉じるには、cmd.exe の起動オプションを /c にして起動します。
コマンドプロンプトを非表示で起動してコマンドを実行する場合は注意が必要です。
PAUSE コマンド、あるいは入力を促すコマンドがあると、非表示のコマンドプロンプトが実行されたままの状態となってしまいます。特にバッチファイルは、コマンドプロンプトを表示して実行することを前提にしている場合があります。
連続してコマンドを実行する場合は、/k オプションでコマンドプロンプトを起動して、最後に EXIT コマンドを送信すれば、コマンドプロンプトを閉じることができます。
EXIT コマンドの実行をしない場合、非表示のコマンドプロンプトが起動したままの状態になってしまいます。
リスト7
コマンドプロンプトを非表示で起動してコマンドを実行する例
コマンド実行後にコマンドプロンプトを閉じる
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ShellAPI;
type
TForm1 = class (TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
{$R *.DFM}
// =============================================================================
// コマンドプロンプトを非表示で起動
// コマンドの実行が終了したらコマンドプロンプトを閉じる
// Windows 2000以上で動作。Windows 9Xでは動作しない
//
// LFile コマンドプロセッサの実行ファイル
// LParams コマンドプロセッサの起動オプション
//
// ShellExecuteの使用にはusesにShellAPIが必要
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LFile : String ;
LParams : String ;
LSaveFile : String ;
begin
LSaveFile := ExtractFilePath(Application.ExeName) + 'dir.txt' ;
LFile := 'cmd.exe' ;
// カレントディレクトリとドライブを変更
// dirコマンドの実行結果をファイルにリダイレクト
LParams := '/c cd /d c:\&dir > ' + AnsiQuotedStr(LSaveFile, '"' );
ShellExecute(Handle,
'open' ,
PChar(LFile),
PChar(LParams),
nil ,
SW_HIDE);
end ;
end .
コマンドプロンプトで実行可能な cmd.exe の内部コマンド類の説明は、コマンドプロンプトを起動した後、次のようなコマンドを実行すると表示またはテキストファイルに保存できます。
help cmd // 起動オプションのヘルプ
help // コマンドのヘルプ
help cd // cdコマンドのヘルプ
help cmd | clip // 起動オプションの説明をクリップボードにコピー
help cmd > D:\000\cmdhelp.txt // 起動オプションの説明をテキストファイルに出力
図5
起動オプションのヘルプ
図6
コマンドのヘルプ
図7
CD コマンドのヘルプ
コマンドプロンプトでユニコードな文字列を扱うには、/U オプションを指定してコマンドプロンプトを起動します。
フォントのサイズや色、ウィンドウのサイズの設定は、以下のリンクの、コンソールアプリの場合と同じように、メニューの操作で変更できます。color コマンドでフォントの色と背景色が変更できますが、起動時であれば /T オプションで指定できます。
コマンドプロンプトのフォントサイズや色は、標準出力のフォントの属性を設定すれば変更できます。[504_コマンドプロンプトのフォント設定用コマンドツール] はそのサンプルです。コマンドプロンプトのウィンドウの表示位置やサイズ、画面バッファのサイズも変更できます。