Solved

Returning DOS command output : Delphi 3

Posted on 1998-07-19
19
1,462 Views
Last Modified: 2008-02-01
I would like to know how to run a DOS command and return it's output to a memo control on a form without spawning the DOS window.

example:

form contains; edit box, button and memo control.

when I click the button the 'DIR' command is run for the directory given in the edit box. The result (file list or any error) is returned in the memo control.

the DOS window must not be shown.

should work for delphi 3 under Win 95/98.

regards

sxh

0
Comment
Question by:sxh
  • 11
  • 4
  • 2
  • +2
19 Comments
 
LVL 2

Expert Comment

by:hrizal
ID: 1358182
how about ...

      begin
         exec('DIR >mydata')
         memo1.lines.readFromFile('mydata')
      end;

exec is procedure to execute shell app.  need details ? .. :)
0
 
LVL 3

Expert Comment

by:vladika
ID: 1358183
Try to use anonymous pipes to redirect the child process's standard input and output.



0
 
LVL 1

Author Comment

by:sxh
ID: 1358184
hi hrizal

thanks for the response.

Your solution is one that would possibly work for the example I gave. However, as this was an example it would not be practical to issue commands like DIR /p.

I will rephrase the question a little, as I am probably after a little more than what I asked for originaly.

regards

sxh


0
 
LVL 1

Author Comment

by:sxh
ID: 1358185
hi vladika

thanks for your response.

I do not know the meaning of anonymous pipes, I know what a pipe is ??

regards

sxh
0
 
LVL 1

Author Comment

by:sxh
ID: 1358186
:additional requirements:

I would like the program to allow me to issue commands which require interactive responses from the user like DIR /p or DIR | more.

Also the program should display interactively, ie if the commamd DIR /o/s *.exe was issued, the memo control would display each line as it was found.

regards

sxh
0
 
LVL 4

Expert Comment

by:erajoj
ID: 1358187
This is code I use often. It's not very sophisticated but it works very well and is easy
enough to understand:

// Generic execute routine...
function ExecuteAndWait( sExeFileName, sCommandLine: string; bHidden: Boolean ): Integer;
 { returns -1 if the Exec failed, otherwise returns the process' exit
   code when the process terminates }
