Search code examples
inno-setuppascalscript

Automatically submitting Inno Setup uninstall prompts


I want to hide the first and the last message of the uninstaller. This code works with a modified version of Inno Setup (Inno Setup Ultra 5.5.1.ee2) but does not work well to hide the first message (appears briefly and disappears):

function FindWindowEx(
  Parent, Child: HWND; ClassName, WindowName: PansiChar): HWND;
  external 'FindWindowExA@user32.dll stdcall';

const
  BM_CLICK    = $00F5;
var
  Timer: TTimer;
  msg: string;
  Wnd, WndEx: HWND;

procedure OnTimer(Sender: TObject);
begin
  Wnd:= FindWindowByWindowName(msg);
  if Wnd > 0 then
  begin
    WndEx:= FindWindowEx(Wnd, 0,'Button', '');
    if WndEx > 0 then
    begin
      PostMessage(WndEx, BM_CLICK, 0, 0);
      Timer.Enabled:= False;
    end;
  end;
end;

function InitializeUninstall:boolean;
begin
  Result := True;
  msg:= SetupMessage(msgUninstallAppFullTitle);
  StringChange(msg, '%1', '{#SetupSetting('AppName')}');
  OnTimer(nil);
  Timer:= TTimer.Create(nil);
  with Timer do
  begin
    OnTimer:= @OnTimer;
    Interval:= 1;
    Enabled:= True;
  end;
end;

procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
  if CurUninstallStep=usPostUninstall then
  begin
    OnTimer(nil);
    Timer:= TTimer.Create(nil);
    with Timer do
    begin
      OnTimer:= @OnTimer;
      Interval:= 1;
      Enabled:= True;
    end;
  end;
end;

How to modify this code to work correctly with the current official version of Inno Setup and to correctly hide both messages?


Solution

  • First, I must say that I do not agree with this at all. But it's interesting problem anyway, and the implementation might be useful for other, more appropriate cases.

    Also you cannot avoid the message to appear briefly. The solution automates the UI, so needs the UI to work. That's one of the reasons I do not like it.


    [Setup]
    AppName=My Program
    
    [Code]
    
    const
      BM_CLICK = $00F5;
    
    function FindWindowEx(Parent, Child: HWND; ClassName, WindowName: string): HWND;
      external 'FindWindowExW@user32.dll stdcall';
    function SetTimer(hWnd, nIDEvent, uElapse, lpTimerFunc: LongWord): LongWord;
      external 'SetTimer@User32.dll stdcall';
    function KillTimer(hWnd, nIDEvent: LongWord): LongWord;
      external 'KillTimer@User32.dll stdcall';
    
    var
      UpcomingMessage: string;  
      SubmitMessageTimer: LongWord;
      SubmitMessagePossible: Boolean;
    
    procedure SubmitMessageProc(
      H: LongWord; Msg: LongWord; IdEvent: LongWord; Time: LongWord);
    var
      WindowHandle, ButtonHandle: HWND;
    begin
      { TODO: Cancel the timer, if the message does not appear within few seconds }
      WindowHandle := FindWindowByWindowName(UpcomingMessage);
      if WindowHandle > 0 then
      begin
        Log(Format('Found message window "%s"', [UpcomingMessage]));
        ButtonHandle := FindWindowEx(WindowHandle, 0, 'Button', '');
        if ButtonHandle > 0 then
        begin
          Log('Found button');
          PostMessage(ButtonHandle, BM_CLICK, 0, 0);
          KillTimer(0, SubmitMessageTimer);
          SubmitMessageTimer := 0;
        end;
      end;
    end;
    
    procedure SubmitUpcomingMessage(Msg: string);
    begin
      if not SubmitMessagePossible then
      begin
        Log('Cannot submit message');
      end
        else
      begin
        if SubmitMessageTimer > 0 then
          KillTimer(0, SubmitMessageTimer);
    
        Log(Format('Want to automatically submit message "%s"', [Msg]));
        UpcomingMessage := Msg;
        SubmitMessageTimer := SetTimer(0, 0, 100, CreateCallback(@SubmitMessageProc));
      end;
    end;
    
    function FmtSetupMessageWithAppName(const ID: TSetupMessageID): string;
    begin
      Result := FmtMessage(SetupMessage(ID), ['{#SetupSetting('AppName')}']);
    end;
    
    function InitializeUninstall:boolean;
    begin
      Result := True;
    
      SubmitMessagePossible :=
        FileCopy(
          ExpandConstant('{app}\InnoCallback.dll'),
          ExpandConstant('{%TEMP}\InnoCallback.dll'), False);
    
      SubmitUpcomingMessage(FmtSetupMessageWithAppName(msgUninstallAppFullTitle));
    end;
    
    procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
    begin
      if CurUninstallStep = usPostUninstall then
      begin
        SubmitUpcomingMessage(FmtSetupMessageWithAppName(msgUninstallAppFullTitle));
      end;
    end;
    

    For CreateCallback function, you need Inno Setup 6.

    If you are stuck with Inno Setup 5, you can use WrapCallback function from InnoTools InnoCallback library (the code needs Unicode version of Inno Setup 5). But using an external DLL library from an uninstaller is tricky and has its drawbacks. See Load external DLL for uninstall process in Inno Setup.


    For a different approach to the problem, see Changing uninstall confirmation prompt.