Uninstall.pas 29 KB

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