Execute DOS Command & Capture The Output

Sangat banyak result dari Google search untuk judul di atas. Namun bila Anda menggunakan Windows 7 (mungkin juga versi sebelumnya) dengan kombinasi Delphi 2009 (default: Unicode/WideString), mungkin Anda akan mengalami masalah. Dari Torry.Net sampai About Delphi, sampai blog-blog tentang Delphi, semuanya membawa masalah dengan Unicode string di Delphi 2009.

Satu-satunya yang berhasil (yang telah saya ujicoba) dengan benar adalah yang ditulis oleh Mas Didin Jo. Anda bisa melihatnya di sini: http://didinjo.web.id/eksekusi-perintah-dos-dan-mendapatkan-outputnya/

Penggunaanya akan memberi hasil seperti gambar berikut ini:

Sumber: http://didinjo.web.id/eksekusi-perintah-dos-dan-mendapatkan-outputnya/

Potongan kode ini menjadi inspirasi dan solusi bagi saya. Hanyasannya, hasil capture baru akan ditampilkan setelah proses eksekusi selesai. Bagaimana jika proses DOS tersebut adalah proses backup & restore database yang butuh waktu berpuluh menit? Tentu akan membosankan bila menunggu selamat itu dan yang terlihat hanya memo kosong, karena proses “still running.” Apalagi, bila eksekusi dilakukan tanpa thread bantu, sehingga aplikasi bisa menjadi hang saat user melakukan interaksi, sementara proses masih berjalan.

Hasil Modifikasi

Modifikasi saya lakukan pada bagian looping membaca dari StdOut Pipe. Yaitu dengan memanggil sebuah prosedur callback, dengan parameter potongan data output yang telah terbaca. Callback akan memproses data tersebut, misalnya ditampilkan di memo atau disave ke file, kemudian mengembalikan kontrol eksekusi looping lagi, dan proses ini berulang hingga tidak ada lagi data terbaca dan/atau proses telah selesai.

Callback

Pertama, saya membuat sebuah type definition baru sebagai prototipe callback yang akan dipakai.

interface
//……………………………………………………………………..
type
TOnCommandRead  =  procedure(ALine: String) of object;

Direktif “of object” menandakan kita harus mendeklarasikan sebuah prosedur callback sebagai method pada sebuah class/objek, bukan standalone method. Kita akan lihat contohnya nanti.

Kemudian, pada sebuah form, misalnya from ExpMod, saya tambahkan sebuah prosedur sesuai prototipe di atas. Kenapa harus pada form? Karena, prosedur ini diberi direktif “of object”, sebagaimana dimaksud di atas, sehingga tidak boleh berupa prosedur biasa yang berdiri sendiri.

Kita lihat kode berikut:


TExpMod = class(TForm)
 //………………………….
 private
 { Private  declarations }
 //…………………………
 public
 { Public declarations }
 //…………………………
procedure OnRead(AText: String);
//……………………………………….
 end;

Anda lihat? Sebuah prosedur dengan satu parameter: AText: String. Prosedur ini sama strukturnya dengan prototipe callback di atas. Dan merupakan bagian dari objek. Anda boleh mendeklarasikannya pada bagian public maupun private.

Prosedur ini nantinya akan mengupdate konten sebuah Memo sehingga langsung terlihat di layar.

Berikut implementasinya:

procedure TExpMod.OnRead(AText:  String);
begin
 SynM.Lines.BeginUpdate;
 try
 SynM.Lines.Text  := SynM.Lines.Text + AText;
 finally
 SynM.Lines.EndUpdate;
 end;
 SynM.SelStart := Length(SynM.Text);
 SynM.Perform(EM_SCROLLCARET,  0, 0);
 Application.ProcessMessages;
end;

SynM.Lines.BeginUpdate; dan SynM.Lines.EndUpdate; bermanfaat untuk mendisable auto-update pada GUI, sehingga proses update teks pada TMemo hanya berlangsung di memori. Hasil setelah update barulah akan diflush ke GUI (Memo) sehingga lebih cepat.

