Setup.UninstallLog.pas 51 KB

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