Setup.Uninstall.pas 34 KB

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