Setup.UninstallLog.pas 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401
  1. unit Setup.UninstallLog;
  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. Uninstallation functions
  8. }
  9. interface
  10. uses
  11. Windows, SysUtils, Shared.Int64Em, Shared.FileClass, Shared.CommonFunc;
  12. const
  13. HighestSupportedVersion = 1048;
  14. { Each time the format of the uninstall log changes (usually a new entry type
  15. is added), HighestSupportedVersion and the file version number of Setup
  16. are incremented to match (51.x). Do NOT do this yourself; doing so could cause
  17. incompatibilities with future Inno Setup releases. It's recommended that you
  18. use the "utUserDefined" log entry type if you wish to implement your own
  19. custom uninstall log entries; see below for more information. }
  20. type
  21. TUninstallRecTyp = type Word;
  22. const
  23. { Values for TUninstallRecTyp.
  24. If you wish to define your own custom uninstall entry type, you should use
  25. "utUserDefined". (Do NOT define your own ut* constants; this could cause
  26. incompatibilities with future Inno Setup releases.) The first field in a
  27. utUserDefined record must be a string which specifies a unique name for
  28. the record type. Example:
  29. UninstLog.Add(utUserDefined, ['MyRecordType', ... ], 0);
  30. }
  31. utUserDefined = $01;
  32. utStartInstall = $10;
  33. utEndInstall = $11;
  34. utCompiledCode = $20;
  35. utRun = $80;
  36. utDeleteDirOrFiles = $81;
  37. utDeleteFile = $82;
  38. utDeleteGroupOrItem = $83;
  39. utIniDeleteEntry = $84;
  40. utIniDeleteSection = $85;
  41. utRegDeleteEntireKey = $86;
  42. utRegClearValue = $87;
  43. utRegDeleteKeyIfEmpty = $88;
  44. utRegDeleteValue = $89;
  45. utDecrementSharedCount = $8A;
  46. utRefreshFileAssoc = $8B;
  47. utMutexCheck = $8C;
  48. { Flags on ExtraData }
  49. utRun_NoWait = 1;
  50. utRun_WaitUntilIdle = 2;
  51. utRun_ShellExec = 4;
  52. utRun_RunMinimized = 8;
  53. utRun_RunMaximized = 16;
  54. utRun_SkipIfDoesntExist = 32;
  55. utRun_RunHidden = 64;
  56. utRun_ShellExecRespectWaitFlags = 128;
  57. utRun_DisableFsRedir = 256;
  58. utRun_DontLogParameters = 512;
  59. utRun_LogOutput = 1024;
  60. utDeleteFile_ExistedBeforeInstall = 1;
  61. utDeleteFile_Extra = 2;
  62. utDeleteFile_IsFont = 4;
  63. utDeleteFile_SharedFile = 8;
  64. utDeleteFile_RegisteredServer = 16;
  65. utDeleteFile_CallChangeNotify = 32;
  66. utDeleteFile_RegisteredTypeLib = 64;
  67. utDeleteFile_RestartDelete = 128;
  68. utDeleteFile_RemoveReadOnly = 256;
  69. utDeleteFile_NoSharedFilePrompt = 512;
  70. utDeleteFile_SharedFileIn64BitKey = 1024;
  71. utDeleteFile_DisableFsRedir = 2048; { also determines whether file was registered as 64-bit }
  72. utDeleteFile_GacInstalled = 4096;
  73. utDeleteFile_PerUserFont = 8192;
  74. utDeleteDirOrFiles_Extra = 1;
  75. utDeleteDirOrFiles_IsDir = 2;
  76. utDeleteDirOrFiles_DeleteFiles = 4;
  77. utDeleteDirOrFiles_DeleteSubdirsAlso = 8;
  78. utDeleteDirOrFiles_CallChangeNotify = 16;
  79. utDeleteDirOrFiles_DisableFsRedir = 32;
  80. utIniDeleteSection_OnlyIfEmpty = 1;
  81. utReg_KeyHandleMask = $80FFFFFF;
  82. utReg_64BitKey = $01000000;
  83. utDecrementSharedCount_64BitKey = 1;
  84. type
  85. PUninstallRec = ^TUninstallRec;
  86. TUninstallRec = record
  87. Prev, Next: PUninstallRec;
  88. ExtraData: Longint;
  89. DataSize: Cardinal;
  90. Typ: TUninstallRecTyp;
  91. Data: array[0..$6FFFFFFF] of Byte; { *must* be last field }
  92. end;
  93. TDeleteUninstallDataFilesProc = procedure;
  94. TUninstallLogFlags = set of (ufAdminInstalled, ufDontCheckRecCRCs,
  95. ufModernStyle, ufAlwaysRestart, ufChangesEnvironment, ufWin64,
  96. ufPowerUserInstalled, ufAdminInstallMode);
  97. TUninstallLog = class
  98. private
  99. FList, FLastList: PUninstallRec;
  100. FCount: Integer;
  101. class function AllocRec(const Typ: TUninstallRecTyp;
  102. const ExtraData: Longint; const DataSize: Integer): PUninstallRec;
  103. function Delete(const Rec: PUninstallRec): PUninstallRec;
  104. procedure InternalAdd(const NewRec: PUninstallRec);
  105. protected
  106. procedure HandleException; virtual; abstract;
  107. function ShouldRemoveSharedFile(const Filename: String): Boolean; virtual;
  108. procedure StatusUpdate(StartingCount, CurCount: Integer); virtual;
  109. public
  110. InstallMode64Bit: Boolean;
  111. AppId, AppName: String;
  112. NeedRestart: Boolean;
  113. Flags: TUninstallLogFlags;
  114. Version: Integer;
  115. constructor Create;
  116. destructor Destroy; override;
  117. procedure Add(const Typ: TUninstallRecTyp; const Data: array of String;
  118. const ExtraData: Longint);
  119. procedure AddReg(const Typ: TUninstallRecTyp; const RegView: TRegView;
  120. const RootKey: HKEY; const Data: array of String);
  121. function CanAppend(const Filename: String;
  122. var ExistingFlags: TUninstallLogFlags): Boolean;
  123. function CheckMutexes: Boolean;
  124. procedure Clear;
  125. class function ExtractRecData(const Rec: PUninstallRec;
  126. var Data: array of String): Integer;
  127. function ExtractLatestRecData(const Typ: TUninstallRecTyp;
  128. const ExtraData: Longint; var Data: array of String): Boolean;
  129. procedure Load(const F: TFile; const Filename: String);
  130. function PerformUninstall(const CallFromUninstaller: Boolean;
  131. const DeleteUninstallDataFilesProc: TDeleteUninstallDataFilesProc): Boolean;
  132. class function WriteSafeHeaderString(Dest: PAnsiChar; const Source: String;
  133. MaxDestBytes: Cardinal): Cardinal;
  134. class function ReadSafeHeaderString(const Source: AnsiString): String;
  135. procedure Save(const Filename: String;
  136. const Append, UpdateUninstallLogAppName: Boolean);
  137. property List: PUninstallRec read FList;
  138. property LastList: PUninstallRec read FLastList;
  139. end;
  140. function ReadUninstallLogFlags(const F: TFile; const Filename: String): TUninstallLogFlags;
  141. implementation
  142. uses
  143. Messages, ShlObj, AnsiStrings,
  144. PathFunc, Shared.Struct, SetupLdrAndSetup.Messages, Shared.SetupMessageIDs, Setup.InstFunc,
  145. Setup.InstFunc.Ole, SetupLdrAndSetup.RedirFunc, Compression.Base,
  146. Setup.LoggingFunc, Setup.RegDLL, Setup.Helper, Setup.DotNetFunc;
  147. type
  148. { Note: TUninstallLogHeader should stay <= 512 bytes in size, so that it
  149. fits into a single disk sector and can be written atomically }
  150. TUninstallLogHeader = packed record
  151. ID: TUninstallLogID;
  152. AppId: array[0..127] of AnsiChar;
  153. AppName: array[0..127] of AnsiChar;
  154. Version, NumRecs: Integer;
  155. EndOffset: LongWord;
  156. Flags: Longint;
  157. Reserved: array[0..26] of Longint; { reserved for future use }
  158. CRC: Longint;
  159. end;
  160. TUninstallCrcHeader = packed record
  161. Size, NotSize: Cardinal;
  162. CRC: Longint;
  163. end;
  164. TUninstallFileRec = packed record
  165. Typ: TUninstallRecTyp;
  166. ExtraData: Longint;
  167. DataSize: Cardinal;
  168. end;
  169. procedure ReadUninstallLogHeader(const F: TFile; const Filename: String;
  170. var Header: TUninstallLogHeader; var Header64Bit: Boolean);
  171. procedure Corrupt;
  172. begin
  173. raise Exception.Create(FmtSetupMessage1(msgUninstallDataCorrupted, Filename));
  174. end;
  175. begin
  176. F.Seek(0);
  177. if F.Read(Header, SizeOf(Header)) <> SizeOf(Header) then
  178. Corrupt;
  179. if (Header.CRC <> $11111111) and
  180. { ^ for debugging purposes, you can change the CRC field in the file to
  181. $11111111 to disable CRC checking on the header}
  182. (Header.CRC <> GetCRC32(Header, SizeOf(Header)-SizeOf(Longint))) then
  183. Corrupt;
  184. if Header.ID = UninstallLogID[False] then
  185. Header64Bit := False
  186. else if Header.ID = UninstallLogID[True] then
  187. Header64Bit := True
  188. else
  189. Corrupt;
  190. end;
  191. function ReadUninstallLogFlags(const F: TFile; const Filename: String): TUninstallLogFlags;
  192. { Reads the flags from the header of the open file F. The Filename parameter
  193. is only used when generating exception error messages. }
  194. var
  195. Header: TUninstallLogHeader;
  196. Header64Bit: Boolean;
  197. begin
  198. ReadUninstallLogHeader(F, Filename, Header, Header64Bit);
  199. Result := TUninstallLogFlags((@Header.Flags)^);
  200. end;
  201. { Misc. uninstallation functions }
  202. function ListContainsPathOrSubdir(const List: TSimpleStringList;
  203. const Path: String): Boolean;
  204. { Returns True if List contains Path or a subdirectory of Path }
  205. var
  206. SlashPath: String;
  207. SlashPathLen, I: Integer;
  208. begin
  209. SlashPath := AddBackslash(Path);
  210. SlashPathLen := Length(SlashPath);
  211. if SlashPathLen > 0 then begin { ...sanity check }
  212. for I := 0 to List.Count-1 do begin
  213. if List[I] = Path then begin
  214. Result := True;
  215. Exit;
  216. end;
  217. if (Length(List[I]) > SlashPathLen) and
  218. CompareMem(Pointer(List[I]), Pointer(SlashPath), SlashPathLen * SizeOf(SlashPath[1])) then begin
  219. Result := True;
  220. Exit;
  221. end;
  222. end;
  223. end;
  224. Result := False;
  225. end;
  226. procedure LoggedRestartDeleteDir(const DisableFsRedir: Boolean; Dir: String);
  227. begin
  228. Dir := PathExpand(Dir);
  229. if not DisableFsRedir then begin
  230. { Work around WOW64 bug present in the IA64 and x64 editions of Windows
  231. XP (3790) and Server 2003 prior to SP1 RC2: MoveFileEx writes filenames
  232. to the registry verbatim without mapping system32->syswow64. }
  233. Dir := ReplaceSystemDirWithSysWow64(Dir);
  234. end;
  235. if not MoveFileExRedir(DisableFsRedir, Dir, '', MOVEFILE_DELAY_UNTIL_REBOOT) then
  236. LogFmt('MoveFileEx failed (%d).', [GetLastError]);
  237. end;
  238. const
  239. drFalse = '0';
  240. drTrue = '1';
  241. function LoggedDeleteDir(const DisableFsRedir: Boolean; const DirName: String;
  242. const DirsNotRemoved, RestartDeleteDirList: TSimpleStringList): Boolean;
  243. const
  244. FILE_ATTRIBUTE_REPARSE_POINT = $00000400;
  245. DirsNotRemovedPrefix: array[Boolean] of Char = (drFalse, drTrue);
  246. var
  247. Attribs, LastError: DWORD;
  248. begin
  249. Attribs := GetFileAttributesRedir(DisableFsRedir, DirName);
  250. { Does the directory exist? }
  251. if (Attribs <> INVALID_FILE_ATTRIBUTES) and
  252. (Attribs and FILE_ATTRIBUTE_DIRECTORY <> 0) then begin
  253. LogFmt('Deleting directory: %s', [DirName]);
  254. { If the directory has the read-only attribute, strip it first }
  255. if Attribs and FILE_ATTRIBUTE_READONLY <> 0 then begin
  256. if (Attribs and FILE_ATTRIBUTE_REPARSE_POINT <> 0) or
  257. IsDirEmpty(DisableFsRedir, DirName) then begin
  258. if SetFileAttributesRedir(DisableFsRedir, DirName, Attribs and not FILE_ATTRIBUTE_READONLY) then
  259. Log('Stripped read-only attribute.')
  260. else
  261. Log('Failed to strip read-only attribute.');
  262. end
  263. else
  264. Log('Not stripping read-only attribute because the directory ' +
  265. 'does not appear to be empty.');
  266. end;
  267. Result := RemoveDirectoryRedir(DisableFsRedir, DirName);
  268. if not Result then begin
  269. LastError := GetLastError;
  270. if Assigned(DirsNotRemoved) then begin
  271. LogFmt('Failed to delete directory (%d). Will retry later.', [LastError]);
  272. DirsNotRemoved.AddIfDoesntExist(DirsNotRemovedPrefix[DisableFsRedir] + DirName);
  273. end
  274. else if Assigned(RestartDeleteDirList) and
  275. ListContainsPathOrSubdir(RestartDeleteDirList, DirName) then begin
  276. LogFmt('Failed to delete directory (%d). Will delete on restart (if empty).',
  277. [LastError]);
  278. LoggedRestartDeleteDir(DisableFsRedir, DirName);
  279. end
  280. else
  281. LogFmt('Failed to delete directory (%d).', [LastError]);
  282. end;
  283. end
  284. else
  285. Result := True;
  286. end;
  287. procedure CrackRegExtraData(const ExtraData: Longint; var RegView: TRegView;
  288. var RootKey: HKEY);
  289. begin
  290. if ExtraData and utReg_64BitKey <> 0 then
  291. RegView := rv64Bit
  292. else
  293. RegView := rv32Bit;
  294. RootKey := ExtraData and utReg_KeyHandleMask;
  295. end;
  296. { TUninstallLog }
  297. constructor TUninstallLog.Create;
  298. begin
  299. inherited Create;
  300. Clear;
  301. end;
  302. destructor TUninstallLog.Destroy;
  303. begin
  304. Clear;
  305. inherited Destroy;
  306. end;
  307. class function TUninstallLog.AllocRec(const Typ: TUninstallRecTyp;
  308. const ExtraData: Longint; const DataSize: Integer): PUninstallRec;
  309. { Allocates a new PUninstallRec, but does not add it to the list. Returns nil
  310. if the value of the DataSize parameter is out of range. }
  311. begin
  312. { Sanity check the size to protect against integer overflows. 128 MB should
  313. be way more than enough. }
  314. if (DataSize < 0) or (DataSize > $08000000) then begin
  315. Result := nil;
  316. Exit;
  317. end;
  318. Result := AllocMem(Integer(@PUninstallRec(nil).Data) + DataSize);
  319. Result.Typ := Typ;
  320. Result.ExtraData := ExtraData;
  321. Result.DataSize := DataSize;
  322. end;
  323. procedure TUninstallLog.InternalAdd(const NewRec: PUninstallRec);
  324. { Adds a new entry to the uninstall list }
  325. begin
  326. if List = nil then begin
  327. FList := NewRec;
  328. FLastList := List;
  329. end
  330. else begin
  331. LastList^.Next := NewRec;
  332. NewRec^.Prev := LastList;
  333. FLastList := NewRec;
  334. end;
  335. Inc(FCount);
  336. end;
  337. procedure TUninstallLog.Add(const Typ: TUninstallRecTyp; const Data: array of String;
  338. const ExtraData: Longint);
  339. var
  340. I, L: Integer;
  341. S, X: AnsiString;
  342. AData: AnsiString;
  343. NewRec: PUninstallRec;
  344. begin
  345. for I := 0 to High(Data) do begin
  346. L := Length(Data[I])*SizeOf(Data[I][1]);
  347. SetLength(X, SizeOf(Byte) + SizeOf(Integer));
  348. X[1] := AnsiChar($FE);
  349. Integer((@X[2])^) := Integer(-L);
  350. S := S + X;
  351. SetString(AData, PAnsiChar(Pointer(Data[I])), L);
  352. S := S + AData;
  353. end;
  354. S := S + AnsiChar($FF);
  355. NewRec := AllocRec(Typ, ExtraData, Length(S)*SizeOf(S[1]));
  356. if NewRec = nil then
  357. InternalError('DataSize range exceeded');
  358. Move(Pointer(S)^, NewRec.Data, NewRec.DataSize);
  359. InternalAdd(NewRec);
  360. if Version < HighestSupportedVersion then
  361. Version := HighestSupportedVersion;
  362. end;
  363. procedure TUninstallLog.AddReg(const Typ: TUninstallRecTyp;
  364. const RegView: TRegView; const RootKey: HKEY; const Data: array of String);
  365. { Adds a new utReg* type entry }
  366. var
  367. ExtraData: Longint;
  368. begin
  369. { If RootKey isn't a predefined key, or has unrecognized garbage in the
  370. high byte (which we use for our own purposes), reject it }
  371. if RootKey shr 24 <> $80 then
  372. Exit;
  373. { ExtraData in a utReg* entry consists of a root key value (HKEY_*)
  374. OR'ed with flag bits in the high byte }
  375. HKEY(ExtraData) := RootKey;
  376. if RegView in RegViews64Bit then
  377. ExtraData := ExtraData or utReg_64BitKey;
  378. Add(Typ, Data, ExtraData);
  379. end;
  380. function TUninstallLog.Delete(const Rec: PUninstallRec): PUninstallRec;
  381. { Removes Rec from the linked list, then frees it. Returns (what was) the
  382. previous record, or nil if there is none. }
  383. begin
  384. Result := Rec.Prev;
  385. if Assigned(Rec.Prev) then
  386. Rec.Prev.Next := Rec.Next;
  387. if Assigned(Rec.Next) then
  388. Rec.Next.Prev := Rec.Prev;
  389. if FList = Rec then
  390. FList := Rec.Next;
  391. if FLastList = Rec then
  392. FLastList := Rec.Prev;
  393. Dec(FCount);
  394. FreeMem(Rec);
  395. end;
  396. procedure TUninstallLog.Clear;
  397. { Frees all entries in the uninstall list and clears AppName/AppDir }
  398. begin
  399. while FLastList <> nil do
  400. Delete(FLastList);
  401. FCount := 0;
  402. AppId := '';
  403. AppName := '';
  404. Flags := [];
  405. end;
  406. type
  407. PDeleteDirData = ^TDeleteDirData;
  408. TDeleteDirData = record
  409. DirsNotRemoved: TSimpleStringList;
  410. end;
  411. function LoggedDeleteDirProc(const DisableFsRedir: Boolean; const DirName: String;
  412. const Param: Pointer): Boolean;
  413. begin
  414. Result := LoggedDeleteDir(DisableFsRedir, DirName, PDeleteDirData(Param)^.DirsNotRemoved, nil);
  415. end;
  416. function LoggedDeleteFileProc(const DisableFsRedir: Boolean; const FileName: String;
  417. const Param: Pointer): Boolean;
  418. begin
  419. LogFmt('Deleting file: %s', [FileName]);
  420. Result := DeleteFileRedir(DisableFsRedir, FileName);
  421. if not Result then
  422. LogFmt('Failed to delete the file; it may be in use (%d).', [GetLastError]);
  423. end;
  424. procedure ProcessMessagesProc; far;
  425. var
  426. Msg: TMsg;
  427. begin
  428. while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do begin
  429. TranslateMessage(Msg);
  430. DispatchMessage(Msg);
  431. end;
  432. end;
  433. class function TUninstallLog.ExtractRecData(const Rec: PUninstallRec;
  434. var Data: array of String): Integer;
  435. var
  436. I, L: Integer;
  437. X: ^Byte;
  438. begin
  439. for I := 0 to High(Data) do
  440. Data[I] := '';
  441. I := 0;
  442. X := @Rec^.Data;
  443. while I <= High(Data) do begin
  444. case X^ of
  445. $00..$FC: begin
  446. L := X^;
  447. Inc(X);
  448. end;
  449. $FD: begin
  450. Inc(X);
  451. L := Word(Pointer(X)^);
  452. Inc(X, SizeOf(Word));
  453. end;
  454. $FE: begin
  455. Inc(X);
  456. L := Integer(Pointer(X)^);
  457. Inc(X, SizeOf(Integer));
  458. end;
  459. $FF: Break;
  460. end;
  461. if L < 0 then begin
  462. L := -L;
  463. SetString(Data[I], PChar(X), L div SizeOf(Char));
  464. end else
  465. SetString(Data[I], PAnsiChar(X), L);
  466. Inc(X, L);
  467. Inc(I);
  468. end;
  469. Result := I;
  470. end;
  471. function TUninstallLog.ExtractLatestRecData(const Typ: TUninstallRecTyp;
  472. const ExtraData: Longint; var Data: array of String): Boolean;
  473. var
  474. CurRec: PUninstallRec;
  475. begin
  476. CurRec := LastList;
  477. while CurRec <> nil do begin
  478. if (CurRec^.Typ = Typ) and (CurRec^.ExtraData = ExtraData) then begin
  479. ExtractRecData(CurRec, Data);
  480. Result := True;
  481. Exit;
  482. end;
  483. CurRec := CurRec^.Prev;
  484. end;
  485. Result := False;
  486. end;
  487. function TUninstallLog.CheckMutexes: Boolean;
  488. var
  489. CurRec: PUninstallRec;
  490. Data: String;
  491. begin
  492. Result := False;
  493. CurRec := LastList;
  494. while CurRec <> nil do begin
  495. if CurRec^.Typ = utMutexCheck then begin
  496. ExtractRecData(CurRec, Data);
  497. if CheckForMutexes(Data) then begin
  498. Result := True;
  499. Exit;
  500. end;
  501. end;
  502. CurRec := CurRec^.Prev;
  503. end;
  504. end;
  505. procedure RunExecLog(const S: String; const Error, FirstLine: Boolean; const Data: NativeInt);
  506. begin
  507. if not Error and FirstLine then
  508. Log('Running Exec output:');
  509. Log(S);
  510. end;
  511. function TUninstallLog.PerformUninstall(const CallFromUninstaller: Boolean;
  512. const DeleteUninstallDataFilesProc: TDeleteUninstallDataFilesProc): Boolean;
  513. { Undoes all the changes in the uninstall list, in reverse order they were
  514. added. Deletes entries that were successfully undone.
  515. Returns True if all elements were successfully removed; False if some
  516. could not be removed. }
  517. var
  518. RefreshFileAssoc: Boolean;
  519. ChangeNotifyList, RunOnceList: TSimpleStringList;
  520. UnregisteredServersList, RestartDeleteDirList: array[Boolean] of TSimpleStringList;
  521. DeleteDirData: TDeleteDirData;
  522. function LoggedFileDelete(const Filename: String; const DisableFsRedir,
  523. NotifyChange, RestartDelete, RemoveReadOnly: Boolean): Boolean;
  524. var
  525. ExistingAttr, LastError: DWORD;
  526. begin
  527. Result := True;
  528. { Automatically delete generated indexes associated with help files }
  529. if SameText(PathExtractExt(Filename), '.hlp') then begin
  530. LoggedFileDelete(PathChangeExt(Filename, '.gid'), DisableFsRedir, False, False, False);
  531. LoggedFileDelete(PathChangeExt(Filename, '.fts'), DisableFsRedir, False, False, False);
  532. end
  533. else if SameText(PathExtractExt(Filename), '.chm') then
  534. LoggedFileDelete(PathChangeExt(Filename, '.chw'), DisableFsRedir, False, False, False);
  535. { Automatically unpin shortcuts }
  536. if SameText(PathExtractExt(Filename), '.lnk') then
  537. UnpinShellLink(Filename);
  538. if NewFileExistsRedir(DisableFsRedir, Filename) then begin
  539. LogFmt('Deleting file: %s', [FileName]);
  540. if RemoveReadOnly then begin
  541. ExistingAttr := GetFileAttributesRedir(DisableFsRedir, Filename);
  542. if (ExistingAttr <> INVALID_FILE_ATTRIBUTES) and
  543. (ExistingAttr and FILE_ATTRIBUTE_READONLY <> 0) then
  544. if SetFileAttributesRedir(DisableFsRedir, Filename,
  545. ExistingAttr and not FILE_ATTRIBUTE_READONLY) then
  546. Log('Stripped read-only attribute.')
  547. else
  548. Log('Failed to strip read-only attribute.');
  549. end;
  550. if not DeleteFileRedir(DisableFsRedir, Filename) then begin
  551. LastError := GetLastError;
  552. if RestartDelete and CallFromUninstaller and
  553. ((LastError = ERROR_ACCESS_DENIED) or (LastError = ERROR_SHARING_VIOLATION)) and
  554. (GetFileAttributesRedir(DisableFsRedir, Filename) and FILE_ATTRIBUTE_READONLY = 0) then begin
  555. LogFmt('The file appears to be in use (%d). Will delete on restart.',
  556. [LastError]);
  557. try
  558. RestartReplace(DisableFsRedir, Filename, '');
  559. NeedRestart := True;
  560. { Add the file's directory to the list of directories that should
  561. be restart-deleted later }
  562. RestartDeleteDirList[DisableFsRedir].AddIfDoesntExist(PathExtractDir(PathExpand(Filename)));
  563. except
  564. Log('Exception message:' + SNewLine + GetExceptMessage);
  565. Result := False;
  566. end;
  567. end
  568. else begin
  569. LogFmt('Failed to delete the file; it may be in use (%d).', [LastError]);
  570. Result := False;
  571. end;
  572. end
  573. else begin
  574. { Note: It is assumed that DisableFsRedir will be False when NotifyChange is True }
  575. if NotifyChange then begin
  576. SHChangeNotify(SHCNE_DELETE, SHCNF_PATH, PChar(Filename), nil);
  577. ChangeNotifyList.AddIfDoesntExist(PathExtractDir(Filename));
  578. end;
  579. end;
  580. end;
  581. end;
  582. function LoggedDecrementSharedCount(const Filename: String;
  583. const Key64Bit: Boolean): Boolean;
  584. const
  585. Bits: array[Boolean] of Integer = (32, 64);
  586. var
  587. RegView: TRegView;
  588. begin
  589. if Key64Bit then
  590. RegView := rv64Bit
  591. else
  592. RegView := rv32Bit;
  593. LogFmt('Decrementing shared count (%d-bit): %s', [Bits[Key64Bit], Filename]);
  594. Result := DecrementSharedCount(RegView, Filename);
  595. if Result then
  596. Log('Shared count reached zero.');
  597. end;
  598. procedure LoggedUnregisterServer(const Is64Bit: Boolean; const Filename: String);
  599. begin
  600. { Just as an optimization, make sure we aren't unregistering
  601. the same file again }
  602. if UnregisteredServersList[Is64Bit].IndexOf(Filename) = -1 then begin
  603. if Is64Bit then
  604. LogFmt('Unregistering 64-bit DLL/OCX: %s', [Filename])
  605. else
  606. LogFmt('Unregistering 32-bit DLL/OCX: %s', [Filename]);
  607. try
  608. RegisterServer(True, Is64Bit, Filename, True);
  609. UnregisteredServersList[Is64Bit].Add(Filename);
  610. Log('Unregistration successful.');
  611. except
  612. Log('Unregistration failed:' + SNewLine + GetExceptMessage);
  613. end;
  614. end
  615. else
  616. LogFmt('Not unregistering DLL/OCX again: %s', [Filename]);
  617. end;
  618. procedure LoggedUnregisterTypeLibrary(const Is64Bit: Boolean;
  619. const Filename: String);
  620. begin
  621. if Is64Bit then
  622. LogFmt('Unregistering 64-bit type library: %s', [Filename])
  623. else
  624. LogFmt('Unregistering 32-bit type library: %s', [Filename]);
  625. try
  626. if Is64Bit then
  627. HelperRegisterTypeLibrary(True, Filename)
  628. else
  629. UnregisterTypeLibrary(Filename);
  630. Log('Unregistration successful.');
  631. except
  632. Log('Unregistration failed:' + SNewLine + GetExceptMessage);
  633. end;
  634. end;
  635. procedure LoggedUninstallAssembly(const StrongAssemblyName: String);
  636. begin
  637. LogFmt('Uninstalling from GAC: %s', [StrongAssemblyName]);
  638. try
  639. with TAssemblyCacheInfo.Create(rvDefault) do try
  640. UninstallAssembly(StrongAssemblyName);
  641. finally
  642. Free;
  643. end;
  644. except
  645. Log('Uninstallation failed:' + SNewLine + GetExceptMessage);
  646. end;
  647. end;
  648. procedure LoggedProcessDirsNotRemoved;
  649. var
  650. I: Integer;
  651. S: String;
  652. DisableFsRedir: Boolean;
  653. begin
  654. for I := 0 to DeleteDirData.DirsNotRemoved.Count-1 do begin
  655. S := DeleteDirData.DirsNotRemoved[I];
  656. { The first character specifies the DisableFsRedir value
  657. (e.g. '0C:\Program Files\My Program') }
  658. DisableFsRedir := (S[1] = drTrue);
  659. System.Delete(S, 1, 1);
  660. LoggedDeleteDir(DisableFsRedir, S, nil, RestartDeleteDirList[DisableFsRedir]);
  661. end;
  662. end;
  663. function GetLogIniFilename(const Filename: String): String;
  664. begin
  665. if Filename <> '' then
  666. Result := Filename
  667. else
  668. Result := 'win.ini';
  669. end;
  670. const
  671. GroupInfoChars: array[0..3] of Char = ('"', '"', ',', ',');
  672. NullChar: Char = #0;
  673. var
  674. StartCount: Integer;
  675. CurRec: PUninstallRec;
  676. CurRecDataPChar: array[0..9] of PChar;
  677. CurRecData: array[0..9] of String;
  678. ShouldDeleteRec, IsTempFile, IsSharedFile, SharedCountDidReachZero: Boolean;
  679. Filename, Section, Key: String;
  680. Subkey, ValueName: PChar;
  681. P, ErrorCode: Integer;
  682. RegView: TRegView;
  683. RootKey, K: HKEY;
  684. Wait: TExecWait;
  685. ShowCmd: Integer;
  686. procedure SplitData(const Rec: PUninstallRec);
  687. var
  688. C, I: Integer;
  689. begin
  690. C := ExtractRecData(Rec, CurRecData);
  691. for I := 0 to 9 do begin
  692. if I < C then
  693. CurRecDataPChar[I] := PChar(CurRecData[I])
  694. else
  695. CurRecDataPChar[I] := nil;
  696. end;
  697. end;
  698. begin
  699. Log('Starting the uninstallation process.');
  700. SetCurrentDir(GetSystemDir);
  701. Result := True;
  702. NeedRestart := False;
  703. RefreshFileAssoc := False;
  704. RunOnceList := nil;
  705. UnregisteredServersList[False] := nil;
  706. UnregisteredServersList[True] := nil;
  707. RestartDeleteDirList[False] := nil;
  708. RestartDeleteDirList[True] := nil;
  709. DeleteDirData.DirsNotRemoved := nil;
  710. ChangeNotifyList := TSimpleStringList.Create;
  711. try
  712. RunOnceList := TSimpleStringList.Create;
  713. UnregisteredServersList[False] := TSimpleStringList.Create;
  714. UnregisteredServersList[True] := TSimpleStringList.Create;
  715. RestartDeleteDirList[False] := TSimpleStringList.Create;
  716. RestartDeleteDirList[True] := TSimpleStringList.Create;
  717. if Assigned(DeleteUninstallDataFilesProc) then
  718. DeleteDirData.DirsNotRemoved := TSimpleStringList.Create;
  719. StartCount := FCount;
  720. StatusUpdate(StartCount, FCount);
  721. { Step 1 - Process all utRun entries }
  722. if CallFromUninstaller then begin
  723. CurRec := LastList;
  724. while CurRec <> nil do begin
  725. if CurRec^.Typ = utRun then begin
  726. try
  727. SplitData(CurRec);
  728. { Verify that a utRun entry with the same RunOnceId has not
  729. already been executed }
  730. if (CurRecData[3] = '') or (RunOnceList.IndexOf(CurRecData[3]) = -1) then begin
  731. Wait := ewWaitUntilTerminated;
  732. if CurRec^.ExtraData and utRun_NoWait <> 0 then
  733. Wait := ewNoWait
  734. else if CurRec^.ExtraData and utRun_WaitUntilIdle <> 0 then
  735. Wait := ewWaitUntilIdle;
  736. ShowCmd := SW_SHOWNORMAL;
  737. if CurRec^.ExtraData and utRun_RunMinimized <> 0 then
  738. ShowCmd := SW_SHOWMINNOACTIVE
  739. else if CurRec^.ExtraData and utRun_RunMaximized <> 0 then
  740. ShowCmd := SW_SHOWMAXIMIZED
  741. else if CurRec^.ExtraData and utRun_RunHidden <> 0 then
  742. ShowCmd := SW_HIDE;
  743. { Note: This code is similar to code in the ProcessRunEntry
  744. function of Main.pas }
  745. if CurRec^.ExtraData and utRun_ShellExec = 0 then begin
  746. Log('Running Exec filename: ' + CurRecData[0]);
  747. if (CurRec^.ExtraData and utRun_DontLogParameters = 0) and (CurRecData[1] <> '') then
  748. Log('Running Exec parameters: ' + CurRecData[1]);
  749. if (CurRec^.ExtraData and utRun_SkipIfDoesntExist = 0) or
  750. NewFileExistsRedir(CurRec^.ExtraData and utRun_DisableFsRedir <> 0, CurRecData[0]) then begin
  751. var OutputReader: TCreateProcessOutputReader := nil;
  752. try
  753. if GetLogActive and (CurRec^.ExtraData and utRun_LogOutput <> 0) then
  754. OutputReader := TCreateProcessOutputReader.Create(RunExecLog, 0);
  755. if not InstExec(CurRec^.ExtraData and utRun_DisableFsRedir <> 0,
  756. CurRecData[0], CurRecData[1], CurRecData[2], Wait,
  757. ShowCmd, ProcessMessagesProc, OutputReader, ErrorCode) then begin
  758. LogFmt('CreateProcess failed (%d).', [ErrorCode]);
  759. Result := False;
  760. end
  761. else begin
  762. if Wait = ewWaitUntilTerminated then
  763. LogFmt('Process exit code: %u', [ErrorCode]);
  764. end;
  765. finally
  766. OutputReader.Free;
  767. end;
  768. end else
  769. Log('File doesn''t exist. Skipping.');
  770. end
  771. else begin
  772. Log('Running ShellExec filename: ' + CurRecData[0]);
  773. if (CurRec^.ExtraData and utRun_DontLogParameters = 0) and (CurRecData[1] <> '') then
  774. Log('Running ShellExec parameters: ' + CurRecData[1]);
  775. if (CurRec^.ExtraData and utRun_SkipIfDoesntExist = 0) or
  776. FileOrDirExists(CurRecData[0]) then begin
  777. if CurRec^.ExtraData and utRun_ShellExecRespectWaitFlags = 0 then
  778. Wait := ewNoWait;
  779. if not InstShellExec(CurRecData[4], CurRecData[0], CurRecData[1], CurRecData[2],
  780. Wait, ShowCmd, ProcessMessagesProc, ErrorCode) then begin
  781. LogFmt('ShellExecuteEx failed (%d).', [ErrorCode]);
  782. Result := False;
  783. end
  784. else begin
  785. if Wait = ewWaitUntilTerminated then
  786. LogFmt('Process exit code: %u', [ErrorCode]);
  787. end;
  788. end else
  789. Log('File/directory doesn''t exist. Skipping.');
  790. end;
  791. if CurRecData[3] <> '' then
  792. RunOnceList.Add(CurRecData[3]);
  793. end else
  794. LogFmt('Skipping RunOnceId "%s" filename: %s', [CurRecData[3], CurRecData[0]]);
  795. except
  796. Result := False;
  797. if not(ExceptObject is EAbort) then
  798. HandleException;
  799. end;
  800. CurRec := Delete(CurRec);
  801. StatusUpdate(StartCount, FCount);
  802. end
  803. else
  804. CurRec := CurRec^.Prev;
  805. end;
  806. end;
  807. { Step 2 - Decrement shared file counts, unregister DLLs/TLBs/fonts, and uninstall from GAC }
  808. CurRec := LastList;
  809. while CurRec <> nil do begin
  810. ShouldDeleteRec := False;
  811. if CurRec^.Typ = utDeleteFile then begin
  812. { Default to deleting the record in case an exception is raised by
  813. DecrementSharedCount, the reference count doesn't reach zero, or the
  814. user opts not to delete the shared file. }
  815. ShouldDeleteRec := True;
  816. try
  817. SplitData(CurRec);
  818. { Note: Some of this code is duplicated in Step 3 }
  819. if CallFromUninstaller or (CurRec^.ExtraData and utDeleteFile_ExistedBeforeInstall = 0) then begin
  820. IsTempFile := not CallFromUninstaller and (CurRecData[1] <> '');
  821. { Decrement shared file count if necessary }
  822. IsSharedFile := CurRec^.ExtraData and utDeleteFile_SharedFile <> 0;
  823. if IsSharedFile then
  824. SharedCountDidReachZero := LoggedDecrementSharedCount(CurRecData[0],
  825. CurRec^.ExtraData and utDeleteFile_SharedFileIn64BitKey <> 0)
  826. else
  827. SharedCountDidReachZero := False; //silence compiler
  828. if not IsSharedFile or
  829. (SharedCountDidReachZero and
  830. (IsTempFile or
  831. not NewFileExistsRedir(CurRec^.ExtraData and utDeleteFile_DisableFsRedir <> 0, CurRecData[0]) or
  832. (CurRec^.ExtraData and utDeleteFile_NoSharedFilePrompt <> 0) or
  833. ShouldRemoveSharedFile(CurRecData[0]))) then begin
  834. { The reference count reached zero and the user did not object
  835. to the file being deleted, so don't delete the record; allow
  836. the file to be deleted in the next step. }
  837. ShouldDeleteRec := False;
  838. { Unregister if necessary }
  839. if not IsTempFile then begin
  840. if CurRec^.ExtraData and utDeleteFile_RegisteredServer <> 0 then begin
  841. LoggedUnregisterServer(CurRec^.ExtraData and utDeleteFile_DisableFsRedir <> 0,
  842. CurRecData[0]);
  843. end;
  844. if CurRec^.ExtraData and utDeleteFile_RegisteredTypeLib <> 0 then begin
  845. LoggedUnregisterTypeLibrary(CurRec^.ExtraData and utDeleteFile_DisableFsRedir <> 0,
  846. CurRecData[0]);
  847. end;
  848. end;
  849. if CurRec^.ExtraData and utDeleteFile_IsFont <> 0 then begin
  850. LogFmt('Unregistering font: %s', [CurRecData[2]]);
  851. UnregisterFont(CurRecData[2], CurRecData[3], CurRec^.ExtraData and utDeleteFile_PerUserFont <> 0);
  852. end;
  853. if CurRec^.ExtraData and utDeleteFile_GacInstalled <> 0 then
  854. LoggedUninstallAssembly(CurRecData[4]);
  855. end;
  856. end
  857. else begin
  858. { This case is handled entirely in Step 3 }
  859. ShouldDeleteRec := False;
  860. end;
  861. except
  862. Result := False;
  863. if not(ExceptObject is EAbort) then
  864. HandleException;
  865. end;
  866. end;
  867. if ShouldDeleteRec then begin
  868. CurRec := Delete(CurRec);
  869. StatusUpdate(StartCount, FCount);
  870. end
  871. else
  872. CurRec := CurRec^.Prev;
  873. end;
  874. { Step 3 - Remaining entries }
  875. CurRec := LastList;
  876. while CurRec <> nil do begin
  877. SplitData(CurRec);
  878. try
  879. case CurRec^.Typ of
  880. utUserDefined: begin
  881. {if CurRecData[0] = 'MyRecordType' then begin
  882. ... your code here ...
  883. end
  884. else}
  885. raise Exception.Create(FmtSetupMessage1(msgUninstallUnknownEntry,
  886. 'utUserDefined:' + CurRecData[0]));
  887. end;
  888. utStartInstall,
  889. utEndInstall,
  890. utCompiledCode: { do nothing on these };
  891. utRun: begin
  892. { Will get here if CallFromUninstaller=False; in that case utRun
  893. entries will still be in the list, unprocessed. Just ignore
  894. them. }
  895. end;
  896. utDeleteDirOrFiles:
  897. if (CallFromUninstaller or (CurRec^.ExtraData and utDeleteDirOrFiles_Extra = 0)) then begin
  898. if DelTree(CurRec^.ExtraData and utDeleteDirOrFiles_DisableFsRedir <> 0,
  899. CurRecData[0], CurRec^.ExtraData and utDeleteDirOrFiles_IsDir <> 0,
  900. CurRec^.ExtraData and utDeleteDirOrFiles_DeleteFiles <> 0,
  901. CurRec^.ExtraData and utDeleteDirOrFiles_DeleteSubdirsAlso <> 0,
  902. False, LoggedDeleteDirProc, LoggedDeleteFileProc, @DeleteDirData) then begin
  903. if (CurRec^.ExtraData and utDeleteDirOrFiles_IsDir <> 0) and
  904. (CurRec^.ExtraData and utDeleteDirOrFiles_CallChangeNotify <> 0) then begin
  905. SHChangeNotify(SHCNE_RMDIR, SHCNF_PATH, CurRecDataPChar[0], nil);
  906. ChangeNotifyList.AddIfDoesntExist(PathExtractDir(CurRecData[0]));
  907. end;
  908. end;
  909. end;
  910. utDeleteFile: begin
  911. { Note: Some of this code is duplicated in Step 2 }
  912. Filename := CurRecData[1];
  913. if CallFromUninstaller or (Filename = '') then
  914. Filename := CurRecData[0];
  915. if CallFromUninstaller or (CurRec^.ExtraData and utDeleteFile_ExistedBeforeInstall = 0) then begin
  916. { Note: We handled utDeleteFile_SharedFile already }
  917. if CallFromUninstaller or (CurRec^.ExtraData and utDeleteFile_Extra = 0) then
  918. if not LoggedFileDelete(Filename, CurRec^.ExtraData and utDeleteFile_DisableFsRedir <> 0,
  919. CurRec^.ExtraData and utDeleteFile_CallChangeNotify <> 0,
  920. CurRec^.ExtraData and utDeleteFile_RestartDelete <> 0,
  921. CurRec^.ExtraData and utDeleteFile_RemoveReadOnly <> 0) then
  922. Result := False;
  923. end
  924. else begin
  925. { We're running from Setup, and the file existed before
  926. installation... }
  927. if CurRec^.ExtraData and utDeleteFile_SharedFile <> 0 then
  928. LoggedDecrementSharedCount(CurRecData[0],
  929. CurRec^.ExtraData and utDeleteFile_SharedFileIn64BitKey <> 0);
  930. { Delete file only if it's a temp file }
  931. if Filename <> CurRecData[0] then
  932. if not LoggedFileDelete(Filename, CurRec^.ExtraData and utDeleteFile_DisableFsRedir <> 0,
  933. CurRec^.ExtraData and utDeleteFile_CallChangeNotify <> 0,
  934. CurRec^.ExtraData and utDeleteFile_RestartDelete <> 0,
  935. CurRec^.ExtraData and utDeleteFile_RemoveReadOnly <> 0) then
  936. Result := False;
  937. end;
  938. end;
  939. utDeleteGroupOrItem: ; { dummy - no longer supported }
  940. utIniDeleteEntry: begin
  941. Section := CurRecData[1];
  942. Key := CurRecData[2];
  943. Filename := CurRecData[0];
  944. LogFmt('Deleting INI entry: %s in section %s in %s', [Key, Section, GetLogIniFilename(Filename)]);
  945. DeleteIniEntry(Section, Key, Filename);
  946. end;
  947. utIniDeleteSection: begin
  948. Section := CurRecData[1];
  949. Filename := CurRecData[0];
  950. if (CurRec^.ExtraData and utIniDeleteSection_OnlyIfEmpty = 0) or
  951. IsIniSectionEmpty(Section, Filename) then begin
  952. LogFmt('Deleting INI section: %s in %s', [Section, GetLogIniFilename(Filename)]);
  953. DeleteIniSection(Section, Filename);
  954. end;
  955. end;
  956. utRegDeleteEntireKey: begin
  957. CrackRegExtraData(CurRec^.ExtraData, RegView, RootKey);
  958. Subkey := CurRecDataPChar[0];
  959. LogFmt('Deleting registry key: %s\%s', [GetRegRootKeyName(RootKey), Subkey]);
  960. ErrorCode := RegDeleteKeyIncludingSubkeys(RegView, RootKey, Subkey);
  961. if not (ErrorCode in [ERROR_SUCCESS, ERROR_FILE_NOT_FOUND]) then begin
  962. LogFmt('Deletion failed (%d).', [ErrorCode]);
  963. Result := False;
  964. end;
  965. end;
  966. utRegClearValue: begin
  967. CrackRegExtraData(CurRec^.ExtraData, RegView, RootKey);
  968. Subkey := CurRecDataPChar[0];
  969. ValueName := CurRecDataPChar[1];
  970. LogFmt('Clearing registry value: %s\%s\%s', [GetRegRootKeyName(RootKey), Subkey, ValueName]);
  971. if RegOpenKeyExView(RegView, RootKey, Subkey, 0, KEY_SET_VALUE, K) = ERROR_SUCCESS then begin
  972. ErrorCode := RegSetValueEx(K, ValueName, 0, REG_SZ, @NullChar, SizeOf(NullChar));
  973. if ErrorCode <> ERROR_SUCCESS then begin
  974. LogFmt('RegSetValueEx failed (%d).', [ErrorCode]);
  975. Result := False;
  976. end;
  977. RegCloseKey(K);
  978. end;
  979. end;
  980. utRegDeleteKeyIfEmpty: begin
  981. CrackRegExtraData(CurRec^.ExtraData, RegView, RootKey);
  982. Subkey := CurRecDataPChar[0];
  983. LogFmt('Deleting empty registry key: %s\%s', [GetRegRootKeyName(RootKey), Subkey]);
  984. ErrorCode := RegDeleteKeyIfEmpty(RegView, RootKey, Subkey);
  985. if ErrorCode = ERROR_DIR_NOT_EMPTY then
  986. Log('Deletion skipped (not empty).')
  987. else if not (ErrorCode in [ERROR_SUCCESS, ERROR_FILE_NOT_FOUND]) then begin
  988. LogFmt('Deletion failed (%d).', [ErrorCode]);
  989. Result := False;
  990. end;
  991. end;
  992. utRegDeleteValue: begin
  993. CrackRegExtraData(CurRec^.ExtraData, RegView, RootKey);
  994. Subkey := CurRecDataPChar[0];
  995. ValueName := CurRecDataPChar[1];
  996. LogFmt('Deleting registry value: %s\%s\%s', [GetRegRootKeyName(RootKey), Subkey, ValueName]);
  997. if RegOpenKeyExView(RegView, RootKey, Subkey, 0, KEY_QUERY_VALUE or KEY_SET_VALUE, K) = ERROR_SUCCESS then begin
  998. if RegValueExists(K, ValueName) then begin
  999. ErrorCode := RegDeleteValue(K, ValueName);
  1000. if ErrorCode <> ERROR_SUCCESS then begin
  1001. LogFmt('RegDeleteValue failed (%d).', [ErrorCode]);
  1002. Result := False;
  1003. end;
  1004. end;
  1005. RegCloseKey(K);
  1006. end;
  1007. end;
  1008. utDecrementSharedCount: begin
  1009. LoggedDecrementSharedCount(CurRecData[0],
  1010. CurRec^.ExtraData and utDecrementSharedCount_64BitKey <> 0);
  1011. end;
  1012. utRefreshFileAssoc:
  1013. RefreshFileAssoc := True;
  1014. utMutexCheck: ; { do nothing; utMutexChecks aren't processed here }
  1015. else
  1016. raise Exception.Create(FmtSetupMessage1(msgUninstallUnknownEntry,
  1017. Format('$%x', [CurRec^.Typ])));
  1018. end;
  1019. except
  1020. Result := False;
  1021. if not(ExceptObject is EAbort) then
  1022. HandleException;
  1023. end;
  1024. CurRec := Delete(CurRec);
  1025. StatusUpdate(StartCount, FCount);
  1026. end;
  1027. if RefreshFileAssoc then
  1028. SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil);
  1029. if ufChangesEnvironment in Flags then
  1030. RefreshEnvironment;
  1031. if Assigned(DeleteUninstallDataFilesProc) then begin
  1032. DeleteUninstallDataFilesProc;
  1033. { Now that uninstall data is deleted, try removing the directories it
  1034. was in that couldn't be deleted before. }
  1035. LoggedProcessDirsNotRemoved;
  1036. end;
  1037. finally
  1038. DeleteDirData.DirsNotRemoved.Free;
  1039. RestartDeleteDirList[True].Free;
  1040. RestartDeleteDirList[False].Free;
  1041. for P := 0 to ChangeNotifyList.Count-1 do
  1042. if DirExists(ChangeNotifyList[P]) then
  1043. SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH or SHCNF_FLUSH,
  1044. PChar(ChangeNotifyList[P]), nil);
  1045. UnregisteredServersList[True].Free;
  1046. UnregisteredServersList[False].Free;
  1047. RunOnceList.Free;
  1048. ChangeNotifyList.Free;
  1049. end;
  1050. Log('Uninstallation process succeeded.');
  1051. end;
  1052. function TUninstallLog.ShouldRemoveSharedFile(const Filename: String): Boolean;
  1053. begin
  1054. Result := True;
  1055. end;
  1056. procedure TUninstallLog.StatusUpdate(StartingCount, CurCount: Integer);
  1057. begin
  1058. end;
  1059. class function TUninstallLog.WriteSafeHeaderString(Dest: PAnsiChar;
  1060. const Source: String; MaxDestBytes: Cardinal): Cardinal;
  1061. { Copies a string into a PAnsiChar including null terminator, either directly
  1062. if Source only contains ASCII characters, or else UTF-8-encoded with a special
  1063. #1 marker. If MaxDestBytes = 0 it returns the amount of bytes needed. }
  1064. var
  1065. N: Integer;
  1066. I: Integer;
  1067. begin
  1068. N := Length(Source);
  1069. { Only UTF-8-encode when non-ASCII characters are present }
  1070. for I := 1 to N do begin
  1071. if Ord(Source[I]) > 126 then begin
  1072. if MaxDestBytes <> 0 then begin
  1073. Dest^ := #1;
  1074. Inc(Dest);
  1075. Dec(MaxDestBytes);
  1076. end;
  1077. Result := SizeOf(Dest^) + UnicodeToUtf8(Dest, MaxDestBytes, PWideChar(Source), N + 1);
  1078. Exit;
  1079. end;
  1080. end;
  1081. if MaxDestBytes <> 0 then
  1082. AnsiStrings.StrPLCopy(Dest, AnsiString(Source), MaxDestBytes - 1);
  1083. Result := (N + 1) * SizeOf(Dest^);
  1084. end;
  1085. class function TUninstallLog.ReadSafeHeaderString(const Source: AnsiString): String;
  1086. begin
  1087. if (Source <> '') and (Source[1] = #1) then
  1088. Result := UTF8ToString(Copy(Source, 2, Maxint))
  1089. else
  1090. Result := String(Source);
  1091. end;
  1092. procedure TUninstallLog.Save(const Filename: String;
  1093. const Append, UpdateUninstallLogAppName: Boolean);
  1094. { Saves all undo data to Filename. If Append is True, it appends the current
  1095. undo data to the end of the existing file. When Append is True, it assumes
  1096. compatibility has already been verified with the Test method. }
  1097. var
  1098. F: TFile;
  1099. Buffer: array[0..4095] of Byte;
  1100. BufCount: Cardinal;
  1101. procedure Flush;
  1102. var
  1103. CrcHeader: TUninstallCrcHeader;
  1104. begin
  1105. if BufCount <> 0 then begin
  1106. CrcHeader.Size := BufCount;
  1107. CrcHeader.NotSize := not CrcHeader.Size;
  1108. CrcHeader.CRC := GetCRC32(Buffer, BufCount);
  1109. F.WriteBuffer(CrcHeader, SizeOf(CrcHeader));
  1110. F.WriteBuffer(Buffer, BufCount);
  1111. BufCount := 0;
  1112. end;
  1113. end;
  1114. procedure WriteBuf(const Buf; Size: Cardinal);
  1115. var
  1116. P: Pointer;
  1117. S: Cardinal;
  1118. begin
  1119. P := @Buf;
  1120. while Size <> 0 do begin
  1121. S := Size;
  1122. if S > SizeOf(Buffer) - BufCount then
  1123. S := SizeOf(Buffer) - BufCount;
  1124. Move(P^, Buffer[BufCount], S);
  1125. Inc(BufCount, S);
  1126. if BufCount = SizeOf(Buffer) then
  1127. Flush;
  1128. Inc(Cardinal(P), S);
  1129. Dec(Size, S);
  1130. end;
  1131. end;
  1132. var
  1133. Header: TUninstallLogHeader;
  1134. FileRec: TUninstallFileRec;
  1135. CurRec: PUninstallRec;
  1136. begin
  1137. BufCount := 0;
  1138. if not Append then
  1139. F := TFile.Create(Filename, fdCreateAlways, faReadWrite, fsNone)
  1140. else
  1141. F := TFile.Create(Filename, fdOpenExisting, faReadWrite, fsNone);
  1142. try
  1143. if not Append then begin
  1144. FillChar(Header, SizeOf(Header), 0);
  1145. F.WriteBuffer(Header, SizeOf(Header));
  1146. { Note: It will go back and fill in the correct values later }
  1147. end
  1148. else begin
  1149. F.ReadBuffer(Header, SizeOf(Header));
  1150. F.Seek(Header.EndOffset);
  1151. { If there's anything past EndOffset (only possible if some kind of
  1152. fatal error occurred while updating the file last time), clear it out }
  1153. F.Truncate;
  1154. end;
  1155. CurRec := List;
  1156. while CurRec <> nil do begin
  1157. FileRec.Typ := Ord(CurRec^.Typ);
  1158. FileRec.ExtraData := CurRec^.ExtraData;
  1159. FileRec.DataSize := CurRec^.DataSize;
  1160. WriteBuf(FileRec, SizeOf(FileRec));
  1161. WriteBuf(CurRec^.Data, CurRec^.DataSize);
  1162. if (Header.NumRecs < 0) or (Header.NumRecs >= High(Header.NumRecs)) then
  1163. InternalError('NumRecs range exceeded');
  1164. Inc(Header.NumRecs);
  1165. CurRec := CurRec^.Next;
  1166. end;
  1167. Flush;
  1168. const NewEndOffset = F.Position;
  1169. if NewEndOffset > High(UInt32) then
  1170. InternalError('EndOffset range exceeded');
  1171. Header.EndOffset := UInt32(NewEndOffset);
  1172. F.Seek(0);
  1173. Header.ID := UninstallLogID[InstallMode64Bit];
  1174. WriteSafeHeaderString(Header.AppId, AppId, SizeOf(Header.AppId));
  1175. if not Append or UpdateUninstallLogAppName then
  1176. WriteSafeHeaderString(Header.AppName, AppName, SizeOf(Header.AppName));
  1177. if Version > Header.Version then
  1178. Header.Version := Version;
  1179. TUninstallLogFlags((@Header.Flags)^) := TUninstallLogFlags((@Header.Flags)^) - [ufModernStyle] + Flags;
  1180. Header.CRC := GetCRC32(Header, SizeOf(Header)-SizeOf(Longint));
  1181. { Prior to rewriting the header with the new EndOffset value, ensure the
  1182. records we wrote earlier are flushed to disk. This should prevent the
  1183. file from ever becoming corrupted/unreadable in the event the system
  1184. crashes a split second from now. At worst, EndOffset will have the old
  1185. value and any extra bytes past EndOffset will be ignored/discarded when
  1186. the file is read at uninstall time, or appended to the next time Setup
  1187. is run. }
  1188. FlushFileBuffers(F.Handle);
  1189. F.WriteBuffer(Header, SizeOf(Header));
  1190. finally
  1191. F.Free;
  1192. end;
  1193. end;
  1194. procedure TUninstallLog.Load(const F: TFile; const Filename: String);
  1195. { Loads all undo data from the open file F. The Filename parameter is only
  1196. used when generating exception error messages.
  1197. Note: The position of the file pointer after calling this function is
  1198. undefined. }
  1199. var
  1200. Buffer: array[0..4095] of Byte;
  1201. BufPos, BufLeft: Cardinal;
  1202. Header: TUninstallLogHeader;
  1203. procedure Corrupt;
  1204. begin
  1205. raise Exception.Create(FmtSetupMessage1(msgUninstallDataCorrupted, Filename));
  1206. end;
  1207. procedure FillBuffer;
  1208. var
  1209. EndOffset, Ofs: Integer64;
  1210. CrcHeader: TUninstallCrcHeader;
  1211. begin
  1212. EndOffset := To64(Header.EndOffset);
  1213. while BufLeft = 0 do begin
  1214. Ofs := F.Position;
  1215. Inc64(Ofs, SizeOf(CrcHeader));
  1216. if Compare64(Ofs, EndOffset) > 0 then
  1217. Corrupt;
  1218. if F.Read(CrcHeader, SizeOf(CrcHeader)) <> SizeOf(CrcHeader) then
  1219. Corrupt;
  1220. Ofs := F.Position;
  1221. Inc64(Ofs, CrcHeader.Size);
  1222. if (CrcHeader.Size <> not CrcHeader.NotSize) or
  1223. (Cardinal(CrcHeader.Size) > Cardinal(SizeOf(Buffer))) or
  1224. (Compare64(Ofs, EndOffset) > 0) then
  1225. Corrupt;
  1226. if F.Read(Buffer, CrcHeader.Size) <> CrcHeader.Size then
  1227. Corrupt;
  1228. if not(ufDontCheckRecCRCs in Flags) and
  1229. (CrcHeader.CRC <> GetCRC32(Buffer, CrcHeader.Size)) then
  1230. Corrupt;
  1231. BufPos := 0;
  1232. BufLeft := CrcHeader.Size;
  1233. end;
  1234. end;
  1235. procedure ReadBuf(var Buf; Size: Cardinal);
  1236. var
  1237. P: Pointer;
  1238. S: Cardinal;
  1239. begin
  1240. P := @Buf;
  1241. while Size <> 0 do begin
  1242. if BufLeft = 0 then
  1243. FillBuffer;
  1244. S := Size;
  1245. if S > BufLeft then
  1246. S := BufLeft;
  1247. Move(Buffer[BufPos], P^, S);
  1248. Inc(BufPos, S);
  1249. Dec(BufLeft, S);
  1250. Inc(Cardinal(P), S);
  1251. Dec(Size, S);
  1252. end;
  1253. end;
  1254. var
  1255. FileRec: TUninstallFileRec;
  1256. I: Integer;
  1257. NewRec: PUninstallRec;
  1258. begin
  1259. BufPos := 0;
  1260. BufLeft := 0;
  1261. ReadUninstallLogHeader(F, Filename, Header, InstallMode64Bit);
  1262. if Header.Version > HighestSupportedVersion then
  1263. raise Exception.Create(FmtSetupMessage1(msgUninstallUnsupportedVer, Filename));
  1264. AppId := ReadSafeHeaderString(Header.AppId);
  1265. AppName := ReadSafeHeaderString(Header.AppName);
  1266. Flags := TUninstallLogFlags((@Header.Flags)^);
  1267. for I := 1 to Header.NumRecs do begin
  1268. ReadBuf(FileRec, SizeOf(FileRec));
  1269. NewRec := AllocRec(FileRec.Typ, FileRec.ExtraData, FileRec.DataSize);
  1270. if NewRec = nil then
  1271. Corrupt; { DataSize was out of range }
  1272. try
  1273. ReadBuf(NewRec.Data, NewRec.DataSize);
  1274. except
  1275. FreeMem(NewRec);
  1276. raise;
  1277. end;
  1278. InternalAdd(NewRec);
  1279. end;
  1280. end;
  1281. function TUninstallLog.CanAppend(const Filename: String;
  1282. var ExistingFlags: TUninstallLogFlags): Boolean;
  1283. { Returns True if Filename is a recognized uninstall log format, and its header
  1284. matches our AppId and InstallMode64Bit settings. When True is returned,
  1285. the existing log's flags are assigned to ExistingFlags. }
  1286. var
  1287. F: TFile;
  1288. Header: TUninstallLogHeader;
  1289. begin
  1290. Result := False;
  1291. try
  1292. F := TFile.Create(Filename, fdOpenExisting, faRead, fsRead);
  1293. try
  1294. if F.Read(Header, SizeOf(Header)) <> SizeOf(Header) then
  1295. Exit;
  1296. if ((Header.CRC <> $11111111) and
  1297. { ^ for debugging purposes, you can change the CRC field in the file to
  1298. $11111111 to disable CRC checking on the header}
  1299. (Header.CRC <> GetCRC32(Header, SizeOf(Header)-SizeOf(Longint)))) or
  1300. (Header.ID <> UninstallLogID[InstallMode64Bit]) or
  1301. (ReadSafeHeaderString(Header.AppId) <> AppId) then
  1302. Exit;
  1303. ExistingFlags := TUninstallLogFlags((@Header.Flags)^);
  1304. Result := True;
  1305. finally
  1306. F.Free;
  1307. end;
  1308. except
  1309. end;
  1310. end;
  1311. end.