123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401 |
- unit Setup.UninstallLog;
- {
- Inno Setup
- Copyright (C) 1997-2025 Jordan Russell
- Portions by Martijn Laan
- For conditions of distribution and use, see LICENSE.TXT.
- Uninstallation functions
- }
- interface
- uses
- Windows, SysUtils, Shared.Int64Em, Shared.FileClass, Shared.CommonFunc;
- const
- HighestSupportedVersion = 1048;
- { Each time the format of the uninstall log changes (usually a new entry type
- is added), HighestSupportedVersion and the file version number of Setup
- are incremented to match (51.x). Do NOT do this yourself; doing so could cause
- incompatibilities with future Inno Setup releases. It's recommended that you
- use the "utUserDefined" log entry type if you wish to implement your own
- custom uninstall log entries; see below for more information. }
- type
- TUninstallRecTyp = type Word;
- const
- { Values for TUninstallRecTyp.
- If you wish to define your own custom uninstall entry type, you should use
- "utUserDefined". (Do NOT define your own ut* constants; this could cause
- incompatibilities with future Inno Setup releases.) The first field in a
- utUserDefined record must be a string which specifies a unique name for
- the record type. Example:
- UninstLog.Add(utUserDefined, ['MyRecordType', ... ], 0);
- }
- utUserDefined = $01;
- utStartInstall = $10;
- utEndInstall = $11;
- utCompiledCode = $20;
- utRun = $80;
- utDeleteDirOrFiles = $81;
- utDeleteFile = $82;
- utDeleteGroupOrItem = $83;
- utIniDeleteEntry = $84;
- utIniDeleteSection = $85;
- utRegDeleteEntireKey = $86;
- utRegClearValue = $87;
- utRegDeleteKeyIfEmpty = $88;
- utRegDeleteValue = $89;
- utDecrementSharedCount = $8A;
- utRefreshFileAssoc = $8B;
- utMutexCheck = $8C;
- { Flags on ExtraData }
- utRun_NoWait = 1;
- utRun_WaitUntilIdle = 2;
- utRun_ShellExec = 4;
- utRun_RunMinimized = 8;
- utRun_RunMaximized = 16;
- utRun_SkipIfDoesntExist = 32;
- utRun_RunHidden = 64;
- utRun_ShellExecRespectWaitFlags = 128;
- utRun_DisableFsRedir = 256;
- utRun_DontLogParameters = 512;
- utRun_LogOutput = 1024;
- utDeleteFile_ExistedBeforeInstall = 1;
- utDeleteFile_Extra = 2;
- utDeleteFile_IsFont = 4;
- utDeleteFile_SharedFile = 8;
- utDeleteFile_RegisteredServer = 16;
- utDeleteFile_CallChangeNotify = 32;
- utDeleteFile_RegisteredTypeLib = 64;
- utDeleteFile_RestartDelete = 128;
- utDeleteFile_RemoveReadOnly = 256;
- utDeleteFile_NoSharedFilePrompt = 512;
- utDeleteFile_SharedFileIn64BitKey = 1024;
- utDeleteFile_DisableFsRedir = 2048; { also determines whether file was registered as 64-bit }
- utDeleteFile_GacInstalled = 4096;
- utDeleteFile_PerUserFont = 8192;
- utDeleteDirOrFiles_Extra = 1;
- utDeleteDirOrFiles_IsDir = 2;
- utDeleteDirOrFiles_DeleteFiles = 4;
- utDeleteDirOrFiles_DeleteSubdirsAlso = 8;
- utDeleteDirOrFiles_CallChangeNotify = 16;
- utDeleteDirOrFiles_DisableFsRedir = 32;
- utIniDeleteSection_OnlyIfEmpty = 1;
- utReg_KeyHandleMask = $80FFFFFF;
- utReg_64BitKey = $01000000;
- utDecrementSharedCount_64BitKey = 1;
- type
- PUninstallRec = ^TUninstallRec;
- TUninstallRec = record
- Prev, Next: PUninstallRec;
- ExtraData: Longint;
- DataSize: Cardinal;
- Typ: TUninstallRecTyp;
- Data: array[0..$6FFFFFFF] of Byte; { *must* be last field }
- end;
- TDeleteUninstallDataFilesProc = procedure;
- TUninstallLogFlags = set of (ufAdminInstalled, ufDontCheckRecCRCs,
- ufModernStyle, ufAlwaysRestart, ufChangesEnvironment, ufWin64,
- ufPowerUserInstalled, ufAdminInstallMode);
- TUninstallLog = class
- private
- FList, FLastList: PUninstallRec;
- FCount: Integer;
- class function AllocRec(const Typ: TUninstallRecTyp;
- const ExtraData: Longint; const DataSize: Integer): PUninstallRec;
- function Delete(const Rec: PUninstallRec): PUninstallRec;
- procedure InternalAdd(const NewRec: PUninstallRec);
- protected
- procedure HandleException; virtual; abstract;
- function ShouldRemoveSharedFile(const Filename: String): Boolean; virtual;
- procedure StatusUpdate(StartingCount, CurCount: Integer); virtual;
- public
- InstallMode64Bit: Boolean;
- AppId, AppName: String;
- NeedRestart: Boolean;
- Flags: TUninstallLogFlags;
- Version: Integer;
- constructor Create;
- destructor Destroy; override;
- procedure Add(const Typ: TUninstallRecTyp; const Data: array of String;
- const ExtraData: Longint);
- procedure AddReg(const Typ: TUninstallRecTyp; const RegView: TRegView;
- const RootKey: HKEY; const Data: array of String);
- function CanAppend(const Filename: String;
- var ExistingFlags: TUninstallLogFlags): Boolean;
- function CheckMutexes: Boolean;
- procedure Clear;
- class function ExtractRecData(const Rec: PUninstallRec;
- var Data: array of String): Integer;
- function ExtractLatestRecData(const Typ: TUninstallRecTyp;
- const ExtraData: Longint; var Data: array of String): Boolean;
- procedure Load(const F: TFile; const Filename: String);
- function PerformUninstall(const CallFromUninstaller: Boolean;
- const DeleteUninstallDataFilesProc: TDeleteUninstallDataFilesProc): Boolean;
- class function WriteSafeHeaderString(Dest: PAnsiChar; const Source: String;
- MaxDestBytes: Cardinal): Cardinal;
- class function ReadSafeHeaderString(const Source: AnsiString): String;
- procedure Save(const Filename: String;
- const Append, UpdateUninstallLogAppName: Boolean);
- property List: PUninstallRec read FList;
- property LastList: PUninstallRec read FLastList;
- end;
- function ReadUninstallLogFlags(const F: TFile; const Filename: String): TUninstallLogFlags;
- implementation
- uses
- Messages, ShlObj, AnsiStrings,
- PathFunc, Shared.Struct, SetupLdrAndSetup.Messages, Shared.SetupMessageIDs, Setup.InstFunc,
- Setup.InstFunc.Ole, SetupLdrAndSetup.RedirFunc, Compression.Base,
- Setup.LoggingFunc, Setup.RegDLL, Setup.Helper, Setup.DotNetFunc;
- type
- { Note: TUninstallLogHeader should stay <= 512 bytes in size, so that it
- fits into a single disk sector and can be written atomically }
- TUninstallLogHeader = packed record
- ID: TUninstallLogID;
- AppId: array[0..127] of AnsiChar;
- AppName: array[0..127] of AnsiChar;
- Version, NumRecs: Integer;
- EndOffset: LongWord;
- Flags: Longint;
- Reserved: array[0..26] of Longint; { reserved for future use }
- CRC: Longint;
- end;
- TUninstallCrcHeader = packed record
- Size, NotSize: Cardinal;
- CRC: Longint;
- end;
- TUninstallFileRec = packed record
- Typ: TUninstallRecTyp;
- ExtraData: Longint;
- DataSize: Cardinal;
- end;
- procedure ReadUninstallLogHeader(const F: TFile; const Filename: String;
- var Header: TUninstallLogHeader; var Header64Bit: Boolean);
- procedure Corrupt;
- begin
- raise Exception.Create(FmtSetupMessage1(msgUninstallDataCorrupted, Filename));
- end;
- begin
- F.Seek(0);
- if F.Read(Header, SizeOf(Header)) <> SizeOf(Header) then
- Corrupt;
- if (Header.CRC <> $11111111) and
- { ^ for debugging purposes, you can change the CRC field in the file to
- $11111111 to disable CRC checking on the header}
- (Header.CRC <> GetCRC32(Header, SizeOf(Header)-SizeOf(Longint))) then
- Corrupt;
- if Header.ID = UninstallLogID[False] then
- Header64Bit := False
- else if Header.ID = UninstallLogID[True] then
- Header64Bit := True
- else
- Corrupt;
- end;
- function ReadUninstallLogFlags(const F: TFile; const Filename: String): TUninstallLogFlags;
- { Reads the flags from the header of the open file F. The Filename parameter
- is only used when generating exception error messages. }
- var
- Header: TUninstallLogHeader;
- Header64Bit: Boolean;
- begin
- ReadUninstallLogHeader(F, Filename, Header, Header64Bit);
- Result := TUninstallLogFlags((@Header.Flags)^);
- end;
- { Misc. uninstallation functions }
- function ListContainsPathOrSubdir(const List: TSimpleStringList;
- const Path: String): Boolean;
- { Returns True if List contains Path or a subdirectory of Path }
- var
- SlashPath: String;
- SlashPathLen, I: Integer;
- begin
- SlashPath := AddBackslash(Path);
- SlashPathLen := Length(SlashPath);
- if SlashPathLen > 0 then begin { ...sanity check }
- for I := 0 to List.Count-1 do begin
- if List[I] = Path then begin
- Result := True;
- Exit;
- end;
- if (Length(List[I]) > SlashPathLen) and
- CompareMem(Pointer(List[I]), Pointer(SlashPath), SlashPathLen * SizeOf(SlashPath[1])) then begin
- Result := True;
- Exit;
- end;
- end;
- end;
- Result := False;
- end;
- procedure LoggedRestartDeleteDir(const DisableFsRedir: Boolean; Dir: String);
- begin
- Dir := PathExpand(Dir);
- if not DisableFsRedir then begin
- { Work around WOW64 bug present in the IA64 and x64 editions of Windows
- XP (3790) and Server 2003 prior to SP1 RC2: MoveFileEx writes filenames
- to the registry verbatim without mapping system32->syswow64. }
- Dir := ReplaceSystemDirWithSysWow64(Dir);
- end;
- if not MoveFileExRedir(DisableFsRedir, Dir, '', MOVEFILE_DELAY_UNTIL_REBOOT) then
- LogFmt('MoveFileEx failed (%d).', [GetLastError]);
- end;
- const
- drFalse = '0';
- drTrue = '1';
- function LoggedDeleteDir(const DisableFsRedir: Boolean; const DirName: String;
- const DirsNotRemoved, RestartDeleteDirList: TSimpleStringList): Boolean;
- const
- FILE_ATTRIBUTE_REPARSE_POINT = $00000400;
- DirsNotRemovedPrefix: array[Boolean] of Char = (drFalse, drTrue);
- var
- Attribs, LastError: DWORD;
- begin
- Attribs := GetFileAttributesRedir(DisableFsRedir, DirName);
- { Does the directory exist? }
- if (Attribs <> INVALID_FILE_ATTRIBUTES) and
- (Attribs and FILE_ATTRIBUTE_DIRECTORY <> 0) then begin
- LogFmt('Deleting directory: %s', [DirName]);
- { If the directory has the read-only attribute, strip it first }
- if Attribs and FILE_ATTRIBUTE_READONLY <> 0 then begin
- if (Attribs and FILE_ATTRIBUTE_REPARSE_POINT <> 0) or
- IsDirEmpty(DisableFsRedir, DirName) then begin
- if SetFileAttributesRedir(DisableFsRedir, DirName, Attribs and not FILE_ATTRIBUTE_READONLY) then
- Log('Stripped read-only attribute.')
- else
- Log('Failed to strip read-only attribute.');
- end
- else
- Log('Not stripping read-only attribute because the directory ' +
- 'does not appear to be empty.');
- end;
- Result := RemoveDirectoryRedir(DisableFsRedir, DirName);
- if not Result then begin
- LastError := GetLastError;
- if Assigned(DirsNotRemoved) then begin
- LogFmt('Failed to delete directory (%d). Will retry later.', [LastError]);
- DirsNotRemoved.AddIfDoesntExist(DirsNotRemovedPrefix[DisableFsRedir] + DirName);
- end
- else if Assigned(RestartDeleteDirList) and
- ListContainsPathOrSubdir(RestartDeleteDirList, DirName) then begin
- LogFmt('Failed to delete directory (%d). Will delete on restart (if empty).',
- [LastError]);
- LoggedRestartDeleteDir(DisableFsRedir, DirName);
- end
- else
- LogFmt('Failed to delete directory (%d).', [LastError]);
- end;
- end
- else
- Result := True;
- end;
- procedure CrackRegExtraData(const ExtraData: Longint; var RegView: TRegView;
- var RootKey: HKEY);
- begin
- if ExtraData and utReg_64BitKey <> 0 then
- RegView := rv64Bit
- else
- RegView := rv32Bit;
- RootKey := ExtraData and utReg_KeyHandleMask;
- end;
- { TUninstallLog }
- constructor TUninstallLog.Create;
- begin
- inherited Create;
- Clear;
- end;
- destructor TUninstallLog.Destroy;
- begin
- Clear;
- inherited Destroy;
- end;
- class function TUninstallLog.AllocRec(const Typ: TUninstallRecTyp;
- const ExtraData: Longint; const DataSize: Integer): PUninstallRec;
- { Allocates a new PUninstallRec, but does not add it to the list. Returns nil
- if the value of the DataSize parameter is out of range. }
- begin
- { Sanity check the size to protect against integer overflows. 128 MB should
- be way more than enough. }
- if (DataSize < 0) or (DataSize > $08000000) then begin
- Result := nil;
- Exit;
- end;
- Result := AllocMem(Integer(@PUninstallRec(nil).Data) + DataSize);
- Result.Typ := Typ;
- Result.ExtraData := ExtraData;
- Result.DataSize := DataSize;
- end;
- procedure TUninstallLog.InternalAdd(const NewRec: PUninstallRec);
- { Adds a new entry to the uninstall list }
- begin
- if List = nil then begin
- FList := NewRec;
- FLastList := List;
- end
- else begin
- LastList^.Next := NewRec;
- NewRec^.Prev := LastList;
- FLastList := NewRec;
- end;
- Inc(FCount);
- end;
- procedure TUninstallLog.Add(const Typ: TUninstallRecTyp; const Data: array of String;
- const ExtraData: Longint);
- var
- I, L: Integer;
- S, X: AnsiString;
- AData: AnsiString;
- NewRec: PUninstallRec;
- begin
- for I := 0 to High(Data) do begin
- L := Length(Data[I])*SizeOf(Data[I][1]);
- SetLength(X, SizeOf(Byte) + SizeOf(Integer));
- X[1] := AnsiChar($FE);
- Integer((@X[2])^) := Integer(-L);
- S := S + X;
- SetString(AData, PAnsiChar(Pointer(Data[I])), L);
- S := S + AData;
- end;
- S := S + AnsiChar($FF);
- NewRec := AllocRec(Typ, ExtraData, Length(S)*SizeOf(S[1]));
- if NewRec = nil then
- InternalError('DataSize range exceeded');
- Move(Pointer(S)^, NewRec.Data, NewRec.DataSize);
- InternalAdd(NewRec);
- if Version < HighestSupportedVersion then
- Version := HighestSupportedVersion;
- end;
- procedure TUninstallLog.AddReg(const Typ: TUninstallRecTyp;
- const RegView: TRegView; const RootKey: HKEY; const Data: array of String);
- { Adds a new utReg* type entry }
- var
- ExtraData: Longint;
- begin
- { If RootKey isn't a predefined key, or has unrecognized garbage in the
- high byte (which we use for our own purposes), reject it }
- if RootKey shr 24 <> $80 then
- Exit;
- { ExtraData in a utReg* entry consists of a root key value (HKEY_*)
- OR'ed with flag bits in the high byte }
- HKEY(ExtraData) := RootKey;
- if RegView in RegViews64Bit then
- ExtraData := ExtraData or utReg_64BitKey;
- Add(Typ, Data, ExtraData);
- end;
- function TUninstallLog.Delete(const Rec: PUninstallRec): PUninstallRec;
- { Removes Rec from the linked list, then frees it. Returns (what was) the
- previous record, or nil if there is none. }
- begin
- Result := Rec.Prev;
- if Assigned(Rec.Prev) then
- Rec.Prev.Next := Rec.Next;
- if Assigned(Rec.Next) then
- Rec.Next.Prev := Rec.Prev;
- if FList = Rec then
- FList := Rec.Next;
- if FLastList = Rec then
- FLastList := Rec.Prev;
- Dec(FCount);
- FreeMem(Rec);
- end;
- procedure TUninstallLog.Clear;
- { Frees all entries in the uninstall list and clears AppName/AppDir }
- begin
- while FLastList <> nil do
- Delete(FLastList);
- FCount := 0;
- AppId := '';
- AppName := '';
- Flags := [];
- end;
- type
- PDeleteDirData = ^TDeleteDirData;
- TDeleteDirData = record
- DirsNotRemoved: TSimpleStringList;
- end;
- function LoggedDeleteDirProc(const DisableFsRedir: Boolean; const DirName: String;
- const Param: Pointer): Boolean;
- begin
- Result := LoggedDeleteDir(DisableFsRedir, DirName, PDeleteDirData(Param)^.DirsNotRemoved, nil);
- end;
- function LoggedDeleteFileProc(const DisableFsRedir: Boolean; const FileName: String;
- const Param: Pointer): Boolean;
- begin
- LogFmt('Deleting file: %s', [FileName]);
- Result := DeleteFileRedir(DisableFsRedir, FileName);
- if not Result then
- LogFmt('Failed to delete the file; it may be in use (%d).', [GetLastError]);
- end;
- procedure ProcessMessagesProc; far;
- var
- Msg: TMsg;
- begin
- while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do begin
- TranslateMessage(Msg);
- DispatchMessage(Msg);
- end;
- end;
- class function TUninstallLog.ExtractRecData(const Rec: PUninstallRec;
- var Data: array of String): Integer;
- var
- I, L: Integer;
- X: ^Byte;
- begin
- for I := 0 to High(Data) do
- Data[I] := '';
- I := 0;
- X := @Rec^.Data;
- while I <= High(Data) do begin
- case X^ of
- $00..$FC: begin
- L := X^;
- Inc(X);
- end;
- $FD: begin
- Inc(X);
- L := Word(Pointer(X)^);
- Inc(X, SizeOf(Word));
- end;
- $FE: begin
- Inc(X);
- L := Integer(Pointer(X)^);
- Inc(X, SizeOf(Integer));
- end;
- $FF: Break;
- end;
- if L < 0 then begin
- L := -L;
- SetString(Data[I], PChar(X), L div SizeOf(Char));
- end else
- SetString(Data[I], PAnsiChar(X), L);
- Inc(X, L);
- Inc(I);
- end;
- Result := I;
- end;
- function TUninstallLog.ExtractLatestRecData(const Typ: TUninstallRecTyp;
- const ExtraData: Longint; var Data: array of String): Boolean;
- var
- CurRec: PUninstallRec;
- begin
- CurRec := LastList;
- while CurRec <> nil do begin
- if (CurRec^.Typ = Typ) and (CurRec^.ExtraData = ExtraData) then begin
- ExtractRecData(CurRec, Data);
- Result := True;
- Exit;
- end;
- CurRec := CurRec^.Prev;
- end;
- Result := False;
- end;
- function TUninstallLog.CheckMutexes: Boolean;
- var
- CurRec: PUninstallRec;
- Data: String;
- begin
- Result := False;
- CurRec := LastList;
- while CurRec <> nil do begin
- if CurRec^.Typ = utMutexCheck then begin
- ExtractRecData(CurRec, Data);
- if CheckForMutexes(Data) then begin
- Result := True;
- Exit;
- end;
- end;
- CurRec := CurRec^.Prev;
- end;
- end;
- procedure RunExecLog(const S: String; const Error, FirstLine: Boolean; const Data: NativeInt);
- begin
- if not Error and FirstLine then
- Log('Running Exec output:');
- Log(S);
- end;
- function TUninstallLog.PerformUninstall(const CallFromUninstaller: Boolean;
- const DeleteUninstallDataFilesProc: TDeleteUninstallDataFilesProc): Boolean;
- { Undoes all the changes in the uninstall list, in reverse order they were
- added. Deletes entries that were successfully undone.
- Returns True if all elements were successfully removed; False if some
- could not be removed. }
- var
- RefreshFileAssoc: Boolean;
- ChangeNotifyList, RunOnceList: TSimpleStringList;
- UnregisteredServersList, RestartDeleteDirList: array[Boolean] of TSimpleStringList;
- DeleteDirData: TDeleteDirData;
- function LoggedFileDelete(const Filename: String; const DisableFsRedir,
- NotifyChange, RestartDelete, RemoveReadOnly: Boolean): Boolean;
- var
- ExistingAttr, LastError: DWORD;
- begin
- Result := True;
- { Automatically delete generated indexes associated with help files }
- if SameText(PathExtractExt(Filename), '.hlp') then begin
- LoggedFileDelete(PathChangeExt(Filename, '.gid'), DisableFsRedir, False, False, False);
- LoggedFileDelete(PathChangeExt(Filename, '.fts'), DisableFsRedir, False, False, False);
- end
- else if SameText(PathExtractExt(Filename), '.chm') then
- LoggedFileDelete(PathChangeExt(Filename, '.chw'), DisableFsRedir, False, False, False);
- { Automatically unpin shortcuts }
- if SameText(PathExtractExt(Filename), '.lnk') then
- UnpinShellLink(Filename);
-
- if NewFileExistsRedir(DisableFsRedir, Filename) then begin
- LogFmt('Deleting file: %s', [FileName]);
- if RemoveReadOnly then begin
- ExistingAttr := GetFileAttributesRedir(DisableFsRedir, Filename);
- if (ExistingAttr <> INVALID_FILE_ATTRIBUTES) and
- (ExistingAttr and FILE_ATTRIBUTE_READONLY <> 0) then
- if SetFileAttributesRedir(DisableFsRedir, Filename,
- ExistingAttr and not FILE_ATTRIBUTE_READONLY) then
- Log('Stripped read-only attribute.')
- else
- Log('Failed to strip read-only attribute.');
- end;
- if not DeleteFileRedir(DisableFsRedir, Filename) then begin
- LastError := GetLastError;
- if RestartDelete and CallFromUninstaller and
- ((LastError = ERROR_ACCESS_DENIED) or (LastError = ERROR_SHARING_VIOLATION)) and
- (GetFileAttributesRedir(DisableFsRedir, Filename) and FILE_ATTRIBUTE_READONLY = 0) then begin
- LogFmt('The file appears to be in use (%d). Will delete on restart.',
- [LastError]);
- try
- RestartReplace(DisableFsRedir, Filename, '');
- NeedRestart := True;
- { Add the file's directory to the list of directories that should
- be restart-deleted later }
- RestartDeleteDirList[DisableFsRedir].AddIfDoesntExist(PathExtractDir(PathExpand(Filename)));
- except
- Log('Exception message:' + SNewLine + GetExceptMessage);
- Result := False;
- end;
- end
- else begin
- LogFmt('Failed to delete the file; it may be in use (%d).', [LastError]);
- Result := False;
- end;
- end
- else begin
- { Note: It is assumed that DisableFsRedir will be False when NotifyChange is True }
- if NotifyChange then begin
- SHChangeNotify(SHCNE_DELETE, SHCNF_PATH, PChar(Filename), nil);
- ChangeNotifyList.AddIfDoesntExist(PathExtractDir(Filename));
- end;
- end;
- end;
- end;
- function LoggedDecrementSharedCount(const Filename: String;
- const Key64Bit: Boolean): Boolean;
- const
- Bits: array[Boolean] of Integer = (32, 64);
- var
- RegView: TRegView;
- begin
- if Key64Bit then
- RegView := rv64Bit
- else
- RegView := rv32Bit;
- LogFmt('Decrementing shared count (%d-bit): %s', [Bits[Key64Bit], Filename]);
- Result := DecrementSharedCount(RegView, Filename);
- if Result then
- Log('Shared count reached zero.');
- end;
- procedure LoggedUnregisterServer(const Is64Bit: Boolean; const Filename: String);
- begin
- { Just as an optimization, make sure we aren't unregistering
- the same file again }
- if UnregisteredServersList[Is64Bit].IndexOf(Filename) = -1 then begin
- if Is64Bit then
- LogFmt('Unregistering 64-bit DLL/OCX: %s', [Filename])
- else
- LogFmt('Unregistering 32-bit DLL/OCX: %s', [Filename]);
- try
- RegisterServer(True, Is64Bit, Filename, True);
- UnregisteredServersList[Is64Bit].Add(Filename);
- Log('Unregistration successful.');
- except
- Log('Unregistration failed:' + SNewLine + GetExceptMessage);
- end;
- end
- else
- LogFmt('Not unregistering DLL/OCX again: %s', [Filename]);
- end;
- procedure LoggedUnregisterTypeLibrary(const Is64Bit: Boolean;
- const Filename: String);
- begin
- if Is64Bit then
- LogFmt('Unregistering 64-bit type library: %s', [Filename])
- else
- LogFmt('Unregistering 32-bit type library: %s', [Filename]);
- try
- if Is64Bit then
- HelperRegisterTypeLibrary(True, Filename)
- else
- UnregisterTypeLibrary(Filename);
- Log('Unregistration successful.');
- except
- Log('Unregistration failed:' + SNewLine + GetExceptMessage);
- end;
- end;
- procedure LoggedUninstallAssembly(const StrongAssemblyName: String);
- begin
- LogFmt('Uninstalling from GAC: %s', [StrongAssemblyName]);
- try
- with TAssemblyCacheInfo.Create(rvDefault) do try
- UninstallAssembly(StrongAssemblyName);
- finally
- Free;
- end;
- except
- Log('Uninstallation failed:' + SNewLine + GetExceptMessage);
- end;
- end;
- procedure LoggedProcessDirsNotRemoved;
- var
- I: Integer;
- S: String;
- DisableFsRedir: Boolean;
- begin
- for I := 0 to DeleteDirData.DirsNotRemoved.Count-1 do begin
- S := DeleteDirData.DirsNotRemoved[I];
- { The first character specifies the DisableFsRedir value
- (e.g. '0C:\Program Files\My Program') }
- DisableFsRedir := (S[1] = drTrue);
- System.Delete(S, 1, 1);
- LoggedDeleteDir(DisableFsRedir, S, nil, RestartDeleteDirList[DisableFsRedir]);
- end;
- end;
-
- function GetLogIniFilename(const Filename: String): String;
- begin
- if Filename <> '' then
- Result := Filename
- else
- Result := 'win.ini';
- end;
- const
- GroupInfoChars: array[0..3] of Char = ('"', '"', ',', ',');
- NullChar: Char = #0;
- var
- StartCount: Integer;
- CurRec: PUninstallRec;
- CurRecDataPChar: array[0..9] of PChar;
- CurRecData: array[0..9] of String;
- ShouldDeleteRec, IsTempFile, IsSharedFile, SharedCountDidReachZero: Boolean;
- Filename, Section, Key: String;
- Subkey, ValueName: PChar;
- P, ErrorCode: Integer;
- RegView: TRegView;
- RootKey, K: HKEY;
- Wait: TExecWait;
- ShowCmd: Integer;
- procedure SplitData(const Rec: PUninstallRec);
- var
- C, I: Integer;
- begin
- C := ExtractRecData(Rec, CurRecData);
- for I := 0 to 9 do begin
- if I < C then
- CurRecDataPChar[I] := PChar(CurRecData[I])
- else
- CurRecDataPChar[I] := nil;
- end;
- end;
- begin
- Log('Starting the uninstallation process.');
- SetCurrentDir(GetSystemDir);
- Result := True;
- NeedRestart := False;
- RefreshFileAssoc := False;
- RunOnceList := nil;
- UnregisteredServersList[False] := nil;
- UnregisteredServersList[True] := nil;
- RestartDeleteDirList[False] := nil;
- RestartDeleteDirList[True] := nil;
- DeleteDirData.DirsNotRemoved := nil;
- ChangeNotifyList := TSimpleStringList.Create;
- try
- RunOnceList := TSimpleStringList.Create;
- UnregisteredServersList[False] := TSimpleStringList.Create;
- UnregisteredServersList[True] := TSimpleStringList.Create;
- RestartDeleteDirList[False] := TSimpleStringList.Create;
- RestartDeleteDirList[True] := TSimpleStringList.Create;
- if Assigned(DeleteUninstallDataFilesProc) then
- DeleteDirData.DirsNotRemoved := TSimpleStringList.Create;
- StartCount := FCount;
- StatusUpdate(StartCount, FCount);
- { Step 1 - Process all utRun entries }
- if CallFromUninstaller then begin
- CurRec := LastList;
- while CurRec <> nil do begin
- if CurRec^.Typ = utRun then begin
- try
- SplitData(CurRec);
- { Verify that a utRun entry with the same RunOnceId has not
- already been executed }
- if (CurRecData[3] = '') or (RunOnceList.IndexOf(CurRecData[3]) = -1) then begin
- Wait := ewWaitUntilTerminated;
- if CurRec^.ExtraData and utRun_NoWait <> 0 then
- Wait := ewNoWait
- else if CurRec^.ExtraData and utRun_WaitUntilIdle <> 0 then
- Wait := ewWaitUntilIdle;
- ShowCmd := SW_SHOWNORMAL;
- if CurRec^.ExtraData and utRun_RunMinimized <> 0 then
- ShowCmd := SW_SHOWMINNOACTIVE
- else if CurRec^.ExtraData and utRun_RunMaximized <> 0 then
- ShowCmd := SW_SHOWMAXIMIZED
- else if CurRec^.ExtraData and utRun_RunHidden <> 0 then
- ShowCmd := SW_HIDE;
- { Note: This code is similar to code in the ProcessRunEntry
- function of Main.pas }
- if CurRec^.ExtraData and utRun_ShellExec = 0 then begin
- Log('Running Exec filename: ' + CurRecData[0]);
- if (CurRec^.ExtraData and utRun_DontLogParameters = 0) and (CurRecData[1] <> '') then
- Log('Running Exec parameters: ' + CurRecData[1]);
- if (CurRec^.ExtraData and utRun_SkipIfDoesntExist = 0) or
- NewFileExistsRedir(CurRec^.ExtraData and utRun_DisableFsRedir <> 0, CurRecData[0]) then begin
- var OutputReader: TCreateProcessOutputReader := nil;
- try
- if GetLogActive and (CurRec^.ExtraData and utRun_LogOutput <> 0) then
- OutputReader := TCreateProcessOutputReader.Create(RunExecLog, 0);
- if not InstExec(CurRec^.ExtraData and utRun_DisableFsRedir <> 0,
- CurRecData[0], CurRecData[1], CurRecData[2], Wait,
- ShowCmd, ProcessMessagesProc, OutputReader, ErrorCode) then begin
- LogFmt('CreateProcess failed (%d).', [ErrorCode]);
- Result := False;
- end
- else begin
- if Wait = ewWaitUntilTerminated then
- LogFmt('Process exit code: %u', [ErrorCode]);
- end;
- finally
- OutputReader.Free;
- end;
- end else
- Log('File doesn''t exist. Skipping.');
- end
- else begin
- Log('Running ShellExec filename: ' + CurRecData[0]);
- if (CurRec^.ExtraData and utRun_DontLogParameters = 0) and (CurRecData[1] <> '') then
- Log('Running ShellExec parameters: ' + CurRecData[1]);
- if (CurRec^.ExtraData and utRun_SkipIfDoesntExist = 0) or
- FileOrDirExists(CurRecData[0]) then begin
- if CurRec^.ExtraData and utRun_ShellExecRespectWaitFlags = 0 then
- Wait := ewNoWait;
- if not InstShellExec(CurRecData[4], CurRecData[0], CurRecData[1], CurRecData[2],
- Wait, ShowCmd, ProcessMessagesProc, ErrorCode) then begin
- LogFmt('ShellExecuteEx failed (%d).', [ErrorCode]);
- Result := False;
- end
- else begin
- if Wait = ewWaitUntilTerminated then
- LogFmt('Process exit code: %u', [ErrorCode]);
- end;
- end else
- Log('File/directory doesn''t exist. Skipping.');
- end;
- if CurRecData[3] <> '' then
- RunOnceList.Add(CurRecData[3]);
- end else
- LogFmt('Skipping RunOnceId "%s" filename: %s', [CurRecData[3], CurRecData[0]]);
- except
- Result := False;
- if not(ExceptObject is EAbort) then
- HandleException;
- end;
- CurRec := Delete(CurRec);
- StatusUpdate(StartCount, FCount);
- end
- else
- CurRec := CurRec^.Prev;
- end;
- end;
- { Step 2 - Decrement shared file counts, unregister DLLs/TLBs/fonts, and uninstall from GAC }
- CurRec := LastList;
- while CurRec <> nil do begin
- ShouldDeleteRec := False;
- if CurRec^.Typ = utDeleteFile then begin
- { Default to deleting the record in case an exception is raised by
- DecrementSharedCount, the reference count doesn't reach zero, or the
- user opts not to delete the shared file. }
- ShouldDeleteRec := True;
- try
- SplitData(CurRec);
- { Note: Some of this code is duplicated in Step 3 }
- if CallFromUninstaller or (CurRec^.ExtraData and utDeleteFile_ExistedBeforeInstall = 0) then begin
- IsTempFile := not CallFromUninstaller and (CurRecData[1] <> '');
- { Decrement shared file count if necessary }
- IsSharedFile := CurRec^.ExtraData and utDeleteFile_SharedFile <> 0;
- if IsSharedFile then
- SharedCountDidReachZero := LoggedDecrementSharedCount(CurRecData[0],
- CurRec^.ExtraData and utDeleteFile_SharedFileIn64BitKey <> 0)
- else
- SharedCountDidReachZero := False; //silence compiler
- if not IsSharedFile or
- (SharedCountDidReachZero and
- (IsTempFile or
- not NewFileExistsRedir(CurRec^.ExtraData and utDeleteFile_DisableFsRedir <> 0, CurRecData[0]) or
- (CurRec^.ExtraData and utDeleteFile_NoSharedFilePrompt <> 0) or
- ShouldRemoveSharedFile(CurRecData[0]))) then begin
- { The reference count reached zero and the user did not object
- to the file being deleted, so don't delete the record; allow
- the file to be deleted in the next step. }
- ShouldDeleteRec := False;
- { Unregister if necessary }
- if not IsTempFile then begin
- if CurRec^.ExtraData and utDeleteFile_RegisteredServer <> 0 then begin
- LoggedUnregisterServer(CurRec^.ExtraData and utDeleteFile_DisableFsRedir <> 0,
- CurRecData[0]);
- end;
- if CurRec^.ExtraData and utDeleteFile_RegisteredTypeLib <> 0 then begin
- LoggedUnregisterTypeLibrary(CurRec^.ExtraData and utDeleteFile_DisableFsRedir <> 0,
- CurRecData[0]);
- end;
- end;
- if CurRec^.ExtraData and utDeleteFile_IsFont <> 0 then begin
- LogFmt('Unregistering font: %s', [CurRecData[2]]);
- UnregisterFont(CurRecData[2], CurRecData[3], CurRec^.ExtraData and utDeleteFile_PerUserFont <> 0);
- end;
- if CurRec^.ExtraData and utDeleteFile_GacInstalled <> 0 then
- LoggedUninstallAssembly(CurRecData[4]);
- end;
- end
- else begin
- { This case is handled entirely in Step 3 }
- ShouldDeleteRec := False;
- end;
- except
- Result := False;
- if not(ExceptObject is EAbort) then
- HandleException;
- end;
- end;
- if ShouldDeleteRec then begin
- CurRec := Delete(CurRec);
- StatusUpdate(StartCount, FCount);
- end
- else
- CurRec := CurRec^.Prev;
- end;
- { Step 3 - Remaining entries }
- CurRec := LastList;
- while CurRec <> nil do begin
- SplitData(CurRec);
- try
- case CurRec^.Typ of
- utUserDefined: begin
- {if CurRecData[0] = 'MyRecordType' then begin
- ... your code here ...
- end
- else}
- raise Exception.Create(FmtSetupMessage1(msgUninstallUnknownEntry,
- 'utUserDefined:' + CurRecData[0]));
- end;
- utStartInstall,
- utEndInstall,
- utCompiledCode: { do nothing on these };
- utRun: begin
- { Will get here if CallFromUninstaller=False; in that case utRun
- entries will still be in the list, unprocessed. Just ignore
- them. }
- end;
- utDeleteDirOrFiles:
- if (CallFromUninstaller or (CurRec^.ExtraData and utDeleteDirOrFiles_Extra = 0)) then begin
- if DelTree(CurRec^.ExtraData and utDeleteDirOrFiles_DisableFsRedir <> 0,
- CurRecData[0], CurRec^.ExtraData and utDeleteDirOrFiles_IsDir <> 0,
- CurRec^.ExtraData and utDeleteDirOrFiles_DeleteFiles <> 0,
- CurRec^.ExtraData and utDeleteDirOrFiles_DeleteSubdirsAlso <> 0,
- False, LoggedDeleteDirProc, LoggedDeleteFileProc, @DeleteDirData) then begin
- if (CurRec^.ExtraData and utDeleteDirOrFiles_IsDir <> 0) and
- (CurRec^.ExtraData and utDeleteDirOrFiles_CallChangeNotify <> 0) then begin
- SHChangeNotify(SHCNE_RMDIR, SHCNF_PATH, CurRecDataPChar[0], nil);
- ChangeNotifyList.AddIfDoesntExist(PathExtractDir(CurRecData[0]));
- end;
- end;
- end;
- utDeleteFile: begin
- { Note: Some of this code is duplicated in Step 2 }
- Filename := CurRecData[1];
- if CallFromUninstaller or (Filename = '') then
- Filename := CurRecData[0];
- if CallFromUninstaller or (CurRec^.ExtraData and utDeleteFile_ExistedBeforeInstall = 0) then begin
- { Note: We handled utDeleteFile_SharedFile already }
- if CallFromUninstaller or (CurRec^.ExtraData and utDeleteFile_Extra = 0) then
- if not LoggedFileDelete(Filename, CurRec^.ExtraData and utDeleteFile_DisableFsRedir <> 0,
- CurRec^.ExtraData and utDeleteFile_CallChangeNotify <> 0,
- CurRec^.ExtraData and utDeleteFile_RestartDelete <> 0,
- CurRec^.ExtraData and utDeleteFile_RemoveReadOnly <> 0) then
- Result := False;
- end
- else begin
- { We're running from Setup, and the file existed before
- installation... }
- if CurRec^.ExtraData and utDeleteFile_SharedFile <> 0 then
- LoggedDecrementSharedCount(CurRecData[0],
- CurRec^.ExtraData and utDeleteFile_SharedFileIn64BitKey <> 0);
- { Delete file only if it's a temp file }
- if Filename <> CurRecData[0] then
- if not LoggedFileDelete(Filename, CurRec^.ExtraData and utDeleteFile_DisableFsRedir <> 0,
- CurRec^.ExtraData and utDeleteFile_CallChangeNotify <> 0,
- CurRec^.ExtraData and utDeleteFile_RestartDelete <> 0,
- CurRec^.ExtraData and utDeleteFile_RemoveReadOnly <> 0) then
- Result := False;
- end;
- end;
- utDeleteGroupOrItem: ; { dummy - no longer supported }
- utIniDeleteEntry: begin
- Section := CurRecData[1];
- Key := CurRecData[2];
- Filename := CurRecData[0];
- LogFmt('Deleting INI entry: %s in section %s in %s', [Key, Section, GetLogIniFilename(Filename)]);
- DeleteIniEntry(Section, Key, Filename);
- end;
- utIniDeleteSection: begin
- Section := CurRecData[1];
- Filename := CurRecData[0];
- if (CurRec^.ExtraData and utIniDeleteSection_OnlyIfEmpty = 0) or
- IsIniSectionEmpty(Section, Filename) then begin
- LogFmt('Deleting INI section: %s in %s', [Section, GetLogIniFilename(Filename)]);
- DeleteIniSection(Section, Filename);
- end;
- end;
- utRegDeleteEntireKey: begin
- CrackRegExtraData(CurRec^.ExtraData, RegView, RootKey);
- Subkey := CurRecDataPChar[0];
- LogFmt('Deleting registry key: %s\%s', [GetRegRootKeyName(RootKey), Subkey]);
- ErrorCode := RegDeleteKeyIncludingSubkeys(RegView, RootKey, Subkey);
- if not (ErrorCode in [ERROR_SUCCESS, ERROR_FILE_NOT_FOUND]) then begin
- LogFmt('Deletion failed (%d).', [ErrorCode]);
- Result := False;
- end;
- end;
- utRegClearValue: begin
- CrackRegExtraData(CurRec^.ExtraData, RegView, RootKey);
- Subkey := CurRecDataPChar[0];
- ValueName := CurRecDataPChar[1];
- LogFmt('Clearing registry value: %s\%s\%s', [GetRegRootKeyName(RootKey), Subkey, ValueName]);
- if RegOpenKeyExView(RegView, RootKey, Subkey, 0, KEY_SET_VALUE, K) = ERROR_SUCCESS then begin
- ErrorCode := RegSetValueEx(K, ValueName, 0, REG_SZ, @NullChar, SizeOf(NullChar));
- if ErrorCode <> ERROR_SUCCESS then begin
- LogFmt('RegSetValueEx failed (%d).', [ErrorCode]);
- Result := False;
- end;
- RegCloseKey(K);
- end;
- end;
- utRegDeleteKeyIfEmpty: begin
- CrackRegExtraData(CurRec^.ExtraData, RegView, RootKey);
- Subkey := CurRecDataPChar[0];
- LogFmt('Deleting empty registry key: %s\%s', [GetRegRootKeyName(RootKey), Subkey]);
- ErrorCode := RegDeleteKeyIfEmpty(RegView, RootKey, Subkey);
- if ErrorCode = ERROR_DIR_NOT_EMPTY then
- Log('Deletion skipped (not empty).')
- else if not (ErrorCode in [ERROR_SUCCESS, ERROR_FILE_NOT_FOUND]) then begin
- LogFmt('Deletion failed (%d).', [ErrorCode]);
- Result := False;
- end;
- end;
- utRegDeleteValue: begin
- CrackRegExtraData(CurRec^.ExtraData, RegView, RootKey);
- Subkey := CurRecDataPChar[0];
- ValueName := CurRecDataPChar[1];
- LogFmt('Deleting registry value: %s\%s\%s', [GetRegRootKeyName(RootKey), Subkey, ValueName]);
- if RegOpenKeyExView(RegView, RootKey, Subkey, 0, KEY_QUERY_VALUE or KEY_SET_VALUE, K) = ERROR_SUCCESS then begin
- if RegValueExists(K, ValueName) then begin
- ErrorCode := RegDeleteValue(K, ValueName);
- if ErrorCode <> ERROR_SUCCESS then begin
- LogFmt('RegDeleteValue failed (%d).', [ErrorCode]);
- Result := False;
- end;
- end;
- RegCloseKey(K);
- end;
- end;
- utDecrementSharedCount: begin
- LoggedDecrementSharedCount(CurRecData[0],
- CurRec^.ExtraData and utDecrementSharedCount_64BitKey <> 0);
- end;
- utRefreshFileAssoc:
- RefreshFileAssoc := True;
- utMutexCheck: ; { do nothing; utMutexChecks aren't processed here }
- else
- raise Exception.Create(FmtSetupMessage1(msgUninstallUnknownEntry,
- Format('$%x', [CurRec^.Typ])));
- end;
- except
- Result := False;
- if not(ExceptObject is EAbort) then
- HandleException;
- end;
- CurRec := Delete(CurRec);
- StatusUpdate(StartCount, FCount);
- end;
- if RefreshFileAssoc then
- SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil);
- if ufChangesEnvironment in Flags then
- RefreshEnvironment;
- if Assigned(DeleteUninstallDataFilesProc) then begin
- DeleteUninstallDataFilesProc;
- { Now that uninstall data is deleted, try removing the directories it
- was in that couldn't be deleted before. }
- LoggedProcessDirsNotRemoved;
- end;
- finally
- DeleteDirData.DirsNotRemoved.Free;
- RestartDeleteDirList[True].Free;
- RestartDeleteDirList[False].Free;
- for P := 0 to ChangeNotifyList.Count-1 do
- if DirExists(ChangeNotifyList[P]) then
- SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH or SHCNF_FLUSH,
- PChar(ChangeNotifyList[P]), nil);
- UnregisteredServersList[True].Free;
- UnregisteredServersList[False].Free;
- RunOnceList.Free;
- ChangeNotifyList.Free;
- end;
- Log('Uninstallation process succeeded.');
- end;
- function TUninstallLog.ShouldRemoveSharedFile(const Filename: String): Boolean;
- begin
- Result := True;
- end;
- procedure TUninstallLog.StatusUpdate(StartingCount, CurCount: Integer);
- begin
- end;
- class function TUninstallLog.WriteSafeHeaderString(Dest: PAnsiChar;
- const Source: String; MaxDestBytes: Cardinal): Cardinal;
- { Copies a string into a PAnsiChar including null terminator, either directly
- if Source only contains ASCII characters, or else UTF-8-encoded with a special
- #1 marker. If MaxDestBytes = 0 it returns the amount of bytes needed. }
- var
- N: Integer;
- I: Integer;
- begin
- N := Length(Source);
- { Only UTF-8-encode when non-ASCII characters are present }
- for I := 1 to N do begin
- if Ord(Source[I]) > 126 then begin
- if MaxDestBytes <> 0 then begin
- Dest^ := #1;
- Inc(Dest);
- Dec(MaxDestBytes);
- end;
- Result := SizeOf(Dest^) + UnicodeToUtf8(Dest, MaxDestBytes, PWideChar(Source), N + 1);
- Exit;
- end;
- end;
- if MaxDestBytes <> 0 then
- AnsiStrings.StrPLCopy(Dest, AnsiString(Source), MaxDestBytes - 1);
- Result := (N + 1) * SizeOf(Dest^);
- end;
- class function TUninstallLog.ReadSafeHeaderString(const Source: AnsiString): String;
- begin
- if (Source <> '') and (Source[1] = #1) then
- Result := UTF8ToString(Copy(Source, 2, Maxint))
- else
- Result := String(Source);
- end;
- procedure TUninstallLog.Save(const Filename: String;
- const Append, UpdateUninstallLogAppName: Boolean);
- { Saves all undo data to Filename. If Append is True, it appends the current
- undo data to the end of the existing file. When Append is True, it assumes
- compatibility has already been verified with the Test method. }
- var
- F: TFile;
- Buffer: array[0..4095] of Byte;
- BufCount: Cardinal;
- procedure Flush;
- var
- CrcHeader: TUninstallCrcHeader;
- begin
- if BufCount <> 0 then begin
- CrcHeader.Size := BufCount;
- CrcHeader.NotSize := not CrcHeader.Size;
- CrcHeader.CRC := GetCRC32(Buffer, BufCount);
- F.WriteBuffer(CrcHeader, SizeOf(CrcHeader));
- F.WriteBuffer(Buffer, BufCount);
- BufCount := 0;
- end;
- end;
- procedure WriteBuf(const Buf; Size: Cardinal);
- var
- P: Pointer;
- S: Cardinal;
- begin
- P := @Buf;
- while Size <> 0 do begin
- S := Size;
- if S > SizeOf(Buffer) - BufCount then
- S := SizeOf(Buffer) - BufCount;
- Move(P^, Buffer[BufCount], S);
- Inc(BufCount, S);
- if BufCount = SizeOf(Buffer) then
- Flush;
- Inc(Cardinal(P), S);
- Dec(Size, S);
- end;
- end;
- var
- Header: TUninstallLogHeader;
- FileRec: TUninstallFileRec;
- CurRec: PUninstallRec;
- begin
- BufCount := 0;
- if not Append then
- F := TFile.Create(Filename, fdCreateAlways, faReadWrite, fsNone)
- else
- F := TFile.Create(Filename, fdOpenExisting, faReadWrite, fsNone);
- try
- if not Append then begin
- FillChar(Header, SizeOf(Header), 0);
- F.WriteBuffer(Header, SizeOf(Header));
- { Note: It will go back and fill in the correct values later }
- end
- else begin
- F.ReadBuffer(Header, SizeOf(Header));
- F.Seek(Header.EndOffset);
- { If there's anything past EndOffset (only possible if some kind of
- fatal error occurred while updating the file last time), clear it out }
- F.Truncate;
- end;
- CurRec := List;
- while CurRec <> nil do begin
- FileRec.Typ := Ord(CurRec^.Typ);
- FileRec.ExtraData := CurRec^.ExtraData;
- FileRec.DataSize := CurRec^.DataSize;
- WriteBuf(FileRec, SizeOf(FileRec));
- WriteBuf(CurRec^.Data, CurRec^.DataSize);
- if (Header.NumRecs < 0) or (Header.NumRecs >= High(Header.NumRecs)) then
- InternalError('NumRecs range exceeded');
- Inc(Header.NumRecs);
- CurRec := CurRec^.Next;
- end;
- Flush;
- const NewEndOffset = F.Position;
- if NewEndOffset > High(UInt32) then
- InternalError('EndOffset range exceeded');
- Header.EndOffset := UInt32(NewEndOffset);
- F.Seek(0);
- Header.ID := UninstallLogID[InstallMode64Bit];
- WriteSafeHeaderString(Header.AppId, AppId, SizeOf(Header.AppId));
- if not Append or UpdateUninstallLogAppName then
- WriteSafeHeaderString(Header.AppName, AppName, SizeOf(Header.AppName));
- if Version > Header.Version then
- Header.Version := Version;
- TUninstallLogFlags((@Header.Flags)^) := TUninstallLogFlags((@Header.Flags)^) - [ufModernStyle] + Flags;
- Header.CRC := GetCRC32(Header, SizeOf(Header)-SizeOf(Longint));
- { Prior to rewriting the header with the new EndOffset value, ensure the
- records we wrote earlier are flushed to disk. This should prevent the
- file from ever becoming corrupted/unreadable in the event the system
- crashes a split second from now. At worst, EndOffset will have the old
- value and any extra bytes past EndOffset will be ignored/discarded when
- the file is read at uninstall time, or appended to the next time Setup
- is run. }
- FlushFileBuffers(F.Handle);
- F.WriteBuffer(Header, SizeOf(Header));
- finally
- F.Free;
- end;
- end;
- procedure TUninstallLog.Load(const F: TFile; const Filename: String);
- { Loads all undo data from the open file F. The Filename parameter is only
- used when generating exception error messages.
- Note: The position of the file pointer after calling this function is
- undefined. }
- var
- Buffer: array[0..4095] of Byte;
- BufPos, BufLeft: Cardinal;
- Header: TUninstallLogHeader;
- procedure Corrupt;
- begin
- raise Exception.Create(FmtSetupMessage1(msgUninstallDataCorrupted, Filename));
- end;
- procedure FillBuffer;
- var
- EndOffset, Ofs: Integer64;
- CrcHeader: TUninstallCrcHeader;
- begin
- EndOffset := To64(Header.EndOffset);
- while BufLeft = 0 do begin
- Ofs := F.Position;
- Inc64(Ofs, SizeOf(CrcHeader));
- if Compare64(Ofs, EndOffset) > 0 then
- Corrupt;
- if F.Read(CrcHeader, SizeOf(CrcHeader)) <> SizeOf(CrcHeader) then
- Corrupt;
- Ofs := F.Position;
- Inc64(Ofs, CrcHeader.Size);
- if (CrcHeader.Size <> not CrcHeader.NotSize) or
- (Cardinal(CrcHeader.Size) > Cardinal(SizeOf(Buffer))) or
- (Compare64(Ofs, EndOffset) > 0) then
- Corrupt;
- if F.Read(Buffer, CrcHeader.Size) <> CrcHeader.Size then
- Corrupt;
- if not(ufDontCheckRecCRCs in Flags) and
- (CrcHeader.CRC <> GetCRC32(Buffer, CrcHeader.Size)) then
- Corrupt;
- BufPos := 0;
- BufLeft := CrcHeader.Size;
- end;
- end;
- procedure ReadBuf(var Buf; Size: Cardinal);
- var
- P: Pointer;
- S: Cardinal;
- begin
- P := @Buf;
- while Size <> 0 do begin
- if BufLeft = 0 then
- FillBuffer;
- S := Size;
- if S > BufLeft then
- S := BufLeft;
- Move(Buffer[BufPos], P^, S);
- Inc(BufPos, S);
- Dec(BufLeft, S);
- Inc(Cardinal(P), S);
- Dec(Size, S);
- end;
- end;
- var
- FileRec: TUninstallFileRec;
- I: Integer;
- NewRec: PUninstallRec;
- begin
- BufPos := 0;
- BufLeft := 0;
- ReadUninstallLogHeader(F, Filename, Header, InstallMode64Bit);
- if Header.Version > HighestSupportedVersion then
- raise Exception.Create(FmtSetupMessage1(msgUninstallUnsupportedVer, Filename));
- AppId := ReadSafeHeaderString(Header.AppId);
- AppName := ReadSafeHeaderString(Header.AppName);
- Flags := TUninstallLogFlags((@Header.Flags)^);
- for I := 1 to Header.NumRecs do begin
- ReadBuf(FileRec, SizeOf(FileRec));
- NewRec := AllocRec(FileRec.Typ, FileRec.ExtraData, FileRec.DataSize);
- if NewRec = nil then
- Corrupt; { DataSize was out of range }
- try
- ReadBuf(NewRec.Data, NewRec.DataSize);
- except
- FreeMem(NewRec);
- raise;
- end;
- InternalAdd(NewRec);
- end;
- end;
- function TUninstallLog.CanAppend(const Filename: String;
- var ExistingFlags: TUninstallLogFlags): Boolean;
- { Returns True if Filename is a recognized uninstall log format, and its header
- matches our AppId and InstallMode64Bit settings. When True is returned,
- the existing log's flags are assigned to ExistingFlags. }
- var
- F: TFile;
- Header: TUninstallLogHeader;
- begin
- Result := False;
- try
- F := TFile.Create(Filename, fdOpenExisting, faRead, fsRead);
- try
- if F.Read(Header, SizeOf(Header)) <> SizeOf(Header) then
- Exit;
- if ((Header.CRC <> $11111111) and
- { ^ for debugging purposes, you can change the CRC field in the file to
- $11111111 to disable CRC checking on the header}
- (Header.CRC <> GetCRC32(Header, SizeOf(Header)-SizeOf(Longint)))) or
- (Header.ID <> UninstallLogID[InstallMode64Bit]) or
- (ReadSafeHeaderString(Header.AppId) <> AppId) then
- Exit;
- ExistingFlags := TUninstallLogFlags((@Header.Flags)^);
- Result := True;
- finally
- F.Free;
- end;
- except
- end;
- end;
- end.
|