Inno Setup安装程序中的嵌入式CMD(在自定义页面上显示命令输出) [英] Embedded CMD in Inno Setup installer (show command output on a custom page)

查看:159
本文介绍了Inno Setup安装程序中的嵌入式CMD(在自定义页面上显示命令输出)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我创建了一个输入页面,该页面使用从这些输入中创建的变量来执行命令行应用程序。自然地, cmd 窗口在我的屏幕上弹出。我想知道是否有任何方法可以将 cmd 窗口(或输出)嵌入到我的Inno Setup安装程序页面上。



我正在运行Inno Setup 5.6.1(因为与Windows XP兼容),但是如果必须切换到最新版本,我可以。



< pre class = lang-pascal prettyprint-override> [代码]
var
邮件:TInputQueryWizardPage;
最终:TWizardPage;
BotonIniciar:Tbutton;

程序BotonIniciarOnClick(Sender:TObject);
开始
WizardForm.NextButton.Onclick(nil);
Exec(ExpandConstant('{tmp} \imapsync.exe'),'MAIL.Values [0]',',SW_SHOW,
ewWaitUntilTerminated,ResultCode);
结尾;

程序InitializeWizard;
开始
邮件:= CreateInputQueryPage(wpWelcome,’’,’,’);
MAIL.Add(‘请输入您的信息’,错);

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;
结尾;

我可能会丢失部分代码,但我认为这是可以理解的。
Fist Ii创建输入页面,然后创建一个带有 OnClick 属性的按钮,该按钮调用 BotonIniciarOnClick 过程。



实际上,该代码非常有用。但是正如我说的那样,我有一个 cmd 浮动窗口。



我希望看到这样的内容:





这只是我从Google拍摄的随机图像。

我想要看到的内容类似于安装程序上的标准显示详细信息选项

解决方案

您可以将命令输出重定向到文件,并监视文件中的更改,将其加载到列表框(或备忘录框)中。

  var 
ProgressPage:TOutputProgressWizardPage;
ProgressListBox:TNewListBox;

函数SetTimer(
Wnd:LongWord; IDEvent,Elapse:LongWord; TimerFunc:LongWord):LongWord;
外部 SetTimer@user32.dll stdcall;
函数KillTimer(hWnd:LongWord; uIDEvent:LongWord):BOOL;
外部 KillTimer@user32.dll stdcall;

var
ProgressFileName:字符串;

函数BufferToAnsi(con​​st Buffer:string):AnsiString;
var
W:单词;
I:整数;
begin
SetLength(Result,Length(Buffer)* 2);
for I:= 1到Length(Buffer)做
开始
W:= Ord(Buffer [I]);
结果[(I * 2)]:= Chr(W shr 8); {高字节}
结果[(I * 2)-1]:= Chr(Byte(W)); {低字节}
结尾;
结尾;

过程UpdateProgress;
var
S:AnsiString;
I,L,Max:整数;
缓冲区:字符串;
流:TFileStream;
行:TStringList;
如果没有FileExists(ProgressFileName),则以
开头;然后

开头。
Log(Format(进度文件%s不存在,[ProgressFileName]));
结束
否则
开始
尝试
{需要共享读取,因为输出文件被锁定写,}
{所以我们不能使用LoadStringFromFile}
Stream:= TFileStream.Create(ProgressFileName,fmOpenRead或fmShareDenyNone);
try
L:= Stream.Size;
Max:= 100 * 2014;如果L>
最大然后
开始
Stream.Position:= L-Max;
L:=最大值;
结尾;
SetLength(Buffer,(L div 2)+(L mod 2));
Stream.ReadBuffer(Buffer,L);
S:= BufferToAnsi(Buffer);
最后
流。免费;
结尾;
,但
除外Log(Format(无法从文件%s-%s中读取进度,[[
ProgressFileName,GetExceptionMessage])));
结尾;
结尾;如果S<>

’’然后
开始
Log(’Progress len =’+ IntToStr(Length(S)));
行:= TStringList.Create();
Lines.Text:= S; I的
:Lines。= 0-如果I< 1则

开始ProgressListBox.Items.Count然后
开始
ProgressListBox.Items [I]:=行[I];
结束
否则
开始
ProgressListBox.Items.Add(Lines [I]);
结束
结束;
ProgressListBox.ItemIndex:= ProgressListBox.Items.Count-1;
ProgressListBox.Selected [ProgressListBox.ItemIndex]:= False;
行。免费;
结尾;

{仅用于泵送Windows消息队列(可能不需要)}
ProgressPage.SetProgress(0,1);
结尾;

过程UpdateProgressProc(
H:LongWord; Msg:LongWord; Event:LongWord; Time:LongWord);
开始
UpdateProgress;
结尾;

程序BotonIniciarOnClick(Sender:TObject);
var
ResultCode:整数;
计时器:LongWord;
AppPath:字符串;
AppError:字符串;
命令:字符串;
开始
ProgressPage:= $​​ b $ b CreateOutputProgressPage(
‘正在安装某些东西’,‘请等待,直到完成...’);
ProgressPage.Show();
ProgressListBox:= TNewListBox.Create(WizardForm);
ProgressListBox.Parent:= ProgressPage.Surface;
ProgressListBox.Top:= 0;
ProgressListBox.Left:= 0;
ProgressListBox.Width:= ProgressPage.SurfaceWidth;
ProgressListBox.Height:= ProgressPage.SurfaceHeight;

{UpdateProgressProc中的Fake SetProgress调用将显示它,}
{确保用户不会看到它}
ProgressPage.ProgressBar.Top:= -100;

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

ExtractTemporaryFile('install.bat');
AppPath:= ExpandConstant(’{tmp} \install.bat’);
ProgressFileName:= ExpandConstant(’{tmp} \progress.txt’);
日志(格式(期待进度,%s,[ProgressFileName]));
命令:=格式(%s>%s [AppPath,ProgressFileName]);
如果不是Exec(ExpandConstant('{cmd}'),'/ c'+ Command,'',SW_HIDE,
ewWaitUntilTerminated,ResultCode)然后
开始
AppError:= '无法启动应用程序';
结束
,否则
,如果ResultCode<> 0,然后
开始
AppError:=格式(应用程序失败,代码为%d,[ResultCode]);
结尾;
UpdateProgress;
最终
{清理}
KillTimer(0,Timer);
ProgressPage.Hide;
DeleteFile(ProgressFileName);
ProgressPage.Free();
结尾;

如果AppError<> ’’然后
开始
{在显示TOutputProgressWizardPage时RaiseException无法正常工作}
RaiseException(AppError);
结尾;
结尾;




上面已用以下批处理文件测试过:

  @echo off 
echo开始
echo正在执行A ...
echo提取某物...
echo正在执行B ...
echo提取某物...
超时/ t 1> nul
echo正在执行...
echo正在提取...
echo正在执行...
echo正在提取...
超时/ t 1> ; nul
echo正在执行...
echo正在提取...
echo正在执行...
echo正在提取...
超时/ t 1> ; nul
...




如果要将输出显示为一部分安装过程的详细信息,而不是单击按钮,请参阅:

在安装并显示后执行批处理文件其输出在Inno Setup中完成页面之前的自定义页面上


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

这篇关于Inno Setup安装程序中的嵌入式CMD(在自定义页面上显示命令输出)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