Setup.Uninstall.pas 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902
  1. unit Setup.Uninstall;
  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. Uninstaller
  8. }
  9. interface
  10. procedure RunUninstaller;
  11. procedure HandleUninstallerEndSession;
  12. implementation
  13. uses
  14. Windows, SysUtils, Messages, Forms, Themes,
  15. PathFunc, BidiCtrls,
  16. Shared.CommonFunc, Shared.CommonFunc.Vcl, Setup.UninstallLog, SetupLdrAndSetup.Messages,
  17. Shared.SetupMessageIDs, SetupLdrAndSetup.InstFunc, Setup.InstFunc, Shared.Struct,
  18. Shared.SetupEntFunc, Setup.UninstallProgressForm, Setup.UninstallSharedFileForm,
  19. Shared.FileClass, Setup.ScriptRunner, Setup.DebugClient, Shared.SetupSteps,
  20. Setup.LoggingFunc, Setup.MainFunc, Setup.SpawnServer;
  21. type
  22. TExtUninstallLog = class(TUninstallLog)
  23. private
  24. FLastUpdateTime: DWORD;
  25. FNoSharedFileDlgs: Boolean;
  26. FRemoveSharedFiles: Boolean;
  27. protected
  28. procedure HandleException; override;
  29. function ShouldRemoveSharedFile(const Filename: String): Boolean; override;
  30. procedure StatusUpdate(StartingCount, CurCount: Integer); override;
  31. end;
  32. const
  33. WM_KillFirstPhase = WM_USER + 333;
  34. var
  35. UninstallExitCode: DWORD = 1;
  36. UninstExeFilename, UninstDataFilename, UninstMsgFilename: String;
  37. UninstDataFile: TFile;
  38. UninstLog: TExtUninstallLog = nil;
  39. Title: String;
  40. DidRespawn, SecondPhase: Boolean;
  41. EnableLogging, Silent, VerySilent, NoRestart: Boolean;
  42. LogFilename: String;
  43. InitialProcessWnd, FirstPhaseWnd, DebugServerWnd: HWND;
  44. OldWindowProc: Pointer;
  45. procedure ShowExceptionMsg;
  46. begin
  47. { Also see ShowExceptionMsg in Setup.inc }
  48. if ExceptObject is EAbort then
  49. Exit;
  50. const Msg = GetExceptMessage;
  51. Log('Exception message:');
  52. LoggedMsgBox(PChar(Msg), Pointer(SetupMessages[msgErrorTitle]),
  53. MB_OK or MB_ICONSTOP, True, IDOK);
  54. { ^ use a Pointer cast instead of a PChar cast so that it will use "nil"
  55. if SetupMessages[msgErrorTitle] is empty due to the messages not being
  56. loaded yet. LoggedMsgBox displays 'Error' as the caption if the lpCaption
  57. parameter is nil, see GetMessageBoxCaption. }
  58. end;
  59. procedure TExtUninstallLog.HandleException;
  60. begin
  61. ShowExceptionMsg;
  62. end;
  63. function TExtUninstallLog.ShouldRemoveSharedFile(const Filename: String): Boolean;
  64. const
  65. SToAll: array[Boolean] of String = ('', ' to All');
  66. begin
  67. if Silent or VerySilent then
  68. Result := True
  69. else begin
  70. if not FNoSharedFileDlgs then begin
  71. { FNoSharedFileDlgs will be set to True if a "...to All" button is clicked }
  72. FRemoveSharedFiles := ExecuteRemoveSharedFileDlg(Filename,
  73. FNoSharedFileDlgs);
  74. LogFmt('Remove shared file %s? User chose %s%s', [Filename, SYesNo[FRemoveSharedFiles], SToAll[FNoSharedFileDlgs]]);
  75. end;
  76. Result := FRemoveSharedFiles;
  77. end;
  78. end;
  79. procedure InitializeUninstallProgressForm;
  80. begin
  81. UninstallProgressForm := AppCreateForm(TUninstallProgressForm) as TUninstallProgressForm;
  82. UninstallProgressForm.Initialize(Title, UninstLog.AppName, ufWizardModern in UninstLog.Flags,
  83. MainIconPostfix, WizardIconsPostfix);
  84. if CodeRunner <> nil then begin
  85. try
  86. CodeRunner.RunProcedures('InitializeUninstallProgressForm', [''], False);
  87. except
  88. Log('InitializeUninstallProgressForm raised an exception (fatal).');
  89. raise;
  90. end;
  91. end;
  92. if not VerySilent then begin
  93. UninstallProgressForm.Show;
  94. { Ensure the form is fully painted now in case
  95. CurUninstallStepChanged(usUninstall) take a long time to return }
  96. UninstallProgressForm.Update;
  97. end;
  98. end;
  99. procedure TExtUninstallLog.StatusUpdate(StartingCount, CurCount: Integer);
  100. var
  101. NowTime: DWORD;
  102. begin
  103. { Only update the progress bar if it's at the beginning or end, or if
  104. 30 ms has passed since the last update (so that updating the progress
  105. bar doesn't slow down the actual uninstallation process). }
  106. NowTime := GetTickCount;
  107. if (Cardinal(NowTime - FLastUpdateTime) >= Cardinal(30)) or
  108. (StartingCount = CurCount) or (CurCount = 0) then begin
  109. FLastUpdateTime := NowTime;
  110. UninstallProgressForm.UpdateProgress(StartingCount - CurCount, StartingCount);
  111. end;
  112. Application.ProcessMessages;
  113. end;
  114. function LoggedMessageBoxFmt1(const ID: TSetupMessageID; const Arg1: String;
  115. const Title: String; const Flags: UINT; const Suppressible: Boolean;
  116. const Default: Integer): Integer;
  117. begin
  118. Result := LoggedMsgBox(PChar(FmtSetupMessage1(ID, Arg1)), PChar(Title),
  119. Flags, Suppressible, Default);
  120. end;
  121. procedure RaiseLastError(const S: String);
  122. var
  123. ErrorCode: DWORD;
  124. begin
  125. ErrorCode := GetLastError;
  126. raise Exception.Create(FmtSetupMessage(msgLastErrorMessage,
  127. [S, IntToStr(ErrorCode), Win32ErrorString(ErrorCode)]));
  128. end;
  129. function Exec(const Filename: String; const Parms: String): THandle;
  130. var
  131. CmdLine: String;
  132. StartupInfo: TStartupInfo;
  133. ProcessInfo: TProcessInformation;
  134. begin
  135. CmdLine := '"' + Filename + '" ' + Parms;
  136. FillChar(StartupInfo, SizeOf(StartupInfo), 0);
  137. StartupInfo.cb := SizeOf(StartupInfo);
  138. if not CreateProcess(nil, PChar(CmdLine), nil, nil, False, 0, nil, nil,
  139. StartupInfo, ProcessInfo) then
  140. RaiseLastError(SetupMessages[msgLdrCannotExecTemp]);
  141. CloseHandle(ProcessInfo.hThread);
  142. Result := ProcessInfo.hProcess;
  143. end;
  144. function ProcessMsgs: Boolean;
  145. var
  146. Msg: TMsg;
  147. begin
  148. Result := False;
  149. while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do begin
  150. if Msg.Message = WM_QUIT then begin
  151. Result := True;
  152. Break;
  153. end;
  154. TranslateMessage(Msg);
  155. DispatchMessage(Msg);
  156. end;
  157. end;
  158. function FirstPhaseWindowProc(Wnd: HWND; Msg: UINT; wParam: WPARAM;
  159. lParam: LPARAM): LRESULT; stdcall;
  160. begin
  161. Result := 0;
  162. case Msg of
  163. WM_QUERYENDSESSION: ; { Return zero to deny any shutdown requests }
  164. WM_KillFirstPhase: begin
  165. PostQuitMessage(0);
  166. { If we got WM_KillFirstPhase, the second phase must have been
  167. successful (up until now, at least). Set an exit code of 0. }
  168. UninstallExitCode := 0;
  169. end;
  170. else
  171. Result := CallWindowProc(OldWindowProc, Wnd, Msg, wParam, lParam);
  172. end;
  173. end;
  174. procedure DeleteUninstallDataFiles;
  175. procedure CreateUninstallDoneFile;
  176. { Creates the _unins-done.tmp file, which is an empty file that signals to
  177. DeleteResidualTempUninstallDirs that the temporary directory needs to be
  178. deleted, because we're terminating the first phase. }
  179. begin
  180. const SelfExeFilename = NewParamStr(0);
  181. if not SameText(PathExtractName(SelfExeFilename), '_unins.tmp') then begin
  182. { This may be the case when debugging }
  183. Log('Current Uninstall EXE is not named "_unins.tmp"; not creating "_unins-done.tmp".');
  184. Exit;
  185. end;
  186. const DoneFilename = PathExtractPath(SelfExeFilename) + '_unins-done.tmp';
  187. Log('Creating "_unins-done.tmp" file.');
  188. const H = CreateFile(PChar(DoneFilename), GENERIC_WRITE, FILE_SHARE_READ,
  189. nil, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
  190. if H = INVALID_HANDLE_VALUE then
  191. LogWithLastError('Failed to create "_unins-done.tmp".');
  192. { The file is intentionally never closed (until it gets closed
  193. automatically when this process terminates) so that it can't be opened
  194. for DELETE access by DeleteResidualTempUninstallDirs while we're still
  195. running; see comments there. }
  196. end;
  197. var
  198. ProcessWnd: HWND;
  199. ProcessID: DWORD;
  200. Process: THandle;
  201. begin
  202. Log('Deleting Uninstall data files.');
  203. { Truncate the .dat file to zero bytes just before relinquishing exclusive
  204. access to it }
  205. try
  206. UninstDataFile.Seek(0);
  207. UninstDataFile.Truncate;
  208. except
  209. { ignore any exceptions, just in case }
  210. end;
  211. FreeAndNil(UninstDataFile);
  212. { Delete the .dat and .msg files }
  213. DeleteFile(UninstDataFilename);
  214. DeleteFile(UninstMsgFilename);
  215. { Tell the first phase to terminate, then delete its .exe }
  216. CreateUninstallDoneFile;
  217. if FirstPhaseWnd <> 0 then begin
  218. if InitialProcessWnd <> 0 then
  219. { If the first phase respawned, wait on the initial process }
  220. ProcessWnd := InitialProcessWnd
  221. else
  222. ProcessWnd := FirstPhaseWnd;
  223. ProcessID := 0;
  224. if GetWindowThreadProcessId(ProcessWnd, @ProcessID) <> 0 then
  225. Process := OpenProcess(SYNCHRONIZE, False, ProcessID)
  226. else
  227. Process := 0; { shouldn't get here }
  228. SendNotifyMessage(FirstPhaseWnd, WM_KillFirstPhase, 0, 0);
  229. if Process <> 0 then begin
  230. WaitForSingleObject(Process, INFINITE);
  231. CloseHandle(Process);
  232. end;
  233. { Sleep for a bit to allow pre-Windows 2000 Add/Remove Programs to finish
  234. bringing itself to the foreground before we take it back below. Also
  235. helps the DelayDeleteFile call succeed on the first try. }
  236. if not Debugging then
  237. Sleep(500);
  238. end;
  239. UninstallExitCode := 0;
  240. DelayDeleteFile(False, UninstExeFilename, 13, 50, 250);
  241. if Debugging then
  242. DebugNotifyUninstExe('');
  243. { Pre-Windows 2000 Add/Remove Programs will try to bring itself to the
  244. foreground after the first phase terminates. Take it back. }
  245. Application.BringToFront;
  246. end;
  247. procedure ProcessCommandLine;
  248. var
  249. WantToSuppressMsgBoxes, ParamIsAutomaticInternal: Boolean;
  250. I: Integer;
  251. ParamName, ParamValue: String;
  252. begin
  253. WantToSuppressMsgBoxes := False;
  254. { NewParamsForCode will hold all params except automatic internal ones like /SECONDPHASE= and /DEBUGWND=
  255. Actually currently only needed in the second phase, but setting always anyway
  256. Also see Main.InitializeSetup }
  257. NewParamsForCode.Add(NewParamStr(0));
  258. for I := 1 to NewParamCount do begin
  259. SplitNewParamStr(I, ParamName, ParamValue);
  260. ParamIsAutomaticInternal := False;
  261. if SameText(ParamName, '/Log') then begin
  262. EnableLogging := True;
  263. LogFilename := '';
  264. end else if SameText(ParamName, '/Log=') then begin
  265. EnableLogging := True;
  266. LogFilename := ParamValue;
  267. end else if SameText(ParamName, '/INITPROCWND=') then begin
  268. ParamIsAutomaticInternal := True;
  269. DidRespawn := True;
  270. InitialProcessWnd := StrToInt(ParamValue);
  271. end else if SameText(ParamName, '/SECONDPHASE=') then begin
  272. ParamIsAutomaticInternal := True;
  273. SecondPhase := True;
  274. UninstExeFilename := ParamValue;
  275. end else if SameText(ParamName, '/FIRSTPHASEWND=') then begin
  276. ParamIsAutomaticInternal := True;
  277. FirstPhaseWnd := StrToInt(ParamValue)
  278. end else if SameText(ParamName, '/SILENT') then
  279. Silent := True
  280. else if SameText(ParamName, '/VERYSILENT') then
  281. VerySilent := True
  282. else if SameText(ParamName, '/NoRestart') then
  283. NoRestart := True
  284. else if SameText(ParamName, '/NoStyle') then
  285. InitNoStyle := True
  286. else if SameText(ParamName, '/SuppressMsgBoxes') then
  287. WantToSuppressMsgBoxes := True
  288. else if SameText(ParamName, '/DEBUGWND=') then begin
  289. ParamIsAutomaticInternal := True;
  290. DebugServerWnd := StrToInt(ParamValue);
  291. end;
  292. if not ParamIsAutomaticInternal then
  293. NewParamsForCode.Add(NewParamStr(I));
  294. end;
  295. if WantToSuppressMsgBoxes and (Silent or VerySilent) then
  296. InitSuppressMsgBoxes := True;
  297. end;
  298. procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep; HandleException: Boolean);
  299. begin
  300. if CodeRunner <> nil then begin
  301. try
  302. CodeRunner.RunProcedures('CurUninstallStepChanged', [Ord(CurUninstallStep)], False);
  303. except
  304. if HandleException then begin
  305. Log('CurUninstallStepChanged raised an exception.');
  306. ShowExceptionMsg;
  307. end
  308. else begin
  309. Log('CurUninstallStepChanged raised an exception (fatal).');
  310. raise;
  311. end;
  312. end;
  313. end;
  314. end;
  315. function OpenUninstDataFile(const AAccess: TFileAccess): TFile;
  316. begin
  317. Result := nil; { avoid warning }
  318. try
  319. Result := TFile.Create(UninstDataFilename, fdOpenExisting, AAccess, fsNone);
  320. except
  321. on E: EFileError do begin
  322. SetLastError(E.ErrorCode);
  323. RaiseLastError(FmtSetupMessage1(msgUninstallOpenError,
  324. UninstDataFilename));
  325. end;
  326. end;
  327. end;
  328. function RespawnFirstPhaseIfNeeded: Boolean;
  329. var
  330. F: TFile;
  331. Flags: TUninstallLogFlags;
  332. RequireAdmin: Boolean;
  333. begin
  334. Result := False;
  335. if DidRespawn then
  336. Exit;
  337. F := OpenUninstDataFile(faRead);
  338. try
  339. Flags := ReadUninstallLogFlags(F, UninstDataFilename);
  340. finally
  341. F.Free;
  342. end;
  343. RequireAdmin := (ufAdminInstalled in Flags) or (ufPowerUserInstalled in Flags);
  344. if NeedToRespawnSelfElevated(RequireAdmin, False) then begin
  345. RespawnSelfElevated(UninstExeFilename,
  346. Format('/INITPROCWND=$%x ', [Application.Handle]) + GetCmdTail,
  347. UninstallExitCode);
  348. Result := True;
  349. end;
  350. end;
  351. procedure RunFirstPhase;
  352. begin
  353. { Copy self to a subdirectory of the TEMP directory with a name like
  354. _iu14D2N.tmp. The actual uninstallation process must be done from
  355. somewhere outside the application directory since EXE's can't delete
  356. themselves while they are running. }
  357. var Wnd: HWND := 0;
  358. var ProcessHandle: THandle := 0;
  359. var ShouldDeleteTempDir := True;
  360. const TempDir = CreateTempDir('-uninstall.tmp', IsAdmin);
  361. const TempFile = AddBackslash(TempDir) + '_unins.tmp';
  362. try
  363. if not CopyFile(PChar(UninstExeFilename), PChar(TempFile), False) then
  364. RaiseLastError(SetupMessages[msgLdrCannotCreateTemp]);
  365. { Don't want any attribute like read-only transferred }
  366. SetFileAttributes(PChar(TempFile), FILE_ATTRIBUTE_NORMAL);
  367. { Create first phase window. This window waits for a WM_KillFirstPhase
  368. message from the second phase process, and terminates itself in
  369. response. The reason the first phase doesn't just terminate
  370. immediately is because the Control Panel Add/Remove applet refreshes
  371. its list as soon as the program terminates. So it waits until the
  372. uninstallation is complete before terminating. }
  373. Wnd := CreateWindowEx(0, 'STATIC', '', 0, 0, 0, 0, 0, HWND_DESKTOP, 0,
  374. HInstance, nil);
  375. Longint(OldWindowProc) := SetWindowLong(Wnd, GWL_WNDPROC,
  376. Longint(@FirstPhaseWindowProc));
  377. { Execute the copy of itself ("second phase") }
  378. ProcessHandle := Exec(TempFile, Format('/SECONDPHASE="%s" /FIRSTPHASEWND=$%x ',
  379. [NewParamStr(0), Wnd]) + GetCmdTail);
  380. ShouldDeleteTempDir := False;
  381. { Wait till the second phase process unexpectedly dies or is ready
  382. for the first phase to terminate. }
  383. while not ProcessMsgs do begin
  384. const WaitResult = MsgWaitForMultipleObjects(1, ProcessHandle, False,
  385. INFINITE, QS_ALLINPUT);
  386. if WaitResult <> WAIT_OBJECT_0 + 1 then begin
  387. { When the second phase process terminates without us receiving a
  388. WM_KillFirstPhase message -- which most commonly happens when the
  389. user clicks No on the confirmation dialog -- it is our
  390. responsibility to delete TempDir.
  391. We also break here if WaitResult is something unexpected like
  392. WAIT_FAILED, but don't attempt to delete TempDir in that case
  393. because we don't know the status of the second phase process. }
  394. if WaitResult = WAIT_OBJECT_0 then
  395. ShouldDeleteTempDir := True;
  396. Break;
  397. end;
  398. end;
  399. finally
  400. if ProcessHandle <> 0 then
  401. CloseHandle(ProcessHandle);
  402. if Wnd <> 0 then
  403. DestroyWindow(Wnd);
  404. if ShouldDeleteTempDir then begin
  405. DelayDeleteFile(False, TempFile, 13, 50, 250);
  406. RemoveDirectory(PChar(TempDir));
  407. end;
  408. end;
  409. end;
  410. procedure AssignCustomMessages(AData: Pointer; ADataSize: Cardinal);
  411. procedure Corrupted;
  412. begin
  413. InternalError('Custom message data corrupted');
  414. end;
  415. procedure Read(var Buf; const Count: Cardinal);
  416. begin
  417. if Count > ADataSize then
  418. Corrupted;
  419. Move(AData^, Buf, Count);
  420. Dec(ADataSize, Count);
  421. Inc(Cardinal(AData), Count);
  422. end;
  423. procedure ReadString(var S: String);
  424. var
  425. N: Integer;
  426. begin
  427. Read(N, SizeOf(N));
  428. if (N < 0) or (N > $FFFFF) then { sanity check }
  429. Corrupted;
  430. SetString(S, nil, N);
  431. if N <> 0 then
  432. Read(Pointer(S)^, N * SizeOf(S[1]));
  433. end;
  434. var
  435. Count, I: Integer;
  436. CustomMessageEntry: PSetupCustomMessageEntry;
  437. begin
  438. Read(Count, SizeOf(Count));
  439. Entries[seCustomMessage].Capacity := Count;
  440. for I := 0 to Count-1 do begin
  441. CustomMessageEntry := AllocMem(SizeOf(TSetupCustomMessageEntry));
  442. try
  443. ReadString(CustomMessageEntry.Name);
  444. ReadString(CustomMessageEntry.Value);
  445. CustomMessageEntry.LangIndex := -1;
  446. except
  447. SEFreeRec(CustomMessageEntry, SetupCustomMessageEntryStrings, SetupCustomMessageEntryAnsiStrings);
  448. raise;
  449. end;
  450. Entries[seCustomMessage].Add(CustomMessageEntry);
  451. end;
  452. end;
  453. function ExtractCompiledCodeText(S: String): AnsiString;
  454. begin
  455. SetString(Result, PAnsiChar(Pointer(S)), Length(S)*SizeOf(S[1]));
  456. end;
  457. procedure RunSecondPhase;
  458. const
  459. RemovedMsgs: array[Boolean] of TSetupMessageID =
  460. (msgUninstalledMost, msgUninstalledAll);
  461. var
  462. RestartSystem: Boolean;
  463. CompiledCodeData: array[0..6] of String;
  464. CompiledCodeText: AnsiString;
  465. Res, RemovedAll, UninstallNeedsRestart: Boolean;
  466. StartTime: DWORD;
  467. begin
  468. RestartSystem := False;
  469. AllowUninstallerShutdown := True;
  470. try
  471. if DebugServerWnd <> 0 then
  472. SetDebugServerWnd(DebugServerWnd, True);
  473. if EnableLogging then begin
  474. try
  475. if LogFilename = '' then
  476. StartLogging('Uninstall')
  477. else
  478. StartLoggingWithFixedFilename(LogFilename);
  479. except
  480. on E: Exception do begin
  481. E.Message := 'Error creating log file:' + SNewLine2 + E.Message;
  482. raise;
  483. end;
  484. end;
  485. end;
  486. Log('Setup version: ' + SetupTitle + ' version ' + SetupVersion);
  487. Log('Original Uninstall EXE: ' + UninstExeFilename);
  488. Log('Uninstall DAT: ' + UninstDataFilename);
  489. LogFmt('Current Uninstall EXE: %s', [NewParamStr(0)]);
  490. Log('Uninstall command line: ' + GetCmdTail);
  491. LogWindowsVersion;
  492. { Open the .dat file for read access }
  493. UninstDataFile := OpenUninstDataFile(faRead);
  494. { Load contents of the .dat file }
  495. UninstLog := TExtUninstallLog.Create;
  496. UninstLog.Load(UninstDataFile, UninstDataFilename);
  497. { Apply style - also see Setup.MainFunc's InitializeSetup }
  498. IsWinDark := DarkModeActive;
  499. if not HighContrastActive and not InitNoStyle then begin
  500. const IsDynamicDark = (ufWizardDarkStyleDynamic in UninstLog.Flags) and IsWinDark;
  501. const IsForcedDark = ufWizardDarkStyleDark in UninstLog.Flags;
  502. if IsDynamicDark then begin
  503. MainIconPostfix := '_DARK';
  504. if FindResource(HInstance, PChar('MAINICON' + MainIconPostfix), RT_GROUP_ICON) = 0 then
  505. MainIconPostfix := '';
  506. end;
  507. if IsDynamicDark or IsForcedDark then begin
  508. IsDarkInstallMode := True;
  509. WizardIconsPostfix := '_DARK';
  510. end;
  511. TStyleManager.AutoDiscoverStyleResources := False;
  512. var StyleName := 'MYSTYLE1';
  513. if IsDynamicDark then
  514. StyleName := StyleName + '_DARK';
  515. var Handle: TStyleManager.TStyleServicesHandle;
  516. if TStyleManager.TryLoadFromResource(HInstance, StyleName, 'VCLSTYLE', Handle) then begin
  517. TStyleManager.SetStyle(Handle);
  518. if not IsDarkInstallMode and (ufWizardLightButtonsUnstyled in UninstLog.Flags) then
  519. TNewButton.DontStyle := True;
  520. end;
  521. end;
  522. { Initialize SetupHeader items used by TSetupForm (LangOptions items already done) }
  523. if ufWizardBorderStyled in UninstLog.Flags then
  524. Include(SetupHeader.Options, shWizardBorderStyled);
  525. if ufWizardKeepAspectRatio in UninstLog.Flags then
  526. Include(SetupHeader.Options, shWizardKeepAspectRatio);
  527. SetupHeader.WizardSizePercentX := UninstLog.WizardSizePercentX;
  528. SetupHeader.WizardSizePercentY := UninstLog.WizardSizePercentY;
  529. Title := FmtSetupMessage1(msgUninstallAppFullTitle, UninstLog.AppName);
  530. { If install was done in Win64, verify that we're still running Win64.
  531. This test shouldn't fail unless the user somehow downgraded their
  532. Windows version, or they're running an uninstaller from another machine
  533. (which they definitely shouldn't be doing). }
  534. if (ufWin64 in UninstLog.Flags) and not IsWin64 then begin
  535. LoggedMsgBox(PChar(SetupMessages[msgUninstallOnlyOnWin64]), PChar(Title),
  536. MB_OK or MB_ICONEXCLAMATION, True, IDOK);
  537. Abort;
  538. end;
  539. { Check if admin privileges are needed to uninstall }
  540. if (ufAdminInstalled in UninstLog.Flags) and not IsAdmin then begin
  541. LoggedMsgBox(PChar(SetupMessages[msgOnlyAdminCanUninstall]), PChar(Title),
  542. MB_OK or MB_ICONEXCLAMATION, True, IDOK);
  543. Abort;
  544. end;
  545. { Reopen the .dat file for exclusive, read/write access and keep it
  546. open for the duration of the uninstall process to prevent a second
  547. instance of the same uninstaller from running. }
  548. FreeAndNil(UninstDataFile);
  549. UninstDataFile := OpenUninstDataFile(faReadWrite);
  550. if not UninstLog.ExtractLatestRecData(utCompiledCode,
  551. SetupBinVersion or Longint($80000000), CompiledCodeData) then
  552. InternalError('Cannot find utCompiledCode record for this version of the uninstaller');
  553. if DebugServerWnd <> 0 then
  554. CompiledCodeText := DebugClientCompiledCodeText
  555. else
  556. CompiledCodeText := ExtractCompiledCodeText(CompiledCodeData[0]);
  557. InitializeAdminInstallMode(ufAdminInstallMode in UninstLog.Flags);
  558. { Initialize install mode }
  559. if UninstLog.InstallMode64Bit then begin
  560. { Sanity check: InstallMode64Bit should never be set without ufWin64 }
  561. if not IsWin64 then
  562. InternalError('Install was done in 64-bit mode but not running 64-bit Windows now');
  563. Initialize64BitInstallMode(True);
  564. end
  565. else
  566. Initialize64BitInstallMode(False);
  567. DeleteResidualTempUninstallDirs;
  568. { Create temporary directory and extract 64-bit helper EXE if necessary }
  569. CreateTempInstallDirAndExtract64BitHelper;
  570. if CompiledCodeText <> '' then begin
  571. { Setup some global variables which are accessible to [Code] }
  572. InitMainNonSHFolderConsts;
  573. LoadSHFolderDLL;
  574. UninstallExeFilename := UninstExeFilename;
  575. UninstallExpandedAppId := UninstLog.AppId;
  576. UninstallSilent := Silent or VerySilent;
  577. UninstallExpandedApp := CompiledCodeData[2];
  578. UninstallExpandedGroup := CompiledCodeData[3];
  579. UninstallExpandedGroupName := CompiledCodeData[4];
  580. UninstallExpandedLanguage := CompiledCodeData[5];
  581. AssignCustomMessages(Pointer(CompiledCodeData[6]), Length(CompiledCodeData[6])*SizeOf(CompiledCodeData[6][1]));
  582. CodeRunner := TScriptRunner.Create();
  583. CodeRunner.NamingAttribute := CodeRunnerNamingAttribute;
  584. CodeRunner.OnLog := CodeRunnerOnLog;
  585. CodeRunner.OnLogFmt := CodeRunnerOnLogFmt;
  586. CodeRunner.OnDllImport := CodeRunnerOnDllImport;
  587. CodeRunner.OnDebug := CodeRunnerOnDebug;
  588. CodeRunner.OnDebugIntermediate := CodeRunnerOnDebugIntermediate;
  589. CodeRunner.OnException := CodeRunnerOnException;
  590. CodeRunner.LoadScript(CompiledCodeText, DebugClientCompiledCodeDebugInfo);
  591. end;
  592. try
  593. try
  594. if CodeRunner <> nil then begin
  595. try
  596. Res := CodeRunner.RunBooleanFunctions('InitializeUninstall', [''], bcFalse, False, True);
  597. except
  598. Log('InitializeUninstall raised an exception (fatal).');
  599. raise;
  600. end;
  601. if not Res then begin
  602. Log('InitializeUninstall returned False; aborting.');
  603. Abort;
  604. end;
  605. end;
  606. { Confirm uninstall }
  607. if not Silent and not VerySilent then begin
  608. if LoggedMessageBoxFmt1(msgConfirmUninstall, UninstLog.AppName, Title,
  609. MB_ICONQUESTION or MB_YESNO or MB_DEFBUTTON2, True, IDYES) <> IDYES then
  610. Abort;
  611. end;
  612. CurUninstallStepChanged(usAppMutexCheck, False);
  613. { Is the app running? }
  614. while UninstLog.CheckMutexes do
  615. { Yes, tell user to close it }
  616. if LoggedMessageBoxFmt1(msgUninstallAppRunningError, UninstLog.AppName, Title,
  617. MB_OKCANCEL or MB_ICONEXCLAMATION, True, IDCANCEL) <> IDOK then
  618. Abort;
  619. { Check for active WM_QUERYENDSESSION/WM_ENDSESSION }
  620. while AcceptedQueryEndSessionInProgress do begin
  621. Sleep(10);
  622. Application.ProcessMessages;
  623. end;
  624. { Disable Uninstall shutdown }
  625. AllowUninstallerShutdown := False;
  626. ShutdownBlockReasonCreate(Application.Handle,
  627. FmtSetupMessage1(msgShutdownBlockReasonUninstallingApp, UninstLog.AppName));
  628. { Create and show the progress form }
  629. InitializeUninstallProgressForm;
  630. CurUninstallStepChanged(usUninstall, False);
  631. { Start the actual uninstall process }
  632. StartTime := GetTickCount;
  633. UninstLog.FLastUpdateTime := StartTime;
  634. RemovedAll := UninstLog.PerformUninstall(True, DeleteUninstallDataFiles);
  635. LogFmt('Removed all? %s', [SYesNo[RemovedAll]]);
  636. UninstallNeedsRestart := UninstLog.NeedRestart or (ufAlwaysRestart in UninstLog.Flags);
  637. if (CodeRunner <> nil) and CodeRunner.FunctionExists('UninstallNeedRestart', True) then begin
  638. if not UninstallNeedsRestart then begin
  639. try
  640. if CodeRunner.RunBooleanFunctions('UninstallNeedRestart', [''], bcTrue, False, False) then begin
  641. UninstallNeedsRestart := True;
  642. Log('Will restart because UninstallNeedRestart returned True.');
  643. end;
  644. except
  645. Log('UninstallNeedRestart raised an exception.');
  646. ShowExceptionMsg;
  647. end;
  648. end
  649. else
  650. Log('Not calling UninstallNeedRestart because a restart has already been deemed necessary.');
  651. end;
  652. LogFmt('Need to restart Windows? %s', [SYesNo[UninstallNeedsRestart]]);
  653. { Ensure at least 1 second has passed since the uninstall process
  654. began, then destroy the form }
  655. if not VerySilent then begin
  656. while True do begin
  657. Application.ProcessMessages;
  658. if Cardinal(GetTickCount - StartTime) >= Cardinal(1000) then
  659. Break;
  660. { Delay for 10 ms, or until a message arrives }
  661. MsgWaitForMultipleObjects(0, THandle(nil^), False, 10, QS_ALLINPUT);
  662. end;
  663. end;
  664. FreeAndNil(UninstallProgressForm);
  665. CurUninstallStepChanged(usPostUninstall, True);
  666. if not UninstallNeedsRestart then begin
  667. if not Silent and not VerySilent then
  668. LoggedMessageBoxFmt1(RemovedMsgs[RemovedAll], UninstLog.AppName,
  669. Title, MB_ICONINFORMATION or MB_OK or MB_SETFOREGROUND, True, IDOK);
  670. end
  671. else begin
  672. if not NoRestart then begin
  673. if VerySilent or
  674. (LoggedMessageBoxFmt1(msgUninstalledAndNeedsRestart, UninstLog.AppName,
  675. Title, MB_ICONQUESTION or MB_YESNO or MB_SETFOREGROUND, True, IDYES) = IDYES) then
  676. RestartSystem := True;
  677. end;
  678. if not RestartSystem then
  679. Log('Will not restart Windows automatically.');
  680. end;
  681. CurUninstallStepChanged(usDone, True);
  682. except
  683. { Show any pending exception here *before* DeinitializeUninstall
  684. is called, which could display an exception message of its own }
  685. ShowExceptionMsg;
  686. Abort;
  687. end;
  688. finally
  689. { Free the form here, too, in case an exception occurred above }
  690. try
  691. FreeAndNil(UninstallProgressForm);
  692. except
  693. ShowExceptionMsg;
  694. end;
  695. if CodeRunner <> nil then begin
  696. try
  697. CodeRunner.RunProcedures('DeinitializeUninstall', [''], False);
  698. except
  699. Log('DeinitializeUninstall raised an exception.');
  700. ShowExceptionMsg;
  701. end;
  702. end;
  703. ShutdownBlockReasonDestroy(Application.Handle);
  704. end;
  705. finally
  706. FreeAndNil(CodeRunner);
  707. UnloadSHFolderDLL;
  708. RemoveTempInstallDir;
  709. UninstLog.Free;
  710. FreeAndNil(UninstDataFile);
  711. end;
  712. if RestartSystem then begin
  713. if not Debugging then begin
  714. Log('Restarting Windows.');
  715. RestartInitiatedByThisProcess := True;
  716. if not RestartComputer then begin
  717. LoggedMsgBox(PChar(SetupMessages[msgErrorRestartingComputer]),
  718. PChar(SetupMessages[msgErrorTitle]), MB_OK or MB_ICONEXCLAMATION,
  719. True, IDOK);
  720. end;
  721. end
  722. else
  723. Log('Not restarting Windows because Uninstall is being run from the debugger.');
  724. end;
  725. end;
  726. procedure RunUninstaller;
  727. var
  728. F: TFile;
  729. UninstallerMsgTail: TUninstallerMsgTail;
  730. begin
  731. { Set default title; it's set again below after the messages are read }
  732. Application.Title := 'Uninstall';
  733. Application.MainFormOnTaskBar := True;
  734. try
  735. InitializeCommonVars;
  736. SetCurrentDir(GetSystemDir);
  737. UninstExeFilename := NewParamStr(0);
  738. ProcessCommandLine; { note: may change UninstExeFile }
  739. UninstDataFilename := PathChangeExt(UninstExeFilename, '.dat');
  740. UninstMsgFilename := PathChangeExt(UninstExeFilename, '.msg');
  741. { Initialize messages }
  742. F := TFile.Create(UninstExeFilename, fdOpenExisting, faRead, fsRead);
  743. try
  744. F.Seek(F.Size - SizeOf(UninstallerMsgTail));
  745. F.ReadBuffer(UninstallerMsgTail, SizeOf(UninstallerMsgTail));
  746. if UninstallerMsgTail.ID <> UninstallerMsgTailID then begin
  747. { No valid UninstallerMsgTail record found at the end of the EXE;
  748. load messages from an external .msg file. }
  749. LoadSetupMessages(UninstMsgFilename, 0, True);
  750. end
  751. else
  752. LoadSetupMessages(UninstExeFilename, UninstallerMsgTail.Offset, True);
  753. finally
  754. F.Free;
  755. end;
  756. LangOptions.DialogFontName := MessagesLangOptions.DialogFontName;
  757. LangOptions.DialogFontSize := MessagesLangOptions.DialogFontSize;
  758. LangOptions.DialogFontBaseScaleWidth := MessagesLangOptions.DialogFontBaseScaleWidth;
  759. LangOptions.DialogFontBaseScaleHeight := MessagesLangOptions.DialogFontBaseScaleHeight;
  760. LangOptions.RightToLeft := lfRightToLeft in MessagesLangOptions.Flags;
  761. SetMessageBoxRightToLeft(LangOptions.RightToLeft);
  762. SetMessageBoxCaption(mbInformation, PChar(SetupMessages[msgInformationTitle]));
  763. SetMessageBoxCaption(mbConfirmation, PChar(SetupMessages[msgConfirmTitle]));
  764. SetMessageBoxCaption(mbError, PChar(SetupMessages[msgErrorTitle]));
  765. SetMessageBoxCaption(mbCriticalError, PChar(SetupMessages[msgErrorTitle]));
  766. Application.Title := SetupMessages[msgUninstallAppTitle];
  767. { Verify that uninstall data file exists }
  768. if not NewFileExists(UninstDataFilename) then begin
  769. LoggedMessageBoxFmt1(msgUninstallNotFound, UninstDataFilename,
  770. SetupMessages[msgUninstallAppTitle], MB_ICONSTOP or MB_OK, True, IDOK);
  771. Abort;
  772. end;
  773. if not SecondPhase then begin
  774. if not RespawnFirstPhaseIfNeeded then
  775. RunFirstPhase;
  776. end
  777. else
  778. RunSecondPhase;
  779. except
  780. ShowExceptionMsg;
  781. end;
  782. { Call EndDebug after all exception messages have been shown and logged in
  783. the IDE's Debug Output }
  784. EndDebug;
  785. Halt(UninstallExitCode);
  786. end;
  787. procedure HandleUninstallerEndSession;
  788. begin
  789. { If second phase, remove the temp dir. The self copy made by the first phase will be
  790. deleted on restart. }
  791. if SecondPhase then begin
  792. Log('Detected restart. Removing temporary directory.');
  793. try
  794. UnloadSHFolderDLL;
  795. RemoveTempInstallDir;
  796. except
  797. ShowExceptionMsg;
  798. end;
  799. EndDebug;
  800. { Don't use Halt. See Setup.dpr }
  801. TerminateProcess(GetCurrentProcess, UninstallExitCode);
  802. end;
  803. end;
  804. end.