Setup.Uninstall.pas 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  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, PathFunc, Shared.CommonFunc.Vcl,
  15. Shared.CommonFunc, Setup.UninstallLog, SetupLdrAndSetup.Messages,
  16. Shared.SetupMessageIDs, SetupLdrAndSetup.InstFunc, Setup.InstFunc, Shared.Struct,
  17. Shared.SetupEntFunc, Setup.UninstallProgressForm, Setup.UninstallSharedFileForm,
  18. Shared.FileClass, Setup.ScriptRunner, Setup.DebugClient, Shared.SetupSteps,
  19. Setup.LoggingFunc, Setup.MainFunc, Setup.SpawnServer;
  20. type
  21. TExtUninstallLog = class(TUninstallLog)
  22. private
  23. FLastUpdateTime: DWORD;
  24. FNoSharedFileDlgs: Boolean;
  25. FRemoveSharedFiles: Boolean;
  26. protected
  27. procedure HandleException; override;
  28. function ShouldRemoveSharedFile(const Filename: String): Boolean; override;
  29. procedure StatusUpdate(StartingCount, CurCount: Integer); override;
  30. end;
  31. const
  32. WM_KillFirstPhase = WM_USER + 333;
  33. var
  34. UninstallExitCode: DWORD = 1;
  35. UninstExeFilename, UninstDataFilename, UninstMsgFilename: String;
  36. UninstDataFile: TFile;
  37. UninstLog: TExtUninstallLog = nil;
  38. Title: String;
  39. DidRespawn, SecondPhase: Boolean;
  40. EnableLogging, Silent, VerySilent, NoRestart: Boolean;
  41. LogFilename: String;
  42. InitialProcessWnd, FirstPhaseWnd, DebugServerWnd: HWND;
  43. OldWindowProc: Pointer;
  44. procedure ShowExceptionMsg;
  45. var
  46. Msg: String;
  47. begin
  48. if ExceptObject is EAbort then
  49. Exit;
  50. Msg := GetExceptMessage;
  51. Log('Exception message:');
  52. LoggedAppMessageBox(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. MessageBox displays 'Error' as the caption if the lpCaption
  57. parameter is nil. }
  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, ufModernStyle in UninstLog.Flags);
  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: UINT; const Suppressible: Boolean;
  115. const Default: Integer): Integer;
  116. begin
  117. Result := LoggedAppMessageBox(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. var
  175. ProcessWnd: HWND;
  176. ProcessID: DWORD;
  177. Process: THandle;
  178. begin
  179. Log('Deleting Uninstall data files.');
  180. { Truncate the .dat file to zero bytes just before relinquishing exclusive
  181. access to it }
  182. try
  183. UninstDataFile.Seek(0);
  184. UninstDataFile.Truncate;
  185. except
  186. { ignore any exceptions, just in case }
  187. end;
  188. FreeAndNil(UninstDataFile);
  189. { Delete the .dat and .msg files }
  190. DeleteFile(UninstDataFilename);
  191. DeleteFile(UninstMsgFilename);
  192. { Tell the first phase to terminate, then delete its .exe }
  193. if FirstPhaseWnd <> 0 then begin
  194. if InitialProcessWnd <> 0 then
  195. { If the first phase respawned, wait on the initial process }
  196. ProcessWnd := InitialProcessWnd
  197. else
  198. ProcessWnd := FirstPhaseWnd;
  199. ProcessID := 0;
  200. if GetWindowThreadProcessId(ProcessWnd, @ProcessID) <> 0 then
  201. Process := OpenProcess(SYNCHRONIZE, False, ProcessID)
  202. else
  203. Process := 0; { shouldn't get here }
  204. SendNotifyMessage(FirstPhaseWnd, WM_KillFirstPhase, 0, 0);
  205. if Process <> 0 then begin
  206. WaitForSingleObject(Process, INFINITE);
  207. CloseHandle(Process);
  208. end;
  209. { Sleep for a bit to allow pre-Windows 2000 Add/Remove Programs to finish
  210. bringing itself to the foreground before we take it back below. Also
  211. helps the DelayDeleteFile call succeed on the first try. }
  212. if not Debugging then
  213. Sleep(500);
  214. end;
  215. UninstallExitCode := 0;
  216. DelayDeleteFile(False, UninstExeFilename, 13, 50, 250);
  217. if Debugging then
  218. DebugNotifyUninstExe('');
  219. { Pre-Windows 2000 Add/Remove Programs will try to bring itself to the
  220. foreground after the first phase terminates. Take it back. }
  221. Application.BringToFront;
  222. end;
  223. procedure ProcessCommandLine;
  224. var
  225. WantToSuppressMsgBoxes, ParamIsAutomaticInternal: Boolean;
  226. I: Integer;
  227. ParamName, ParamValue: String;
  228. begin
  229. WantToSuppressMsgBoxes := False;
  230. { NewParamsForCode will hold all params except automatic internal ones like /SECONDPHASE= and /DEBUGWND=
  231. Actually currently only needed in the second phase, but setting always anyway
  232. Also see Main.InitializeSetup }
  233. NewParamsForCode.Add(NewParamStr(0));
  234. for I := 1 to NewParamCount do begin
  235. SplitNewParamStr(I, ParamName, ParamValue);
  236. ParamIsAutomaticInternal := False;
  237. if CompareText(ParamName, '/Log') = 0 then begin
  238. EnableLogging := True;
  239. LogFilename := '';
  240. end else if CompareText(ParamName, '/Log=') = 0 then begin
  241. EnableLogging := True;
  242. LogFilename := ParamValue;
  243. end else if CompareText(ParamName, '/INITPROCWND=') = 0 then begin
  244. ParamIsAutomaticInternal := True;
  245. DidRespawn := True;
  246. InitialProcessWnd := StrToInt(ParamValue);
  247. end else if CompareText(ParamName, '/SECONDPHASE=') = 0 then begin
  248. ParamIsAutomaticInternal := True;
  249. SecondPhase := True;
  250. UninstExeFilename := ParamValue;
  251. end else if CompareText(ParamName, '/FIRSTPHASEWND=') = 0 then begin
  252. ParamIsAutomaticInternal := True;
  253. FirstPhaseWnd := StrToInt(ParamValue)
  254. end else if CompareText(ParamName, '/SILENT') = 0 then
  255. Silent := True
  256. else if CompareText(ParamName, '/VERYSILENT') = 0 then
  257. VerySilent := True
  258. else if CompareText(ParamName, '/NoRestart') = 0 then
  259. NoRestart := True
  260. else if CompareText(ParamName, '/SuppressMsgBoxes') = 0 then
  261. WantToSuppressMsgBoxes := True
  262. else if CompareText(ParamName, '/DEBUGWND=') = 0 then begin
  263. ParamIsAutomaticInternal := True;
  264. DebugServerWnd := StrToInt(ParamValue);
  265. end;
  266. if not ParamIsAutomaticInternal then
  267. NewParamsForCode.Add(NewParamStr(I));
  268. end;
  269. if WantToSuppressMsgBoxes and (Silent or VerySilent) then
  270. InitSuppressMsgBoxes := True;
  271. end;
  272. procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep; HandleException: Boolean);
  273. begin
  274. if CodeRunner <> nil then begin
  275. try
  276. CodeRunner.RunProcedures('CurUninstallStepChanged', [Ord(CurUninstallStep)], False);
  277. except
  278. if HandleException then begin
  279. Log('CurUninstallStepChanged raised an exception.');
  280. ShowExceptionMsg;
  281. end
  282. else begin
  283. Log('CurUninstallStepChanged raised an exception (fatal).');
  284. raise;
  285. end;
  286. end;
  287. end;
  288. end;
  289. function OpenUninstDataFile(const AAccess: TFileAccess): TFile;
  290. begin
  291. Result := nil; { avoid warning }
  292. try
  293. Result := TFile.Create(UninstDataFilename, fdOpenExisting, AAccess, fsNone);
  294. except
  295. on E: EFileError do begin
  296. SetLastError(E.ErrorCode);
  297. RaiseLastError(FmtSetupMessage1(msgUninstallOpenError,
  298. UninstDataFilename));
  299. end;
  300. end;
  301. end;
  302. function RespawnFirstPhaseIfNeeded: Boolean;
  303. var
  304. F: TFile;
  305. Flags: TUninstallLogFlags;
  306. RequireAdmin: Boolean;
  307. begin
  308. Result := False;
  309. if DidRespawn then
  310. Exit;
  311. F := OpenUninstDataFile(faRead);
  312. try
  313. Flags := ReadUninstallLogFlags(F, UninstDataFilename);
  314. finally
  315. F.Free;
  316. end;
  317. RequireAdmin := (ufAdminInstalled in Flags) or (ufPowerUserInstalled in Flags);
  318. if NeedToRespawnSelfElevated(RequireAdmin, False) then begin
  319. RespawnSelfElevated(UninstExeFilename,
  320. Format('/INITPROCWND=$%x ', [Application.Handle]) + GetCmdTail,
  321. UninstallExitCode);
  322. Result := True;
  323. end;
  324. end;
  325. procedure RunFirstPhase;
  326. var
  327. TempDir, TempFile: String;
  328. TempDirExisted: Boolean;
  329. Wnd: HWND;
  330. ProcessHandle: THandle;
  331. begin
  332. { Copy self to a subdirectory of the TEMP directory with a name like
  333. _iu14D2N.tmp. The actual uninstallation process must be done from
  334. somewhere outside the application directory since EXE's can't delete
  335. themselves while they are running. }
  336. TempDirExisted := GenerateNonRandomUniqueTempDir(IsAdmin, GetTempDir, TempDir);
  337. TempFile := AddBackslash(TempDir) + '_unins.tmp';
  338. if not TempDirExisted then
  339. try
  340. RestartReplace(False, TempFile, '');
  341. RestartReplace(False, TempDir, '');
  342. except
  343. { ignore exceptions }
  344. end;
  345. if not CopyFile(PChar(UninstExeFilename), PChar(TempFile), False) then
  346. RaiseLastError(SetupMessages[msgLdrCannotCreateTemp]);
  347. { Don't want any attribute like read-only transferred }
  348. SetFileAttributes(PChar(TempFile), FILE_ATTRIBUTE_NORMAL);
  349. { Create first phase window. This window waits for a WM_KillFirstPhase
  350. message from the second phase process, and terminates itself in
  351. response. The reason the first phase doesn't just terminate
  352. immediately is because the Control Panel Add/Remove applet refreshes
  353. its list as soon as the program terminates. So it waits until the
  354. uninstallation is complete before terminating. }
  355. Wnd := CreateWindowEx(0, 'STATIC', '', 0, 0, 0, 0, 0, HWND_DESKTOP, 0,
  356. HInstance, nil);
  357. Longint(OldWindowProc) := SetWindowLong(Wnd, GWL_WNDPROC,
  358. Longint(@FirstPhaseWindowProc));
  359. try
  360. { Execute the copy of itself ("second phase") }
  361. ProcessHandle := Exec(TempFile, Format('/SECONDPHASE="%s" /FIRSTPHASEWND=$%x ',
  362. [NewParamStr(0), Wnd]) + GetCmdTail);
  363. { Wait till the second phase process unexpectedly dies or is ready
  364. for the first phase to terminate. }
  365. repeat until ProcessMsgs or (MsgWaitForMultipleObjects(1,
  366. ProcessHandle, False, INFINITE, QS_ALLINPUT) <> WAIT_OBJECT_0+1);
  367. CloseHandle(ProcessHandle);
  368. finally
  369. DestroyWindow(Wnd);
  370. end;
  371. end;
  372. procedure AssignCustomMessages(AData: Pointer; ADataSize: Cardinal);
  373. procedure Corrupted;
  374. begin
  375. InternalError('Custom message data corrupted');
  376. end;
  377. procedure Read(var Buf; const Count: Cardinal);
  378. begin
  379. if Count > ADataSize then
  380. Corrupted;
  381. Move(AData^, Buf, Count);
  382. Dec(ADataSize, Count);
  383. Inc(Cardinal(AData), Count);
  384. end;
  385. procedure ReadString(var S: String);
  386. var
  387. N: Integer;
  388. begin
  389. Read(N, SizeOf(N));
  390. if (N < 0) or (N > $FFFFF) then { sanity check }
  391. Corrupted;
  392. SetString(S, nil, N);
  393. if N <> 0 then
  394. Read(Pointer(S)^, N * SizeOf(S[1]));
  395. end;
  396. var
  397. Count, I: Integer;
  398. CustomMessageEntry: PSetupCustomMessageEntry;
  399. begin
  400. Read(Count, SizeOf(Count));
  401. Entries[seCustomMessage].Capacity := Count;
  402. for I := 0 to Count-1 do begin
  403. CustomMessageEntry := AllocMem(SizeOf(TSetupCustomMessageEntry));
  404. try
  405. ReadString(CustomMessageEntry.Name);
  406. ReadString(CustomMessageEntry.Value);
  407. CustomMessageEntry.LangIndex := -1;
  408. except
  409. SEFreeRec(CustomMessageEntry, SetupCustomMessageEntryStrings, SetupCustomMessageEntryAnsiStrings);
  410. raise;
  411. end;
  412. Entries[seCustomMessage].Add(CustomMessageEntry);
  413. end;
  414. end;
  415. function ExtractCompiledCodeText(S: String): AnsiString;
  416. begin
  417. SetString(Result, PAnsiChar(Pointer(S)), Length(S)*SizeOf(S[1]));
  418. end;
  419. procedure RunSecondPhase;
  420. const
  421. RemovedMsgs: array[Boolean] of TSetupMessageID =
  422. (msgUninstalledMost, msgUninstalledAll);
  423. var
  424. RestartSystem: Boolean;
  425. CompiledCodeData: array[0..6] of String;
  426. CompiledCodeText: AnsiString;
  427. Res, RemovedAll, UninstallNeedsRestart: Boolean;
  428. StartTime: DWORD;
  429. begin
  430. RestartSystem := False;
  431. AllowUninstallerShutdown := True;
  432. try
  433. if DebugServerWnd <> 0 then
  434. SetDebugServerWnd(DebugServerWnd, True);
  435. if EnableLogging then begin
  436. try
  437. if LogFilename = '' then
  438. StartLogging('Uninstall')
  439. else
  440. StartLoggingWithFixedFilename(LogFilename);
  441. except
  442. on E: Exception do begin
  443. E.Message := 'Error creating log file:' + SNewLine2 + E.Message;
  444. raise;
  445. end;
  446. end;
  447. end;
  448. Log('Setup version: ' + SetupTitle + ' version ' + SetupVersion);
  449. Log('Original Uninstall EXE: ' + UninstExeFilename);
  450. Log('Uninstall DAT: ' + UninstDataFilename);
  451. Log('Uninstall command line: ' + GetCmdTail);
  452. LogWindowsVersion;
  453. { Open the .dat file for read access }
  454. UninstDataFile := OpenUninstDataFile(faRead);
  455. { Load contents of the .dat file }
  456. UninstLog := TExtUninstallLog.Create;
  457. UninstLog.Load(UninstDataFile, UninstDataFilename);
  458. Title := FmtSetupMessage1(msgUninstallAppFullTitle, UninstLog.AppName);
  459. { If install was done in Win64, verify that we're still running Win64.
  460. This test shouldn't fail unless the user somehow downgraded their
  461. Windows version, or they're running an uninstaller from another machine
  462. (which they definitely shouldn't be doing). }
  463. if (ufWin64 in UninstLog.Flags) and not IsWin64 then begin
  464. LoggedAppMessageBox(PChar(SetupMessages[msgUninstallOnlyOnWin64]), PChar(Title),
  465. MB_OK or MB_ICONEXCLAMATION, True, IDOK);
  466. Abort;
  467. end;
  468. { Check if admin privileges are needed to uninstall }
  469. if (ufAdminInstalled in UninstLog.Flags) and not IsAdmin then begin
  470. LoggedAppMessageBox(PChar(SetupMessages[msgOnlyAdminCanUninstall]), PChar(Title),
  471. MB_OK or MB_ICONEXCLAMATION, True, IDOK);
  472. Abort;
  473. end;
  474. { Reopen the .dat file for exclusive, read/write access and keep it
  475. open for the duration of the uninstall process to prevent a second
  476. instance of the same uninstaller from running. }
  477. FreeAndNil(UninstDataFile);
  478. UninstDataFile := OpenUninstDataFile(faReadWrite);
  479. if not UninstLog.ExtractLatestRecData(utCompiledCode,
  480. SetupBinVersion or Longint($80000000), CompiledCodeData) then
  481. InternalError('Cannot find utCompiledCode record for this version of the uninstaller');
  482. if DebugServerWnd <> 0 then
  483. CompiledCodeText := DebugClientCompiledCodeText
  484. else
  485. CompiledCodeText := ExtractCompiledCodeText(CompiledCodeData[0]);
  486. InitializeAdminInstallMode(ufAdminInstallMode in UninstLog.Flags);
  487. { Initialize install mode }
  488. if UninstLog.InstallMode64Bit then begin
  489. { Sanity check: InstallMode64Bit should never be set without ufWin64 }
  490. if not IsWin64 then
  491. InternalError('Install was done in 64-bit mode but not running 64-bit Windows now');
  492. Initialize64BitInstallMode(True);
  493. end
  494. else
  495. Initialize64BitInstallMode(False);
  496. { Create temporary directory and extract 64-bit helper EXE if necessary }
  497. CreateTempInstallDirAndExtract64BitHelper;
  498. if CompiledCodeText <> '' then begin
  499. { Setup some global variables which are accessible to [Code] }
  500. InitMainNonSHFolderConsts;
  501. LoadSHFolderDLL;
  502. UninstallExeFilename := UninstExeFilename;
  503. UninstallExpandedAppId := UninstLog.AppId;
  504. UninstallSilent := Silent or VerySilent;
  505. UninstallExpandedApp := CompiledCodeData[2];
  506. UninstallExpandedGroup := CompiledCodeData[3];
  507. UninstallExpandedGroupName := CompiledCodeData[4];
  508. UninstallExpandedLanguage := CompiledCodeData[5];
  509. AssignCustomMessages(Pointer(CompiledCodeData[6]), Length(CompiledCodeData[6])*SizeOf(CompiledCodeData[6][1]));
  510. CodeRunner := TScriptRunner.Create();
  511. CodeRunner.NamingAttribute := CodeRunnerNamingAttribute;
  512. CodeRunner.OnLog := CodeRunnerOnLog;
  513. CodeRunner.OnLogFmt := CodeRunnerOnLogFmt;
  514. CodeRunner.OnDllImport := CodeRunnerOnDllImport;
  515. CodeRunner.OnDebug := CodeRunnerOnDebug;
  516. CodeRunner.OnDebugIntermediate := CodeRunnerOnDebugIntermediate;
  517. CodeRunner.OnException := CodeRunnerOnException;
  518. CodeRunner.LoadScript(CompiledCodeText, DebugClientCompiledCodeDebugInfo);
  519. end;
  520. try
  521. try
  522. if CodeRunner <> nil then begin
  523. try
  524. Res := CodeRunner.RunBooleanFunctions('InitializeUninstall', [''], bcFalse, False, True);
  525. except
  526. Log('InitializeUninstall raised an exception (fatal).');
  527. raise;
  528. end;
  529. if not Res then begin
  530. Log('InitializeUninstall returned False; aborting.');
  531. Abort;
  532. end;
  533. end;
  534. { Confirm uninstall }
  535. if not Silent and not VerySilent then begin
  536. if LoggedMessageBoxFmt1(msgConfirmUninstall, UninstLog.AppName, Title,
  537. MB_ICONQUESTION or MB_YESNO or MB_DEFBUTTON2, True, IDYES) <> IDYES then
  538. Abort;
  539. end;
  540. CurUninstallStepChanged(usAppMutexCheck, False);
  541. { Is the app running? }
  542. while UninstLog.CheckMutexes do
  543. { Yes, tell user to close it }
  544. if LoggedMessageBoxFmt1(msgUninstallAppRunningError, UninstLog.AppName, Title,
  545. MB_OKCANCEL or MB_ICONEXCLAMATION, True, IDCANCEL) <> IDOK then
  546. Abort;
  547. { Check for active WM_QUERYENDSESSION/WM_ENDSESSION }
  548. while AcceptedQueryEndSessionInProgress do begin
  549. Sleep(10);
  550. Application.ProcessMessages;
  551. end;
  552. { Disable Uninstall shutdown }
  553. AllowUninstallerShutdown := False;
  554. ShutdownBlockReasonCreate(Application.Handle,
  555. FmtSetupMessage1(msgShutdownBlockReasonUninstallingApp, UninstLog.AppName));
  556. { Create and show the progress form }
  557. InitializeUninstallProgressForm;
  558. CurUninstallStepChanged(usUninstall, False);
  559. { Start the actual uninstall process }
  560. StartTime := GetTickCount;
  561. UninstLog.FLastUpdateTime := StartTime;
  562. RemovedAll := UninstLog.PerformUninstall(True, DeleteUninstallDataFiles);
  563. LogFmt('Removed all? %s', [SYesNo[RemovedAll]]);
  564. UninstallNeedsRestart := UninstLog.NeedRestart or (ufAlwaysRestart in UninstLog.Flags);
  565. if (CodeRunner <> nil) and CodeRunner.FunctionExists('UninstallNeedRestart', True) then begin
  566. if not UninstallNeedsRestart then begin
  567. try
  568. if CodeRunner.RunBooleanFunctions('UninstallNeedRestart', [''], bcTrue, False, False) then begin
  569. UninstallNeedsRestart := True;
  570. Log('Will restart because UninstallNeedRestart returned True.');
  571. end;
  572. except
  573. Log('UninstallNeedRestart raised an exception.');
  574. ShowExceptionMsg;
  575. end;
  576. end
  577. else
  578. Log('Not calling UninstallNeedRestart because a restart has already been deemed necessary.');
  579. end;
  580. LogFmt('Need to restart Windows? %s', [SYesNo[UninstallNeedsRestart]]);
  581. { Ensure at least 1 second has passed since the uninstall process
  582. began, then destroy the form }
  583. if not VerySilent then begin
  584. while True do begin
  585. Application.ProcessMessages;
  586. if Cardinal(GetTickCount - StartTime) >= Cardinal(1000) then
  587. Break;
  588. { Delay for 10 ms, or until a message arrives }
  589. MsgWaitForMultipleObjects(0, THandle(nil^), False, 10, QS_ALLINPUT);
  590. end;
  591. end;
  592. FreeAndNil(UninstallProgressForm);
  593. CurUninstallStepChanged(usPostUninstall, True);
  594. if not UninstallNeedsRestart then begin
  595. if not Silent and not VerySilent then
  596. LoggedMessageBoxFmt1(RemovedMsgs[RemovedAll], UninstLog.AppName,
  597. Title, MB_ICONINFORMATION or MB_OK or MB_SETFOREGROUND, True, IDOK);
  598. end
  599. else begin
  600. if not NoRestart then begin
  601. if VerySilent or
  602. (LoggedMessageBoxFmt1(msgUninstalledAndNeedsRestart, UninstLog.AppName,
  603. Title, MB_ICONQUESTION or MB_YESNO or MB_SETFOREGROUND, True, IDYES) = IDYES) then
  604. RestartSystem := True;
  605. end;
  606. if not RestartSystem then
  607. Log('Will not restart Windows automatically.');
  608. end;
  609. CurUninstallStepChanged(usDone, True);
  610. except
  611. { Show any pending exception here *before* DeinitializeUninstall
  612. is called, which could display an exception message of its own }
  613. ShowExceptionMsg;
  614. Abort;
  615. end;
  616. finally
  617. { Free the form here, too, in case an exception occurred above }
  618. try
  619. FreeAndNil(UninstallProgressForm);
  620. except
  621. ShowExceptionMsg;
  622. end;
  623. if CodeRunner <> nil then begin
  624. try
  625. CodeRunner.RunProcedures('DeinitializeUninstall', [''], False);
  626. except
  627. Log('DeinitializeUninstall raised an exception.');
  628. ShowExceptionMsg;
  629. end;
  630. end;
  631. ShutdownBlockReasonDestroy(Application.Handle);
  632. end;
  633. finally
  634. FreeAndNil(CodeRunner);
  635. UnloadSHFolderDLL;
  636. RemoveTempInstallDir;
  637. UninstLog.Free;
  638. FreeAndNil(UninstDataFile);
  639. end;
  640. if RestartSystem then begin
  641. if not Debugging then begin
  642. Log('Restarting Windows.');
  643. RestartInitiatedByThisProcess := True;
  644. if not RestartComputer then begin
  645. LoggedAppMessageBox(PChar(SetupMessages[msgErrorRestartingComputer]),
  646. PChar(SetupMessages[msgErrorTitle]), MB_OK or MB_ICONEXCLAMATION,
  647. True, IDOK);
  648. end;
  649. end
  650. else
  651. Log('Not restarting Windows because Uninstall is being run from the debugger.');
  652. end;
  653. end;
  654. procedure RunUninstaller;
  655. var
  656. F: TFile;
  657. UninstallerMsgTail: TUninstallerMsgTail;
  658. begin
  659. { Set default title; it's set again below after the messages are read }
  660. Application.Title := 'Uninstall';
  661. Application.MainFormOnTaskBar := True;
  662. try
  663. InitializeCommonVars;
  664. SetCurrentDir(GetSystemDir);
  665. UninstExeFilename := NewParamStr(0);
  666. ProcessCommandLine; { note: may change UninstExeFile }
  667. UninstDataFilename := PathChangeExt(UninstExeFilename, '.dat');
  668. UninstMsgFilename := PathChangeExt(UninstExeFilename, '.msg');
  669. { Initialize messages }
  670. F := TFile.Create(UninstExeFilename, fdOpenExisting, faRead, fsRead);
  671. try
  672. F.Seek(F.Size - SizeOf(UninstallerMsgTail));
  673. F.ReadBuffer(UninstallerMsgTail, SizeOf(UninstallerMsgTail));
  674. if UninstallerMsgTail.ID <> UninstallerMsgTailID then begin
  675. { No valid UninstallerMsgTail record found at the end of the EXE;
  676. load messages from an external .msg file. }
  677. LoadSetupMessages(UninstMsgFilename, 0, True);
  678. end
  679. else
  680. LoadSetupMessages(UninstExeFilename, UninstallerMsgTail.Offset, True);
  681. finally
  682. F.Free;
  683. end;
  684. LangOptions.DialogFontName := MessagesLangOptions.DialogFontName;
  685. LangOptions.DialogFontSize := MessagesLangOptions.DialogFontSize;
  686. LangOptions.RightToLeft := lfRightToLeft in MessagesLangOptions.Flags;
  687. SetMessageBoxRightToLeft(LangOptions.RightToLeft);
  688. SetMessageBoxCaption(mbInformation, PChar(SetupMessages[msgInformationTitle]));
  689. SetMessageBoxCaption(mbConfirmation, PChar(SetupMessages[msgConfirmTitle]));
  690. SetMessageBoxCaption(mbError, PChar(SetupMessages[msgErrorTitle]));
  691. SetMessageBoxCaption(mbCriticalError, PChar(SetupMessages[msgErrorTitle]));
  692. Application.Title := SetupMessages[msgUninstallAppTitle];
  693. { Verify that uninstall data file exists }
  694. if not NewFileExists(UninstDataFilename) then begin
  695. LoggedMessageBoxFmt1(msgUninstallNotFound, UninstDataFilename,
  696. SetupMessages[msgUninstallAppTitle], MB_ICONSTOP or MB_OK, True, IDOK);
  697. Abort;
  698. end;
  699. if not SecondPhase then begin
  700. if not RespawnFirstPhaseIfNeeded then
  701. RunFirstPhase;
  702. end
  703. else
  704. RunSecondPhase;
  705. except
  706. ShowExceptionMsg;
  707. end;
  708. { Call EndDebug after all exception messages have been shown and logged in
  709. the IDE's Debug Output }
  710. EndDebug;
  711. Halt(UninstallExitCode);
  712. end;
  713. procedure HandleUninstallerEndSession;
  714. begin
  715. { If second phase, remove the temp dir. The self copy made by the first phase will be
  716. deleted on restart. }
  717. if SecondPhase then begin
  718. Log('Detected restart. Removing temporary directory.');
  719. try
  720. UnloadSHFolderDLL;
  721. RemoveTempInstallDir;
  722. except
  723. ShowExceptionMsg;
  724. end;
  725. EndDebug;
  726. { Don't use Halt. See Setup.dpr }
  727. TerminateProcess(GetCurrentProcess, UninstallExitCode);
  728. end;
  729. end;
  730. end.