| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411 |
- 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.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,
- ufWizardModern, ufAlwaysRestart, ufChangesEnvironment, ufWin64,
- ufPowerUserInstalled, ufAdminInstallMode, ufWizardDarkStyleDark,
- ufWizardDarkStyleDynamic, ufWizardBorderStyled,
- ufWizardLightButtonsUnstyled, ufWizardKeepAspectRatio);
- 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;
- WizardSizePercentX, WizardSizePercentY: 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, Setup.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: UInt32;
- Flags: Integer;
- WizardSizePercentX, WizardSizePercentY: Integer;
- Reserved: array[0..24] of Integer; { reserved for future use }
- CRC: Longint;
- end;
- TUninstallCrcHeader = packed record
- Size, NotSize: Cardinal;
- CRC: Longint;
- end;
- TUninstallFileRec = packed record
- Typ: TUninstallRecTyp;
- ExtraData: Integer;
- 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 AppId/AppName/Flags/WizardSizePercentX/Y }
- begin
- while FLastList <> nil do
- Delete(FLastList);
- FCount := 0;
- AppId := '';
- AppName := '';
- Flags := [];
- WizardSizePercentX := 0;
- WizardSizePercentY := 0;
- 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)^) -
- [ufWizardModern, ufWizardDarkStyleDark, ufWizardDarkStyleDynamic, ufWizardBorderStyled, ufWizardLightButtonsUnstyled, ufWizardKeepAspectRatio] + Flags;
- Header.WizardSizePercentX := WizardSizePercentX;
- Header.WizardSizePercentY := WizardSizePercentY;
- 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
- CrcHeader: TUninstallCrcHeader;
- begin
- var EndOffset: Int64 := Header.EndOffset;
- while BufLeft = 0 do begin
- var Ofs := F.Position;
- Inc(Ofs, SizeOf(CrcHeader));
- if Ofs > EndOffset then
- Corrupt;
- if F.Read(CrcHeader, SizeOf(CrcHeader)) <> SizeOf(CrcHeader) then
- Corrupt;
- Ofs := F.Position;
- Inc(Ofs, CrcHeader.Size);
- if (CrcHeader.Size <> not CrcHeader.NotSize) or
- (Cardinal(CrcHeader.Size) > Cardinal(SizeOf(Buffer))) or
- (Ofs > EndOffset) 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)^);
- WizardSizePercentX := Header.WizardSizePercentX;
- WizardSizePercentY := Header.WizardSizePercentY;
- 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.
|