var
  sCommand    : string;
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
begin
  sCommand := sExeFilename+' '+sCommandLine;
  FillChar( StartupInfo, SizeOf( StartupInfo ), #0);
  StartupInfo.cb := SizeOf( StartupInfo );
  StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
  if bHidden
  then StartupInfo.wShowWindow := SW_HIDE
  else StartupInfo.wShowWindow := SW_SHOWNORMAL;
  if not CreateProcess( nil,
      PChar( sCommand ),                { pointer to command line string }
      nil,                           { pointer to process security attributes }
      nil,                           { pointer to thread security attributes }
      False,                         { handle inheritance flag }
      CREATE_NEW_CONSOLE or          { creation flags }
      NORMAL_PRIORITY_CLASS,
      nil,                           { pointer to new environment block }
      nil,                           { pointer to current directory name }
      StartupInfo,                   { pointer to STARTUPINFO }
      ProcessInfo ) then              { pointer to PROCESS_INF }
    Result := -1
  else begin
    WaitforSingleObject( ProcessInfo.hProcess, INFINITE );
    GetExitCodeProcess( ProcessInfo.hProcess, Result );
  end;
end;

function ExecuteAndRedirect( sCommand, sCommandLine: string; List: TStrings ): Boolean;
var
  sTempFilename: string;
  iIndex: Integer;
begin
  // first validate parameters...
  if ( sCommand = '' ) or ( sCommandLine = '' ) or ( not Assigned( List ) ) then begin
    Result := False;
    Exit;
  end;
  iIndex := 0;
  repeat // repeat until unique filename ( not really needed? )
    sTempFilename := Format( 'redir%s.$$$', [ IntToHex( iIndex, 3 ) ] );
  until ( not FileExists( sTempFilename ) );
  Result := ( -1 <> ExecuteAndWait(
    'command.exe /c ' + sCommand, // "command.exe /c" needed for 95/98 & NT "cmd.exe /c" is not enough for 95/98
    sCommandLine + ' >' + sTempFilename, // redirection included
    True // hidden
  ) );
  List.LoadFromFile( sTempFilename ); // load to any TStrings-decendant like TMemo
  DeleteFile( sTempFilename ); // remove tempfile
end;

Usage:
  bResult := ExecuteAndRedirect( 'dir', '/o/s', MyMemo );

Probably typos everywhere, but it's supposed to work fine.
Study "CreateProcess", "WaitForSingleObject", "GetExitCodeProcess", "TStrings", "command.exe"-parameters!
There are a lot of goodies...

/// John
0
 
LVL 3

Expert Comment

by:vladika
ID: 1358188
Erajoj,
I think it will not work for 'DIR /p'


0
 
LVL 4

Expert Comment

by:erajoj
ID: 1358189
Sorry, can't help you there...

/// John
0
 
LVL 1

Author Comment

by:sxh
ID: 1358190
hi erajoj

Great answer.

I have seen the execute and wait code before, but a different version.

Your answer does not give me the interactivity specified. The controlling procedure ExecuteAndRedirect calls the
ExecuteAndWait procedure, it's not until the command has finished executing that the call to load the redirected output into the memo is processed, therefor the user would have to wait until the command had finished processing before any results were made available.

regards

sxh
0
Better Security Awareness With Threat Intelligence

See how one of the leading financial services organizations uses Recorded Future as part of a holistic threat intelligence program to promote security awareness and proactively and efficiently identify threats.

 
LVL 1

Author Comment

by:sxh
ID: 1358191
hi erajoj

Great answer.

I have seen the execute and wait code before, but a different version.

Your answer does not give me the interactivity specified. The controlling procedure ExecuteAndRedirect calls the
ExecuteAndWait procedure, it's not until the command has finished executing that the call to load the redirected output into the memo is processed, therefor the user would have to wait until the command had finished processing before any results were made available.

regards

sxh
0
 
LVL 1

Author Comment

by:sxh
ID: 1358192
hi erajoj

Great answer.

I have seen the execute and wait code before, but a different version.

Your answer does not give me the interactivity specified. The controlling procedure ExecuteAndRedirect calls the
ExecuteAndWait procedure, it's not until the command has finished executing that the call to load the redirected output into the memo is processed, therefor the user would have to wait until the command had finished processing before any results were made available.

regards

sxh
0
 
LVL 1

Author Comment

by:sxh
ID: 1358193
hi all

Maybe I should be using some kind of console in place of the memo control ????.

How would I do that on the same form ????

regards

sxh
0
 
LVL 6

Expert Comment

by:Stuart_Johnson
ID: 1358194
I would be very interested to see if you get this working.  I have tried plenty of times to try and get some interactivity happening between some of my apps and the command prompt, however, on every occassion, I have not really been successful in getting what was returned by the command prompt.

For instance, if you type DIR *.EXE /S /P or DIR *.EXE /S | MORE, the command processor waits for keyboard input before proceeding with the next screenful of text.  As you are piping this to a file (this is the only method I can think of), you have no way of knowing when the "press any key to continue..." prompt appears.  A problem with the text file is that you will not be able to open it until DOS has finished writing too it anyway.  The file will be locked.

A few years ago, I wrote a program which would capture a text screen and could then work out what was typed.  However, this doesnt work in Windows properly for some reason (and I dont think it would work in Delphi anyway).  

What you need to try and achieve is a way of reading line by line what is shown on the DOS screen and scanning it for recognisable items.  Then, using a send key function, type back to the DOS window.  

I dont know if that is useful for you or not, but I seriously cant think of any way of doing this at all - sort of writing your own command interpreter which just supports the very basic DOS functions.

Best of luck!

Stuart.
0
 
LVL 3

Accepted Solution

by:
vladika earned 500 total points
ID: 1358195
Try my solution
The main idea:
create pipes and set its handle instead of StdInput, StdOutput, StdError
when you call CreateProcess.

When child process write to stdout it write into pipe and we read from pipe,
also we can write into pipe and child process read from pipe as from stdin.

If you have questions you may ask me by email: demon@dezcom.mephi.ru

unit Unit1;

interface

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

type
  TPipeStream = class(TStream)
  private
    FReadEnd, FWriteEnd: THandle;
  public
    constructor Create;
    destructor Destroy; override;
    function Read(var Buffer; Count: Longint): Longint; override;
    function Write(const Buffer; Count: Longint): Longint; override;
    function Seek(Offset: Longint; Origin: Word): Longint; override;
    property ReadEnd: THandle read FReadEnd;
    property WriteEnd: THandle read FWriteEnd;
  end;

  TReadThread = class(TThread)
  private
    FOnReadyToRead: TNotifyEvent;
    procedure Read;
  protected
    procedure Execute; override;
  public
    property OnReadyToRead: TNotifyEvent read FOnReadyToRead write FOnReadyToRead;
  end;

  TTestForm = class(TForm)
    ExecBtn: TButton;
    CommandEdit: TEdit;
    InputEdit: TEdit;
    InputBtn: TButton;
    OutputMemo: TMemo;
    ClearMemoBtn: TButton;
    ReadRest: TButton;
    procedure ExecBtnClick(Sender: TObject);
    procedure InputBtnClick(Sender: TObject);
    procedure ClearMemoBtnClick(Sender: TObject);
    procedure ReadRestClick(Sender: TObject);
  private
    FReadPipe, FWritePipe: TPipeStream;
    FInProcess: Boolean;
    FThread: TReadThread;
    FRest: string;
    procedure ReadFromPipe(Sender: TObject);
  end;

var
  TestForm: TTestForm;

implementation

{$R *.DFM}

{ TPipeStream }

constructor TPipeStream.Create;
var saAttr: TSecurityAttributes;
begin
  // Set the bInheritHandle flag so pipe handles are inherited. }
  saAttr.nLength := SizeOf(TSecurityAttributes);
  saAttr.bInheritHandle := True;
  saAttr.lpSecurityDescriptor := nil;
  if not CreatePipe(FReadEnd, FWriteEnd, @saAttr, 0) then
    RaiseLastWin32Error;
end;

destructor TPipeStream.Destroy;
begin
  CloseHandle(FReadEnd);
  CloseHandle(FWriteEnd);
  inherited Destroy;
end;

function TPipeStream.Read(var Buffer; Count: Longint): Longint;
begin
  Result := FileRead(FReadEnd, Buffer, Count); // read from pipe
  if Result = -1 then Result := 0;
end;

function TPipeStream.Write(const Buffer; Count: Longint): Longint;
begin
  Result := FileWrite(FWriteEnd, Buffer, Count); // write to pipe
  if Result = -1 then Result := 0;
end;

function TPipeStream.Seek(Offset: Longint; Origin: Word): Longint;
begin
  if Origin = 2 then PeekNamedPipe(FReadEnd, nil, 0, nil, @Result, nil)
  else Result := 0;  // return number of bytes in pipe if Origin=2
end;

{ TReadThread }

procedure TReadThread.Read;
begin
  if Assigned(FOnReadyToRead) then FOnReadyToRead(Self);
end;

procedure TReadThread.Execute;
begin
  while not Terminated do Synchronize(Read);
end;

{ TForm1 }

procedure TTestForm.ExecBtnClick(Sender: TObject);
var piProcInfo: TProcessInformation;
    siStartInfo: TStartupInfo;
begin
  if FInProcess then Exit;
  FInProcess := True;
  try
    FReadPipe := TPipeStream.Create;
    try
      FWritePipe := TPipeStream.Create;
      try
        FThread := TReadThread.Create(False);
        try
          FRest := '';
          FThread.OnReadyToRead := ReadFromPipe;
          FillChar(siStartInfo, SizeOf(siStartInfo), 0);
          siStartInfo.cb := SizeOf(TStartupInfo);
          siStartInfo.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
//          siStartInfo.wShowWindow := SW_SHOWNORMAL;
          siStartInfo.hStdInput  := FWritePipe.ReadEnd; // we write -> child read
          siStartInfo.hStdOutput := FReadPipe.WriteEnd; // child write -> we read
          siStartInfo.hStdError  := FReadPipe.WriteEnd; // child write -> we read

          if not CreateProcess(nil, PChar(CommandEdit.Text), nil, nil, True, 0, nil, nil,
            siStartInfo, piProcInfo) then RaiseLastWin32Error;
          CloseHandle(piProcInfo.hThread);
          while WaitForSingleObject(piProcInfo.hProcess, 0) = WAIT_TIMEOUT do
            Application.HandleMessage;
          CloseHandle(piProcInfo.hProcess);
        finally
          FThread.Terminate;
          FThread.Free;
        end;
      finally
        FWritePipe.Free;
        FWritePipe := nil;
      end;
    finally
      FReadPipe.Free;
      FReadPipe := nil;
    end;
  finally
    FInProcess := False;
  end;
end;

procedure TTestForm.ReadFromPipe(Sender: TObject);
var S, SubS: string;
    Start, I, L: Integer;
begin
  if not Assigned(FReadPipe) then Exit;
  L := FReadPipe.Size;
  if L > 0 then
  begin
    SetString(S, nil, L);
    FReadPipe.Read(PChar(S)^, L); // read from child stdout and stderr
    S := FRest + S;
    // Process S
    I := 1;
    L := Length(S);
    FRest := '';
    while I <= L do
    begin
      Start := I;
      while (I <= L) and not (S[I] in [#10, #13]) do Inc(I);
      if (I > L) or (S[I] = #13) and (I > L-1) then
      begin
        FRest := Copy(S, Start, MaxInt);
        Break;
      end;
      SubS := Copy(S, Start, I - Start);
      OutputMemo.Lines.Add(SubS);
      SendMessage(OutputMemo.Handle, EM_LINESCROLL, 0, OutputMemo.Lines.Count);
      if S[I] = #13 then Inc(I);
      if (I <= L) and (S[I] = #10) then Inc(I);
    end;
  end;
end;

procedure TTestForm.InputBtnClick(Sender: TObject);
var S: string;
begin
  if not Assigned(FWritePipe) then Exit;
  S := InputEdit.Text + #13;
  FWritePipe.Write(PChar(S)^, Length(S));  // write S into child stdin
end;

procedure TTestForm.ClearMemoBtnClick(Sender: TObject);
begin
  OutputMemo.Clear;
end;

procedure TTestForm.ReadRestClick(Sender: TObject);
begin
  if FRest = '' then Exit;
  OutputMemo.Lines.Add(FRest);
  FRest := '';
  SendMessage(OutputMemo.Handle, EM_LINESCROLL, 0, OutputMemo.Lines.Count);
end;

end.

//////////////////
object TestForm: TTestForm
  Left = 200
  Top = 108
  Width = 597
  Height = 370
  Caption = 'TestForm'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  PixelsPerInch = 96
  TextHeight = 13
  object ExecBtn: TButton
    Left = 192
    Top = 24
    Width = 75
    Height = 25
    Caption = 'ExecBtn'
    TabOrder = 0
    OnClick = ExecBtnClick
  end
  object CommandEdit: TEdit
    Left = 24
    Top = 24
    Width = 121
    Height = 21
    TabOrder = 1
    Text = 'command.com'
  end
  object InputEdit: TEdit
    Left = 24
    Top = 72
    Width = 121
    Height = 21
    TabOrder = 2
  end
  object InputBtn: TButton
    Left = 192
    Top = 72
    Width = 75
    Height = 25
    Caption = 'InputBtn'
    TabOrder = 3
    OnClick = InputBtnClick
  end
  object OutputMemo: TMemo
    Left = 24
    Top = 120
    Width = 545
    Height = 209
    ScrollBars = ssBoth
    TabOrder = 4
  end
  object ClearMemoBtn: TButton
    Left = 288
    Top = 24
    Width = 75
    Height = 25
    Caption = 'ClearMemo'
    TabOrder = 5
    OnClick = ClearMemoBtnClick
  end
  object ReadRest: TButton
    Left = 288
    Top = 72
    Width = 75
    Height = 25
    Caption = 'ReadRest'
    TabOrder = 6
    OnClick = ReadRestClick
  end
end

0
 
LVL 1

Author Comment

by:sxh
ID: 1358196
hi vladika

I'm sure that all of the people who follow this thread would definately learn something from your innovative solution.

I can see where you are coming from now, and with a little work I could tweak your solution to my needs.

There is one question I have regarding your solution:

After running a standard 'DIR' command, I can't exit from the form unless I do a Program Reset in the IDE. This also leaves the 'winoldap' and 'redir32' processes running and can be seen in the task manager..... How do I cancel these after the command has been executed and finished???

many thanks for your input, I have raised the points to 500.

well done

sxh
0
 
LVL 1

Author Comment

by:sxh
ID: 1358197
hi stuart johnson

I have accepted the answer from vladika, as you can see from this, there are ways of doing a task other than the obvious.

I would recommend you debug vladika's code and pleasure at it's operation. This may be what you've been looking for too....

regards

sxh
0
 
LVL 1

Author Comment

by:sxh
ID: 1358198
hi vladika

I've found a safe way to destroy the processes, no need to reply.

regards

sxh
0
 
LVL 3

Expert Comment

by:vladika
ID: 1358199
Hi, Sxh

I think if you run process by typing in CommandEdit 'command.com /c dir'
then command.com run 'DIR' command and exit.

If you run 'command.com' and then type into InputEdit 'DIR'
then command.com run 'DIR' command but not exit after it.
You have to type 'EXIT' into InputEdit to shutdown command.com

And what is your way to destroy the processes?

Vladika

0
 
LVL 1

Author Comment

by:sxh
ID: 1358200
hi vladika

I shut down the processes by typing exit into the inputedit box.

When I move onto the project in which this will be used, I will incorporate these commands into a command class and then just simply create and destroy through the class.

regards

sxh
0

Featured Post

Threat Intelligence Starter Resources

Integrating threat intelligence can be challenging, and not all companies are ready. These resources can help you build awareness and prepare for defense.

Join & Write a Comment

Have you ever had your Delphi form/application just hanging while waiting for data to load? This is the article to read if you want to learn some things about adding threads for data loading in the background. First, I'll setup a general applica…
Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
In this tutorial you'll learn about bandwidth monitoring with flows and packet sniffing with our network monitoring solution PRTG Network Monitor (https://www.paessler.com/prtg). If you're interested in additional methods for monitoring bandwidt…
This video explains how to create simple products associated to Magento configurable product and offers fast way of their generation with Store Manager for Magento tool.

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

20 Experts available now in Live!

Get 1:1 Help Now