Setup.Uninstall.pas 33 KB

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