Setup.Install.HelperFunc.pas 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  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: LongInt; 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: Longint);
  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,
  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: LongInt; 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. N: Integer;
  108. CurFile: PSetupFileEntry;
  109. begin
  110. InstallFilesSize := 0;
  111. AfterInstallFilesSize := InstallFilesSize;
  112. for N := 0 to Entries[seFile].Count-1 do begin
  113. CurFile := PSetupFileEntry(Entries[seFile][N]);
  114. if ShouldProcessFileEntry(WizardComponents, WizardTasks, CurFile, False) then begin
  115. with CurFile^ do begin
  116. var FileSize: Int64;
  117. if LocationEntry <> -1 then { not an "external" file }
  118. FileSize := PSetupFileLocationEntry(Entries[seFileLocation][
  119. LocationEntry])^.OriginalSize
  120. else
  121. FileSize := ExternalSize;
  122. Inc(InstallFilesSize, FileSize);
  123. if not (foDeleteAfterInstall in Options) then
  124. Inc(AfterInstallFilesSize, FileSize);
  125. end;
  126. end;
  127. end;
  128. end;
  129. var
  130. CurProgressValue: Int64;
  131. ProgressShiftCount: Cardinal;
  132. procedure InitProgressGauge(const InstallFilesSize: Int64);
  133. begin
  134. { Calculate the MaxValue for the progress meter }
  135. var NewMaxValue: Int64 := 1000 * Entries[seIcon].Count;
  136. if Entries[seIni].Count <> 0 then Inc(NewMaxValue, 1000);
  137. if Entries[seRegistry].Count <> 0 then Inc(NewMaxValue, 1000);
  138. Inc(NewMaxValue, InstallFilesSize);
  139. { To avoid progress updates that are too small to result in any visible
  140. change, divide the Max value by 2 until it's under 1500 }
  141. ProgressShiftCount := 0;
  142. while NewMaxValue >= 1500 do begin
  143. NewMaxValue := NewMaxValue shr 1;
  144. Inc(ProgressShiftCount);
  145. end;
  146. WizardForm.ProgressGauge.Max := NewMaxValue;
  147. SetMessageBoxCallbackFunc(InstallMessageBoxCallback, 0);
  148. end;
  149. procedure UpdateProgressGauge;
  150. begin
  151. var NewPosition := Integer(CurProgressValue shr ProgressShiftCount);
  152. if WizardForm.ProgressGauge.Position <> NewPosition then begin
  153. WizardForm.ProgressGauge.Position := NewPosition;
  154. WizardForm.ProgressGauge.Update;
  155. end;
  156. SetAppTaskbarProgressValue(NewPosition, WizardForm.ProgressGauge.Max);
  157. if (CodeRunner <> nil) and CodeRunner.FunctionExists('CurInstallProgressChanged', True) then begin
  158. try
  159. CodeRunner.RunProcedures('CurInstallProgressChanged', [NewPosition,
  160. WizardForm.ProgressGauge.Max], False);
  161. except
  162. Log('CurInstallProgressChanged raised an exception.');
  163. Application.HandleException(nil);
  164. end;
  165. end;
  166. end;
  167. procedure FinishProgressGauge(const HideGauge: Boolean);
  168. begin
  169. SetMessageBoxCallbackFunc(nil, 0);
  170. if HideGauge then
  171. WizardForm.ProgressGauge.Visible := False;
  172. SetAppTaskbarProgressState(tpsNoProgress);
  173. end;
  174. procedure SetProgress(const AProgress: Int64);
  175. begin
  176. CurProgressValue := AProgress;
  177. UpdateProgressGauge;
  178. end;
  179. procedure IncProgress(const N: Int64);
  180. begin
  181. Inc(CurProgressValue, N);
  182. UpdateProgressGauge;
  183. end;
  184. function CurProgress: Int64;
  185. begin
  186. Result := CurProgressValue;
  187. end;
  188. procedure ProcessEvents;
  189. { Processes any waiting events. Must call this this periodically or else
  190. events like clicking the Cancel button won't be processed.
  191. Calls Abort if NeedToAbortInstall is True, which is usually the result of
  192. the user clicking Cancel and the form closing. }
  193. begin
  194. if NeedToAbortInstall then Abort;
  195. Application.ProcessMessages;
  196. if NeedToAbortInstall then Abort;
  197. end;
  198. procedure InternalProgressProc(const Bytes: Cardinal);
  199. begin
  200. IncProgress(Bytes);
  201. ProcessEvents;
  202. end;
  203. procedure ExternalProgressProc64(const Bytes, MaxProgress: Int64);
  204. begin
  205. var NewProgress := CurProgress;
  206. Inc(NewProgress, Bytes);
  207. { In case the source file was larger than we thought it was, stop the
  208. progress bar at the maximum amount. Also see CopySourceFileToDestFile. }
  209. if NewProgress > MaxProgress then
  210. NewProgress := MaxProgress;
  211. SetProgress(NewProgress);
  212. ProcessEvents;
  213. end;
  214. procedure JustProcessEventsProc64(const Bytes, Param: Int64);
  215. begin
  216. ProcessEvents;
  217. end;
  218. function AbortRetryIgnoreTaskDialogMsgBox(const Text: String;
  219. const RetryIgnoreAbortButtonLabels: array of String): Boolean;
  220. { Returns True if Ignore was selected, False if Retry was selected, or
  221. calls Abort if Abort was selected. }
  222. begin
  223. Result := False;
  224. case LoggedTaskDialogMsgBox('', SetupMessages[msgAbortRetryIgnoreSelectAction], Text, '',
  225. mbError, MB_ABORTRETRYIGNORE, RetryIgnoreAbortButtonLabels, 0, True, IDABORT) of
  226. IDABORT: Abort;
  227. IDRETRY: ;
  228. IDIGNORE: Result := True;
  229. else
  230. Log('LoggedTaskDialogMsgBox returned an unexpected value. Assuming Abort.');
  231. Abort;
  232. end;
  233. end;
  234. function FileTimeToStr(const AFileTime: TFileTime): String;
  235. { Converts a TFileTime into a string for log purposes. }
  236. var
  237. FT: TFileTime;
  238. ST: TSystemTime;
  239. begin
  240. FileTimeToLocalFileTime(AFileTime, FT);
  241. if FileTimeToSystemTime(FT, ST) then
  242. Result := Format('%.4u-%.2u-%.2u %.2u:%.2u:%.2u.%.3u',
  243. [ST.wYear, ST.wMonth, ST.wDay, ST.wHour, ST.wMinute, ST.wSecond,
  244. ST.wMilliseconds])
  245. else
  246. Result := '(invalid)';
  247. end;
  248. function TryToGetSHA256OfFile(const DisableFsRedir: Boolean; const Filename: String;
  249. var Sum: TSHA256Digest): Boolean;
  250. { Like GetSHA256OfFile but traps exceptions locally. Returns True if successful. }
  251. begin
  252. try
  253. Sum := GetSHA256OfFile(DisableFsRedir, Filename);
  254. Result := True;
  255. except
  256. Result := False;
  257. end;
  258. end;
  259. procedure CopySourceFileToDestFile(const SourceF, DestF: TFile;
  260. [ref] const Verification: TSetupFileVerification; const ISSigSourceFilename: String;
  261. const AExpectedSize: Int64);
  262. { Copies all bytes from SourceF to DestF, incrementing process meter as it
  263. goes. Assumes file pointers of both are 0. }
  264. var
  265. Buf: array[0..16383] of Byte;
  266. Context: TSHA256Context;
  267. begin
  268. var ExpectedFileHash: TSHA256Digest;
  269. if Verification.Typ <> fvNone then begin
  270. if Verification.Typ = fvHash then
  271. ExpectedFileHash := Verification.Hash
  272. else
  273. DoISSigVerify(SourceF, nil, ISSigSourceFilename, True, Verification.ISSigAllowedKeys, ExpectedFileHash);
  274. { ExpectedFileHash checked below after copy }
  275. SHA256Init(Context);
  276. end;
  277. var MaxProgress := CurProgress;
  278. Inc(MaxProgress, AExpectedSize);
  279. var BytesLeft := SourceF.Size;
  280. { To avoid file system fragmentation, preallocate all of the bytes in the
  281. destination file }
  282. DestF.Seek(BytesLeft);
  283. DestF.Truncate;
  284. DestF.Seek(0);
  285. while BytesLeft > 0 do begin
  286. var BufSize: Cardinal := SizeOf(Buf);
  287. if BytesLeft < BufSize then
  288. BufSize := Cardinal(BytesLeft);
  289. SourceF.ReadBuffer(Buf, BufSize);
  290. DestF.WriteBuffer(Buf, BufSize);
  291. Dec(BytesLeft, BufSize);
  292. if Verification.Typ <> fvNone then
  293. SHA256Update(Context, Buf, BufSize);
  294. ExternalProgressProc64(BufSize, MaxProgress);
  295. end;
  296. if Verification.Typ <> fvNone then begin
  297. if not SHA256DigestsEqual(SHA256Final(Context), ExpectedFileHash) then
  298. VerificationError(veFileHashIncorrect);
  299. Log(VerificationSuccessfulLogMessage);
  300. end;
  301. { In case the source file was shorter than we thought it was, bump the
  302. progress bar to the maximum amount }
  303. SetProgress(MaxProgress);
  304. end;
  305. function ShortenOrExpandFontFilename(const Filename: String): String;
  306. { Expands Filename, except if it's in the Fonts directory, in which case it
  307. removes the path }
  308. var
  309. FontDir: String;
  310. begin
  311. Result := PathExpand(Filename);
  312. FontDir := GetShellFolder(False, sfFonts);
  313. if FontDir <> '' then
  314. if PathCompare(PathExtractDir(Result), FontDir) = 0 then
  315. Result := PathExtractName(Result);
  316. end;
  317. function GetLocalTimeAsStr: String;
  318. var
  319. SysTime: TSystemTime;
  320. begin
  321. GetLocalTime(SysTime);
  322. SetString(Result, PChar(@SysTime), SizeOf(SysTime) div SizeOf(Char));
  323. end;
  324. procedure PackCustomMessagesIntoString(var S: String);
  325. var
  326. M: TMemoryStream;
  327. Count, I, N: Integer;
  328. begin
  329. M := TMemoryStream.Create;
  330. try
  331. Count := 0;
  332. M.WriteBuffer(Count, SizeOf(Count)); { overwritten later }
  333. for I := 0 to Entries[seCustomMessage].Count-1 do begin
  334. with PSetupCustomMessageEntry(Entries[seCustomMessage][I])^ do begin
  335. if (LangIndex = -1) or (LangIndex = ActiveLanguage) then begin
  336. N := Length(Name);
  337. M.WriteBuffer(N, SizeOf(N));
  338. M.WriteBuffer(Name[1], N*SizeOf(Name[1]));
  339. N := Length(Value);
  340. M.WriteBuffer(N, SizeOf(N));
  341. M.WriteBuffer(Value[1], N*SizeOf(Value[1]));
  342. Inc(Count);
  343. end;
  344. end;
  345. end;
  346. M.Seek(0, soFromBeginning);
  347. M.WriteBuffer(Count, SizeOf(Count));
  348. SetString(S, PChar(M.Memory), M.Size div SizeOf(Char));
  349. finally
  350. M.Free;
  351. end;
  352. end;
  353. function PackCompiledCodeTextIntoString(const CompiledCodeText: AnsiString): String;
  354. var
  355. N: Integer;
  356. begin
  357. N := Length(CompiledCodeText);
  358. if N mod 2 = 1 then
  359. Inc(N); { This will lead to 1 extra byte being moved but that's ok since it is the #0 }
  360. N := N div 2;
  361. SetString(Result, PChar(Pointer(CompiledCodeText)), N);
  362. end;
  363. procedure RegError(const Func: TRegErrorFunc; const RootKey: HKEY;
  364. const KeyName: String; const ErrorCode: Longint);
  365. const
  366. ErrorMsgs: array[TRegErrorFunc] of TSetupMessageID =
  367. (msgErrorRegWriteKey, msgErrorRegCreateKey, msgErrorRegOpenKey);
  368. FuncNames: array[TRegErrorFunc] of String =
  369. ('RegSetValueEx', 'RegCreateKeyEx', 'RegOpenKeyEx');
  370. begin
  371. raise Exception.Create(FmtSetupMessage(ErrorMsgs[Func],
  372. [GetRegRootKeyName(RootKey), KeyName]) + SNewLine2 +
  373. FmtSetupMessage(msgErrorFunctionFailedWithMessage,
  374. [FuncNames[Func], IntToStr(ErrorCode), Win32ErrorString(ErrorCode)]));
  375. end;
  376. procedure WriteMsgData(const F: TFile);
  377. var
  378. MsgLangOpts: TMessagesLangOptions;
  379. LangEntry: PSetupLanguageEntry;
  380. begin
  381. FillChar(MsgLangOpts, SizeOf(MsgLangOpts), 0);
  382. MsgLangOpts.ID := MessagesLangOptionsID;
  383. StrPLCopy(MsgLangOpts.DialogFontName, LangOptions.DialogFontName,
  384. (SizeOf(MsgLangOpts.DialogFontName) div SizeOf(MsgLangOpts.DialogFontName[0])) - 1);
  385. MsgLangOpts.DialogFontSize := LangOptions.DialogFontSize;
  386. MsgLangOpts.DialogFontBaseScaleWidth := LangOptions.DialogFontBaseScaleWidth;
  387. MsgLangOpts.DialogFontBaseScaleHeight := LangOptions.DialogFontBaseScaleHeight;
  388. if LangOptions.RightToLeft then
  389. Include(MsgLangOpts.Flags, lfRightToLeft);
  390. LangEntry := Entries[seLanguage][ActiveLanguage];
  391. F.WriteBuffer(LangEntry.Data[1], Length(LangEntry.Data));
  392. F.WriteBuffer(MsgLangOpts, SizeOf(MsgLangOpts));
  393. end;
  394. procedure MarkExeHeader(const F: TFile; const ModeID: Longint);
  395. begin
  396. F.Seek(SetupExeModeOffset);
  397. F.WriteBuffer(ModeID, SizeOf(ModeID));
  398. end;
  399. procedure ProcessInstallDeleteEntries;
  400. var
  401. I: Integer;
  402. begin
  403. for I := 0 to Entries[seInstallDelete].Count-1 do
  404. with PSetupDeleteEntry(Entries[seInstallDelete][I])^ do
  405. if ShouldProcessEntry(WizardComponents, WizardTasks, Components, Tasks, Languages, Check) then begin
  406. DebugNotifyEntry(seInstallDelete, I);
  407. NotifyBeforeInstallEntry(BeforeInstall);
  408. case DeleteType of
  409. dfFiles, dfFilesAndOrSubdirs:
  410. DelTree(InstallDefaultDisableFsRedir, ExpandConst(Name), False, True, DeleteType = dfFilesAndOrSubdirs, False,
  411. nil, nil, nil);
  412. dfDirIfEmpty:
  413. DelTree(InstallDefaultDisableFsRedir, ExpandConst(Name), True, False, False, False, nil, nil, nil);
  414. end;
  415. NotifyAfterInstallEntry(AfterInstall);
  416. end;
  417. end;
  418. procedure ProcessNeedRestartEvent;
  419. begin
  420. if (CodeRunner <> nil) and CodeRunner.FunctionExists('NeedRestart', True) then begin
  421. if not NeedsRestart then begin
  422. try
  423. if CodeRunner.RunBooleanFunctions('NeedRestart', [''], bcTrue, False, False) then begin
  424. NeedsRestart := True;
  425. Log('Will restart because NeedRestart returned True.');
  426. end;
  427. except
  428. Log('NeedRestart raised an exception.');
  429. Application.HandleException(nil);
  430. end;
  431. end
  432. else
  433. Log('Not calling NeedRestart because a restart has already been deemed necessary.');
  434. end;
  435. end;
  436. procedure ProcessComponentEntries;
  437. var
  438. I: Integer;
  439. begin
  440. for I := 0 to Entries[seComponent].Count-1 do begin
  441. with PSetupComponentEntry(Entries[seComponent][I])^ do begin
  442. if ShouldProcessEntry(WizardComponents, nil, Name, '', Languages, '') and (coRestart in Options) then begin
  443. NeedsRestart := True;
  444. Break;
  445. end;
  446. end;
  447. end;
  448. end;
  449. procedure ProcessTasksEntries;
  450. var
  451. I: Integer;
  452. begin
  453. for I := 0 to Entries[seTask].Count-1 do begin
  454. with PSetupTaskEntry(Entries[seTask][I])^ do begin
  455. if ShouldProcessEntry(nil, WizardTasks, '', Name, Languages, '') and (toRestart in Options) then begin
  456. NeedsRestart := True;
  457. Break;
  458. end;
  459. end;
  460. end;
  461. end;
  462. procedure ShutdownApplications;
  463. const
  464. ERROR_FAIL_SHUTDOWN = 351;
  465. ForcedStrings: array [Boolean] of String = ('', ' (forced)');
  466. ForcedActionFlag: array [Boolean] of ULONG = (0, RmForceShutdown);
  467. var
  468. Forced: Boolean;
  469. Error: DWORD;
  470. begin
  471. Forced := InitForceCloseApplications or
  472. ((shForceCloseApplications in SetupHeader.Options) and not InitNoForceCloseApplications);
  473. Log('Shutting down applications using our files.' + ForcedStrings[Forced]);
  474. RmDoRestart := True;
  475. Error := RmShutdown(RmSessionHandle, ForcedActionFlag[Forced], nil);
  476. while Error = ERROR_FAIL_SHUTDOWN do begin
  477. Log('Some applications could not be shut down.');
  478. if AbortRetryIgnoreTaskDialogMsgBox(
  479. SetupMessages[msgErrorCloseApplications],
  480. [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgAbortRetryIgnoreIgnore], SetupMessages[msgAbortRetryIgnoreCancel]]) then
  481. Break;
  482. Log('Retrying to shut down applications using our files.' + ForcedStrings[Forced]);
  483. Error := RmShutdown(RmSessionHandle, ForcedActionFlag[Forced], nil);
  484. end;
  485. { Close session on all errors except for ERROR_FAIL_SHUTDOWN, should still call RmRestart in that case. }
  486. if (Error <> ERROR_SUCCESS) and (Error <> ERROR_FAIL_SHUTDOWN) then begin
  487. RmEndSession(RmSessionHandle);
  488. LogFmt('RmShutdown returned an error: %d', [Error]);
  489. RmDoRestart := False;
  490. end;
  491. end;
  492. end.