SynM.SelStart := Length(SynM.Text);
SynM.Perform(EM_SCROLLCARET, 0, 0);
berfungsi untuk menunjukkan baris yang aktif pada Memo, yaitu baris terakhir, dan melakukan SendMessage dengan EM_SCROLLCARET sehingga Memo akan melakukan scroll dan baris tersebut akan baris tampil di layar. Bila tidak begini, baris yang tampil tetap baris pertama, sehingga user harus melakukan scoll manual untuk melihat baris terakhir. Bagaimana jika telah 1000 baris?

Main Function

Pada form ExpMod, saya tambahkan sebuah fungsi baru:

function  GetDosOutput(CommandLine, Params:  string; cbOnDataRead: TOnCommandRead; Work: string = ''):  DWORD;

Dan kode di atas menjadi:


TExpMod = class(TForm)
 //………………………….
 private
 { Private  declarations }
 //…………………………
 public
 { Public declarations }
 //…………………………
 procedure OnRead(AText: String);
 //……………………………………….
 function  GetDosOutput(CommandLine, Params: string;  cbOnDataRead: TOnCommandRead; Work: string = ''): DWORD;
 end;

Ini adalah fungsi utama untuk mengekekusi DOS command. Perhatikan cbOnDataRead: TOnCommandRead; yang merupakan penunjuk prosedur callback yang akan dipanggil setiap ada data terbaca pada proses looping.

Langsung ke implementasinya:


function TExpMod.GetDosOutput(CommandLine, Params: string;
 cbOnDataRead:  TOnCommandRead; Work: string): DWORD;
var
 SA: TSecurityAttributes;
 SI: TStartupInfo;
 PI: TProcessInformation;
 StdOutPipeRead,  StdOutPipeWrite: THandle;
 WasOK: Boolean;
 Buffer: array[0..255] of  AnsiChar;
 BytesRead: Cardinal;
 WorkDir: string;
 Handle:  Boolean;
 Parameter: PChar;
 DataRead, tmp: String;
 i:integer;
 iexit: Cardinal;
 procedure OutLine(const AText:String);
 begin
 if Assigned(cbOnDataRead) then
 cbOnDataRead(AText);
 end;
