Setup.inc 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. {
  2. Inno Setup
  3. Copyright (C) 1997-2025 Jordan Russell
  4. Portions by Martijn Laan
  5. For conditions of distribution and use, see LICENSE.TXT.
  6. Setup program .dpr code
  7. }
  8. {$SETPEOSVERSION 6.1}
  9. {$SETPESUBSYSVERSION 6.1}
  10. {$WEAKLINKRTTI ON}
  11. { SetupCustomStyle: The compiler may delete some of the resources included here }
  12. {$R Res\Setup.icon.res}
  13. {$R Res\Setup.images.res}
  14. {$R Res\Setup.version.res}
  15. procedure ShowExceptionMsg;
  16. begin
  17. { Also see ShowExceptionMsg in Setup.Uninstall.pas }
  18. if ExceptObject is EAbort then begin
  19. Log('Got EAbort exception.');
  20. Exit;
  21. end;
  22. const Msg = GetExceptMessage;
  23. Log('Exception message:');
  24. LoggedMsgBox(PChar(Msg), Pointer(SetupMessages[msgErrorTitle]),
  25. MB_OK or MB_ICONSTOP, True, IDOK);
  26. { ^ use a Pointer cast instead of a PChar cast so that it will use "nil"
  27. if SetupMessages[msgErrorTitle] is empty due to the messages not being
  28. loaded yet. LoggedMsgBox displays 'Error' as the caption if the lpCaption
  29. parameter is nil, see GetMessageBoxCaption. }
  30. end;
  31. type
  32. TDummyClass = class
  33. private
  34. class function AntiShutdownHook(var Message: TMessage): Boolean;
  35. end;
  36. class function TDummyClass.AntiShutdownHook(var Message: TMessage): Boolean;
  37. begin
  38. { This causes Setup/Uninstall/RegSvr to all deny shutdown attempts.
  39. - If we were to return 1, Windows will send us a WM_ENDSESSION message and
  40. TApplication.WndProc will call Halt in response. This is no good because
  41. it would cause an unclean shutdown of Setup, and it would also prevent
  42. the right exit code from being returned.
  43. Even if TApplication.WndProc didn't call Halt, it is my understanding
  44. that Windows could kill us off after sending us the WM_ENDSESSION message
  45. (see the Remarks section of the WM_ENDSESSION docs).
  46. - SetupLdr denys shutdown attempts as well, so there is little point in
  47. Setup trying to handle them. (Depending on the version of Windows, we
  48. may never even get a WM_QUERYENDSESSION message because of that.)
  49. Note: TSetupForm also has a WM_QUERYENDSESSION handler of its own to
  50. prevent CloseQuery from being called. }
  51. Result := False;
  52. case Message.Msg of
  53. WM_QUERYENDSESSION: begin
  54. { Return zero, except if RestartInitiatedByThisProcess is set
  55. (which means we called RestartComputer previously) }
  56. if RestartInitiatedByThisProcess or (IsUninstaller and AllowUninstallerShutdown) then begin
  57. AcceptedQueryEndSessionInProgress := True;
  58. Message.Result := 1
  59. end else
  60. Message.Result := 0;
  61. Result := True;
  62. end;
  63. WM_ENDSESSION: begin
  64. { Should only get here if RestartInitiatedByThisProcess is set or an
  65. Uninstaller shutdown was allowed, or if the user forced a shutdown.
  66. Skip the default handling which calls Halt. No code of ours depends
  67. on the Halt call to clean up, and it could theoretically result in
  68. obscure reentrancy bugs.
  69. Example: I've found that combo boxes pump incoming sent messages
  70. when they are destroyed*; if one of those messages were a
  71. WM_ENDSESSION, the Halt call could cause another destructor to be
  72. to be entered (DoneApplication frees all still-existing forms)
  73. before the combo box's destructor has returned.
  74. * arguably a Windows bug. The internal ComboLBox window is created
  75. as a child (WS_CHILD) of GetDesktopWindow(); when it is destroyed,
  76. a WM_PARENTNOTIFY(WM_DESTROY) message is sent to the desktop window.
  77. Because the desktop window is on a separate thread, pending sent
  78. messages are dispatched during the SendMessage call. }
  79. if Bool(Message.wParam) = True then begin
  80. if not RestartInitiatedByThisProcess and IsUninstaller then
  81. HandleUninstallerEndSession;
  82. end else
  83. AcceptedQueryEndSessionInProgress := False;
  84. Result := True;
  85. end;
  86. end;
  87. end;
  88. procedure DisableWindowGhosting;
  89. var
  90. Proc: procedure; stdcall;
  91. begin
  92. Proc := GetProcAddress(GetModuleHandle(user32), 'DisableProcessWindowsGhosting');
  93. if Assigned(Proc) then
  94. Proc;
  95. end;
  96. procedure SelectMode;
  97. { Determines whether we should run as Setup, Uninstall, or RegSvr }
  98. var
  99. ParamName, ParamValue: String;
  100. Mode: (smSetup, smUninstaller, smRegSvr);
  101. F: TFile;
  102. ID: Longint;
  103. I: Integer;
  104. begin
  105. { When SignedUninstaller=yes, the EXE header specifies uninstaller mode by
  106. default. Use Setup mode instead if we're being called from SetupLdr. }
  107. SplitNewParamStr(1, ParamName, ParamValue);
  108. if SameText(ParamName, '/SL5=') then
  109. Exit;
  110. Mode := smSetup;
  111. for I := 1 to NewParamCount do begin
  112. if SameText(NewParamStr(I), '/UNINSTMODE') then begin
  113. Mode := smUninstaller;
  114. Break;
  115. end;
  116. if SameText(NewParamStr(I), '/REGSVRMODE') then begin
  117. Mode := smRegSvr;
  118. Break;
  119. end;
  120. end;
  121. if Mode = smSetup then begin
  122. { No mode specified on the command line; check the EXE header for one }
  123. F := TFile.Create(NewParamStr(0), fdOpenExisting, faRead, fsRead);
  124. try
  125. F.Seek(SetupExeModeOffset);
  126. F.ReadBuffer(ID, SizeOf(ID));
  127. finally
  128. F.Free;
  129. end;
  130. case ID of
  131. SetupExeModeUninstaller: Mode := smUninstaller;
  132. SetupExeModeRegSvr: Mode := smRegSvr;
  133. end;
  134. end;
  135. case Mode of
  136. smUninstaller: begin
  137. IsUninstaller := True;
  138. AllowUninstallerShutdown := False;
  139. RunUninstaller;
  140. { Shouldn't get here; RunUninstaller should Halt itself }
  141. Halt(1);
  142. end;
  143. smRegSvr: begin
  144. try
  145. RunRegSvr;
  146. except
  147. ShowExceptionMsg;
  148. end;
  149. Halt;
  150. end;
  151. end;
  152. end;
  153. begin
  154. try
  155. {$IFDEF DEBUG}
  156. ReportMemoryLeaksOnShutdown := True;
  157. {$ENDIF}
  158. SetErrorMode(SEM_FAILCRITICALERRORS);
  159. DisableWindowGhosting;
  160. Application.HookMainWindow(TDummyClass.AntiShutdownHook);
  161. TRichEditViewer.CustomShellExecute := ShellExecuteAsOriginalUser;
  162. { Don't respect the show command passed by the parent process.
  163. "Maximized" makes no sense as our windows don't have maximize/restore
  164. buttons, and "Minimized" is problematic as the VCL doesn't realize the
  165. app is minimized (Application.Restore has no effect because
  166. FAppIconic=False).
  167. If the parent process is SetupLdr, then there shouldn't be a non-normal
  168. show command because SetupLdr doesn't specify a show command when
  169. starting Setup. So this should really only matter when UseSetupLdr=no.
  170. First, overwrite the System.CmdShow variable to ensure that
  171. Application.Run (if called) doesn't mess with the main form's
  172. WindowState.
  173. Second, because ShowWindow overrides the value of nCmdShow on the first
  174. call if it's SW_SHOWNORMAL, SW_SHOW, or SW_SHOWDEFAULT (which isn't
  175. specifically documented; I tested each value), make a first call to
  176. ShowWindow here that doesn't actually do anything (the app window is
  177. already hidden at this point, and SW_HIDE is not one of the values that
  178. get overridden), so that when we show our first form, it will be the
  179. second call to ShowWindow and won't have its SW_SHOWNORMAL nCmdShow
  180. value overridden. }
  181. CmdShow := SW_SHOWNORMAL;
  182. ShowWindow(Application.Handle, SW_HIDE);
  183. SelectMode; { Only returns if we should run as Setup }
  184. except
  185. { Halt on any exception }
  186. ShowExceptionMsg;
  187. Halt(ecInitializationError);
  188. end;
  189. { Initialize.
  190. Note: There's no need to localize the following line since it's changed in
  191. InitializeSetup }
  192. Application.Title := 'Setup';
  193. Application.ShowMainForm := False;
  194. Application.OnException := TMainForm.ShowException;
  195. try
  196. Application.Initialize;
  197. Application.MainFormOnTaskBar := True;
  198. InitializeSetup;
  199. MainForm := TMainForm.Create(Application);
  200. InitializeWizard;
  201. except
  202. { Halt on any exception }
  203. ShowExceptionMsg;
  204. try
  205. DeinitSetup(False);
  206. except
  207. { don't propagate any exceptions, so that Halt is always called }
  208. ShowExceptionMsg;
  209. end;
  210. if SetupExitCode <> 0 then
  211. Halt(SetupExitCode)
  212. else
  213. Halt(ecInitializationError);
  214. end;
  215. { Run }
  216. try
  217. Application.Run;
  218. except
  219. { Show any exception and continue }
  220. ShowExceptionMsg;
  221. end;
  222. { Deinitialize (clean up) }
  223. try
  224. DeinitSetup(SetupExitCode = 0);
  225. except
  226. { Show any exception and continue }
  227. ShowExceptionMsg;
  228. end;
  229. {$IFDEF DEBUG}
  230. { Prevent a warning about an expected memory leak, at least when exiting normally }
  231. Application.UnhookMainWindow(TDummyClass.AntiShutdownHook);
  232. {$ENDIF}
  233. Halt(SetupExitCode);
  234. end.