Setup.Start.pas 8.8 KB

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