Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Use DOS commands in an application 5

Status
Not open for further replies.

doctorjellybean

Programmer
May 5, 2003
145
I have several DOS batch files which I want to create applications for. What I basically need, are 2 things:

1) Execute a DOS command from within the application
2) Display the output in a Memo instead of a console window

I Googled a bit, and found this article:


The code (as used in this article), using a Memo and Button:

Code:
procedure RunDosInMemo(DosApp:String;AMemo:TMemo) ;
  const
     ReadBuffer = 2400;
  var
   Security : TSecurityAttributes;
   ReadPipe,WritePipe : THandle;
   start : TStartUpInfo;
   ProcessInfo : TProcessInformation;
   Buffer : Pchar;
   BytesRead : DWord;
   Apprunning : DWord;
  begin
   With Security do begin
    nlength := SizeOf(TSecurityAttributes) ;
    binherithandle := true;
    lpsecuritydescriptor := nil;
   end;
   if Createpipe (ReadPipe, WritePipe,
                  @Security, 0) then begin
    Buffer := AllocMem(ReadBuffer + 1) ;
    FillChar(Start,Sizeof(Start),#0) ;
    start.cb := SizeOf(start) ;
    start.hStdOutput := WritePipe;
    start.hStdInput := ReadPipe;
    start.dwFlags := STARTF_USESTDHANDLES +
                         STARTF_USESHOWWINDOW;
    start.wShowWindow := SW_HIDE;

    if CreateProcess(nil,
           PChar(DosApp),
           @Security,
           @Security,
           true,
           NORMAL_PRIORITY_CLASS,
           nil,
           nil,
           start,
           ProcessInfo)
    then
    begin
     repeat
      Apprunning := WaitForSingleObject
                   (ProcessInfo.hProcess,100) ;
      Application.ProcessMessages;
     until (Apprunning <> WAIT_TIMEOUT) ;
      Repeat
        BytesRead := 0;
        ReadFile(ReadPipe,Buffer[0],
ReadBuffer,BytesRead,nil) ;
        Buffer[BytesRead]:= #0;
        OemToAnsi(Buffer,Buffer) ;
        AMemo.Text := AMemo.text + String(Buffer) ;
      until (BytesRead < ReadBuffer) ;
   end;
   FreeMem(Buffer) ;
   CloseHandle(ProcessInfo.hProcess) ;
   CloseHandle(ProcessInfo.hThread) ;
   CloseHandle(ReadPipe) ;
   CloseHandle(WritePipe) ;
   end;
  end;

procedure TForm1.Button1Click(Sender: TObject) ;
begin {button 1 code}
    RunDosInMemo('chkdsk.exe c:\',Memo1) ;
end;

If I use this example, the following appears in the memo:

The drive, the path, or the file name is not valid.

If I run chkdsk in a normal command window, it works.

Is there something wrong or missing from the above code?
 
this will work, but I don't receive the output

RunDosInMemo('chkdsk.exe c:',Memo1) ; (ditch the backslash)

/Daddy


-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
What about piping the output to a file and then you load the file in afterwards?

Use shell execute to call:
chkdsk.exe c: >sometextfile.txt

When the process has completed you can load the sometextfile.txt.

That should work as long as the dos app doesn't require any user input.
 
Ditching the backslash works, and I get output too

8a0ssp3.jpg


I've not tried shellexecute, as I don't want the command window to appear. It doesn't need any user input, so I'll look at that.
 
Okay, this requires some knowledge of the DOS environment. Your problem is that chkdsk.exe isn't being found. In calling other programs, you want to make sure you always either:

1) Make sure you provide the full path name to the executable file when you call it.
2) Make sure the PATH defined in your shell is valid. You can typically check this by typing "PATH" in most cases.
and sometimes
3) If you are trying to call a command-interpreter command or run a batch file, you need to call "COMSPEC" and pass the command you want. (I'll make a separate thread when I gather the data, I have a problem on this one I'm trying to solve at the moment)

Barring that, I took a copy of the code - I've been looking for a good sample of code regarding named pipes for a project of mine I've had in mind to begin soon. I'll check it out as soon as I can and see if I see any problems (I already see one for XP systems, but I'm sure I'll see more when I look)

----------
Measurement is not management.
 
Thanks Glenn999.

The problem is in fact that he is not supplying the path to the chkdsk.exe file. Normally cmd.exe will search the directories listed in the PATH environment variable to find programs, but CreateProcess() will not. It only executes what you tell it to.

The file is in the user's Windows System directory (which is usually C:\WINDOWS\System32, but not always).

Get the actual path thus:
Code:
function GetSystemPath: string;
  begin
  SetLength( result, GetSystemDirectory( nil, 0 ) );
  GetSystemDirectory( PChar( result ), Length( result ) );
  result := IncludeTrailingPathDelimiter( result )
  end;

Now you can execute the program as simply as:
Code:
RunDosInMemo( GetSystemPath +'chkdsk.exe', Memo1 );

Hope this helps.
 
Guys, no need to provide a path.

cmd can do this for you.
doctorjellybean I adapted the function a bit to get the output while chkdsk is running. the reason I said I didn't get any output was that chkdsk takes a long time on my computer. Now I get realtime output:


Code:
procedure RunDosInMemo(DosApp:String;AMemo:TMemo) ;
  const
     ReadBuffer = 2400;
  var
   Security : TSecurityAttributes;
   ReadPipe,WritePipe : THandle;
   start : TStartUpInfo;
   ProcessInfo : TProcessInformation;
   Buffer : Pchar;
   BytesRead : DWord;
   Apprunning : DWord;
  begin
   With Security do begin
    nlength := SizeOf(TSecurityAttributes) ;
    binherithandle := true;
    lpsecuritydescriptor := nil;
   end;
   if Createpipe (ReadPipe, WritePipe,
                  @Security, 0) then begin
    Buffer := AllocMem(ReadBuffer + 1) ;
    FillChar(Start,Sizeof(Start),#0) ;
    start.cb := SizeOf(start) ;
    start.hStdOutput := WritePipe;
    start.hStdInput := ReadPipe;
    start.dwFlags := STARTF_USESTDHANDLES +
                         STARTF_USESHOWWINDOW;
    start.wShowWindow := SW_HIDE;

    if CreateProcess(nil,
           PChar(DosApp),
           @Security,
           @Security,
           true,
           NORMAL_PRIORITY_CLASS,
           nil,
           nil,
           start,
           ProcessInfo)
    then
    begin
     repeat
      Apprunning := WaitForSingleObject
                   (ProcessInfo.hProcess,100) ;
      Application.ProcessMessages;
      Repeat
        BytesRead := 0;
        ReadFile(ReadPipe,Buffer[0], ReadBuffer,BytesRead,nil) ;
        Buffer[BytesRead]:= #0;
        OemToAnsi(Buffer,Buffer) ;
        AMemo.Text := AMemo.text + String(Buffer) ;
      until (BytesRead < ReadBuffer) ;
     until (Apprunning <> WAIT_TIMEOUT) ;
   end;
   FreeMem(Buffer) ;
   CloseHandle(ProcessInfo.hProcess) ;
   CloseHandle(ProcessInfo.hThread) ;
   CloseHandle(ReadPipe) ;
   CloseHandle(WritePipe) ;
  end;
end;

procedure TForm7.Button1Click(Sender: TObject);
begin
 RunDosInMemo('cmd /c "chkdsk c:"',Memo1) ;
end;

this code runs under 2000/XP and vista

-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
Thanks Daddy!

Slightly off topic: the Get System Path code posted by Duoas.

I thought I could use that to see if a file exists in the system folder, and used his function. I then used
Code:
if fileexists(GetSystemPath +'drwatson.exe') then ..
It doesn't find the file, yet if I assign GetSystemPath to a label, it displays the path. It does find the file if I hardcode the path
Code:
if fileexists('c:\windows\system32\drwatson.exe') then ..

Any ideas?
 
The code posted by Duoas is buggy in that it appends the trailing path delimiter after the null byte at the end of the string.

Try this, slightly simpler, version:
Code:
function GetSystemPath: string;
var
  buffer: array [ 0..MAX_PATH ] of char;
begin
  GetSystemDirectory( buffer, MAX_PATH );
  result := IncludeTrailingPathDelimiter( String(buffer) );
end;

Andrew
Hampshire, UK
 
Glenn9999
Okay, this requires some knowledge of the DOS environment.

Think I better explain what I'm trying to do. Background: I do a lot of video editing and DVD creation. The audio files, which are in AC3 format, needs to be normalized before burning the discs. To do that, a program called Avisynth needs to be installed as it is being used as part of the process.

In a folder where the AC3 files are being copied to be normalized, are some DLL's and applications. The existing batch file consists of the following lines:

Code:
del *.avs


for %%a in ("*.ac3") do @echo SetMemoryMax(768) >"%%~na.avs"
for %%a in ("*.ac3") do @echo LoadPlugin("bassAudio.dll") >>"%%~na.avs"
for %%a in ("*.ac3") do @echo LoadPlugin("AudioLimiter.dll") >>"%%~na.avs"
for %%a in ("*.ac3") do @echo BassAudioSource("%%a").normalize(0.98)  >>"%%~na.avs"
for %%a in ("*.avs") do wavi "%%a" - | aften.exe -v 0 -b 384 -readtoeof 1 - "Normalized-%%~na_Music.ac3"

If I paste the above code into it's own line
Code:
RunDosInMemo('for %%a in ("*.ac3") do SetMemoryMax(768) >"%%~na.avs"', Memo1 );
etc, nothing happens. I don't worry about the @echo part.

So now I'm wondering if the syntax being used in the RunDosInmemo code is correct, for Delphi to execute it properly.
 
I was reading this and was going to pull out my GetSystemDirectory code, but I see that got solved, but I did see one thing I will point out with whosrdaddy's code:

Code:
RunDosInMemo('cmd /c "chkdsk c:"',Memo1) ;

This is essentially doing #3 on my list above. The problem with what was done here is essentially the same one that causes us to have GetSystemDirectory type code. The command interpreter path can change for a number of reasons between systems. So it's good to have code which will provide COMSPEC for this too. I don't have it handy but I can post it later, if necessary.

----------
Measurement is not management.
 
Are you wanting to run the batch file or convert the batch file into the program so the program does all the work?

To run the batch file, you just call it through the command-interpreter (keeping in mind my previous post, of course):

Code:
RunDosInMemo('cmd /c "mybatchfile.bat"',Memo1) ;

For going through the program, it's probably better to write equivalent functionality for the program (FindFirst/FindNext in place of For, etc).

----------
Measurement is not management.
 
doctorjellybean,

just call the batch file like this:

Code:
RunDosInMemo('cmd /c "batchfile.bat"', Memo1) ;

you need to use cmd as it understands the batchfile language.

Glenn,

chkdsk is a common tool between windows versions and resides in the system path, so no path should be provided
unless the PATH environment variable is changed before calling this function.

a quick check with SET PATH learns me that the systempath is included....

/Daddy


-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
Glen9999
Are you wanting to run the batch file or convert the batch file into the program so the program does all the work?

Ideally I would like the program to do all the work, otherwise I could just, as you and Daddy said, call the batch file through the command interpreter. The latter depends on the batch file being present, therefore for the sake of simplicity, I would prefer the program doing the job.

Of course, I don't want to reinvent the wheel lol.
 
then you don't need this piece of code and use shellexecute and use Findfirst/Findnext like Glenn said...

-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
chkdsk is a common tool between windows versions and resides in the system path

the call of chkdsk wasn't the issue, the issue was the hard-coding of the command-line interpreter. I could put the code I quoted on a couple of environments here and have it break simply because you hard coded it (not to mention other innane things like the user installing windows to a directory other than C:\WINDOWS, blowing away the path, etc).

Never assume anything - I've gotten bit several times for doing that when it comes to paths, etc. Safe coding practice is to always locate the file and always use the full path, and never hard-code anything unless it can be specifically guaranteed on all environments.

Anyhow, the code I promised before:
Code:
function GetComSpec: String;
   var
     PathName: PChar;
     Buffer: array[0..255] of char;
   begin
     PathName := PChar('COMSPEC');
     GetEnvironmentVariable(PathName, @Buffer, Sizeof(Buffer));
     Result := String(Buffer);
   end;

----------
Measurement is not management.
 
Glenn, agreed, COMSPEC contains the name and fullpath of the command line interpreter

sure it would be safer to

call RunDosInMemo(getCOMSPEC + ' /c "chkdsk"');

but it wouldn't matter on 2000/XP/Vista systems

only if you go to older systems like Win95,98 and ME this will make a difference since the interpreter is command.com and not cmd. Thanks for pointing that out anyway :)

/Daddy


-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
I'm looking at this one, and am trying to fix a few problems. One of them being the way the original just "slopped" text onto the TMemo (almost there on that one).

More bothersome:
The more bothersome one is the fact that ReadFile tends to lock up for me most times if I'm at an EOF condition or anywhere close to it.

As well in some cases, the text gets rearranged so it does not appear as it did in the DOS command box.

Any ideas?

----------
Measurement is not management.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top