unit Setup.Start; { Inno Setup Copyright (C) 1997-2025 Jordan Russell Portions by Martijn Laan For conditions of distribution and use, see LICENSE.TXT. Setup program .dpr code } interface procedure Start; implementation {$SETPEOSVERSION 6.1} {$SETPESUBSYSVERSION 6.1} {$WEAKLINKRTTI ON} { SetupCustomStyle: The compiler may delete some of the resources included here } {$R Res\Setup.icon.res} {$R Res\Setup.images.res} {$R Res\Setup.version.res} uses Windows, Messages, SysUtils, Forms, RichEditViewer, Shared.CommonFunc, Shared.SetupMessageIDs, Shared.FileClass, Shared.Struct, SetupLdrAndSetup.Messages, SetupLdrAndSetup.InstFunc, Setup.LoggingFunc, Setup.MainFunc, Setup.Uninstall, Setup.RegSvr, Setup.MainForm; procedure ShowExceptionMsg; begin { Also see ShowExceptionMsg in Setup.Uninstall.pas } if ExceptObject is EAbort then begin Log('Got EAbort exception.'); Exit; end; const Msg = GetExceptMessage; Log('Exception message:'); LoggedMsgBox(PChar(Msg), Pointer(SetupMessages[msgErrorTitle]), MB_OK or MB_ICONSTOP, True, IDOK); { ^ use a Pointer cast instead of a PChar cast so that it will use "nil" if SetupMessages[msgErrorTitle] is empty due to the messages not being loaded yet. LoggedMsgBox displays 'Error' as the caption if the lpCaption parameter is nil, see GetMessageBoxCaption. } end; type TDummyClass = class private class function AntiShutdownHook(var Message: TMessage): Boolean; end; class function TDummyClass.AntiShutdownHook(var Message: TMessage): Boolean; begin { This causes Setup/Uninstall/RegSvr to all deny shutdown attempts. - If we were to return 1, Windows will send us a WM_ENDSESSION message and TApplication.WndProc will call Halt in response. This is no good because it would cause an unclean shutdown of Setup, and it would also prevent the right exit code from being returned. Even if TApplication.WndProc didn't call Halt, it is my understanding that Windows could kill us off after sending us the WM_ENDSESSION message (see the Remarks section of the WM_ENDSESSION docs). - SetupLdr denys shutdown attempts as well, so there is little point in Setup trying to handle them. (Depending on the version of Windows, we may never even get a WM_QUERYENDSESSION message because of that.) Note: TSetupForm also has a WM_QUERYENDSESSION handler of its own to prevent CloseQuery from being called. } Result := False; case Message.Msg of WM_QUERYENDSESSION: begin { Return zero, except if RestartInitiatedByThisProcess is set (which means we called RestartComputer previously) } if RestartInitiatedByThisProcess or (IsUninstaller and AllowUninstallerShutdown) then begin AcceptedQueryEndSessionInProgress := True; Message.Result := 1 end else Message.Result := 0; Result := True; end; WM_ENDSESSION: begin { Should only get here if RestartInitiatedByThisProcess is set or an Uninstaller shutdown was allowed, or if the user forced a shutdown. Skip the default handling which calls Halt. No code of ours depends on the Halt call to clean up, and it could theoretically result in obscure reentrancy bugs. Example: I've found that combo boxes pump incoming sent messages when they are destroyed*; if one of those messages were a WM_ENDSESSION, the Halt call could cause another destructor to be to be entered (DoneApplication frees all still-existing forms) before the combo box's destructor has returned. * arguably a Windows bug. The internal ComboLBox window is created as a child (WS_CHILD) of GetDesktopWindow(); when it is destroyed, a WM_PARENTNOTIFY(WM_DESTROY) message is sent to the desktop window. Because the desktop window is on a separate thread, pending sent messages are dispatched during the SendMessage call. } if Bool(Message.wParam) = True then begin if not RestartInitiatedByThisProcess and IsUninstaller then HandleUninstallerEndSession; end else AcceptedQueryEndSessionInProgress := False; Result := True; end; end; end; procedure DisableWindowGhosting; var Proc: procedure; stdcall; begin Proc := GetProcAddress(GetModuleHandle(user32), 'DisableProcessWindowsGhosting'); if Assigned(Proc) then Proc; end; procedure SelectMode; { Determines whether we should run as Setup, Uninstall, or RegSvr } var ParamName, ParamValue: String; Mode: (smSetup, smUninstaller, smRegSvr); F: TFile; ID: Longint; I: Integer; begin { When SignedUninstaller=yes, the EXE header specifies uninstaller mode by default. Use Setup mode instead if we're being called from SetupLdr. } SplitNewParamStr(1, ParamName, ParamValue); if SameText(ParamName, '/SL5=') then Exit; Mode := smSetup; for I := 1 to NewParamCount do begin if SameText(NewParamStr(I), '/UNINSTMODE') then begin Mode := smUninstaller; Break; end; if SameText(NewParamStr(I), '/REGSVRMODE') then begin Mode := smRegSvr; Break; end; end; if Mode = smSetup then begin { No mode specified on the command line; check the EXE header for one } F := TFile.Create(NewParamStr(0), fdOpenExisting, faRead, fsRead); try F.Seek(SetupExeModeOffset); F.ReadBuffer(ID, SizeOf(ID)); finally F.Free; end; case ID of SetupExeModeUninstaller: Mode := smUninstaller; SetupExeModeRegSvr: Mode := smRegSvr; end; end; case Mode of smUninstaller: begin IsUninstaller := True; AllowUninstallerShutdown := False; RunUninstaller; { Shouldn't get here; RunUninstaller should Halt itself } Halt(1); end; smRegSvr: begin try RunRegSvr; except ShowExceptionMsg; end; Halt; end; end; end; procedure Start; begin try {$IFDEF DEBUG} ReportMemoryLeaksOnShutdown := True; {$ENDIF} SetErrorMode(SEM_FAILCRITICALERRORS); DisableWindowGhosting; Application.HookMainWindow(TDummyClass.AntiShutdownHook); TRichEditViewer.CustomShellExecute := ShellExecuteAsOriginalUser; { Don't respect the show command passed by the parent process. "Maximized" makes no sense as our windows don't have maximize/restore buttons, and "Minimized" is problematic as the VCL doesn't realize the app is minimized (Application.Restore has no effect because FAppIconic=False). If the parent process is SetupLdr, then there shouldn't be a non-normal show command because SetupLdr doesn't specify a show command when starting Setup. So this should really only matter when UseSetupLdr=no. First, overwrite the System.CmdShow variable to ensure that Application.Run (if called) doesn't mess with the main form's WindowState. Second, because ShowWindow overrides the value of nCmdShow on the first call if it's SW_SHOWNORMAL, SW_SHOW, or SW_SHOWDEFAULT (which isn't specifically documented; I tested each value), make a first call to ShowWindow here that doesn't actually do anything (the app window is already hidden at this point, and SW_HIDE is not one of the values that get overridden), so that when we show our first form, it will be the second call to ShowWindow and won't have its SW_SHOWNORMAL nCmdShow value overridden. } CmdShow := SW_SHOWNORMAL; ShowWindow(Application.Handle, SW_HIDE); SelectMode; { Only returns if we should run as Setup } except { Halt on any exception } ShowExceptionMsg; Halt(ecInitializationError); end; { Initialize. Note: There's no need to localize the following line since it's changed in InitializeSetup } Application.Title := 'Setup'; Application.ShowMainForm := False; Application.OnException := TMainForm.ShowException; try Application.Initialize; Application.MainFormOnTaskBar := True; InitializeSetup; MainForm := TMainForm.Create(Application); InitializeWizard; except { Halt on any exception } ShowExceptionMsg; try DeinitSetup(False); except { don't propagate any exceptions, so that Halt is always called } ShowExceptionMsg; end; if SetupExitCode <> 0 then begin System.ExitCode := SetupExitCode; Halt; end else Halt(ecInitializationError); end; { Run } try Application.Run; except { Show any exception and continue } ShowExceptionMsg; end; { Deinitialize (clean up) } try DeinitSetup(SetupExitCode = 0); except { Show any exception and continue } ShowExceptionMsg; end; {$IFDEF DEBUG} { Prevent a warning about an expected memory leak, at least when exiting normally } Application.UnhookMainWindow(TDummyClass.AntiShutdownHook); {$ENDIF} System.ExitCode := SetupExitCode; Halt; end; end.