2024-08-03 20:33:25 +02:00
|
|
|
unit Setup.UninstallLog;
|
2011-10-06 20:53:09 +02:00
|
|
|
|
|
|
|
{
|
|
|
|
Inno Setup
|
2025-06-11 20:46:41 +02:00
|
|
|
Copyright (C) 1997-2025 Jordan Russell
|
2011-10-06 20:53:09 +02:00
|
|
|
Portions by Martijn Laan
|
|
|
|
For conditions of distribution and use, see LICENSE.TXT.
|
|
|
|
|
2024-08-03 20:33:25 +02:00
|
|
|
Uninstallation functions
|
2011-10-06 20:53:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
interface
|
|
|
|
|
|
|
|
uses
|
2024-08-04 19:59:25 +02:00
|
|
|
Windows, SysUtils, Shared.Int64Em, Shared.FileClass, Shared.CommonFunc;
|
2011-10-06 20:53:09 +02:00
|
|
|
|
|
|
|
const
|
2024-03-31 16:10:26 +02:00
|
|
|
HighestSupportedVersion = 1048;
|
2011-10-06 20:53:09 +02:00
|
|
|
{ Each time the format of the uninstall log changes (usually a new entry type
|
2018-07-25 09:03:20 +02:00
|
|
|
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
|
2020-07-07 18:40:10 +02:00
|
|
|
incompatibilities with future Inno Setup releases. It's recommended that you
|
2011-10-06 20:53:09 +02:00
|
|
|
use the "utUserDefined" log entry type if you wish to implement your own
|
2024-03-31 16:10:26 +02:00
|
|
|
custom uninstall log entries; see below for more information. }
|
2011-10-06 20:53:09 +02:00
|
|
|
|
|
|
|
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
|
2020-07-07 18:40:10 +02:00
|
|
|
incompatibilities with future Inno Setup releases.) The first field in a
|
2011-10-06 20:53:09 +02:00
|
|
|
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;
|
2020-06-11 15:28:58 +02:00
|
|
|
utRun_DontLogParameters = 512;
|
2024-05-08 08:01:10 +02:00
|
|
|
utRun_LogOutput = 1024;
|
2011-10-06 20:53:09 +02:00
|
|
|
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;
|
2020-07-03 10:05:28 +02:00
|
|
|
utDeleteFile_PerUserFont = 8192;
|
2011-10-06 20:53:09 +02:00
|
|
|
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;
|
2020-07-20 19:46:56 +02:00
|
|
|
Data: array[0..$6FFFFFFF] of Byte; { *must* be last field }
|
2011-10-06 20:53:09 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
TDeleteUninstallDataFilesProc = procedure;
|
|
|
|
|
|
|
|
TUninstallLogFlags = set of (ufAdminInstalled, ufDontCheckRecCRCs,
|
2019-01-05 16:49:35 +01:00
|
|
|
ufModernStyle, ufAlwaysRestart, ufChangesEnvironment, ufWin64,
|
2019-01-10 06:08:16 +01:00
|
|
|
ufPowerUserInstalled, ufAdminInstallMode);
|
2011-10-06 20:53:09 +02:00
|
|
|
|
|
|
|
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
|
2019-01-29 20:40:51 +01:00
|
|
|
Messages, ShlObj, AnsiStrings,
|
2024-08-06 19:12:28 +02:00
|
|
|
PathFunc, Shared.Struct, SetupLdrAndSetup.Messages, Shared.SetupMessageIDs, Setup.InstFunc,
|
2024-08-06 13:54:25 +02:00
|
|
|
Setup.InstFunc.Ole, SetupLdrAndSetup.RedirFunc, Compression.Base,
|
2024-08-03 20:33:25 +02:00
|
|
|
Setup.LoggingFunc, Setup.RegDLL, Setup.Helper, Setup.DotNetFunc;
|
2011-10-06 20:53:09 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2025-05-20 13:31:46 +02:00
|
|
|
procedure LoggedRestartDeleteDir(const DisableFsRedir: Boolean; Dir: String);
|
2011-10-06 20:53:09 +02:00
|
|
|
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';
|
|
|
|
|
2025-05-20 13:31:46 +02:00
|
|
|
function LoggedDeleteDir(const DisableFsRedir: Boolean; const DirName: String;
|
2011-10-06 20:53:09 +02:00
|
|
|
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? }
|
2025-04-05 12:50:16 +02:00
|
|
|
if (Attribs <> INVALID_FILE_ATTRIBUTES) and
|
2011-10-06 20:53:09 +02:00
|
|
|
(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
|
2025-05-20 13:31:46 +02:00
|
|
|
IsDirEmpty(DisableFsRedir, DirName) then begin
|
2011-10-06 20:53:09 +02:00
|
|
|
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
|
2024-03-29 08:00:28 +01:00
|
|
|
ListContainsPathOrSubdir(RestartDeleteDirList, DirName) then begin
|
2011-10-06 20:53:09 +02:00
|
|
|
LogFmt('Failed to delete directory (%d). Will delete on restart (if empty).',
|
|
|
|
[LastError]);
|
2025-05-20 13:31:46 +02:00
|
|
|
LoggedRestartDeleteDir(DisableFsRedir, DirName);
|
2011-10-06 20:53:09 +02:00
|
|
|
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]);
|
|
|
|
|
2024-03-31 16:10:26 +02:00
|
|
|
SetLength(X, SizeOf(Byte) + SizeOf(Integer));
|
|
|
|
X[1] := AnsiChar($FE);
|
|
|
|
Integer((@X[2])^) := Integer(-L);
|
|
|
|
S := S + X;
|
2011-10-06 20:53:09 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2021-03-05 17:16:20 +01:00
|
|
|
function LoggedDeleteDirProc(const DisableFsRedir: Boolean; const DirName: String;
|
2011-10-06 20:53:09 +02:00
|
|
|
const Param: Pointer): Boolean;
|
|
|
|
begin
|
2025-05-20 13:31:46 +02:00
|
|
|
Result := LoggedDeleteDir(DisableFsRedir, DirName, PDeleteDirData(Param)^.DirsNotRemoved, nil);
|
2011-10-06 20:53:09 +02:00
|
|
|
end;
|
|
|
|
|
2021-03-05 17:16:20 +01:00
|
|
|
function LoggedDeleteFileProc(const DisableFsRedir: Boolean; const FileName: String;
|
2011-10-06 20:53:09 +02:00
|
|
|
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;
|
|
|
|
|
2024-05-05 19:32:12 +02:00
|
|
|
procedure RunExecLog(const S: String; const Error, FirstLine: Boolean; const Data: NativeInt);
|
2024-05-05 17:23:33 +02:00
|
|
|
begin
|
2024-05-05 19:32:12 +02:00
|
|
|
if not Error and FirstLine then
|
2024-05-05 17:23:33 +02:00
|
|
|
Log('Running Exec output:');
|
|
|
|
Log(S);
|
|
|
|
end;
|
|
|
|
|
2011-10-06 20:53:09 +02:00
|
|
|
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;
|
|
|
|
|
2021-03-05 17:16:20 +01:00
|
|
|
function LoggedFileDelete(const Filename: String; const DisableFsRedir,
|
2011-10-06 20:53:09 +02:00
|
|
|
NotifyChange, RestartDelete, RemoveReadOnly: Boolean): Boolean;
|
|
|
|
var
|
|
|
|
ExistingAttr, LastError: DWORD;
|
|
|
|
begin
|
|
|
|
Result := True;
|
|
|
|
|
|
|
|
{ Automatically delete generated indexes associated with help files }
|
2024-04-29 19:33:37 +02:00
|
|
|
if SameText(PathExtractExt(Filename), '.hlp') then begin
|
2021-03-05 17:16:20 +01:00
|
|
|
LoggedFileDelete(PathChangeExt(Filename, '.gid'), DisableFsRedir, False, False, False);
|
|
|
|
LoggedFileDelete(PathChangeExt(Filename, '.fts'), DisableFsRedir, False, False, False);
|
2011-10-06 20:53:09 +02:00
|
|
|
end
|
2024-04-29 19:33:37 +02:00
|
|
|
else if SameText(PathExtractExt(Filename), '.chm') then
|
2021-03-05 17:16:20 +01:00
|
|
|
LoggedFileDelete(PathChangeExt(Filename, '.chw'), DisableFsRedir, False, False, False);
|
2011-10-06 20:53:09 +02:00
|
|
|
|
|
|
|
{ Automatically unpin shortcuts }
|
2024-04-29 19:33:37 +02:00
|
|
|
if SameText(PathExtractExt(Filename), '.lnk') then
|
2011-10-06 20:53:09 +02:00
|
|
|
UnpinShellLink(Filename);
|
|
|
|
|
|
|
|
if NewFileExistsRedir(DisableFsRedir, Filename) then begin
|
|
|
|
LogFmt('Deleting file: %s', [FileName]);
|
|
|
|
if RemoveReadOnly then begin
|
|
|
|
ExistingAttr := GetFileAttributesRedir(DisableFsRedir, Filename);
|
2025-04-05 12:50:16 +02:00
|
|
|
if (ExistingAttr <> INVALID_FILE_ATTRIBUTES) and
|
2011-10-06 20:53:09 +02:00
|
|
|
(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
|
2025-05-20 13:31:46 +02:00
|
|
|
RestartReplace(DisableFsRedir, Filename, '');
|
2011-10-06 20:53:09 +02:00
|
|
|
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;
|
|
|
|
|
2021-03-05 17:16:20 +01:00
|
|
|
function LoggedDecrementSharedCount(const Filename: String;
|
2011-10-06 20:53:09 +02:00
|
|
|
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;
|
|
|
|
|
2021-03-05 17:16:20 +01:00
|
|
|
procedure LoggedUnregisterServer(const Is64Bit: Boolean; const Filename: String);
|
2011-10-06 20:53:09 +02:00
|
|
|
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;
|
|
|
|
|
2021-03-05 17:16:20 +01:00
|
|
|
procedure LoggedUnregisterTypeLibrary(const Is64Bit: Boolean;
|
2011-10-06 20:53:09 +02:00
|
|
|
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;
|
|
|
|
|
2021-03-05 17:16:20 +01:00
|
|
|
procedure LoggedUninstallAssembly(const StrongAssemblyName: String);
|
2011-10-06 20:53:09 +02:00
|
|
|
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;
|
|
|
|
|
2021-03-05 17:16:20 +01:00
|
|
|
procedure LoggedProcessDirsNotRemoved;
|
2011-10-06 20:53:09 +02:00
|
|
|
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);
|
2025-05-20 13:31:46 +02:00
|
|
|
LoggedDeleteDir(DisableFsRedir, S, nil, RestartDeleteDirList[DisableFsRedir]);
|
2011-10-06 20:53:09 +02:00
|
|
|
end;
|
|
|
|
end;
|
2021-03-05 17:16:20 +01:00
|
|
|
|
|
|
|
function GetLogIniFilename(const Filename: String): String;
|
|
|
|
begin
|
|
|
|
if Filename <> '' then
|
|
|
|
Result := Filename
|
|
|
|
else
|
|
|
|
Result := 'win.ini';
|
|
|
|
end;
|
2011-10-06 20:53:09 +02:00
|
|
|
|
|
|
|
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;
|
2021-03-05 17:16:20 +01:00
|
|
|
Filename, Section, Key: String;
|
|
|
|
Subkey, ValueName: PChar;
|
2011-10-06 20:53:09 +02:00
|
|
|
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]);
|
2020-06-11 15:28:58 +02:00
|
|
|
if (CurRec^.ExtraData and utRun_DontLogParameters = 0) and (CurRecData[1] <> '') then
|
2011-10-06 20:53:09 +02:00
|
|
|
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
|
2024-07-02 16:02:59 +01:00
|
|
|
var OutputReader: TCreateProcessOutputReader := nil;
|
|
|
|
try
|
|
|
|
if GetLogActive and (CurRec^.ExtraData and utRun_LogOutput <> 0) then
|
|
|
|
OutputReader := TCreateProcessOutputReader.Create(RunExecLog, 0);
|
2025-05-20 13:31:46 +02:00
|
|
|
if not InstExec(CurRec^.ExtraData and utRun_DisableFsRedir <> 0,
|
2024-07-02 16:02:59 +01:00
|
|
|
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;
|
2011-10-06 20:53:09 +02:00
|
|
|
end;
|
|
|
|
end else
|
|
|
|
Log('File doesn''t exist. Skipping.');
|
|
|
|
end
|
|
|
|
else begin
|
|
|
|
Log('Running ShellExec filename: ' + CurRecData[0]);
|
2020-06-11 15:28:58 +02:00
|
|
|
if (CurRec^.ExtraData and utRun_DontLogParameters = 0) and (CurRecData[1] <> '') then
|
2011-10-06 20:53:09 +02:00
|
|
|
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
|
2021-03-05 17:16:20 +01:00
|
|
|
SharedCountDidReachZero := LoggedDecrementSharedCount(CurRecData[0],
|
2011-10-06 20:53:09 +02:00
|
|
|
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
|
2021-03-05 17:16:20 +01:00
|
|
|
LoggedUnregisterServer(CurRec^.ExtraData and utDeleteFile_DisableFsRedir <> 0,
|
2011-10-06 20:53:09 +02:00
|
|
|
CurRecData[0]);
|
|
|
|
end;
|
|
|
|
if CurRec^.ExtraData and utDeleteFile_RegisteredTypeLib <> 0 then begin
|
2021-03-05 17:16:20 +01:00
|
|
|
LoggedUnregisterTypeLibrary(CurRec^.ExtraData and utDeleteFile_DisableFsRedir <> 0,
|
2011-10-06 20:53:09 +02:00
|
|
|
CurRecData[0]);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
if CurRec^.ExtraData and utDeleteFile_IsFont <> 0 then begin
|
|
|
|
LogFmt('Unregistering font: %s', [CurRecData[2]]);
|
2020-07-03 10:05:28 +02:00
|
|
|
UnregisterFont(CurRecData[2], CurRecData[3], CurRec^.ExtraData and utDeleteFile_PerUserFont <> 0);
|
2011-10-06 20:53:09 +02:00
|
|
|
end;
|
|
|
|
if CurRec^.ExtraData and utDeleteFile_GacInstalled <> 0 then
|
2021-03-05 17:16:20 +01:00
|
|
|
LoggedUninstallAssembly(CurRecData[4]);
|
2011-10-06 20:53:09 +02:00
|
|
|
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
|
2025-05-20 13:31:46 +02:00
|
|
|
if DelTree(CurRec^.ExtraData and utDeleteDirOrFiles_DisableFsRedir <> 0,
|
2011-10-06 20:53:09 +02:00
|
|
|
CurRecData[0], CurRec^.ExtraData and utDeleteDirOrFiles_IsDir <> 0,
|
|
|
|
CurRec^.ExtraData and utDeleteDirOrFiles_DeleteFiles <> 0,
|
|
|
|
CurRec^.ExtraData and utDeleteDirOrFiles_DeleteSubdirsAlso <> 0,
|
2021-03-05 17:16:20 +01:00
|
|
|
False, LoggedDeleteDirProc, LoggedDeleteFileProc, @DeleteDirData) then begin
|
2011-10-06 20:53:09 +02:00
|
|
|
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 }
|
2021-03-05 17:16:20 +01:00
|
|
|
Filename := CurRecData[1];
|
|
|
|
if CallFromUninstaller or (Filename = '') then
|
|
|
|
Filename := CurRecData[0];
|
2011-10-06 20:53:09 +02:00
|
|
|
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
|
2021-03-05 17:16:20 +01:00
|
|
|
if not LoggedFileDelete(Filename, CurRec^.ExtraData and utDeleteFile_DisableFsRedir <> 0,
|
2011-10-06 20:53:09 +02:00
|
|
|
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
|
2021-03-05 17:16:20 +01:00
|
|
|
LoggedDecrementSharedCount(CurRecData[0],
|
2011-10-06 20:53:09 +02:00
|
|
|
CurRec^.ExtraData and utDeleteFile_SharedFileIn64BitKey <> 0);
|
|
|
|
{ Delete file only if it's a temp file }
|
2021-03-05 17:16:20 +01:00
|
|
|
if Filename <> CurRecData[0] then
|
|
|
|
if not LoggedFileDelete(Filename, CurRec^.ExtraData and utDeleteFile_DisableFsRedir <> 0,
|
2011-10-06 20:53:09 +02:00
|
|
|
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
|
2021-03-05 17:16:20 +01:00
|
|
|
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);
|
2011-10-06 20:53:09 +02:00
|
|
|
end;
|
|
|
|
utIniDeleteSection: begin
|
2021-03-05 17:16:20 +01:00
|
|
|
Section := CurRecData[1];
|
|
|
|
Filename := CurRecData[0];
|
2011-10-06 20:53:09 +02:00
|
|
|
if (CurRec^.ExtraData and utIniDeleteSection_OnlyIfEmpty = 0) or
|
2021-03-05 17:16:20 +01:00
|
|
|
IsIniSectionEmpty(Section, Filename) then begin
|
|
|
|
LogFmt('Deleting INI section: %s in %s', [Section, GetLogIniFilename(Filename)]);
|
|
|
|
DeleteIniSection(Section, Filename);
|
|
|
|
end;
|
2011-10-06 20:53:09 +02:00
|
|
|
end;
|
|
|
|
utRegDeleteEntireKey: begin
|
|
|
|
CrackRegExtraData(CurRec^.ExtraData, RegView, RootKey);
|
2021-03-05 17:16:20 +01:00
|
|
|
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]);
|
2011-10-06 20:53:09 +02:00
|
|
|
Result := False;
|
2021-03-05 17:16:20 +01:00
|
|
|
end;
|
2011-10-06 20:53:09 +02:00
|
|
|
end;
|
|
|
|
utRegClearValue: begin
|
|
|
|
CrackRegExtraData(CurRec^.ExtraData, RegView, RootKey);
|
2021-03-05 17:16:20 +01:00
|
|
|
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]);
|
2011-10-06 20:53:09 +02:00
|
|
|
Result := False;
|
2021-03-05 17:16:20 +01:00
|
|
|
end;
|
2011-10-06 20:53:09 +02:00
|
|
|
RegCloseKey(K);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
utRegDeleteKeyIfEmpty: begin
|
|
|
|
CrackRegExtraData(CurRec^.ExtraData, RegView, RootKey);
|
2021-03-05 17:16:20 +01:00
|
|
|
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]);
|
2011-10-06 20:53:09 +02:00
|
|
|
Result := False;
|
2021-03-05 17:16:20 +01:00
|
|
|
end;
|
2011-10-06 20:53:09 +02:00
|
|
|
end;
|
|
|
|
utRegDeleteValue: begin
|
|
|
|
CrackRegExtraData(CurRec^.ExtraData, RegView, RootKey);
|
2021-03-05 17:16:20 +01:00
|
|
|
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;
|
2011-10-06 20:53:09 +02:00
|
|
|
RegCloseKey(K);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
utDecrementSharedCount: begin
|
2021-03-05 17:16:20 +01:00
|
|
|
LoggedDecrementSharedCount(CurRecData[0],
|
2011-10-06 20:53:09 +02:00
|
|
|
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. }
|
2021-03-05 17:16:20 +01:00
|
|
|
LoggedProcessDirsNotRemoved;
|
2011-10-06 20:53:09 +02:00
|
|
|
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
|
2019-01-29 20:40:51 +01:00
|
|
|
AnsiStrings.StrPLCopy(Dest, AnsiString(Source), MaxDestBytes - 1);
|
2011-10-06 20:53:09 +02:00
|
|
|
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;
|
|
|
|
|
|
|
|
if F.Position.Hi <> 0 then
|
|
|
|
InternalError('EndOffset range exceeded');
|
|
|
|
Header.EndOffset := F.Position.Lo;
|
|
|
|
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;
|
2019-01-05 16:49:35 +01:00
|
|
|
TUninstallLogFlags((@Header.Flags)^) := TUninstallLogFlags((@Header.Flags)^) - [ufModernStyle] + Flags;
|
2011-10-06 20:53:09 +02:00
|
|
|
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
|
2025-06-11 20:46:41 +02:00
|
|
|
EndOffset := To64(Header.EndOffset);
|
2011-10-06 20:53:09 +02:00
|
|
|
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.
|