Solved

Need help with launching console app.

Posted on 2008-10-27
18
896 Views
Last Modified: 2012-05-05
Hi

I'm launching a console app using this code:

procedure TmainForm.ExecWithPipe(FName : PChar; OutPut : TStrings);
var
  StartupInfo : TStartupInfo;
  ProcessInfo : TProcessInformation;
  Buffer : array[0..255] of char;
  Wynik : string;
  bRead  : cardinal;
  licznik : integer;
  hRead, hWrite : THandle;
  saAttr : TSECURITYATTRIBUTES;
  OutSt : TMemoryStream;

begin
  // Set the bInheritHandle flag so pipe handles are inherited.
  saAttr.nLength := sizeof(TSECURITYATTRIBUTES);
  saAttr.bInheritHandle := true;
  saAttr.lpSecurityDescriptor := nil;
  if not(CreatePipe(hRead, hWrite, @saAttr, 0)) then
    begin
      ShowMessage('Can not create the pipe!');
      Exit;
    end;
  if not(AllocConsole) then
    begin
      ShowMessage('Can not allocate console for the child!');
      Exit;
    end;

  try
    FillChar(StartupInfo,Sizeof(StartupInfo),#0);
    StartupInfo.cb := Sizeof(StartupInfo);
    StartupInfo.dwFlags := STARTF_USESTDHANDLES;
    StartupInfo.wShowWindow := SW_HIDE; //SW_SHOWMINIMIZED;
    //Associate our handles with our child process
    StartupInfo.hStdInput := GetStdHandle(STD_INPUT_HANDLE);
    StartupInfo.hStdOutput:= hWrite; //we catch output
    StartupInfo.hStdError := hWrite; //and also error
    if not(CreateProcess(nil, FName, nil, nil, true, CREATE_NO_WINDOW or HIGH_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo)) then
      ShowMessage('Can not create process')
    else
      begin
        Sleep(300);
        //loop until terminated
        while (WaitforSingleObject(ProcessInfo.hProcess, 0) <> WAIT_OBJECT_0) do
          begin
            licznik := 0;
            repeat
              Application.ProcessMessages;
              Sleep(10);
              licznik := licznik + 10;
            until (licznik >= 1000);
            //now read all the output of the child and put it to a memo
            OutSt := TMemoryStream.Create;
            repeat
              if ReadFile(hRead, Buffer, 80, bRead, nil) then
                OutSt.WriteBuffer(Buffer, bRead)
              else
                Break;
            until bRead <> 80;
            OutSt.Seek(0,0); //seek to begining
            //read memo from stream
            SetLength(Wynik, OutSt.Size);
            OutSt.ReadBuffer(PChar(Wynik)^, OutSt.Size);
            OutPut.Clear;
            OutPut.Add(Wynik);
//            OutPut.LoadFromStream(OutSt);
            Sleep(100);
            OutSt.Free;
          end;
      end;
  finally
    //close read and write handles of our pipe
    Sleep(300);
    CloseHandle(hRead);
    CloseHandle(hWrite);
    if not(FreeConsole) then MessageBeep(0);
  end;
end;

it works, but I need to hide the console window (the line StartupInfo.wShowWindow := SW_HIDE; doesn't do the trick); and also while i'm using var wynik to pass the results the console window sometimes doesn't close properly (it works fine when i use the OutPut.LoadFromStream - the thing is I need to copy the results to a string for later use); Can anyone help ?
0
Comment
Question by:tadzio_blah
  • 11
  • 7
18 Comments
 
LVL 26

Expert Comment

by:Russell Libby
ID: 22814454
You have a couple of bugs going on. First, you need to tell the CreateProcess to use the wShowWindow param by passing the flags:

StartupInfo.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES

Also, you never close the process or thread handles returned from CreateProcess. Either fix the code up, or download my Pipes wrapper from:

http://home.roadrunner.com/~rllibby/downloads/pipes.zip

You will find a TPipeConsole component that simplifies what you are trying to do (as well as offering additional functionality). An example of usage:

procedure TForm1.Button1Click(Sender: TObject);
var  strmOut:       TMemoryStream;
     listData:      TStringList;
begin

  strmOut:=TMemoryStream.Create;
  try
     // Synchronized execute (application, command line, output stream, error stream, time out {optional})
     if (PipeConsole1.Execute('', 'cmd.exe /c dir c:', strmOut, nil, 5000) = ERROR_SUCCESS) then
     begin
        listData:=TStringList.Create;
        try
           strmOut.Position:=0;
           listData.LoadFromStream(strmOut);
           ShowMessage(listData.Text);
        finally
           listData.Free;
        end;
     end;
  finally
     strmOut.Free;
  end;

end;


Regards,
Russell

0
 

Author Comment

by:tadzio_blah
ID: 22816128
Hi. Since I need to read the progress from the app I don't think that the code you wrote applies here. And I've tried adding STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES to my code, and it didn't help - the console window still shows itself (note: i need to hide it, and not minimize it).

"you never close the process or thread handles returned from CreateProcess" - can't really find what this is regarding in my code (the code that i've found here on EE, that was supposed to work perfectly). But what i can see is that the 2 created hadnles are closed, and the console is freed - what else needs to be freed ??
0
 

Author Comment

by:tadzio_blah
ID: 22816208
was this what you meant:
    CloseHandle(ProcessInfo.hThread);
    CloseHandle(ProcessInfo.hProcess);
?
0
ScreenConnect 6.0 Free Trial

Want empowering updates? You're in the right place! Discover new features in ScreenConnect 6.0, based on partner feedback, to keep you business operating smoothly and optimally (the way it should be). Explore all of the extras and enhancements for yourself!

 
LVL 26

Expert Comment

by:Russell Libby
ID: 22816452
I realize you are trying to start it hidden.

I'm just saying that the way you specified the flags originally, the CreateProcess would not acknowledge the flag that you set. If it is still showing up visible, then it is possible that the console app has been written to show itself. (makes its own call to ShowWindow). In that case, you could attempt to call ShowWindow(handle, SW_HIDE) after the console window has been created. The console app will appear briefly,  but there won't be much way around it.

As to the progress... you obviously didn't even check the free component offered to you. It allows for a number of things NOT offered in other redirect packages. eg:

- Sync handling (as exampled above)
- Threaded handling with events to capture OnOutput, OnError, and OnStop. To handle your "progress", you would just handle the stream passed in the OnOutput event. If the console closes itself, you will receive an OnStop event. If you need to close it manually, just call .Stop
- Ctrl-Break and Ctrl-C sending to the process
- Proper cleanup of pipes and process/thread handles.
- Visibility property set before calling Start will be applied to the CreateProcess. Setting it after will call ShowWindow(...) for you.

And yes, when I said thread and process handles that was what I meant.

>> was this what you meant:
    CloseHandle(ProcessInfo.hThread);
    CloseHandle(ProcessInfo.hProcess);

--

Russell




0
 

Author Comment

by:tadzio_blah
ID: 22828165
can you post a sample code for using your pipes unit to launch an app in the background and read messages from it ? (my app will display only one line of progress, or an error msg).
0
 

Author Comment

by:tadzio_blah
ID: 22828182
some warnings came up while i was building my project with the pipes.pas included:

[DCC Warning] Pipes.pas(3179): W1000 Symbol 'RaiseLastWin32Error' is deprecated
[DCC Warning] Pipes.pas(3187): W1000 Symbol 'RaiseLastWin32Error' is deprecated
[DCC Warning] Pipes.pas(3193): W1000 Symbol 'RaiseLastWin32Error' is deprecated
[DCC Warning] Pipes.pas(3656): W1000 Symbol 'RaiseList' is deprecated
[DCC Warning] Pipes.pas(3659): W1000 Symbol 'RaiseList' is deprecated
[DCC Warning] Pipes.pas(3661): W1000 Symbol 'RaiseList' is deprecated
[DCC Warning] Pipes.pas(4384): W1000 Symbol 'RaiseLastWin32Error' is deprecated
[DCC Warning] Pipes.pas(4388): W1000 Symbol 'RaiseLastWin32Error' is deprecated

is this ok ?
0
 

Author Comment

by:tadzio_blah
ID: 22828195
delphi help system says something like this:

RaiseLastWin32Error is a deprecated Windows-only procedure. New applications should use RaiseLastOSError instead.
0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 22834253
Give me an hour, and I will provide sample code. As to the deprecated warnings; they are ok to ignore. Keep in mind that this source was written on D5, but has been made compatible with later versions.

Russell
0
 

Author Comment

by:tadzio_blah
ID: 22834607
I've changed the RaiseLastWin32Error to RaiseLastOSError, don't really know what's the substitute for RaiseList, so I've left it alone. I'd like to attach the console app that I'll be working with, but I can't due to extension restrictions.
0
 
LVL 26

Accepted Solution

by:
Russell Libby earned 200 total points
ID: 22834967
Ok,

1 - Re-download the pipes.zip from my site again. (which means you will need to replace the RaiseLastWin32Errors again... or just ignore the warning). I added code injection to have the console return its own HWND, vs enumerating the process windows. It will fall back to enum if needed.

2 - Recompile the component set

3 . Example source code below. Run it, and just paste your command into the command line to run. Then hit start.

4. Let me know how it goes.

Russell

--- source---
unit main;

interface

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

type
  TForm1            = class(TForm)
    btnStart:       TButton;
    btnStop:        TButton;
    btnCtrlbreak:   TButton;
    btnCtrlC:       TButton;
    txtOutput:      TMemo;
    PipeConsole1:   TPipeConsole;
    txtError:       TMemo;
    Label1:         TLabel;
    txtCommand:     TEdit;
    procedure       FormCreate(Sender: TObject);
    procedure       btnStartClick(Sender: TObject);
    procedure       PipeConsole1Stop(Sender: TObject; ExitValue: Cardinal);
    procedure       PipeConsole1Output(Sender: TObject; Stream: TStream);
    procedure       btnStopClick(Sender: TObject);
    procedure       btnCtrlbreakClick(Sender: TObject);
    procedure       btnCtrlCClick(Sender: TObject);
    procedure       PipeConsole1Error(Sender: TObject; Stream: TStream);
  private
     // Private declarations
  public
     // Public declarations
  end;

var
  Form1:            TForm1;

implementation
{$R *.DFM}

procedure TForm1.btnStartClick(Sender: TObject);
begin

  //
  // Example to demo switching visibility after the process has started. You
  // would normally leave this set to the desired value in order to have it
  // applied during process creating
  //
  PipeConsole1.Visible:=True;

  // Check command line to run with "cmd"
  if (Length(txtCommand.Text) > 0) then
  begin
     // Build command line
     if PipeConsole1.Start('', txtCommand.Text) then
     begin

        //
        // NOTE: For console apps that show themselves regardless of StartupInfo
        // settings, you can apply the visible at this time.
        //
        PipeConsole1.Visible:=False;
        //

        // Set button state
        btnStart.Enabled:=False;
        btnStop.Enabled:=True;
        btnCtrlbreak.Enabled:=True;
        btnCtrlC.Enabled:=True;

     end;
  end;

end;

procedure TForm1.FormCreate(Sender: TObject);
begin

  // Set initial button state
  btnStart.Enabled:=True;
  btnStop.Enabled:=False;
  btnCtrlbreak.Enabled:=False;
  btnCtrlC.Enabled:=False;

end;

procedure TForm1.PipeConsole1Stop(Sender: TObject; ExitValue: Cardinal);
begin

  // Reset button state
  btnStart.Enabled:=True;
  btnStop.Enabled:=False;
  btnCtrlbreak.Enabled:=False;
  btnCtrlC.Enabled:=False;

end;

procedure TForm1.PipeConsole1Output(Sender: TObject; Stream: TStream);
var  listTemp:      TStringList;
begin

  // Create string list for processing
  listTemp:=TStringList.Create;

  // Resource protection
  try
     // Rewind the stream
     Stream.Position:=0;
     // Load form stream
     listTemp.LoadFromStream(Stream);
     // Lock the list
     txtOutput.Lines.EndUpdate;
     // Resource protection
     try

        // Example: append the new lines to the memo (to get total result)
        //
        // txtOutput.Lines.AddStrings(listTemp);
        //

        //
        // FOR DEMO PURPOSES: limit the number of lines displayed. For example,
        // if running "dir c:\ /s" the output can be huge (not a problem), but the
        // displaying of this in a memo will get slower as the memo gets more data
        //
        txtOutput.Lines.Assign(listTemp);


     finally
        // Unlock the list
        txtOutput.Lines.EndUpdate;
     end;
  finally
     // Free the list
     listTemp.Free;
  end;

end;

procedure TForm1.btnStopClick(Sender: TObject);
begin

  // Force the process to STOP now. You can also pass a desired exit code
  PipeConsole1.Stop(0);

end;

procedure TForm1.btnCtrlbreakClick(Sender: TObject);
begin

  // Submit a control - break to the process
  PipeConsole1.SendCtrlBreak;

end;

procedure TForm1.btnCtrlCClick(Sender: TObject);
begin

  // Submit a control - c to the process
  PipeConsole1.SendCtrlC;

end;

procedure TForm1.PipeConsole1Error(Sender: TObject; Stream: TStream);
var  listTemp:      TStringList;
begin

  // Create string list for processing
  listTemp:=TStringList.Create;

  // Resource protection
  try
     // Rewind the stream
     Stream.Position:=0;
     // Load form stream
     listTemp.LoadFromStream(Stream);
     // Lock the list
     txtError.Lines.EndUpdate;
     // Resource protection
     try

        // Example: append the new lines to the memo (to get total result)
        //
        // txtError.Lines.AddStrings(listTemp);
        //

        //
        // FOR DEMO PURPOSES: limit the number of lines displayed. For example,
        // if running "dir c:\ /s" the output can be huge (not a problem), but the
        // displaying of this in a memo will get slower as the memo gets more data
        //
        txtError.Lines.Assign(listTemp);


     finally
        // Unlock the list
        txtError.Lines.EndUpdate;
     end;
  finally
     // Free the list
     listTemp.Free;
  end;

end;

end.

--- dfm ---

object Form1: TForm1
  Left = 302
  Top = 285
  Width = 725
  Height = 536
  Caption = 'Console Runner'
  Color = clBtnFace
  Font.Charset = ANSI_CHARSET
  Font.Color = clWindowText
  Font.Height = -13
  Font.Name = 'Courier New'
  Font.Style = []
  OldCreateOrder = False
  OnCreate = FormCreate
  PixelsPerInch = 96
  TextHeight = 16
  object Label1: TLabel
    Left = 112
    Top = 12
    Width = 152
    Height = 16
    Caption = 'Command Line To Run'
  end
  object btnStart: TButton
    Left = 8
    Top = 12
    Width = 93
    Height = 25
    Caption = 'Start'
    TabOrder = 0
    OnClick = btnStartClick
  end
  object btnStop: TButton
    Left = 8
    Top = 44
    Width = 93
    Height = 25
    Caption = 'Stop'
    TabOrder = 1
    OnClick = btnStopClick
  end
  object txtOutput: TMemo
    Left = 112
    Top = 64
    Width = 597
    Height = 273
    ScrollBars = ssBoth
    TabOrder = 2
  end
  object txtCommand: TEdit
    Left = 112
    Top = 36
    Width = 597
    Height = 24
    TabOrder = 3
    Text = 'cmd /c dir c:\ /s'
  end
  object txtError: TMemo
    Left = 112
    Top = 344
    Width = 597
    Height = 141
    ScrollBars = ssBoth
    TabOrder = 4
  end
  object btnCtrlbreak: TButton
    Left = 8
    Top = 76
    Width = 93
    Height = 25
    Caption = 'Ctrl-Break'
    TabOrder = 5
    OnClick = btnCtrlbreakClick
  end
  object btnCtrlC: TButton
    Left = 8
    Top = 108
    Width = 93
    Height = 25
    Caption = 'Ctrl-C'
    TabOrder = 6
    OnClick = btnCtrlCClick
  end
  object PipeConsole1: TPipeConsole
    LastError = 0
    OnError = PipeConsole1Error
    OnOutput = PipeConsole1Output
    OnStop = PipeConsole1Stop
    Visible = True
    Left = 8
    Top = 148
  end
end

0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 22835287
Btw,

RaiseList = AcquireExceptionObject. You will find the following in the pipes.pas. If I get time, I will try and take a pass at the defines once I get a list of the compiler versions together.


//// Thread window procedure ///////////////////////////////////////////////////
function ThreadWndProc(Window: HWND; Message, wParam, lParam: Longint): Longint; stdcall;
begin

  {$IFDEF VER140} { Borland Delphi 6.0 }
     {$DEFINE DELPHI6_OR_7}
  {$ENDIF}

  {$IFDEF VER150} { Borland Delphi 7.0 }
     {$DEFINE DELPHI6_OR_7}
  {$ENDIF}

  // Handle the window message
  case Message of
     // Exceute the method in thread
     CM_EXECPROC       :
     begin
        // The lParam constains the thread sync information
        with TThreadSync(lParam) do
        begin
           // Set message result
           result:=0;
           // Exception trap
           try
              // Clear the exception
              FSyncRaise:=nil;
              // Call the method
              FMethod;
           except
              {$IFNDEF DELPHI6_OR_7}
              if not(RaiseList = nil) then
              begin
                 // Get exception object from frame
                 FSyncRaise:=PRaiseFrame(RaiseList)^.ExceptObject;
                 // Clear frame exception object
                 PRaiseFrame(RaiseList)^.ExceptObject:=nil;
              end;
              {$ELSE}
              FSyncRaise:=AcquireExceptionObject;
              {$ENDIF}
           end;
        end;
     end;
     // Thead destroying
     CM_DESTROYWINDOW  :
     begin
        // Get instance of sync manager
        TSyncManager.Instance.DoDestroyWindow(TSyncInfo(lParam));
        // Set message result
        result:=0;
     end;
  else
     // Call the default window procedure
     result:=DefWindowProc(Window, Message, wParam, lParam);
  end;

end;
0
 

Author Comment

by:tadzio_blah
ID: 22835865
works fine... thank you.
0
 

Author Comment

by:tadzio_blah
ID: 22835889
thanks for the info, i'll try to change them and see if it works ok.
0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 22836182
Your welcome. I will let you know when I have the pipes.pas updated to handle the deprecated warnings (should be in the morning).

Russell
0
 

Author Comment

by:tadzio_blah
ID: 22837705
Ok.
I'll be greatfull if you could tell me one more thing... how can I set the priority of the launched app ?
0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 22842901
Code on my site has been updated. Defines for D6 and above to handle deprecated warnings and a .Priority property of the console component. The values are the same as the TThreadPriority. Check the ForcePriority(...) method of the component for an example of calling SetThreadPriority.


Russell
0
 

Author Comment

by:tadzio_blah
ID: 22847981
thanks, but the priority of the launched app isn't changing after setting the .Priority to for eg. tpHighest
0
 

Author Comment

by:tadzio_blah
ID: 22848016
is it possible to change the console apps priority class to HIGH_PRIORITY_CLASS and priority to tpHighest ?
0

Featured Post

Problems using Powershell and Active Directory?

Managing Active Directory does not always have to be complicated.  If you are spending more time trying instead of doing, then it's time to look at something else. For nearly 20 years, AD admins around the world have used one tool for day-to-day AD management: Hyena. Discover why

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

This article explains how to create forms/units independent of other forms/units object names in a delphi project. Have you ever created a form for user input in a Delphi project and then had the need to have that same form in a other Delphi proj…
In this tutorial I will show you how to use the Windows Speech API in Delphi. I will only cover basic functions such as text to speech and controlling the speed of the speech. SAPI Installation First you need to install the SAPI type library, th…
This Micro Tutorial will give you a basic overview how to record your screen with Microsoft Expression Encoder. This program is still free and open for the public to download. This will be demonstrated using Microsoft Expression Encoder 4.
With Secure Portal Encryption, the recipient is sent a link to their email address directing them to the email laundry delivery page. From there, the recipient will be required to enter a user name and password to enter the page. Once the recipient …

777 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