Setup.MainForm.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. unit Setup.MainForm;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2025 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. }
  8. interface
  9. uses
  10. Windows, SysUtils, Classes,
  11. Shared.SetupSteps;
  12. type
  13. TMainForm = class(TComponent)
  14. private
  15. class procedure AppOnGetActiveFormHandle(var AHandle: HWND);
  16. public
  17. CurStep: TSetupStep;
  18. destructor Destroy; override;
  19. procedure Close;
  20. procedure Finish(const FromPreparingPage: Boolean);
  21. function Install: Boolean;
  22. procedure SetStep(const AStep: TSetupStep; const HandleExceptions: Boolean);
  23. class procedure ShowException(Sender: TObject; E: Exception);
  24. class procedure ShowExceptionMsg(const S: String);
  25. end;
  26. var
  27. MainForm: TMainForm;
  28. implementation
  29. uses
  30. Messages, ShlObj,
  31. Forms,
  32. SHA256, RestartManager,
  33. Shared.Struct, Shared.CommonFunc, Shared.CommonFunc.Vcl, Shared.SetupMessageIDs,
  34. SetupLdrAndSetup.Messages, Setup.Install,
  35. Setup.MainFunc, Setup.InstFunc, Setup.WizardForm, Setup.LoggingFunc, Shared.SetupTypes;
  36. destructor TMainForm.Destroy;
  37. begin
  38. MainForm := nil; { just to detect use-after-free }
  39. inherited;
  40. end;
  41. class procedure TMainForm.ShowExceptionMsg(const S: String);
  42. begin
  43. Log('Exception message:');
  44. LoggedMsgBox(S, Application.Title, mbCriticalError, MB_OK, True, IDOK);
  45. end;
  46. class procedure TMainForm.ShowException(Sender: TObject; E: Exception);
  47. begin
  48. ShowExceptionMsg(AddPeriod(E.Message));
  49. end;
  50. procedure TMainForm.SetStep(const AStep: TSetupStep; const HandleExceptions: Boolean);
  51. begin
  52. CurStep := AStep;
  53. if CodeRunner <> nil then begin
  54. try
  55. CodeRunner.RunProcedures('CurStepChanged', [Ord(CurStep)], False);
  56. except
  57. if HandleExceptions then begin
  58. Log('CurStepChanged raised an exception.');
  59. Application.HandleException(Self);
  60. end
  61. else begin
  62. Log('CurStepChanged raised an exception (fatal).');
  63. raise;
  64. end;
  65. end;
  66. end;
  67. end;
  68. procedure TerminateApp;
  69. begin
  70. { Work around shell32 bug: Don't use PostQuitMessage/Application.Terminate
  71. here.
  72. When ShellExecute is called with the name of a folder, it internally
  73. creates a window used for DDE communication with Windows Explorer. After
  74. ShellExecute returns, this window eventually receives a posted WM_DDE_ACK
  75. message back from the DDE server (Windows Explorer), and in response, it
  76. tries to flush the queue of DDE messages by using a PeekMessage loop.
  77. Problem is, PeekMessage will return WM_QUIT messages posted with
  78. PostQuitMessage regardless of the message range specified, and the loop was
  79. not written with this in mind.
  80. In previous IS versions, this was causing our WM_QUIT message to be eaten
  81. if Application.Terminate was called very shortly after a shellexec [Run]
  82. entry was processed (e.g. if DisableFinishedPage=yes).
  83. A WM_QUIT message posted with PostMessage instead of PostQuitMessage will
  84. not be returned by a GetMessage/PeekMessage call with a message range that
  85. does not include WM_QUIT. }
  86. PostMessage(0, WM_QUIT, 0, 0);
  87. end;
  88. function TMainForm.Install: Boolean;
  89. procedure ProcessRunEntries;
  90. var
  91. CheckIfRestartNeeded: Boolean;
  92. ChecksumBefore, ChecksumAfter: TSHA256Digest;
  93. WindowDisabler: TWindowDisabler;
  94. RunEntry: PSetupRunEntry;
  95. begin
  96. if Entries[seRun].Count <> 0 then begin
  97. CheckIfRestartNeeded := (shRestartIfNeededByRun in SetupHeader.Options) and
  98. not NeedsRestart;
  99. if CheckIfRestartNeeded then
  100. ChecksumBefore := MakePendingFileRenameOperationsChecksum;
  101. var WizardWasHidden := False;
  102. WindowDisabler := nil;
  103. try
  104. for var I := 0 to Entries[seRun].Count-1 do begin
  105. RunEntry := PSetupRunEntry(Entries[seRun][I]);
  106. if not(roPostInstall in RunEntry.Options) and
  107. ShouldProcessRunEntry(WizardComponents, WizardTasks, RunEntry) then begin
  108. { Disable windows during execution of [Run] entries so that a nice
  109. "beep" is produced if the user tries clicking on WizardForm }
  110. if WindowDisabler = nil then
  111. WindowDisabler := TWindowDisabler.Create;
  112. if RunEntry.StatusMsg <> '' then begin
  113. try
  114. WizardForm.StatusLabel.Caption := ExpandConst(RunEntry.StatusMsg);
  115. except
  116. { Don't die if the expansion fails with an exception. Just
  117. display the exception message, and proceed with the default
  118. status message. }
  119. Application.HandleException(Self);
  120. WizardForm.StatusLabel.Caption := SetupMessages[msgStatusRunProgram];
  121. end;
  122. end
  123. else
  124. WizardForm.StatusLabel.Caption := SetupMessages[msgStatusRunProgram];
  125. WizardForm.StatusLabel.Update;
  126. if roHideWizard in RunEntry.Options then begin
  127. if WizardForm.Visible and not WizardWasHidden then begin
  128. WizardWasHidden := True;
  129. WizardForm.Hide;
  130. end;
  131. end
  132. else begin
  133. if WizardWasHidden then begin
  134. WizardWasHidden := False;
  135. WizardForm.Visible := True;
  136. end;
  137. end;
  138. DebugNotifyEntry(seRun, I);
  139. NotifyBeforeInstallEntry(RunEntry.BeforeInstall);
  140. ProcessRunEntry(RunEntry);
  141. NotifyAfterInstallEntry(RunEntry.AfterInstall);
  142. end;
  143. end;
  144. finally
  145. if WizardWasHidden then
  146. WizardForm.Visible := True;
  147. WindowDisabler.Free;
  148. if CheckIfRestartNeeded then begin
  149. ChecksumAfter := MakePendingFileRenameOperationsChecksum;
  150. if not SHA256DigestsEqual(ChecksumBefore, ChecksumAfter) then
  151. NeedsRestart := True;
  152. end;
  153. end;
  154. if WizardForm.WindowState <> wsMinimized then { VCL bug workaround }
  155. Application.BringToFront;
  156. end;
  157. end;
  158. procedure RestartApplications;
  159. const
  160. ERROR_FAIL_RESTART = 353;
  161. var
  162. Error: DWORD;
  163. WindowDisabler: TWindowDisabler;
  164. begin
  165. if not NeedsRestart then begin
  166. WizardForm.StatusLabel.Caption := SetupMessages[msgStatusRestartingApplications];
  167. WizardForm.StatusLabel.Update;
  168. Log('Attempting to restart applications.');
  169. { Disable windows during application restart so that a nice
  170. "beep" is produced if the user tries clicking on WizardForm }
  171. WindowDisabler := TWindowDisabler.Create;
  172. try
  173. Error := RmRestart(RmSessionHandle, 0, nil);
  174. finally
  175. WindowDisabler.Free;
  176. end;
  177. if WizardForm.WindowState <> wsMinimized then { VCL bug workaround }
  178. Application.BringToFront;
  179. if Error = ERROR_FAIL_RESTART then
  180. Log('One or more applications could not be restarted.')
  181. else if Error <> ERROR_SUCCESS then begin
  182. RmEndSession(RmSessionHandle);
  183. RmSessionStarted := False;
  184. LogFmt('RmRestart returned an error: %d', [Error]);
  185. end;
  186. end else
  187. Log('Need to restart Windows, not attempting to restart applications');
  188. end;
  189. var
  190. Succeeded, ChangesEnvironment, ChangesAssociations: Boolean;
  191. S: String;
  192. begin
  193. Result := False;
  194. try
  195. if not WizardForm.ValidateDirEdit then
  196. Abort;
  197. WizardDirValue := WizardForm.DirEdit.Text;
  198. if not WizardForm.ValidateGroupEdit then
  199. Abort;
  200. WizardGroupValue := WizardForm.GroupEdit.Text;
  201. WizardNoIcons := WizardForm.NoIconsCheck.Checked;
  202. WizardSetupType := WizardForm.GetSetupType();
  203. WizardForm.GetComponents(WizardComponents, WizardDeselectedComponents);
  204. WizardForm.GetTasks(WizardTasks, WizardDeselectedTasks);
  205. WizardPreparingYesRadio := WizardForm.PreparingYesRadio.Checked;
  206. if InitSaveInf <> '' then
  207. SaveInf(InitSaveInf);
  208. Application.Restore;
  209. if InstallMode = imSilent then
  210. WizardForm.Visible := True;
  211. WizardForm.Update;
  212. SetStep(ssInstall, False);
  213. ChangesEnvironment := EvalDirectiveCheck(SetupHeader.ChangesEnvironment);
  214. ChangesAssociations := EvalDirectiveCheck(SetupHeader.ChangesAssociations);
  215. PerformInstall(Succeeded, ChangesEnvironment, ChangesAssociations);
  216. if not Succeeded then begin
  217. { The user canceled the install or there was a fatal error }
  218. TerminateApp;
  219. Exit;
  220. end;
  221. { Can't cancel at any point after PerformInstall, so disable the button }
  222. WizardForm.CancelButton.Enabled := False;
  223. ProcessRunEntries;
  224. if RmDoRestart and
  225. (InitRestartApplications or
  226. ((shRestartApplications in SetupHeader.Options) and not InitNoRestartApplications)) then
  227. RestartApplications;
  228. SetStep(ssPostInstall, True);
  229. { Notify Windows of assocations/environment changes *after* ssPostInstall
  230. since user might set more stuff there }
  231. if ChangesAssociations then
  232. SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil);
  233. if ChangesEnvironment then
  234. RefreshEnvironment;
  235. if InstallMode <> imNormal then
  236. WizardForm.Hide;
  237. LogFmt('Need to restart Windows? %s', [SYesNo[NeedsRestart]]);
  238. if NeedsRestart and not InitNoRestart then begin
  239. with WizardForm do begin
  240. ChangeFinishedLabel(ExpandSetupMessage(msgFinishedRestartLabel));
  241. YesRadio.Visible := True;
  242. NoRadio.Visible := True;
  243. end;
  244. end else begin
  245. if CreatedIcon then
  246. S := ExpandSetupMessage(msgFinishedLabel)
  247. else
  248. S := ExpandSetupMessage(msgFinishedLabelNoIcons);
  249. with WizardForm do begin
  250. ChangeFinishedLabel(S + SNewLine2 + SetupMessages[msgClickFinish]);
  251. if not NeedsRestart then begin
  252. UpdateRunList(WizardComponents, WizardTasks);
  253. RunList.Visible := RunList.Items.Count > 0;
  254. end;
  255. end;
  256. end;
  257. if InstallMode = imNormal then
  258. Application.Restore;
  259. Result := True;
  260. except
  261. { If an exception was raised, display the message, then terminate }
  262. Application.HandleException(Self);
  263. SetupExitCode := ecNextStepError;
  264. TerminateApp;
  265. end;
  266. end;
  267. procedure ProcessMessagesProc; far;
  268. begin
  269. Application.ProcessMessages;
  270. end;
  271. procedure TMainForm.Finish(const FromPreparingPage: Boolean);
  272. procedure WaitForForegroundLoss;
  273. function IsForegroundProcess: Boolean;
  274. var
  275. W: HWND;
  276. PID: DWORD;
  277. begin
  278. W := GetForegroundWindow;
  279. Result := False;
  280. if (W <> 0) and (GetWindowThreadProcessId(W, @PID) <> 0) then
  281. Result := (PID = GetCurrentProcessId);
  282. end;
  283. var
  284. StartTick: DWORD;
  285. begin
  286. StartTick := GetTickCount;
  287. while IsForegroundProcess do begin
  288. { Stop if it's taking too long (e.g. if the spawned process never
  289. displays a window) }
  290. if Cardinal(GetTickCount - StartTick) >= Cardinal(1000) then
  291. Break;
  292. ProcessMessagesProc;
  293. WaitMessageWithTimeout(10);
  294. ProcessMessagesProc;
  295. end;
  296. end;
  297. procedure ProcessPostInstallRunEntries;
  298. var
  299. WindowDisabler: TWindowDisabler;
  300. ProcessedNoWait: Boolean;
  301. I: Integer;
  302. RunEntry: PSetupRunEntry;
  303. begin
  304. WindowDisabler := nil;
  305. try
  306. ProcessedNoWait := False;
  307. with WizardForm do begin
  308. for I := 0 to RunList.Items.Count-1 do begin
  309. if RunList.Checked[I] then begin
  310. { Disable windows before processing the first entry }
  311. if WindowDisabler = nil then
  312. WindowDisabler := TWindowDisabler.Create;
  313. RunEntry := PSetupRunEntry(Entries[seRun][Integer(RunList.ItemObject[I])]);
  314. DebugNotifyEntry(seRun, Integer(RunList.ItemObject[I]));
  315. NotifyBeforeInstallEntry(RunEntry.BeforeInstall);
  316. ProcessRunEntry(RunEntry);
  317. NotifyAfterInstallEntry(RunEntry.AfterInstall);
  318. if RunEntry.Wait = rwNoWait then
  319. ProcessedNoWait := True;
  320. end;
  321. end;
  322. end;
  323. { Give nowait processes some time to bring themselves to the
  324. foreground before Setup exits. Without this delay, the application
  325. underneath Setup can end up coming to the foreground instead.
  326. (Note: Windows are already disabled at this point.) }
  327. if ProcessedNoWait then
  328. WaitForForegroundLoss;
  329. finally
  330. WindowDisabler.Free;
  331. end;
  332. end;
  333. var
  334. S: String;
  335. begin
  336. try
  337. { Deactivate WizardForm so another application doesn't come to the
  338. foreground when Hide is called. (Needed by WaitForForegroundLoss.) }
  339. if GetForegroundWindow = WizardForm.Handle then
  340. SetActiveWindow(Application.Handle);
  341. WizardForm.Hide;
  342. if not FromPreparingPage and not NeedsRestart then begin
  343. ProcessPostInstallRunEntries;
  344. end else begin
  345. if FromPreparingPage then
  346. SetupExitCode := ecPrepareToInstallFailedRestartNeeded
  347. else if InitRestartExitCode <> 0 then
  348. SetupExitCode := InitRestartExitCode;
  349. if InitNoRestart then
  350. RestartSystem := False
  351. else begin
  352. case InstallMode of
  353. imNormal:
  354. if FromPreparingPage then
  355. RestartSystem := WizardForm.PreparingYesRadio.Checked
  356. else
  357. RestartSystem := WizardForm.YesRadio.Checked;
  358. imSilent:
  359. begin
  360. if FromPreparingPage then
  361. S := WizardForm.PrepareToInstallFailureMessage + SNewLine +
  362. SNewLine + SNewLine + ExpandSetupMessage(msgPrepareToInstallNeedsRestart)
  363. else
  364. S := ExpandSetupMessage(msgFinishedRestartMessage);
  365. RestartSystem :=
  366. LoggedMsgBox(S, '', mbConfirmation, MB_YESNO, True, IDYES) = IDYES;
  367. end;
  368. imVerySilent:
  369. RestartSystem := True;
  370. end;
  371. end;
  372. if not RestartSystem then
  373. Log('Will not restart Windows automatically.');
  374. end;
  375. SetStep(ssDone, True);
  376. except
  377. Application.HandleException(Self);
  378. SetupExitCode := ecNextStepError;
  379. end;
  380. TerminateApp;
  381. end;
  382. procedure TMainForm.Close;
  383. function ConfirmCancel(const DefaultConfirm: Boolean): Boolean;
  384. var
  385. Cancel, Confirm: Boolean;
  386. begin
  387. Cancel := True;
  388. Confirm := DefaultConfirm;
  389. WizardForm.CallCancelButtonClick(Cancel, Confirm);
  390. Result := Cancel and (not Confirm or ExitSetupMsgBox);
  391. end;
  392. begin
  393. if Assigned(WizardForm) and WizardForm.HandleAllocated and
  394. IsWindowVisible(WizardForm.Handle) and IsWindowEnabled(WizardForm.Handle) and
  395. WizardForm.CancelButton.CanFocus then begin
  396. case CurStep of
  397. ssPreInstall:
  398. if ConfirmCancel((WizardForm.CurPageID <> wpPreparing) or (WizardForm.PrepareToInstallFailureMessage = '')) then begin
  399. if WizardForm.CurPageID = wpPreparing then
  400. SetupExitCode := ecPrepareToInstallFailed
  401. else
  402. SetupExitCode := ecCancelledBeforeInstall;
  403. TerminateApp;
  404. end;
  405. ssInstall:
  406. if (shAllowCancelDuringInstall in SetupHeader.Options) and not InitNoCancel then
  407. if ConfirmCancel(True) then
  408. NeedToAbortInstall := True;
  409. end;
  410. end;
  411. end;
  412. class procedure TMainForm.AppOnGetActiveFormHandle(var AHandle: HWND);
  413. begin
  414. { IDE's TMainForm has this too; see comments there }
  415. if Application.MainFormOnTaskBar then begin
  416. AHandle := GetActiveWindow;
  417. if ((AHandle = 0) or (AHandle = Application.Handle)) and
  418. Assigned(Application.MainForm) and
  419. Application.MainForm.HandleAllocated then
  420. AHandle := GetLastActivePopup(Application.MainFormHandle);
  421. end;
  422. end;
  423. initialization
  424. Application.OnGetActiveFormHandle := TMainForm.AppOnGetActiveFormHandle;
  425. end.