Undo.pas 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389
  1. unit Undo;
  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. Uninstallation Procedures
  8. }
  9. { Note: This unit is shared by both the 'Setup' and 'Uninst' projects }
  10. interface
  11. {$I VERSION.INC}
  12. uses
  13. Windows, SysUtils, Int64Em, FileClass, CmnFunc2;
  14. const
  15. HighestSupportedVersion = 1048;
  16. { Each time the format of the uninstall log changes (usually a new entry type
  17. is added), HighestSupportedVersion and the file version number of Setup
  18. are incremented to match (51.x). Do NOT do this yourself; doing so could cause
  19. incompatibilities with future Inno Setup releases. It's recommended that you
  20. use the "utUserDefined" log entry type if you wish to implement your own
  21. custom uninstall log entries; see below for more information. }
  22. type
  23. TUninstallRecTyp = type Word;
  24. const
  25. { Values for TUninstallRecTyp.
  26. If you wish to define your own custom uninstall entry type, you should use
  27. "utUserDefined". (Do NOT define your own ut* constants; this could cause
  28. incompatibilities with future Inno Setup releases.) The first field in a
  29. utUserDefined record must be a string which specifies a unique name for
  30. the record type. Example:
  31. UninstLog.Add(utUserDefined, ['MyRecordType', ... ], 0);
  32. }
  33. utUserDefined = $01;
  34. utStartInstall = $10;
  35. utEndInstall = $11;
  36. utCompiledCode = $20;
  37. utRun = $80;
  38. utDeleteDirOrFiles = $81;
  39. utDeleteFile = $82;
  40. utDeleteGroupOrItem = $83;
  41. utIniDeleteEntry = $84;
  42. utIniDeleteSection = $85;
  43. utRegDeleteEntireKey = $86;
  44. utRegClearValue = $87;
  45. utRegDeleteKeyIfEmpty = $88;
  46. utRegDeleteValue = $89;
  47. utDecrementSharedCount = $8A;
  48. utRefreshFileAssoc = $8B;
  49. utMutexCheck = $8C;
  50. { Flags on ExtraData }
  51. utRun_NoWait = 1;
  52. utRun_WaitUntilIdle = 2;
  53. utRun_ShellExec = 4;
  54. utRun_RunMinimized = 8;
  55. utRun_RunMaximized = 16;
  56. utRun_SkipIfDoesntExist = 32;
  57. utRun_RunHidden = 64;
  58. utRun_ShellExecRespectWaitFlags = 128;
  59. utRun_DisableFsRedir = 256;
  60. utRun_DontLogParameters = 512;
  61. utDeleteFile_ExistedBeforeInstall = 1;
  62. utDeleteFile_Extra = 2;
  63. utDeleteFile_IsFont = 4;
  64. utDeleteFile_SharedFile = 8;
  65. utDeleteFile_RegisteredServer = 16;
  66. utDeleteFile_CallChangeNotify = 32;
  67. utDeleteFile_RegisteredTypeLib = 64;
  68. utDeleteFile_RestartDelete = 128;
  69. utDeleteFile_RemoveReadOnly = 256;
  70. utDeleteFile_NoSharedFilePrompt = 512;
  71. utDeleteFile_SharedFileIn64BitKey = 1024;
  72. utDeleteFile_DisableFsRedir = 2048; { also determines whether file was registered as 64-bit }
  73. utDeleteFile_GacInstalled = 4096;
  74. utDeleteFile_PerUserFont = 8192;
  75. utDeleteDirOrFiles_Extra = 1;
  76. utDeleteDirOrFiles_IsDir = 2;
  77. utDeleteDirOrFiles_DeleteFiles = 4;
  78. utDeleteDirOrFiles_DeleteSubdirsAlso = 8;
  79. utDeleteDirOrFiles_CallChangeNotify = 16;
  80. utDeleteDirOrFiles_DisableFsRedir = 32;
  81. utIniDeleteSection_OnlyIfEmpty = 1;
  82. utReg_KeyHandleMask = $80FFFFFF;
  83. utReg_64BitKey = $01000000;
  84. utDecrementSharedCount_64BitKey = 1;
  85. type
  86. PUninstallRec = ^TUninstallRec;
  87. TUninstallRec = record
  88. Prev, Next: PUninstallRec;
  89. ExtraData: Longint;
  90. DataSize: Cardinal;
  91. Typ: TUninstallRecTyp;
  92. Data: array[0..$6FFFFFFF] of Byte; { *must* be last field }
  93. end;
  94. TDeleteUninstallDataFilesProc = procedure;
  95. TUninstallLogFlags = set of (ufAdminInstalled, ufDontCheckRecCRCs,
  96. ufModernStyle, ufAlwaysRestart, ufChangesEnvironment, ufWin64,
  97. ufPowerUserInstalled, ufAdminInstallMode);
  98. TUninstallLog = class
  99. private
  100. FList, FLastList: PUninstallRec;
  101. FCount: Integer;
  102. class function AllocRec(const Typ: TUninstallRecTyp;
  103. const ExtraData: Longint; const DataSize: Integer): PUninstallRec;
  104. function Delete(const Rec: PUninstallRec): PUninstallRec;
  105. procedure InternalAdd(const NewRec: PUninstallRec);
  106. protected
  107. procedure HandleException; virtual; abstract;
  108. function ShouldRemoveSharedFile(const Filename: String): Boolean; virtual;
  109. procedure StatusUpdate(StartingCount, CurCount: Integer); virtual;
  110. public
  111. InstallMode64Bit: Boolean;
  112. AppId, AppName: String;
  113. NeedRestart: Boolean;
  114. Flags: TUninstallLogFlags;
  115. Version: Integer;
  116. constructor Create;
  117. destructor Destroy; override;
  118. procedure Add(const Typ: TUninstallRecTyp; const Data: array of String;
  119. const ExtraData: Longint);
  120. procedure AddReg(const Typ: TUninstallRecTyp; const RegView: TRegView;
  121. const RootKey: HKEY; const Data: array of String);
  122. function CanAppend(const Filename: String;
  123. var ExistingFlags: TUninstallLogFlags): Boolean;
  124. function CheckMutexes: Boolean;
  125. procedure Clear;
  126. class function ExtractRecData(const Rec: PUninstallRec;
  127. var Data: array of String): Integer;
  128. function ExtractLatestRecData(const Typ: TUninstallRecTyp;
  129. const ExtraData: Longint; var Data: array of String): Boolean;
  130. procedure Load(const F: TFile; const Filename: String);
  131. function PerformUninstall(const CallFromUninstaller: Boolean;
  132. const DeleteUninstallDataFilesProc: TDeleteUninstallDataFilesProc): Boolean;
  133. class function WriteSafeHeaderString(Dest: PAnsiChar; const Source: String;
  134. MaxDestBytes: Cardinal): Cardinal;
  135. class function ReadSafeHeaderString(const Source: AnsiString): String;
  136. procedure Save(const Filename: String;
  137. const Append, UpdateUninstallLogAppName: Boolean);
  138. property List: PUninstallRec read FList;
  139. property LastList: PUninstallRec read FLastList;
  140. end;
  141. function ReadUninstallLogFlags(const F: TFile; const Filename: String): TUninstallLogFlags;
  142. implementation
  143. uses
  144. Messages, ShlObj, AnsiStrings,
  145. PathFunc, Struct, Msgs, MsgIDs, InstFunc, InstFnc2, RedirFunc, Compress,
  146. Logging, RegDLL, Helper, DotNet;
  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 <> $FFFFFFFF) 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. function TUninstallLog.PerformUninstall(const CallFromUninstaller: Boolean;
  506. const DeleteUninstallDataFilesProc: TDeleteUninstallDataFilesProc): Boolean;
  507. { Undoes all the changes in the uninstall list, in reverse order they were
  508. added. Deletes entries that were successfully undone.
  509. Returns True if all elements were successfully removed; False if some
  510. could not be removed. }
  511. var
  512. RefreshFileAssoc: Boolean;
  513. ChangeNotifyList, RunOnceList: TSimpleStringList;
  514. UnregisteredServersList, RestartDeleteDirList: array[Boolean] of TSimpleStringList;
  515. DeleteDirData: TDeleteDirData;
  516. function LoggedFileDelete(const Filename: String; const DisableFsRedir,
  517. NotifyChange, RestartDelete, RemoveReadOnly: Boolean): Boolean;
  518. var
  519. ExistingAttr, LastError: DWORD;
  520. begin
  521. Result := True;
  522. { Automatically delete generated indexes associated with help files }
  523. if CompareText(PathExtractExt(Filename), '.hlp') = 0 then begin
  524. LoggedFileDelete(PathChangeExt(Filename, '.gid'), DisableFsRedir, False, False, False);
  525. LoggedFileDelete(PathChangeExt(Filename, '.fts'), DisableFsRedir, False, False, False);
  526. end
  527. else if CompareText(PathExtractExt(Filename), '.chm') = 0 then
  528. LoggedFileDelete(PathChangeExt(Filename, '.chw'), DisableFsRedir, False, False, False);
  529. { Automatically unpin shortcuts }
  530. if CompareText(PathExtractExt(Filename), '.lnk') = 0 then
  531. UnpinShellLink(Filename);
  532. if NewFileExistsRedir(DisableFsRedir, Filename) then begin
  533. LogFmt('Deleting file: %s', [FileName]);
  534. if RemoveReadOnly then begin
  535. ExistingAttr := GetFileAttributesRedir(DisableFsRedir, Filename);
  536. if (ExistingAttr <> $FFFFFFFF) and
  537. (ExistingAttr and FILE_ATTRIBUTE_READONLY <> 0) then
  538. if SetFileAttributesRedir(DisableFsRedir, Filename,
  539. ExistingAttr and not FILE_ATTRIBUTE_READONLY) then
  540. Log('Stripped read-only attribute.')
  541. else
  542. Log('Failed to strip read-only attribute.');
  543. end;
  544. if not DeleteFileRedir(DisableFsRedir, Filename) then begin
  545. LastError := GetLastError;
  546. if RestartDelete and CallFromUninstaller and
  547. ((LastError = ERROR_ACCESS_DENIED) or (LastError = ERROR_SHARING_VIOLATION)) and
  548. (GetFileAttributesRedir(DisableFsRedir, Filename) and FILE_ATTRIBUTE_READONLY = 0) then begin
  549. LogFmt('The file appears to be in use (%d). Will delete on restart.',
  550. [LastError]);
  551. try
  552. RestartReplace(DisableFsRedir, Filename, '');
  553. NeedRestart := True;
  554. { Add the file's directory to the list of directories that should
  555. be restart-deleted later }
  556. RestartDeleteDirList[DisableFsRedir].AddIfDoesntExist(PathExtractDir(PathExpand(Filename)));
  557. except
  558. Log('Exception message:' + SNewLine + GetExceptMessage);
  559. Result := False;
  560. end;
  561. end
  562. else begin
  563. LogFmt('Failed to delete the file; it may be in use (%d).', [LastError]);
  564. Result := False;
  565. end;
  566. end
  567. else begin
  568. { Note: It is assumed that DisableFsRedir will be False when NotifyChange is True }
  569. if NotifyChange then begin
  570. SHChangeNotify(SHCNE_DELETE, SHCNF_PATH, PChar(Filename), nil);
  571. ChangeNotifyList.AddIfDoesntExist(PathExtractDir(Filename));
  572. end;
  573. end;
  574. end;
  575. end;
  576. function LoggedDecrementSharedCount(const Filename: String;
  577. const Key64Bit: Boolean): Boolean;
  578. const
  579. Bits: array[Boolean] of Integer = (32, 64);
  580. var
  581. RegView: TRegView;
  582. begin
  583. if Key64Bit then
  584. RegView := rv64Bit
  585. else
  586. RegView := rv32Bit;
  587. LogFmt('Decrementing shared count (%d-bit): %s', [Bits[Key64Bit], Filename]);
  588. Result := DecrementSharedCount(RegView, Filename);
  589. if Result then
  590. Log('Shared count reached zero.');
  591. end;
  592. procedure LoggedUnregisterServer(const Is64Bit: Boolean; const Filename: String);
  593. begin
  594. { Just as an optimization, make sure we aren't unregistering
  595. the same file again }
  596. if UnregisteredServersList[Is64Bit].IndexOf(Filename) = -1 then begin
  597. if Is64Bit then
  598. LogFmt('Unregistering 64-bit DLL/OCX: %s', [Filename])
  599. else
  600. LogFmt('Unregistering 32-bit DLL/OCX: %s', [Filename]);
  601. try
  602. RegisterServer(True, Is64Bit, Filename, True);
  603. UnregisteredServersList[Is64Bit].Add(Filename);
  604. Log('Unregistration successful.');
  605. except
  606. Log('Unregistration failed:' + SNewLine + GetExceptMessage);
  607. end;
  608. end
  609. else
  610. LogFmt('Not unregistering DLL/OCX again: %s', [Filename]);
  611. end;
  612. procedure LoggedUnregisterTypeLibrary(const Is64Bit: Boolean;
  613. const Filename: String);
  614. begin
  615. if Is64Bit then
  616. LogFmt('Unregistering 64-bit type library: %s', [Filename])
  617. else
  618. LogFmt('Unregistering 32-bit type library: %s', [Filename]);
  619. try
  620. if Is64Bit then
  621. HelperRegisterTypeLibrary(True, Filename)
  622. else
  623. UnregisterTypeLibrary(Filename);
  624. Log('Unregistration successful.');
  625. except
  626. Log('Unregistration failed:' + SNewLine + GetExceptMessage);
  627. end;
  628. end;
  629. procedure LoggedUninstallAssembly(const StrongAssemblyName: String);
  630. begin
  631. LogFmt('Uninstalling from GAC: %s', [StrongAssemblyName]);
  632. try
  633. with TAssemblyCacheInfo.Create(rvDefault) do try
  634. UninstallAssembly(StrongAssemblyName);
  635. finally
  636. Free;
  637. end;
  638. except
  639. Log('Uninstallation failed:' + SNewLine + GetExceptMessage);
  640. end;
  641. end;
  642. procedure LoggedProcessDirsNotRemoved;
  643. var
  644. I: Integer;
  645. S: String;
  646. DisableFsRedir: Boolean;
  647. begin
  648. for I := 0 to DeleteDirData.DirsNotRemoved.Count-1 do begin
  649. S := DeleteDirData.DirsNotRemoved[I];
  650. { The first character specifies the DisableFsRedir value
  651. (e.g. '0C:\Program Files\My Program') }
  652. DisableFsRedir := (S[1] = drTrue);
  653. System.Delete(S, 1, 1);
  654. LoggedDeleteDir(DisableFsRedir, S, nil, RestartDeleteDirList[DisableFsRedir]);
  655. end;
  656. end;
  657. function GetLogIniFilename(const Filename: String): String;
  658. begin
  659. if Filename <> '' then
  660. Result := Filename
  661. else
  662. Result := 'win.ini';
  663. end;
  664. const
  665. GroupInfoChars: array[0..3] of Char = ('"', '"', ',', ',');
  666. NullChar: Char = #0;
  667. var
  668. StartCount: Integer;
  669. CurRec: PUninstallRec;
  670. CurRecDataPChar: array[0..9] of PChar;
  671. CurRecData: array[0..9] of String;
  672. ShouldDeleteRec, IsTempFile, IsSharedFile, SharedCountDidReachZero: Boolean;
  673. Filename, Section, Key: String;
  674. Subkey, ValueName: PChar;
  675. P, ErrorCode: Integer;
  676. RegView: TRegView;
  677. RootKey, K: HKEY;
  678. Wait: TExecWait;
  679. ShowCmd: Integer;
  680. procedure SplitData(const Rec: PUninstallRec);
  681. var
  682. C, I: Integer;
  683. begin
  684. C := ExtractRecData(Rec, CurRecData);
  685. for I := 0 to 9 do begin
  686. if I < C then
  687. CurRecDataPChar[I] := PChar(CurRecData[I])
  688. else
  689. CurRecDataPChar[I] := nil;
  690. end;
  691. end;
  692. begin
  693. Log('Starting the uninstallation process.');
  694. SetCurrentDir(GetSystemDir);
  695. Result := True;
  696. NeedRestart := False;
  697. RefreshFileAssoc := False;
  698. RunOnceList := nil;
  699. UnregisteredServersList[False] := nil;
  700. UnregisteredServersList[True] := nil;
  701. RestartDeleteDirList[False] := nil;
  702. RestartDeleteDirList[True] := nil;
  703. DeleteDirData.DirsNotRemoved := nil;
  704. ChangeNotifyList := TSimpleStringList.Create;
  705. try
  706. RunOnceList := TSimpleStringList.Create;
  707. UnregisteredServersList[False] := TSimpleStringList.Create;
  708. UnregisteredServersList[True] := TSimpleStringList.Create;
  709. RestartDeleteDirList[False] := TSimpleStringList.Create;
  710. RestartDeleteDirList[True] := TSimpleStringList.Create;
  711. if Assigned(DeleteUninstallDataFilesProc) then
  712. DeleteDirData.DirsNotRemoved := TSimpleStringList.Create;
  713. StartCount := FCount;
  714. StatusUpdate(StartCount, FCount);
  715. { Step 1 - Process all utRun entries }
  716. if CallFromUninstaller then begin
  717. CurRec := LastList;
  718. while CurRec <> nil do begin
  719. if CurRec^.Typ = utRun then begin
  720. try
  721. SplitData(CurRec);
  722. { Verify that a utRun entry with the same RunOnceId has not
  723. already been executed }
  724. if (CurRecData[3] = '') or (RunOnceList.IndexOf(CurRecData[3]) = -1) then begin
  725. Wait := ewWaitUntilTerminated;
  726. if CurRec^.ExtraData and utRun_NoWait <> 0 then
  727. Wait := ewNoWait
  728. else if CurRec^.ExtraData and utRun_WaitUntilIdle <> 0 then
  729. Wait := ewWaitUntilIdle;
  730. ShowCmd := SW_SHOWNORMAL;
  731. if CurRec^.ExtraData and utRun_RunMinimized <> 0 then
  732. ShowCmd := SW_SHOWMINNOACTIVE
  733. else if CurRec^.ExtraData and utRun_RunMaximized <> 0 then
  734. ShowCmd := SW_SHOWMAXIMIZED
  735. else if CurRec^.ExtraData and utRun_RunHidden <> 0 then
  736. ShowCmd := SW_HIDE;
  737. { Note: This code is similar to code in the ProcessRunEntry
  738. function of Main.pas }
  739. if CurRec^.ExtraData and utRun_ShellExec = 0 then begin
  740. Log('Running Exec filename: ' + CurRecData[0]);
  741. if (CurRec^.ExtraData and utRun_DontLogParameters = 0) and (CurRecData[1] <> '') then
  742. Log('Running Exec parameters: ' + CurRecData[1]);
  743. if (CurRec^.ExtraData and utRun_SkipIfDoesntExist = 0) or
  744. NewFileExistsRedir(CurRec^.ExtraData and utRun_DisableFsRedir <> 0, CurRecData[0]) then begin
  745. if not InstExec(CurRec^.ExtraData and utRun_DisableFsRedir <> 0,
  746. CurRecData[0], CurRecData[1], CurRecData[2], Wait,
  747. ShowCmd, ProcessMessagesProc, ErrorCode) then begin
  748. LogFmt('CreateProcess failed (%d).', [ErrorCode]);
  749. Result := False;
  750. end
  751. else begin
  752. if Wait = ewWaitUntilTerminated then
  753. LogFmt('Process exit code: %u', [ErrorCode]);
  754. end;
  755. end else
  756. Log('File doesn''t exist. Skipping.');
  757. end
  758. else begin
  759. Log('Running ShellExec filename: ' + CurRecData[0]);
  760. if (CurRec^.ExtraData and utRun_DontLogParameters = 0) and (CurRecData[1] <> '') then
  761. Log('Running ShellExec parameters: ' + CurRecData[1]);
  762. if (CurRec^.ExtraData and utRun_SkipIfDoesntExist = 0) or
  763. FileOrDirExists(CurRecData[0]) then begin
  764. if CurRec^.ExtraData and utRun_ShellExecRespectWaitFlags = 0 then
  765. Wait := ewNoWait;
  766. if not InstShellExec(CurRecData[4], CurRecData[0], CurRecData[1], CurRecData[2],
  767. Wait, ShowCmd, ProcessMessagesProc, ErrorCode) then begin
  768. LogFmt('ShellExecuteEx failed (%d).', [ErrorCode]);
  769. Result := False;
  770. end
  771. else begin
  772. if Wait = ewWaitUntilTerminated then
  773. LogFmt('Process exit code: %u', [ErrorCode]);
  774. end;
  775. end else
  776. Log('File/directory doesn''t exist. Skipping.');
  777. end;
  778. if CurRecData[3] <> '' then
  779. RunOnceList.Add(CurRecData[3]);
  780. end else
  781. LogFmt('Skipping RunOnceId "%s" filename: %s', [CurRecData[3], CurRecData[0]]);
  782. except
  783. Result := False;
  784. if not(ExceptObject is EAbort) then
  785. HandleException;
  786. end;
  787. CurRec := Delete(CurRec);
  788. StatusUpdate(StartCount, FCount);
  789. end
  790. else
  791. CurRec := CurRec^.Prev;
  792. end;
  793. end;
  794. { Step 2 - Decrement shared file counts, unregister DLLs/TLBs/fonts, and uninstall from GAC }
  795. CurRec := LastList;
  796. while CurRec <> nil do begin
  797. ShouldDeleteRec := False;
  798. if CurRec^.Typ = utDeleteFile then begin
  799. { Default to deleting the record in case an exception is raised by
  800. DecrementSharedCount, the reference count doesn't reach zero, or the
  801. user opts not to delete the shared file. }
  802. ShouldDeleteRec := True;
  803. try
  804. SplitData(CurRec);
  805. { Note: Some of this code is duplicated in Step 3 }
  806. if CallFromUninstaller or (CurRec^.ExtraData and utDeleteFile_ExistedBeforeInstall = 0) then begin
  807. IsTempFile := not CallFromUninstaller and (CurRecData[1] <> '');
  808. { Decrement shared file count if necessary }
  809. IsSharedFile := CurRec^.ExtraData and utDeleteFile_SharedFile <> 0;
  810. if IsSharedFile then
  811. SharedCountDidReachZero := LoggedDecrementSharedCount(CurRecData[0],
  812. CurRec^.ExtraData and utDeleteFile_SharedFileIn64BitKey <> 0)
  813. else
  814. SharedCountDidReachZero := False; //silence compiler
  815. if not IsSharedFile or
  816. (SharedCountDidReachZero and
  817. (IsTempFile or
  818. not NewFileExistsRedir(CurRec^.ExtraData and utDeleteFile_DisableFsRedir <> 0, CurRecData[0]) or
  819. (CurRec^.ExtraData and utDeleteFile_NoSharedFilePrompt <> 0) or
  820. ShouldRemoveSharedFile(CurRecData[0]))) then begin
  821. { The reference count reached zero and the user did not object
  822. to the file being deleted, so don't delete the record; allow
  823. the file to be deleted in the next step. }
  824. ShouldDeleteRec := False;
  825. { Unregister if necessary }
  826. if not IsTempFile then begin
  827. if CurRec^.ExtraData and utDeleteFile_RegisteredServer <> 0 then begin
  828. LoggedUnregisterServer(CurRec^.ExtraData and utDeleteFile_DisableFsRedir <> 0,
  829. CurRecData[0]);
  830. end;
  831. if CurRec^.ExtraData and utDeleteFile_RegisteredTypeLib <> 0 then begin
  832. LoggedUnregisterTypeLibrary(CurRec^.ExtraData and utDeleteFile_DisableFsRedir <> 0,
  833. CurRecData[0]);
  834. end;
  835. end;
  836. if CurRec^.ExtraData and utDeleteFile_IsFont <> 0 then begin
  837. LogFmt('Unregistering font: %s', [CurRecData[2]]);
  838. UnregisterFont(CurRecData[2], CurRecData[3], CurRec^.ExtraData and utDeleteFile_PerUserFont <> 0);
  839. end;
  840. if CurRec^.ExtraData and utDeleteFile_GacInstalled <> 0 then
  841. LoggedUninstallAssembly(CurRecData[4]);
  842. end;
  843. end
  844. else begin
  845. { This case is handled entirely in Step 3 }
  846. ShouldDeleteRec := False;
  847. end;
  848. except
  849. Result := False;
  850. if not(ExceptObject is EAbort) then
  851. HandleException;
  852. end;
  853. end;
  854. if ShouldDeleteRec then begin
  855. CurRec := Delete(CurRec);
  856. StatusUpdate(StartCount, FCount);
  857. end
  858. else
  859. CurRec := CurRec^.Prev;
  860. end;
  861. { Step 3 - Remaining entries }
  862. CurRec := LastList;
  863. while CurRec <> nil do begin
  864. SplitData(CurRec);
  865. try
  866. case CurRec^.Typ of
  867. utUserDefined: begin
  868. {if CurRecData[0] = 'MyRecordType' then begin
  869. ... your code here ...
  870. end
  871. else}
  872. raise Exception.Create(FmtSetupMessage1(msgUninstallUnknownEntry,
  873. 'utUserDefined:' + CurRecData[0]));
  874. end;
  875. utStartInstall,
  876. utEndInstall,
  877. utCompiledCode: { do nothing on these };
  878. utRun: begin
  879. { Will get here if CallFromUninstaller=False; in that case utRun
  880. entries will still be in the list, unprocessed. Just ignore
  881. them. }
  882. end;
  883. utDeleteDirOrFiles:
  884. if (CallFromUninstaller or (CurRec^.ExtraData and utDeleteDirOrFiles_Extra = 0)) then begin
  885. if DelTree(CurRec^.ExtraData and utDeleteDirOrFiles_DisableFsRedir <> 0,
  886. CurRecData[0], CurRec^.ExtraData and utDeleteDirOrFiles_IsDir <> 0,
  887. CurRec^.ExtraData and utDeleteDirOrFiles_DeleteFiles <> 0,
  888. CurRec^.ExtraData and utDeleteDirOrFiles_DeleteSubdirsAlso <> 0,
  889. False, LoggedDeleteDirProc, LoggedDeleteFileProc, @DeleteDirData) then begin
  890. if (CurRec^.ExtraData and utDeleteDirOrFiles_IsDir <> 0) and
  891. (CurRec^.ExtraData and utDeleteDirOrFiles_CallChangeNotify <> 0) then begin
  892. SHChangeNotify(SHCNE_RMDIR, SHCNF_PATH, CurRecDataPChar[0], nil);
  893. ChangeNotifyList.AddIfDoesntExist(PathExtractDir(CurRecData[0]));
  894. end;
  895. end;
  896. end;
  897. utDeleteFile: begin
  898. { Note: Some of this code is duplicated in Step 2 }
  899. Filename := CurRecData[1];
  900. if CallFromUninstaller or (Filename = '') then
  901. Filename := CurRecData[0];
  902. if CallFromUninstaller or (CurRec^.ExtraData and utDeleteFile_ExistedBeforeInstall = 0) then begin
  903. { Note: We handled utDeleteFile_SharedFile already }
  904. if CallFromUninstaller or (CurRec^.ExtraData and utDeleteFile_Extra = 0) then
  905. if not LoggedFileDelete(Filename, CurRec^.ExtraData and utDeleteFile_DisableFsRedir <> 0,
  906. CurRec^.ExtraData and utDeleteFile_CallChangeNotify <> 0,
  907. CurRec^.ExtraData and utDeleteFile_RestartDelete <> 0,
  908. CurRec^.ExtraData and utDeleteFile_RemoveReadOnly <> 0) then
  909. Result := False;
  910. end
  911. else begin
  912. { We're running from Setup, and the file existed before
  913. installation... }
  914. if CurRec^.ExtraData and utDeleteFile_SharedFile <> 0 then
  915. LoggedDecrementSharedCount(CurRecData[0],
  916. CurRec^.ExtraData and utDeleteFile_SharedFileIn64BitKey <> 0);
  917. { Delete file only if it's a temp file }
  918. if Filename <> CurRecData[0] then
  919. if not LoggedFileDelete(Filename, CurRec^.ExtraData and utDeleteFile_DisableFsRedir <> 0,
  920. CurRec^.ExtraData and utDeleteFile_CallChangeNotify <> 0,
  921. CurRec^.ExtraData and utDeleteFile_RestartDelete <> 0,
  922. CurRec^.ExtraData and utDeleteFile_RemoveReadOnly <> 0) then
  923. Result := False;
  924. end;
  925. end;
  926. utDeleteGroupOrItem: ; { dummy - no longer supported }
  927. utIniDeleteEntry: begin
  928. Section := CurRecData[1];
  929. Key := CurRecData[2];
  930. Filename := CurRecData[0];
  931. LogFmt('Deleting INI entry: %s in section %s in %s', [Key, Section, GetLogIniFilename(Filename)]);
  932. DeleteIniEntry(Section, Key, Filename);
  933. end;
  934. utIniDeleteSection: begin
  935. Section := CurRecData[1];
  936. Filename := CurRecData[0];
  937. if (CurRec^.ExtraData and utIniDeleteSection_OnlyIfEmpty = 0) or
  938. IsIniSectionEmpty(Section, Filename) then begin
  939. LogFmt('Deleting INI section: %s in %s', [Section, GetLogIniFilename(Filename)]);
  940. DeleteIniSection(Section, Filename);
  941. end;
  942. end;
  943. utRegDeleteEntireKey: begin
  944. CrackRegExtraData(CurRec^.ExtraData, RegView, RootKey);
  945. Subkey := CurRecDataPChar[0];
  946. LogFmt('Deleting registry key: %s\%s', [GetRegRootKeyName(RootKey), Subkey]);
  947. ErrorCode := RegDeleteKeyIncludingSubkeys(RegView, RootKey, Subkey);
  948. if not (ErrorCode in [ERROR_SUCCESS, ERROR_FILE_NOT_FOUND]) then begin
  949. LogFmt('Deletion failed (%d).', [ErrorCode]);
  950. Result := False;
  951. end;
  952. end;
  953. utRegClearValue: begin
  954. CrackRegExtraData(CurRec^.ExtraData, RegView, RootKey);
  955. Subkey := CurRecDataPChar[0];
  956. ValueName := CurRecDataPChar[1];
  957. LogFmt('Clearing registry value: %s\%s\%s', [GetRegRootKeyName(RootKey), Subkey, ValueName]);
  958. if RegOpenKeyExView(RegView, RootKey, Subkey, 0, KEY_SET_VALUE, K) = ERROR_SUCCESS then begin
  959. ErrorCode := RegSetValueEx(K, ValueName, 0, REG_SZ, @NullChar, SizeOf(NullChar));
  960. if ErrorCode <> ERROR_SUCCESS then begin
  961. LogFmt('RegSetValueEx failed (%d).', [ErrorCode]);
  962. Result := False;
  963. end;
  964. RegCloseKey(K);
  965. end;
  966. end;
  967. utRegDeleteKeyIfEmpty: begin
  968. CrackRegExtraData(CurRec^.ExtraData, RegView, RootKey);
  969. Subkey := CurRecDataPChar[0];
  970. LogFmt('Deleting empty registry key: %s\%s', [GetRegRootKeyName(RootKey), Subkey]);
  971. ErrorCode := RegDeleteKeyIfEmpty(RegView, RootKey, Subkey);
  972. if ErrorCode = ERROR_DIR_NOT_EMPTY then
  973. Log('Deletion skipped (not empty).')
  974. else if not (ErrorCode in [ERROR_SUCCESS, ERROR_FILE_NOT_FOUND]) then begin
  975. LogFmt('Deletion failed (%d).', [ErrorCode]);
  976. Result := False;
  977. end;
  978. end;
  979. utRegDeleteValue: begin
  980. CrackRegExtraData(CurRec^.ExtraData, RegView, RootKey);
  981. Subkey := CurRecDataPChar[0];
  982. ValueName := CurRecDataPChar[1];
  983. LogFmt('Deleting registry value: %s\%s\%s', [GetRegRootKeyName(RootKey), Subkey, ValueName]);
  984. if RegOpenKeyExView(RegView, RootKey, Subkey, 0, KEY_QUERY_VALUE or KEY_SET_VALUE, K) = ERROR_SUCCESS then begin
  985. if RegValueExists(K, ValueName) then begin
  986. ErrorCode := RegDeleteValue(K, ValueName);
  987. if ErrorCode <> ERROR_SUCCESS then begin
  988. LogFmt('RegDeleteValue failed (%d).', [ErrorCode]);
  989. Result := False;
  990. end;
  991. end;
  992. RegCloseKey(K);
  993. end;
  994. end;
  995. utDecrementSharedCount: begin
  996. LoggedDecrementSharedCount(CurRecData[0],
  997. CurRec^.ExtraData and utDecrementSharedCount_64BitKey <> 0);
  998. end;
  999. utRefreshFileAssoc:
  1000. RefreshFileAssoc := True;
  1001. utMutexCheck: ; { do nothing; utMutexChecks aren't processed here }
  1002. else
  1003. raise Exception.Create(FmtSetupMessage1(msgUninstallUnknownEntry,
  1004. Format('$%x', [CurRec^.Typ])));
  1005. end;
  1006. except
  1007. Result := False;
  1008. if not(ExceptObject is EAbort) then
  1009. HandleException;
  1010. end;
  1011. CurRec := Delete(CurRec);
  1012. StatusUpdate(StartCount, FCount);
  1013. end;
  1014. if RefreshFileAssoc then
  1015. SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil);
  1016. if ufChangesEnvironment in Flags then
  1017. RefreshEnvironment;
  1018. if Assigned(DeleteUninstallDataFilesProc) then begin
  1019. DeleteUninstallDataFilesProc;
  1020. { Now that uninstall data is deleted, try removing the directories it
  1021. was in that couldn't be deleted before. }
  1022. LoggedProcessDirsNotRemoved;
  1023. end;
  1024. finally
  1025. DeleteDirData.DirsNotRemoved.Free;
  1026. RestartDeleteDirList[True].Free;
  1027. RestartDeleteDirList[False].Free;
  1028. for P := 0 to ChangeNotifyList.Count-1 do
  1029. if DirExists(ChangeNotifyList[P]) then
  1030. SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH or SHCNF_FLUSH,
  1031. PChar(ChangeNotifyList[P]), nil);
  1032. UnregisteredServersList[True].Free;
  1033. UnregisteredServersList[False].Free;
  1034. RunOnceList.Free;
  1035. ChangeNotifyList.Free;
  1036. end;
  1037. Log('Uninstallation process succeeded.');
  1038. end;
  1039. function TUninstallLog.ShouldRemoveSharedFile(const Filename: String): Boolean;
  1040. begin
  1041. Result := True;
  1042. end;
  1043. procedure TUninstallLog.StatusUpdate(StartingCount, CurCount: Integer);
  1044. begin
  1045. end;
  1046. class function TUninstallLog.WriteSafeHeaderString(Dest: PAnsiChar;
  1047. const Source: String; MaxDestBytes: Cardinal): Cardinal;
  1048. { Copies a string into a PAnsiChar including null terminator, either directly
  1049. if Source only contains ASCII characters, or else UTF-8-encoded with a special
  1050. #1 marker. If MaxDestBytes = 0 it returns the amount of bytes needed. }
  1051. var
  1052. N: Integer;
  1053. I: Integer;
  1054. begin
  1055. N := Length(Source);
  1056. { Only UTF-8-encode when non-ASCII characters are present }
  1057. for I := 1 to N do begin
  1058. if Ord(Source[I]) > 126 then begin
  1059. if MaxDestBytes <> 0 then begin
  1060. Dest^ := #1;
  1061. Inc(Dest);
  1062. Dec(MaxDestBytes);
  1063. end;
  1064. Result := SizeOf(Dest^) + UnicodeToUtf8(Dest, MaxDestBytes, PWideChar(Source), N + 1);
  1065. Exit;
  1066. end;
  1067. end;
  1068. if MaxDestBytes <> 0 then
  1069. AnsiStrings.StrPLCopy(Dest, AnsiString(Source), MaxDestBytes - 1);
  1070. Result := (N + 1) * SizeOf(Dest^);
  1071. end;
  1072. class function TUninstallLog.ReadSafeHeaderString(const Source: AnsiString): String;
  1073. begin
  1074. if (Source <> '') and (Source[1] = #1) then
  1075. Result := UTF8ToString(Copy(Source, 2, Maxint))
  1076. else
  1077. Result := String(Source);
  1078. end;
  1079. procedure TUninstallLog.Save(const Filename: String;
  1080. const Append, UpdateUninstallLogAppName: Boolean);
  1081. { Saves all undo data to Filename. If Append is True, it appends the current
  1082. undo data to the end of the existing file. When Append is True, it assumes
  1083. compatibility has already been verified with the Test method. }
  1084. var
  1085. F: TFile;
  1086. Buffer: array[0..4095] of Byte;
  1087. BufCount: Cardinal;
  1088. procedure Flush;
  1089. var
  1090. CrcHeader: TUninstallCrcHeader;
  1091. begin
  1092. if BufCount <> 0 then begin
  1093. CrcHeader.Size := BufCount;
  1094. CrcHeader.NotSize := not CrcHeader.Size;
  1095. CrcHeader.CRC := GetCRC32(Buffer, BufCount);
  1096. F.WriteBuffer(CrcHeader, SizeOf(CrcHeader));
  1097. F.WriteBuffer(Buffer, BufCount);
  1098. BufCount := 0;
  1099. end;
  1100. end;
  1101. procedure WriteBuf(const Buf; Size: Cardinal);
  1102. var
  1103. P: Pointer;
  1104. S: Cardinal;
  1105. begin
  1106. P := @Buf;
  1107. while Size <> 0 do begin
  1108. S := Size;
  1109. if S > SizeOf(Buffer) - BufCount then
  1110. S := SizeOf(Buffer) - BufCount;
  1111. Move(P^, Buffer[BufCount], S);
  1112. Inc(BufCount, S);
  1113. if BufCount = SizeOf(Buffer) then
  1114. Flush;
  1115. Inc(Cardinal(P), S);
  1116. Dec(Size, S);
  1117. end;
  1118. end;
  1119. var
  1120. Header: TUninstallLogHeader;
  1121. FileRec: TUninstallFileRec;
  1122. CurRec: PUninstallRec;
  1123. begin
  1124. BufCount := 0;
  1125. if not Append then
  1126. F := TFile.Create(Filename, fdCreateAlways, faReadWrite, fsNone)
  1127. else
  1128. F := TFile.Create(Filename, fdOpenExisting, faReadWrite, fsNone);
  1129. try
  1130. if not Append then begin
  1131. FillChar(Header, SizeOf(Header), 0);
  1132. F.WriteBuffer(Header, SizeOf(Header));
  1133. { Note: It will go back and fill in the correct values later }
  1134. end
  1135. else begin
  1136. F.ReadBuffer(Header, SizeOf(Header));
  1137. F.Seek(Header.EndOffset);
  1138. { If there's anything past EndOffset (only possible if some kind of
  1139. fatal error occurred while updating the file last time), clear it out }
  1140. F.Truncate;
  1141. end;
  1142. CurRec := List;
  1143. while CurRec <> nil do begin
  1144. FileRec.Typ := Ord(CurRec^.Typ);
  1145. FileRec.ExtraData := CurRec^.ExtraData;
  1146. FileRec.DataSize := CurRec^.DataSize;
  1147. WriteBuf(FileRec, SizeOf(FileRec));
  1148. WriteBuf(CurRec^.Data, CurRec^.DataSize);
  1149. if (Header.NumRecs < 0) or (Header.NumRecs >= High(Header.NumRecs)) then
  1150. InternalError('NumRecs range exceeded');
  1151. Inc(Header.NumRecs);
  1152. CurRec := CurRec^.Next;
  1153. end;
  1154. Flush;
  1155. if F.Position.Hi <> 0 then
  1156. InternalError('EndOffset range exceeded');
  1157. Header.EndOffset := F.Position.Lo;
  1158. F.Seek(0);
  1159. Header.ID := UninstallLogID[InstallMode64Bit];
  1160. WriteSafeHeaderString(Header.AppId, AppId, SizeOf(Header.AppId));
  1161. if not Append or UpdateUninstallLogAppName then
  1162. WriteSafeHeaderString(Header.AppName, AppName, SizeOf(Header.AppName));
  1163. if Version > Header.Version then
  1164. Header.Version := Version;
  1165. TUninstallLogFlags((@Header.Flags)^) := TUninstallLogFlags((@Header.Flags)^) - [ufModernStyle] + Flags;
  1166. Header.CRC := GetCRC32(Header, SizeOf(Header)-SizeOf(Longint));
  1167. { Prior to rewriting the header with the new EndOffset value, ensure the
  1168. records we wrote earlier are flushed to disk. This should prevent the
  1169. file from ever becoming corrupted/unreadable in the event the system
  1170. crashes a split second from now. At worst, EndOffset will have the old
  1171. value and any extra bytes past EndOffset will be ignored/discarded when
  1172. the file is read at uninstall time, or appended to the next time Setup
  1173. is run. }
  1174. FlushFileBuffers(F.Handle);
  1175. F.WriteBuffer(Header, SizeOf(Header));
  1176. finally
  1177. F.Free;
  1178. end;
  1179. end;
  1180. procedure TUninstallLog.Load(const F: TFile; const Filename: String);
  1181. { Loads all undo data from the open file F. The Filename parameter is only
  1182. used when generating exception error messages.
  1183. Note: The position of the file pointer after calling this function is
  1184. undefined. }
  1185. var
  1186. Buffer: array[0..4095] of Byte;
  1187. BufPos, BufLeft: Cardinal;
  1188. Header: TUninstallLogHeader;
  1189. procedure Corrupt;
  1190. begin
  1191. raise Exception.Create(FmtSetupMessage1(msgUninstallDataCorrupted, Filename));
  1192. end;
  1193. procedure FillBuffer;
  1194. var
  1195. EndOffset, Ofs: Integer64;
  1196. CrcHeader: TUninstallCrcHeader;
  1197. begin
  1198. EndOffset.Lo := Header.EndOffset;
  1199. EndOffset.Hi := 0;
  1200. while BufLeft = 0 do begin
  1201. Ofs := F.Position;
  1202. Inc64(Ofs, SizeOf(CrcHeader));
  1203. if Compare64(Ofs, EndOffset) > 0 then
  1204. Corrupt;
  1205. if F.Read(CrcHeader, SizeOf(CrcHeader)) <> SizeOf(CrcHeader) then
  1206. Corrupt;
  1207. Ofs := F.Position;
  1208. Inc64(Ofs, CrcHeader.Size);
  1209. if (CrcHeader.Size <> not CrcHeader.NotSize) or
  1210. (Cardinal(CrcHeader.Size) > Cardinal(SizeOf(Buffer))) or
  1211. (Compare64(Ofs, EndOffset) > 0) then
  1212. Corrupt;
  1213. if F.Read(Buffer, CrcHeader.Size) <> CrcHeader.Size then
  1214. Corrupt;
  1215. if not(ufDontCheckRecCRCs in Flags) and
  1216. (CrcHeader.CRC <> GetCRC32(Buffer, CrcHeader.Size)) then
  1217. Corrupt;
  1218. BufPos := 0;
  1219. BufLeft := CrcHeader.Size;
  1220. end;
  1221. end;
  1222. procedure ReadBuf(var Buf; Size: Cardinal);
  1223. var
  1224. P: Pointer;
  1225. S: Cardinal;
  1226. begin
  1227. P := @Buf;
  1228. while Size <> 0 do begin
  1229. if BufLeft = 0 then
  1230. FillBuffer;
  1231. S := Size;
  1232. if S > BufLeft then
  1233. S := BufLeft;
  1234. Move(Buffer[BufPos], P^, S);
  1235. Inc(BufPos, S);
  1236. Dec(BufLeft, S);
  1237. Inc(Cardinal(P), S);
  1238. Dec(Size, S);
  1239. end;
  1240. end;
  1241. var
  1242. FileRec: TUninstallFileRec;
  1243. I: Integer;
  1244. NewRec: PUninstallRec;
  1245. begin
  1246. BufPos := 0;
  1247. BufLeft := 0;
  1248. ReadUninstallLogHeader(F, Filename, Header, InstallMode64Bit);
  1249. if Header.Version > HighestSupportedVersion then
  1250. raise Exception.Create(FmtSetupMessage1(msgUninstallUnsupportedVer, Filename));
  1251. AppId := ReadSafeHeaderString(Header.AppId);
  1252. AppName := ReadSafeHeaderString(Header.AppName);
  1253. Flags := TUninstallLogFlags((@Header.Flags)^);
  1254. for I := 1 to Header.NumRecs do begin
  1255. ReadBuf(FileRec, SizeOf(FileRec));
  1256. NewRec := AllocRec(FileRec.Typ, FileRec.ExtraData, FileRec.DataSize);
  1257. if NewRec = nil then
  1258. Corrupt; { DataSize was out of range }
  1259. try
  1260. ReadBuf(NewRec.Data, NewRec.DataSize);
  1261. except
  1262. FreeMem(NewRec);
  1263. raise;
  1264. end;
  1265. InternalAdd(NewRec);
  1266. end;
  1267. end;
  1268. function TUninstallLog.CanAppend(const Filename: String;
  1269. var ExistingFlags: TUninstallLogFlags): Boolean;
  1270. { Returns True if Filename is a recognized uninstall log format, and its header
  1271. matches our AppId and InstallMode64Bit settings. When True is returned,
  1272. the existing log's flags are assigned to ExistingFlags. }
  1273. var
  1274. F: TFile;
  1275. Header: TUninstallLogHeader;
  1276. begin
  1277. Result := False;
  1278. try
  1279. F := TFile.Create(Filename, fdOpenExisting, faRead, fsRead);
  1280. try
  1281. if F.Read(Header, SizeOf(Header)) <> SizeOf(Header) then
  1282. Exit;
  1283. if ((Header.CRC <> $11111111) and
  1284. { ^ for debugging purposes, you can change the CRC field in the file to
  1285. $11111111 to disable CRC checking on the header}
  1286. (Header.CRC <> GetCRC32(Header, SizeOf(Header)-SizeOf(Longint)))) or
  1287. (Header.ID <> UninstallLogID[InstallMode64Bit]) or
  1288. (ReadSafeHeaderString(Header.AppId) <> AppId) then
  1289. Exit;
  1290. ExistingFlags := TUninstallLogFlags((@Header.Flags)^);
  1291. Result := True;
  1292. finally
  1293. F.Free;
  1294. end;
  1295. except
  1296. end;
  1297. end;
  1298. end.