begin
 DataRead := '';
 if not FileExists(CommandLine) then
 begin
 DataRead := 'File '+ CommandLine+ ' tidak ditemukan!';
 OutLine(DataRead);
 Application.ProcessMessages;
 Result :=  0;
 exit;
 end;
 OutLine('Starting '+  CommandLine+'...'#13#10);
 with SA do begin
 nLength :=  SizeOf(SA);
 bInheritHandle := True;
 lpSecurityDescriptor :=  nil;
 end;
 CreatePipe(StdOutPipeRead, StdOutPipeWrite, @SA, 0);
 try
 with SI do
 begin
 FillChar(SI, SizeOf(SI),  0);
 cb := SizeOf(SI);
 dwFlags := STARTF_USESHOWWINDOW or  STARTF_USESTDHANDLES;
 wShowWindow := SW_HIDE;
 hStdInput :=  GetStdHandle(STD_INPUT_HANDLE); // don't redirect stdin
 hStdOutput :=  StdOutPipeWrite;
 hStdError := StdOutPipeWrite;
 end;
 if  not DirectoryExists(Work) then
 WorkDir :=  ExtractFileDir(CommandLine)
 else
 WorkDir := Work;
 Parameter := PChar('"'+CommandLine + '" '+Params);
 try
 Handle :=  CreateProcess(nil{PChar( CommandLine )},  Parameter,
 nil, nil, True, 0,  nil,
 PChar(WorkDir), SI, PI);
 CloseHandle(StdOutPipeWrite);
 if Handle then
 try
 repeat
 FillChar(Buffer, sizeof(Buffer),  0);
 WasOK := ReadFile(StdOutPipeRead, Buffer, 255, BytesRead,  nil);
 if BytesRead > 0 then
 begin
 Buffer[BytesRead] := #0;
 DataRead :=  Buffer;
 end;
 OutLine(DataRead);
 DataRead := '';
 Application.ProcessMessages;
 GetExitCodeProcess(pi.hProcess,iExit);
 until (not WasOK )or  (BytesRead = 0);// or (iexit <> STILL_ACTIVE);
 WaitForSingleObject(pi.hProcess, INFINITE);
 if DataRead<>''  then
 OutLine(DataRead);
 finally
 CloseHandle(PI.hThread);
 CloseHandle(PI.hProcess);
 end
 else
 begin
 OutLine('Failed.');
 end;
 Except on e: exception do
 OutLine(e.Message+#13#10);
 end;
 finally
 CloseHandle(StdOutPipeRead);
 end;
end;

Pada fungsi di atas saya telah tambahkan nested routine, yaitu procedure OutLine(const AText:String); yang akan memeriksa apakah CallBack diisi. Ini akan mencegah error “Access violation. Read of adress 00000000 blah blah blah…” yang telah menjadi langganan kita dengan Delphi. Smile

Pada looping berikut

repeat
 FillChar(Buffer, sizeof(Buffer), 0);
 WasOK  := ReadFile(StdOutPipeRead, Buffer, 255, BytesRead, nil);
 if  BytesRead > 0 then
 begin
 Buffer[BytesRead] :=  #0;
 DataRead := Buffer;
 end;
 OutLine(DataRead);
 DataRead := '';
 Application.ProcessMessages;
 GetExitCodeProcess(pi.hProcess,iExit);
 until (not WasOK )or  (BytesRead = 0);// or (iexit <> STILL_ACTIVE);

data akan dibaca sebesar 255 byte dan bila berhasil, akan dikirim ke prosedur callback untuk ditampilkan di Memo. Proses ini akan berulang selama masih ada terbaca dan/atau selama eksekusi masih berjalan.

Sample

Fungsi di atas sudah well-tested dan telah saya pakai di beberapa aplikasi, termasuk aplikasi Sistem Informasi Keuangan Daerah dan aplikasi-aplikasi lainnya.

Potongan kode berikut adalah contoh penggunaan fungsi di atas. Sebagai info, kode berikut untuk membackup sebuah tabel dari PostgreSQL Server.

var
 cp  : TConProps;
 cmd : String;
 insert : String;
begin
 OnRead('Dumping table '+ ATable+'...'#13#10);
 cp :=  COnnectionProp;
 CleanPGPass;
 SetPgPass([cp]);
 if UseSQL  then
 insert := '--inserts --column-inserts'
 else
 insert :=  '';
 if DataOnly then
 cmd := '-f "'+TargetFile+'" -t '+ATable+'  --format=p -a -v -b -O -i -x '+insert+' --disable-dollar-quoting  --host='+cp.Host+' --port='+IntToStr(cp.Port)+' --username='+cp.User+' "'+  cp.Database+'"'
 else
 cmd := '-f "'+TargetFile+'" -t '+ATable+'  --format=p -v -b -O -i -x --inserts --column-inserts --disable-dollar-quoting  --host='+cp.Host+' --port='+IntToStr(cp.Port)+' --username='+cp.User+' "'+  cp.Database+'"';
 try
 GetDosOutput(PGDUMP_, cmd,  OnRead);
finally
 AddText(#13#10' Done.');
 end;

Ini contohnya:

Eh, salah. Yang benar yang di bawah ini… (serius amat! Dingin2 dikit tuh kepala wakakakakak…😀 )

66813_134523953271779_126434357414072_204387_4614577_n

Bila Anda ingin mendownload source code dan file EXE untuk tulisan ini, silahkan kunjungi tulisan saya:

PostgreSQL [Auto]Backup Tool

Di sana tersedia sebuah aplikasi untuk membackup database PostgreSQL pada waktu tertentu dan juga ada proses backup manual dan restore.

Terimakasih atas kunjungan Anda. Selamat Tahun Baru 2011, semoga segala hal menjadi lebih baik untuk Anda di tahun baru ini. Amin.

Salam, Joko Rivai.

3 thoughts on “Execute DOS Command & Capture The Output

  1. senengnya, blog saya disebut di posting ini.hehe.. thx mas. eh, thx juga tipsnya, sangat membantu lho. mesti bnyak blajar nih…
    btw, ok juga tuh foto cewek diatas.. heheheh

  2. kok punya ku hang ya mas process selesai baru text muncul di memo …
    ada cara lain ngk mas ,klo chkdsk jalan aja sih tapi klo pake app dos yang saya buat dariC++ ,trus jalan kan src masa di delphi sih bisa tapi hang dolo setelah proces selesai baru text muncul

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s