Setup.UninstallLog.pas 53 KB

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