简体   繁体   中英

Embedded CMD in Inno Setup installer (show command output on a custom page)

I created an Input page that executes a command line app using the created variables from those inputs. Naturally, the cmd window pop ups on my screen. I would like to know if there is any way to embed the cmd window (or the output) on my Inno Setup installer page.

I'm running Inno Setup 5.6.1 (because of Windows XP compatibility), but I'm OK if i have to switch to the last version.

[Code]
var
  MAIL: TInputQueryWizardPage;
  Final: TWizardPage;
  BotonIniciar: Tbutton;

procedure BotonIniciarOnClick(Sender: TObject);
begin
  WizardForm.NextButton.Onclick(nil);
  Exec(ExpandConstant('{tmp}\imapsync.exe'),'MAIL.Values[0]','', SW_SHOW,
    ewWaitUntilTerminated, ResultCode);
end;

procedure InitializeWizard;
begin
  MAIL := CreateInputQueryPage(wpWelcome, '', '', '');
  MAIL.Add('Please input your information', False);

  BotonIniciar := TNewButton.Create(MAIL);
  BotonIniciar.Caption := 'Iniciar';
  BotonIniciar.OnClick := @BotonIniciarOnClick;
  BotonIniciar.Parent :=  WizardForm;
  BotonIniciar.Left := WizardForm.NextButton.Left - 250 ;
  BotonIniciar.Top := WizardForm.CancelButton.Top - 10;
  BotonIniciar.Width := WizardForm.NextButton.Width + 60;
  BotonIniciar.Height := WizardForm.NextButton.Height + 10;
end;

I'm might be missing some parts of the code, but I think it's understandable. Fist Ii create the Input page, then I create a button with the OnClick property that calls to the BotonIniciarOnClick procedure.

Actually, the code works great. But as I said I'm having a floating cmd window.

I would like to see something like this:

例子

Its just a random image I took from google.
What I want to see is similar to a standard "show details" option on an installer

You can redirect the command output to a file and monitor the file for changes, loading them to list box (or maybe a memo box).

var
  ProgressPage: TOutputProgressWizardPage;
  ProgressListBox: TNewListBox;

function SetTimer(
  Wnd: LongWord; IDEvent, Elapse: LongWord; TimerFunc: LongWord): LongWord;
  external 'SetTimer@user32.dll stdcall';
function KillTimer(hWnd: LongWord; uIDEvent: LongWord): BOOL;
  external 'KillTimer@user32.dll stdcall';

var
  ProgressFileName: string;

function BufferToAnsi(const Buffer: string): AnsiString;
var
  W: Word;
  I: Integer;
begin
  SetLength(Result, Length(Buffer) * 2);
  for I := 1 to Length(Buffer) do
  begin
    W := Ord(Buffer[I]);
    Result[(I * 2)] := Chr(W shr 8); { high byte }
    Result[(I * 2) - 1] := Chr(Byte(W)); { low byte }
  end;
end;

procedure UpdateProgress;
var
  S: AnsiString;
  I, L, Max: Integer;
  Buffer: string;
  Stream: TFileStream;
  Lines: TStringList;
begin
  if not FileExists(ProgressFileName) then
  begin
    Log(Format('Progress file %s does not exist', [ProgressFileName]));
  end
    else
  begin
    try
      { Need shared read as the output file is locked for writting, }
      { so we cannot use LoadStringFromFile }
      Stream := TFileStream.Create(ProgressFileName, fmOpenRead or fmShareDenyNone);
      try
        L := Stream.Size;
        Max := 100*2014;
        if L > Max then
        begin
          Stream.Position := L - Max;
          L := Max;
        end;
        SetLength(Buffer, (L div 2) + (L mod 2));
        Stream.ReadBuffer(Buffer, L);
        S := BufferToAnsi(Buffer);
      finally
        Stream.Free;
      end;
    except
      Log(Format('Failed to read progress from file %s - %s', [
                 ProgressFileName, GetExceptionMessage]));
    end;
  end;

  if S <> '' then
  begin
    Log('Progress len = ' + IntToStr(Length(S)));
    Lines := TStringList.Create();
    Lines.Text := S;
    for I := 0 to Lines.Count - 1 do
    begin
      if I < ProgressListBox.Items.Count then
      begin
        ProgressListBox.Items[I] := Lines[I];
      end
        else
      begin
        ProgressListBox.Items.Add(Lines[I]);
      end
    end;
    ProgressListBox.ItemIndex := ProgressListBox.Items.Count - 1;
    ProgressListBox.Selected[ProgressListBox.ItemIndex] := False;
    Lines.Free;
  end;

  { Just to pump a Windows message queue (maybe not be needed) }
  ProgressPage.SetProgress(0, 1);
end;

procedure UpdateProgressProc(
  H: LongWord; Msg: LongWord; Event: LongWord; Time: LongWord);
begin
  UpdateProgress;
end;

procedure BotonIniciarOnClick(Sender: TObject);
var
  ResultCode: Integer;
  Timer: LongWord;
  AppPath: string;
  AppError: string;
  Command: string;
begin
  ProgressPage :=
    CreateOutputProgressPage(
      'Installing something', 'Please wait until this finishes...');
  ProgressPage.Show();
  ProgressListBox := TNewListBox.Create(WizardForm);
  ProgressListBox.Parent := ProgressPage.Surface;
  ProgressListBox.Top := 0;
  ProgressListBox.Left := 0;
  ProgressListBox.Width := ProgressPage.SurfaceWidth;
  ProgressListBox.Height := ProgressPage.SurfaceHeight;

  { Fake SetProgress call in UpdateProgressProc will show it, }
  { make sure that user won't see it }
  ProgressPage.ProgressBar.Top := -100;

  try
    Timer := SetTimer(0, 0, 250, CreateCallback(@UpdateProgressProc));

    ExtractTemporaryFile('install.bat');
    AppPath := ExpandConstant('{tmp}\install.bat');
    ProgressFileName := ExpandConstant('{tmp}\progress.txt');
    Log(Format('Expecting progress in %s', [ProgressFileName]));
    Command := Format('""%s" > "%s""', [AppPath, ProgressFileName]);
    if not Exec(ExpandConstant('{cmd}'), '/c ' + Command, '', SW_HIDE,
         ewWaitUntilTerminated, ResultCode) then
    begin
      AppError := 'Cannot start app';
    end
      else
    if ResultCode <> 0 then
    begin
      AppError := Format('App failed with code %d', [ResultCode]);
    end;
    UpdateProgress;
  finally
    { Clean up }
    KillTimer(0, Timer);
    ProgressPage.Hide;
    DeleteFile(ProgressFileName);
    ProgressPage.Free();
  end;

  if AppError <> '' then
  begin 
    { RaiseException does not work properly while TOutputProgressWizardPage is shown }
    RaiseException(AppError);
  end;
end;

在此处输入图片说明


Above was tested with a batch file like:

@echo off
echo Starting
echo Doing A...
echo Extracting something...
echo Doing B...
echo Extracting something...
timeout /t 1 > nul
echo Doing C...
echo Extracting something...
echo Doing D...
echo Extracting something...
timeout /t 1 > nul
echo Doing E...
echo Extracting something...
echo Doing F...
echo Extracting something...
timeout /t 1 > nul
...

If you want to display the output as part of the installation process, instead of on a button click, see:
Execute a batch file after installation and display its output on a custom page before Finished page in Inno Setup

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM