Setup.Install.HelperFunc.pas 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. unit Setup.Install.HelperFunc;
  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. Installation helper functions which don't need install state such as UninstLog and RegisterFileList
  8. Only to be called by Setup.Install: if you want to reuse any of these functione from another unit
  9. you should move the function so somewhere else, like Setup.InstFunc
  10. }
  11. interface
  12. uses
  13. Windows, SHA256, Shared.FileClass, Shared.Struct, Setup.UninstallLog;
  14. type
  15. TSetupUninstallLog = class(TUninstallLog)
  16. protected
  17. procedure HandleException; override;
  18. end;
  19. TRegErrorFunc = (reRegSetValueEx, reRegCreateKeyEx, reRegOpenKeyEx);
  20. procedure SetFilenameLabelText(const S: String; const CallUpdate: Boolean);
  21. procedure SetStatusLabelText(const S: String;
  22. const ClearFilenameLabelText: Boolean = True);
  23. procedure InstallMessageBoxCallback(const Flags: Cardinal; const After: Boolean;
  24. const Param: LongInt);
  25. procedure CalcFilesSize(var InstallFilesSize, AfterInstallFilesSize: Int64);
  26. procedure InitProgressGauge(const InstallFilesSize: Int64);
  27. procedure UpdateProgressGauge;
  28. procedure FinishProgressGauge(const HideGauge: Boolean);
  29. procedure SetProgress(const AProgress: Int64);
  30. procedure IncProgress(const N: Int64);
  31. function CurProgress: Int64;
  32. procedure ProcessEvents;
  33. procedure InternalProgressProc(const Bytes: Cardinal);
  34. procedure ExternalProgressProc64(const Bytes, MaxProgress: Int64);
  35. procedure JustProcessEventsProc64(const Bytes, Param: Int64);
  36. function AbortRetryIgnoreTaskDialogMsgBox(const Text: String;
  37. const RetryIgnoreAbortButtonLabels: array of String): Boolean;
  38. function FileTimeToStr(const AFileTime: TFileTime): String;
  39. function TryToGetSHA256OfFile(const DisableFsRedir: Boolean; const Filename: String;
  40. var Sum: TSHA256Digest): Boolean;
  41. procedure CopySourceFileToDestFile(const SourceF, DestF: TFile;
  42. [ref] const Verification: TSetupFileVerification; const ISSigSourceFilename: String;
  43. const AExpectedSize: Int64);
  44. function ShortenOrExpandFontFilename(const Filename: String): String;
  45. function GetLocalTimeAsStr: String;
  46. procedure PackCustomMessagesIntoString(var S: String);
  47. function PackCompiledCodeTextIntoString(const CompiledCodeText: AnsiString): String;
  48. procedure RegError(const Func: TRegErrorFunc; const RootKey: HKEY;
  49. const KeyName: String; const ErrorCode: DWORD);
  50. procedure WriteMsgData(const F: TFile);
  51. procedure MarkExeHeader(const F: TFile; const ModeID: Longint);
  52. procedure ProcessInstallDeleteEntries;
  53. procedure ProcessNeedRestartEvent;
  54. procedure ProcessComponentEntries;
  55. procedure ProcessTasksEntries;
  56. procedure ShutdownApplications;
  57. implementation
  58. uses
  59. Classes, SysUtils, Forms,
  60. NewProgressBar, PathFunc, RestartManager, TaskbarProgressFunc, UnsignedFunc,
  61. Shared.CommonFunc, Shared.CommonFunc.Vcl, Shared.SetupMessageIDs, Shared.SetupTypes,
  62. SetupLdrAndSetup.Messages,
  63. Setup.InstFunc, Setup.ISSigVerifyFunc, Setup.LoggingFunc, Setup.MainFunc, Setup.ScriptRunner,
  64. Setup.WizardForm;
  65. procedure TSetupUninstallLog.HandleException;
  66. begin
  67. Application.HandleException(Self);
  68. end;
  69. procedure SetFilenameLabelText(const S: String; const CallUpdate: Boolean);
  70. begin
  71. WizardForm.FilenameLabel.Caption := MinimizePathName(S, WizardForm.FilenameLabel.Font, WizardForm.FileNameLabel.Width);
  72. if CallUpdate then
  73. WizardForm.FilenameLabel.Update;
  74. end;
  75. procedure SetStatusLabelText(const S: String;
  76. const ClearFilenameLabelText: Boolean = True);
  77. begin
  78. if WizardForm.StatusLabel.Caption <> S then begin
  79. WizardForm.StatusLabel.Caption := S;
  80. WizardForm.StatusLabel.Update;
  81. end;
  82. if ClearFilenameLabelText then
  83. SetFilenameLabelText('', True);
  84. end;
  85. procedure InstallMessageBoxCallback(const Flags: Cardinal; const After: Boolean;
  86. const Param: LongInt);
  87. const
  88. States: array [TNewProgressBarState] of TTaskbarProgressState =
  89. (tpsNormal, tpsError, tpsPaused);
  90. var
  91. NewState: TNewProgressBarState;
  92. begin
  93. if After then
  94. NewState := npbsNormal
  95. else if (Flags and MB_ICONSTOP) <> 0 then
  96. NewState := npbsError
  97. else
  98. NewState := npbsPaused;
  99. with WizardForm.ProgressGauge do begin
  100. State := NewState;
  101. Invalidate;
  102. end;
  103. SetAppTaskbarProgressState(States[NewState]);
  104. end;
  105. procedure CalcFilesSize(var InstallFilesSize, AfterInstallFilesSize: Int64);
  106. var
  107. CurFile: PSetupFileEntry;
  108. begin
  109. InstallFilesSize := 0;
  110. AfterInstallFilesSize := InstallFilesSize;
  111. for var N := 0 to Entries[seFile].Count-1 do begin
  112. CurFile := PSetupFileEntry(Entries[seFile][N]);
  113. if ShouldProcessFileEntry(WizardComponents, WizardTasks, CurFile, False) then begin
  114. with CurFile^ do begin
  115. var FileSize: Int64;
  116. if LocationEntry <> -1 then { not an "external" file }
  117. FileSize := PSetupFileLocationEntry(Entries[seFileLocation][
  118. LocationEntry])^.OriginalSize
  119. else
  120. FileSize := ExternalSize;
  121. Inc(InstallFilesSize, FileSize);
  122. if not (foDeleteAfterInstall in Options) then
  123. Inc(AfterInstallFilesSize, FileSize);
  124. end;
  125. end;
  126. end;
  127. end;
  128. var
  129. CurProgressValue: Int64;
  130. ProgressShiftCount: Cardinal;
  131. procedure InitProgressGauge(const InstallFilesSize: Int64);
  132. begin
  133. { Calculate the MaxValue for the progress meter }
  134. var NewMaxValue: Int64 := 1000 * Entries[seIcon].Count;
  135. if Entries[seIni].Count <> 0 then Inc(NewMaxValue, 1000);
  136. if Entries[seRegistry].Count <> 0 then Inc(NewMaxValue, 1000);
  137. Inc(NewMaxValue, InstallFilesSize);
  138. { To avoid progress updates that are too small to result in any visible
  139. change, divide the Max value by 2 until it's under 1500 }
  140. ProgressShiftCount := 0;
  141. while NewMaxValue >= 1500 do begin
  142. NewMaxValue := NewMaxValue shr 1;
  143. Inc(ProgressShiftCount);
  144. end;
  145. WizardForm.ProgressGauge.Max := Integer(NewMaxValue);
  146. SetMessageBoxCallbackFunc(InstallMessageBoxCallback, 0);
  147. end;
  148. procedure UpdateProgressGauge;
  149. begin
  150. var NewPosition := Integer(CurProgressValue shr ProgressShiftCount);
  151. if WizardForm.ProgressGauge.Position <> NewPosition then begin
  152. WizardForm.ProgressGauge.Position := NewPosition;
  153. WizardForm.ProgressGauge.Update;
  154. end;
  155. SetAppTaskbarProgressValue(UInt64(NewPosition), UInt64(WizardForm.ProgressGauge.Max));
  156. if (CodeRunner <> nil) and CodeRunner.FunctionExists('CurInstallProgressChanged', True) then begin
  157. try
  158. CodeRunner.RunProcedures('CurInstallProgressChanged', [NewPosition,
  159. WizardForm.ProgressGauge.Max], False);
  160. except
  161. Log('CurInstallProgressChanged raised an exception.');
  162. Application.HandleException(nil);
  163. end;
  164. end;
  165. end;
  166. procedure FinishProgressGauge(const HideGauge: Boolean);
  167. begin
  168. SetMessageBoxCallbackFunc(nil, 0);
  169. if HideGauge then
  170. WizardForm.ProgressGauge.Visible := False;
  171. SetAppTaskbarProgressState(tpsNoProgress);
  172. end;
  173. procedure SetProgress(const AProgress: Int64);
  174. begin
  175. CurProgressValue := AProgress;
  176. UpdateProgressGauge;
  177. end;
  178. procedure IncProgress(const N: Int64);
  179. begin
  180. Inc(CurProgressValue, N);
  181. UpdateProgressGauge;
  182. end;
  183. function CurProgress: Int64;
  184. begin
  185. Result := CurProgressValue;
  186. end;
  187. procedure ProcessEvents;
  188. { Processes any waiting events. Must call this this periodically or else
  189. events like clicking the Cancel button won't be processed.
  190. Calls Abort if NeedToAbortInstall is True, which is usually the result of
  191. the user clicking Cancel and the form closing. }
  192. begin
  193. if NeedToAbortInstall then Abort;
  194. Application.ProcessMessages;
  195. if NeedToAbortInstall then Abort;
  196. end;
  197. procedure InternalProgressProc(const Bytes: Cardinal);
  198. begin
  199. IncProgress(Bytes);
  200. ProcessEvents;
  201. end;
  202. procedure ExternalProgressProc64(const Bytes, MaxProgress: Int64);
  203. begin
  204. var NewProgress := CurProgress;
  205. Inc(NewProgress, Bytes);
  206. { In case the source file was larger than we thought it was, stop the
  207. progress bar at the maximum amount. Also see CopySourceFileToDestFile. }
  208. if NewProgress > MaxProgress then
  209. NewProgress := MaxProgress;
  210. SetProgress(NewProgress);
  211. ProcessEvents;
  212. end;
  213. procedure JustProcessEventsProc64(const Bytes, Param: Int64);
  214. begin
  215. ProcessEvents;
  216. end;
  217. function AbortRetryIgnoreTaskDialogMsgBox(const Text: String;
  218. const RetryIgnoreAbortButtonLabels: array of String): Boolean;
  219. { Returns True if Ignore was selected, False if Retry was selected, or
  220. calls Abort if Abort was selected. }
  221. begin
  222. Result := False;
  223. case LoggedTaskDialogMsgBox('', SetupMessages[msgAbortRetryIgnoreSelectAction], Text, '',
  224. mbError, MB_ABORTRETRYIGNORE, RetryIgnoreAbortButtonLabels, 0, True, IDABORT) of
  225. IDABORT: Abort;
  226. IDRETRY: ;
  227. IDIGNORE: Result := True;
  228. else
  229. Log('LoggedTaskDialogMsgBox returned an unexpected value. Assuming Abort.');
  230. Abort;
  231. end;
  232. end;
  233. function FileTimeToStr(const AFileTime: TFileTime): String;
  234. { Converts a TFileTime into a string for log purposes. }
  235. var
  236. FT: TFileTime;
  237. ST: TSystemTime;
  238. begin
  239. FileTimeToLocalFileTime(AFileTime, FT);
  240. if FileTimeToSystemTime(FT, ST) then
  241. Result := Format('%.4u-%.2u-%.2u %.2u:%.2u:%.2u.%.3u',
  242. [ST.wYear, ST.wMonth, ST.wDay, ST.wHour, ST.wMinute, ST.wSecond,
  243. ST.wMilliseconds])
  244. else
  245. Result := '(invalid)';
  246. end;
  247. function TryToGetSHA256OfFile(const DisableFsRedir: Boolean; const Filename: String;
  248. var Sum: TSHA256Digest): Boolean;
  249. { Like GetSHA256OfFile but traps exceptions locally. Returns True if successful. }
  250. begin
  251. try
  252. Sum := GetSHA256OfFile(DisableFsRedir, Filename);
  253. Result := True;
  254. except
  255. Result := False;
  256. end;
  257. end;
  258. procedure CopySourceFileToDestFile(const SourceF, DestF: TFile;
  259. [ref] const Verification: TSetupFileVerification; const ISSigSourceFilename: String;
  260. const AExpectedSize: Int64);
  261. { Copies all bytes from SourceF to DestF, incrementing process meter as it
  262. goes. Assumes file pointers of both are 0. }
  263. var
  264. Buf: array[0..16383] of Byte;
  265. Context: TSHA256Context;
  266. begin
  267. var ExpectedFileHash: TSHA256Digest;
  268. if Verification.Typ <> fvNone then begin
  269. if Verification.Typ = fvHash then
  270. ExpectedFileHash := Verification.Hash
  271. else
  272. DoISSigVerify(SourceF, nil, ISSigSourceFilename, True, Verification.ISSigAllowedKeys, ExpectedFileHash);
  273. { ExpectedFileHash checked below after copy }
  274. SHA256Init(Context);
  275. end;
  276. var MaxProgress := CurProgress;
  277. Inc(MaxProgress, AExpectedSize);
  278. var BytesLeft := SourceF.Size;
  279. { To avoid file system fragmentation, preallocate all of the bytes in the
  280. destination file }
  281. DestF.Seek(BytesLeft);
  282. DestF.Truncate;
  283. DestF.Seek(0);
  284. while BytesLeft > 0 do begin
  285. var BufSize: Cardinal := SizeOf(Buf);
  286. if BytesLeft < BufSize then
  287. BufSize := Cardinal(BytesLeft);
  288. SourceF.ReadBuffer(Buf, BufSize);
  289. DestF.WriteBuffer(Buf, BufSize);
  290. Dec(BytesLeft, BufSize);
  291. if Verification.Typ <> fvNone then
  292. SHA256Update(Context, Buf, BufSize);
  293. ExternalProgressProc64(BufSize, MaxProgress);
  294. end;
  295. if Verification.Typ <> fvNone then begin
  296. if not SHA256DigestsEqual(SHA256Final(Context), ExpectedFileHash) then
  297. VerificationError(veFileHashIncorrect);
  298. Log(VerificationSuccessfulLogMessage);
  299. end;
  300. { In case the source file was shorter than we thought it was, bump the
  301. progress bar to the maximum amount }
  302. SetProgress(MaxProgress);
  303. end;
  304. function ShortenOrExpandFontFilename(const Filename: String): String;
  305. { Expands Filename, except if it's in the Fonts directory, in which case it
  306. removes the path }
  307. var
  308. FontDir: String;
  309. begin
  310. Result := PathExpand(Filename);
  311. FontDir := GetShellFolder(False, sfFonts);
  312. if FontDir <> '' then
  313. if PathCompare(PathExtractDir(Result), FontDir) = 0 then
  314. Result := PathExtractName(Result);
  315. end;
  316. function GetLocalTimeAsStr: String;
  317. var
  318. SysTime: TSystemTime;
  319. begin
  320. GetLocalTime(SysTime);
  321. SetString(Result, PChar(@SysTime), SizeOf(SysTime) div SizeOf(Char));
  322. end;
  323. procedure PackCustomMessagesIntoString(var S: String);
  324. var
  325. M: TMemoryStream;
  326. Count, N: Integer;
  327. begin
  328. M := TMemoryStream.Create;
  329. try
  330. Count := 0;
  331. M.WriteBuffer(Count, SizeOf(Count)); { overwritten later }
  332. for var I := 0 to Entries[seCustomMessage].Count-1 do begin
  333. with PSetupCustomMessageEntry(Entries[seCustomMessage][I])^ do begin
  334. if (LangIndex = -1) or (LangIndex = ActiveLanguage) then begin
  335. N := Length(Name);
  336. M.WriteBuffer(N, SizeOf(N));
  337. M.WriteBuffer(Name[1], N*SizeOf(Name[1]));
  338. N := Length(Value);
  339. M.WriteBuffer(N, SizeOf(N));
  340. M.WriteBuffer(Value[1], N*SizeOf(Value[1]));
  341. Inc(Count);
  342. end;
  343. end;
  344. end;
  345. M.Seek(0, soFromBeginning);
  346. M.WriteBuffer(Count, SizeOf(Count));
  347. SetString(S, PChar(M.Memory), M.Size div SizeOf(Char));
  348. finally
  349. M.Free;
  350. end;
  351. end;
  352. function PackCompiledCodeTextIntoString(const CompiledCodeText: AnsiString): String;
  353. var
  354. N: Integer;
  355. begin
  356. N := Length(CompiledCodeText);
  357. if N mod 2 = 1 then
  358. Inc(N); { This will lead to 1 extra byte being moved but that's ok since it is the #0 }
  359. N := N div 2;
  360. SetString(Result, PChar(Pointer(CompiledCodeText)), N);
  361. end;
  362. procedure RegError(const Func: TRegErrorFunc; const RootKey: HKEY;
  363. const KeyName: String; const ErrorCode: DWORD);
  364. const
  365. ErrorMsgs: array[TRegErrorFunc] of TSetupMessageID =
  366. (msgErrorRegWriteKey, msgErrorRegCreateKey, msgErrorRegOpenKey);
  367. FuncNames: array[TRegErrorFunc] of String =
  368. ('RegSetValueEx', 'RegCreateKeyEx', 'RegOpenKeyEx');
  369. begin
  370. raise Exception.Create(FmtSetupMessage(ErrorMsgs[Func],
  371. [GetRegRootKeyName(RootKey), KeyName]) + SNewLine2 +
  372. FmtSetupMessage(msgErrorFunctionFailedWithMessage,
  373. [FuncNames[Func], IntToStr(ErrorCode), Win32ErrorString(DWORD(ErrorCode))]));
  374. end;
  375. procedure WriteMsgData(const F: TFile);
  376. var
  377. MsgLangOpts: TMessagesLangOptions;
  378. LangEntry: PSetupLanguageEntry;
  379. begin
  380. FillChar(MsgLangOpts, SizeOf(MsgLangOpts), 0);
  381. MsgLangOpts.ID := MessagesLangOptionsID;
  382. { TMessagesLangOptions fields and flags from LangOptions - together these are a simplified
  383. version of TSetupLanguageEntry }
  384. StrPLCopy(MsgLangOpts.DialogFontName, LangOptions.DialogFontName,
  385. (SizeOf(MsgLangOpts.DialogFontName) div SizeOf(MsgLangOpts.DialogFontName[0])) - 1);
  386. MsgLangOpts.DialogFontSize := LangOptions.DialogFontSize;
  387. MsgLangOpts.DialogFontBaseScaleWidth := LangOptions.DialogFontBaseScaleWidth;
  388. MsgLangOpts.DialogFontBaseScaleHeight := LangOptions.DialogFontBaseScaleHeight;
  389. if LangOptions.RightToLeft then
  390. Include(MsgLangOpts.Flags, lfRightToLeft);
  391. { Other TMessagesLangOptions fields and flags - all appearance only }
  392. MsgLangOpts.WizardSizePercentX := SetupHeader.WizardSizePercentX;
  393. MsgLangOpts.WizardSizePercentY := SetupHeader.WizardSizePercentY;
  394. MsgLangOpts.WizardBackColor := OrigSetupHeaderWizardBackColor; { See Setup.MainFunc }
  395. MsgLangOpts.WizardBackColorDynamicDark := SetupHeader.WizardBackColorDynamicDark;
  396. MsgLangOpts.WizardLightControlStyling := SetupHeader.WizardLightControlStyling;
  397. if shWizardModern in SetupHeader.Options then
  398. Include(MsgLangOpts.Flags, lfWizardModern);
  399. if shWizardBorderStyled in SetupHeader.Options then
  400. Include(MsgLangOpts.Flags, lfWizardBorderStyled);
  401. if shWizardKeepAspectRatio in SetupHeader.Options then
  402. Include(MsgLangOpts.Flags, lfWizardKeepAspectRatio);
  403. if shWizardBevelsHidden in SetupHeader.Options then
  404. Include(MsgLangOpts.Flags, lfWizardBevelsHidden);
  405. if SetupHeader.WizardDarkStyle = wdsDark then
  406. Include(MsgLangOpts.Flags, lfWizardDarkStyleDark)
  407. else if SetupHeader.WizardDarkStyle = wdsDynamic then
  408. Include(MsgLangOpts.Flags, lfWizardDarkStyleDynamic);
  409. LangEntry := Entries[seLanguage][ActiveLanguage];
  410. F.WriteBuffer(LangEntry.Data[1], ULength(LangEntry.Data));
  411. F.WriteBuffer(MsgLangOpts, SizeOf(MsgLangOpts));
  412. end;
  413. procedure MarkExeHeader(const F: TFile; const ModeID: Longint);
  414. begin
  415. F.Seek(SetupExeModeOffset);
  416. F.WriteBuffer(ModeID, SizeOf(ModeID));
  417. end;
  418. procedure ProcessInstallDeleteEntries;
  419. begin
  420. for var I := 0 to Entries[seInstallDelete].Count-1 do
  421. with PSetupDeleteEntry(Entries[seInstallDelete][I])^ do
  422. if ShouldProcessEntry(WizardComponents, WizardTasks, Components, Tasks, Languages, Check) then begin
  423. DebugNotifyEntry(seInstallDelete, I);
  424. NotifyBeforeInstallEntry(BeforeInstall);
  425. case DeleteType of
  426. dfFiles, dfFilesAndOrSubdirs:
  427. DelTree(InstallDefaultDisableFsRedir, ExpandConst(Name), False, True, DeleteType = dfFilesAndOrSubdirs, False,
  428. nil, nil, nil);
  429. dfDirIfEmpty:
  430. DelTree(InstallDefaultDisableFsRedir, ExpandConst(Name), True, False, False, False, nil, nil, nil);
  431. end;
  432. NotifyAfterInstallEntry(AfterInstall);
  433. end;
  434. end;
  435. procedure ProcessNeedRestartEvent;
  436. begin
  437. if (CodeRunner <> nil) and CodeRunner.FunctionExists('NeedRestart', True) then begin
  438. if not NeedsRestart then begin
  439. try
  440. if CodeRunner.RunBooleanFunctions('NeedRestart', [''], bcTrue, False, False) then begin
  441. NeedsRestart := True;
  442. Log('Will restart because NeedRestart returned True.');
  443. end;
  444. except
  445. Log('NeedRestart raised an exception.');
  446. Application.HandleException(nil);
  447. end;
  448. end
  449. else
  450. Log('Not calling NeedRestart because a restart has already been deemed necessary.');
  451. end;
  452. end;
  453. procedure ProcessComponentEntries;
  454. begin
  455. for var I := 0 to Entries[seComponent].Count-1 do begin
  456. with PSetupComponentEntry(Entries[seComponent][I])^ do begin
  457. if ShouldProcessEntry(WizardComponents, nil, Name, '', Languages, '') and (coRestart in Options) then begin
  458. NeedsRestart := True;
  459. Break;
  460. end;
  461. end;
  462. end;
  463. end;
  464. procedure ProcessTasksEntries;
  465. begin
  466. for var I := 0 to Entries[seTask].Count-1 do begin
  467. with PSetupTaskEntry(Entries[seTask][I])^ do begin
  468. if ShouldProcessEntry(nil, WizardTasks, '', Name, Languages, '') and (toRestart in Options) then begin
  469. NeedsRestart := True;
  470. Break;
  471. end;
  472. end;
  473. end;
  474. end;
  475. procedure ShutdownApplications;
  476. const
  477. ERROR_FAIL_SHUTDOWN = 351;
  478. ForcedStrings: array [Boolean] of String = ('', ' (forced)');
  479. ForcedActionFlag: array [Boolean] of ULONG = (0, RmForceShutdown);
  480. var
  481. Forced: Boolean;
  482. Error: DWORD;
  483. begin
  484. Forced := InitForceCloseApplications or
  485. ((shForceCloseApplications in SetupHeader.Options) and not InitNoForceCloseApplications);
  486. Log('Shutting down applications using our files.' + ForcedStrings[Forced]);
  487. RmDoRestart := True;
  488. Error := RmShutdown(RmSessionHandle, ForcedActionFlag[Forced], nil);
  489. while Error = ERROR_FAIL_SHUTDOWN do begin
  490. Log('Some applications could not be shut down.');
  491. if AbortRetryIgnoreTaskDialogMsgBox(
  492. SetupMessages[msgErrorCloseApplications],
  493. [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgAbortRetryIgnoreIgnore], SetupMessages[msgAbortRetryIgnoreCancel]]) then
  494. Break;
  495. Log('Retrying to shut down applications using our files.' + ForcedStrings[Forced]);
  496. Error := RmShutdown(RmSessionHandle, ForcedActionFlag[Forced], nil);
  497. end;
  498. { Close session on all errors except for ERROR_FAIL_SHUTDOWN, should still call RmRestart in that case. }
  499. if (Error <> ERROR_SUCCESS) and (Error <> ERROR_FAIL_SHUTDOWN) then begin
  500. RmEndSession(RmSessionHandle);
  501. LogFmt('RmShutdown returned an error: %d', [Error]);
  502. RmDoRestart := False;
  503. end;
  504. end;
  505. end.