Setup.UninstallLog.pas 53 KB

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