添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

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

Ask Question

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
  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';
  ProgressFileName: string;
function BufferToAnsi(const Buffer: string): AnsiString;
    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 }
procedure UpdateProgress;
    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]));
      begin
          { Need shared read as the output file is locked for writting, }
          { so we cannot use LoadStringFromFile }
          Stream := TFileStream.Create(ProgressFileName, fmOpenRead or fmShareDenyNone);
            L := Stream.Size;
            Max := 100*2014;
            if L > Max then
            begin
              Stream.Position := L - Max;
              L := Max;
            SetLength(Buffer, (L div 2) + (L mod 2));
            Stream.ReadBuffer(Buffer, L);
            S := BufferToAnsi(Buffer);
          finally
            Stream.Free;
        except
          Log(Format('Failed to read progress from file %s - %s', [
                     ProgressFileName, GetExceptionMessage]));
    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];
          begin
            ProgressListBox.Items.Add(Lines[I]);
        ProgressListBox.ItemIndex := ProgressListBox.Items.Count - 1;
        ProgressListBox.Selected[ProgressListBox.ItemIndex] := False;
        Lines.Free;
    { Just to pump a Windows message queue (maybe not be needed) }
    ProgressPage.SetProgress(0, 1);
procedure UpdateProgressProc(
  H: LongWord; Msg: LongWord; Event: LongWord; Time: LongWord);
begin
  UpdateProgress;
procedure RunInstallBatInsideProgress;
      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;
      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';
        if ResultCode <> 0 then
          begin
            AppError := Format('App failed with code %d', [ResultCode]);
      UpdateProgress;
    finally
        { Clean up }
        KillTimer(0, Timer);
        ProgressPage.Hide;
        DeleteFile(ProgressFileName);
        ProgressPage.Free();
    if AppError <> '' then
      begin 
        { RaiseException does not work properly while TOutputProgressWizardPage is shown }
        RaiseException(AppError);
procedure CurStepChanged(CurStep: TSetupStep);
  begin
    if CurStep=ssPostInstall then
      begin
           RunInstallBatInsideProgress;

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
                    Sorry about that, in reproducing my code, that was a mistake. I have edited the snippet to add that back in, as it is definitely part of my code, and it definitely runs the bat :)
    – FuzzkingCool
                    Jul 16, 2020 at 13:13
                    Your updated code works for me. – Add some logging to your code using Log function to debug the problem.
    – Martin Prikryl
                    Jul 16, 2020 at 16:15
    

    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.

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.