简体   繁体   中英

Inno Setup installer page with Exec batch and file Stream progress never exits Stream and hangs indefinitely even with proper bat termination

I am running into problems with my inno setup hanging indefinitely while logging output from a bat file. The bat file is launched properly, and terminates with an EXIT 0. This has been tested by launching the bat with the regular [RUN] {cmd} approach and wait enabled. As well, I have tried changing the exec command to run with SW_SHOW , and it does run the bat, and the window closes.

Hoping to get some help with this please!

The code creates a page and a window for a file Stream from output of the {cmd} batch to be logged. This works, and the bat file logs until it's last line and exits. However, the page hangs indefinitely. No Response code is ever caught by Exec, and/or the file Stream logic is flawed somehow since I actually do get the input back from the log, but it never seems to stop looping. In Inno Setup's debug console, the UpdateProgress method just keeps showing the same number for a long time... then shows a box like: ☐ However, it never finishes this loop and the page never moves forward.

(Working from code provided in the answer @ Embedded CMD in Inno Setup installer (show command output on a custom page) by Martin Prikryl )

[code]
// Embed installation bat into installation progress page
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 RunInstallBatInsideProgress;
  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));
      AppPath := ExpandConstant('{app}\installers\install.bat');
      ProgressFileName := ExpandConstant('{app}\logs\install-progress.log');
      Log(Format('Expecting progress in %s', [ProgressFileName]));
      Command := Format('""%s" > "%s""', [AppPath, ProgressFileName]);
      if not Exec(ExpandConstant('{cmd}'), '/c ' + Command + ExpandConstant(' {app}\PIPE'), '', 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;


procedure CurStepChanged(CurStep: TSetupStep);
  begin
    if CurStep=ssPostInstall then
      begin
           RunInstallBatInsideProgress;
      end
  end;   

I have adapted this code slightly so as to do the following:

  • Instead of as in the original example this being launched from a button, I have changed the procedure method to "RunInstallBatInsideProgress", and run it from the procedure CurStepChanged , on the step of ssPostInstall
  • In addition, I have added some parameters to my bat file, while also expanding those variables.

Finally, in my bat file, the only thing I am doing is running some git clone commands. For a sample install.bat, anything will do as long as it does an EXIT 0, but try this:

SETLOCAL ENABLEDELAYEDEXPANSION 
setlocal
::: Set backward slashes to forward slashes
SET variable=%CLONE_DIR%
IF "%variable%"=="" SET variable= %~1
SET CLONE_DIR=%variable:\=/%

git clone https://github.com/poelzi/git-clone-test.git %CLONE_DIR%

ECHO "SUCCESS"
EXIT 0

Thanks Martin Prikryl for checking my sample code and letting me know it worked. I guess I had some red herrings in my testing process, because I was also able to get it to work with the sample.

However... My actual bat file, which had quite a few more calls to git in it, still looped indefinitely. I began to wonder if git was somehow still attached to my bat as a subprocess, and tried a number of different approaches including CALL /b and START /b within my bat file. I also tried this inside my inno setup pascal instead of {cmd}.

I also tried GOTO:EOF, EXIT, EXIT 0, EXIT /b, EXIT /b 0 in my bat file.

However none of these worked.

Solution:

Instead of using {cmd}, in my inno setup pascal, I just called the bat file directly. Wa-la, Frankly, I am not 100% sure if it was git as a subprocess. or some other peculiarity with windows batch commands, When I have some time, I will use process explorer to try and figure it out, but for now. I can finish my project.

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