Setup.MainForm.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  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. LoggedAppMessageBox(PChar(S), PChar(Application.Title), MB_OK or MB_ICONSTOP, 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. I: Integer;
  95. RunEntry: PSetupRunEntry;
  96. begin
  97. if Entries[seRun].Count <> 0 then begin
  98. CheckIfRestartNeeded := (shRestartIfNeededByRun in SetupHeader.Options) and
  99. not NeedsRestart;
  100. if CheckIfRestartNeeded then
  101. ChecksumBefore := MakePendingFileRenameOperationsChecksum;
  102. var WizardWasHidden := False;
  103. WindowDisabler := nil;
  104. try
  105. for I := 0 to Entries[seRun].Count-1 do begin
  106. RunEntry := PSetupRunEntry(Entries[seRun][I]);
  107. if not(roPostInstall in RunEntry.Options) and
  108. ShouldProcessRunEntry(WizardComponents, WizardTasks, RunEntry) then begin
  109. { Disable windows during execution of [Run] entries so that a nice
  110. "beep" is produced if the user tries clicking on WizardForm }
  111. if WindowDisabler = nil then
  112. WindowDisabler := TWindowDisabler.Create;
  113. if RunEntry.StatusMsg <> '' then begin
  114. try
  115. WizardForm.StatusLabel.Caption := ExpandConst(RunEntry.StatusMsg);
  116. except
  117. { Don't die if the expansion fails with an exception. Just
  118. display the exception message, and proceed with the default
  119. status message. }
  120. Application.HandleException(Self);
  121. WizardForm.StatusLabel.Caption := SetupMessages[msgStatusRunProgram];
  122. end;
  123. end
  124. else
  125. WizardForm.StatusLabel.Caption := SetupMessages[msgStatusRunProgram];
  126. WizardForm.StatusLabel.Update;
  127. if roHideWizard in RunEntry.Options then begin
  128. if WizardForm.Visible and not WizardWasHidden then begin
  129. WizardWasHidden := True;
  130. WizardForm.Hide;
  131. end;
  132. end
  133. else begin
  134. if WizardWasHidden then begin
  135. WizardWasHidden := False;
  136. WizardForm.Visible := True;
  137. end;
  138. end;
  139. DebugNotifyEntry(seRun, I);
  140. NotifyBeforeInstallEntry(RunEntry.BeforeInstall);
  141. ProcessRunEntry(RunEntry);
  142. NotifyAfterInstallEntry(RunEntry.AfterInstall);
  143. end;
  144. end;
  145. finally
  146. if WizardWasHidden then
  147. WizardForm.Visible := True;
  148. WindowDisabler.Free;
  149. if CheckIfRestartNeeded then begin
  150. ChecksumAfter := MakePendingFileRenameOperationsChecksum;
  151. if not SHA256DigestsEqual(ChecksumBefore, ChecksumAfter) then
  152. NeedsRestart := True;
  153. end;
  154. end;
  155. if WizardForm.WindowState <> wsMinimized then { VCL bug workaround }
  156. Application.BringToFront;
  157. end;
  158. end;
  159. procedure RestartApplications;
  160. const
  161. ERROR_FAIL_RESTART = 353;
  162. var
  163. Error: DWORD;
  164. WindowDisabler: TWindowDisabler;
  165. begin
  166. if not NeedsRestart then begin
  167. WizardForm.StatusLabel.Caption := SetupMessages[msgStatusRestartingApplications];
  168. WizardForm.StatusLabel.Update;
  169. Log('Attempting to restart applications.');
  170. { Disable windows during application restart so that a nice
  171. "beep" is produced if the user tries clicking on WizardForm }
  172. WindowDisabler := TWindowDisabler.Create;
  173. try
  174. Error := RmRestart(RmSessionHandle, 0, nil);
  175. finally
  176. WindowDisabler.Free;
  177. end;
  178. if WizardForm.WindowState <> wsMinimized then { VCL bug workaround }
  179. Application.BringToFront;
  180. if Error = ERROR_FAIL_RESTART then
  181. Log('One or more applications could not be restarted.')
  182. else if Error <> ERROR_SUCCESS then begin
  183. RmEndSession(RmSessionHandle);
  184. RmSessionStarted := False;
  185. LogFmt('RmRestart returned an error: %d', [Error]);
  186. end;
  187. end else
  188. Log('Need to restart Windows, not attempting to restart applications');
  189. end;
  190. var
  191. Succeeded, ChangesEnvironment, ChangesAssociations: Boolean;
  192. S: String;
  193. begin
  194. Result := False;
  195. try
  196. if not WizardForm.ValidateDirEdit then
  197. Abort;
  198. WizardDirValue := WizardForm.DirEdit.Text;
  199. if not WizardForm.ValidateGroupEdit then
  200. Abort;
  201. WizardGroupValue := WizardForm.GroupEdit.Text;
  202. WizardNoIcons := WizardForm.NoIconsCheck.Checked;
  203. WizardSetupType := WizardForm.GetSetupType();
  204. WizardForm.GetComponents(WizardComponents, WizardDeselectedComponents);
  205. WizardForm.GetTasks(WizardTasks, WizardDeselectedTasks);
  206. WizardPreparingYesRadio := WizardForm.PreparingYesRadio.Checked;
  207. if InitSaveInf <> '' then
  208. SaveInf(InitSaveInf);
  209. Application.Restore;
  210. if InstallMode = imSilent then
  211. WizardForm.Visible := True;
  212. WizardForm.Update;
  213. SetStep(ssInstall, False);
  214. ChangesEnvironment := EvalDirectiveCheck(SetupHeader.ChangesEnvironment);
  215. ChangesAssociations := EvalDirectiveCheck(SetupHeader.ChangesAssociations);
  216. PerformInstall(Succeeded, ChangesEnvironment, ChangesAssociations);
  217. if not Succeeded then begin
  218. { The user canceled the install or there was a fatal error }
  219. TerminateApp;
  220. Exit;
  221. end;
  222. { Can't cancel at any point after PerformInstall, so disable the button }
  223. WizardForm.CancelButton.Enabled := False;
  224. ProcessRunEntries;
  225. if RmDoRestart and
  226. (InitRestartApplications or
  227. ((shRestartApplications in SetupHeader.Options) and not InitNoRestartApplications)) then
  228. RestartApplications;
  229. SetStep(ssPostInstall, True);
  230. { Notify Windows of assocations/environment changes *after* ssPostInstall
  231. since user might set more stuff there }
  232. if ChangesAssociations then
  233. SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil);
  234. if ChangesEnvironment then
  235. RefreshEnvironment;
  236. if InstallMode <> imNormal then
  237. WizardForm.Hide;
  238. LogFmt('Need to restart Windows? %s', [SYesNo[NeedsRestart]]);
  239. if NeedsRestart and not InitNoRestart then begin
  240. with WizardForm do begin
  241. ChangeFinishedLabel(ExpandSetupMessage(msgFinishedRestartLabel));
  242. YesRadio.Visible := True;
  243. NoRadio.Visible := True;
  244. end;
  245. end else begin
  246. if CreatedIcon then
  247. S := ExpandSetupMessage(msgFinishedLabel)
  248. else
  249. S := ExpandSetupMessage(msgFinishedLabelNoIcons);
  250. with WizardForm do begin
  251. ChangeFinishedLabel(S + SNewLine2 + SetupMessages[msgClickFinish]);
  252. if not NeedsRestart then begin
  253. UpdateRunList(WizardComponents, WizardTasks);
  254. RunList.Visible := RunList.Items.Count > 0;
  255. end;
  256. end;
  257. end;
  258. if InstallMode = imNormal then
  259. Application.Restore;
  260. Result := True;
  261. except
  262. { If an exception was raised, display the message, then terminate }
  263. Application.HandleException(Self);
  264. SetupExitCode := ecNextStepError;
  265. TerminateApp;
  266. end;
  267. end;
  268. procedure ProcessMessagesProc; far;
  269. begin
  270. Application.ProcessMessages;
  271. end;
  272. procedure TMainForm.Finish(const FromPreparingPage: Boolean);
  273. procedure WaitForForegroundLoss;
  274. function IsForegroundProcess: Boolean;
  275. var
  276. W: HWND;
  277. PID: DWORD;
  278. begin
  279. W := GetForegroundWindow;
  280. Result := False;
  281. if (W <> 0) and (GetWindowThreadProcessId(W, @PID) <> 0) then
  282. Result := (PID = GetCurrentProcessId);
  283. end;
  284. var
  285. StartTick: DWORD;
  286. begin
  287. StartTick := GetTickCount;
  288. while IsForegroundProcess do begin
  289. { Stop if it's taking too long (e.g. if the spawned process never
  290. displays a window) }
  291. if Cardinal(GetTickCount - StartTick) >= Cardinal(1000) then
  292. Break;
  293. ProcessMessagesProc;
  294. WaitMessageWithTimeout(10);
  295. ProcessMessagesProc;
  296. end;
  297. end;
  298. procedure ProcessPostInstallRunEntries;
  299. var
  300. WindowDisabler: TWindowDisabler;
  301. ProcessedNoWait: Boolean;
  302. I: Integer;
  303. RunEntry: PSetupRunEntry;
  304. begin
  305. WindowDisabler := nil;
  306. try
  307. ProcessedNoWait := False;
  308. with WizardForm do begin
  309. for I := 0 to RunList.Items.Count-1 do begin
  310. if RunList.Checked[I] then begin
  311. { Disable windows before processing the first entry }
  312. if WindowDisabler = nil then
  313. WindowDisabler := TWindowDisabler.Create;
  314. RunEntry := PSetupRunEntry(Entries[seRun][Integer(RunList.ItemObject[I])]);
  315. DebugNotifyEntry(seRun, Integer(RunList.ItemObject[I]));
  316. NotifyBeforeInstallEntry(RunEntry.BeforeInstall);
  317. ProcessRunEntry(RunEntry);
  318. NotifyAfterInstallEntry(RunEntry.AfterInstall);
  319. if RunEntry.Wait = rwNoWait then
  320. ProcessedNoWait := True;
  321. end;
  322. end;
  323. end;
  324. { Give nowait processes some time to bring themselves to the
  325. foreground before Setup exits. Without this delay, the application
  326. underneath Setup can end up coming to the foreground instead.
  327. (Note: Windows are already disabled at this point.) }
  328. if ProcessedNoWait then
  329. WaitForForegroundLoss;
  330. finally
  331. WindowDisabler.Free;
  332. end;
  333. end;
  334. var
  335. S: String;
  336. begin
  337. try
  338. { Deactivate WizardForm so another application doesn't come to the
  339. foreground when Hide is called. (Needed by WaitForForegroundLoss.) }
  340. if GetForegroundWindow = WizardForm.Handle then
  341. SetActiveWindow(Application.Handle);
  342. WizardForm.Hide;
  343. if not FromPreparingPage and not NeedsRestart then begin
  344. ProcessPostInstallRunEntries;
  345. end else begin
  346. if FromPreparingPage then
  347. SetupExitCode := ecPrepareToInstallFailedRestartNeeded
  348. else if InitRestartExitCode <> 0 then
  349. SetupExitCode := InitRestartExitCode;
  350. if InitNoRestart then
  351. RestartSystem := False
  352. else begin
  353. case InstallMode of
  354. imNormal:
  355. if FromPreparingPage then
  356. RestartSystem := WizardForm.PreparingYesRadio.Checked
  357. else
  358. RestartSystem := WizardForm.YesRadio.Checked;
  359. imSilent:
  360. begin
  361. if FromPreparingPage then
  362. S := WizardForm.PrepareToInstallFailureMessage + SNewLine +
  363. SNewLine + SNewLine + ExpandSetupMessage(msgPrepareToInstallNeedsRestart)
  364. else
  365. S := ExpandSetupMessage(msgFinishedRestartMessage);
  366. RestartSystem :=
  367. LoggedMsgBox(S, '', mbConfirmation, MB_YESNO, True, IDYES) = IDYES;
  368. end;
  369. imVerySilent:
  370. RestartSystem := True;
  371. end;
  372. end;
  373. if not RestartSystem then
  374. Log('Will not restart Windows automatically.');
  375. end;
  376. SetStep(ssDone, True);
  377. except
  378. Application.HandleException(Self);
  379. SetupExitCode := ecNextStepError;
  380. end;
  381. TerminateApp;
  382. end;
  383. procedure TMainForm.Close;
  384. function ConfirmCancel(const DefaultConfirm: Boolean): Boolean;
  385. var
  386. Cancel, Confirm: Boolean;
  387. begin
  388. Cancel := True;
  389. Confirm := DefaultConfirm;
  390. WizardForm.CallCancelButtonClick(Cancel, Confirm);
  391. Result := Cancel and (not Confirm or ExitSetupMsgBox);
  392. end;
  393. begin
  394. if Assigned(WizardForm) and WizardForm.HandleAllocated and
  395. IsWindowVisible(WizardForm.Handle) and IsWindowEnabled(WizardForm.Handle) and
  396. WizardForm.CancelButton.CanFocus then begin
  397. case CurStep of
  398. ssPreInstall:
  399. if ConfirmCancel((WizardForm.CurPageID <> wpPreparing) or (WizardForm.PrepareToInstallFailureMessage = '')) then begin
  400. if WizardForm.CurPageID = wpPreparing then
  401. SetupExitCode := ecPrepareToInstallFailed
  402. else
  403. SetupExitCode := ecCancelledBeforeInstall;
  404. TerminateApp;
  405. end;
  406. ssInstall:
  407. if (shAllowCancelDuringInstall in SetupHeader.Options) and not InitNoCancel then
  408. if ConfirmCancel(True) then
  409. NeedToAbortInstall := True;
  410. end;
  411. end;
  412. end;
  413. class procedure TMainForm.AppOnGetActiveFormHandle(var AHandle: HWND);
  414. begin
  415. { IDE's TMainForm has this too; see comments there }
  416. if Application.MainFormOnTaskBar then begin
  417. AHandle := GetActiveWindow;
  418. if ((AHandle = 0) or (AHandle = Application.Handle)) and
  419. Assigned(Application.MainForm) and
  420. Application.MainForm.HandleAllocated then
  421. AHandle := GetLastActivePopup(Application.MainFormHandle);
  422. end;
  423. end;
  424. initialization
  425. Application.OnGetActiveFormHandle := TMainForm.AppOnGetActiveFormHandle;
  426. end.