447 lines
12 KiB
Text
447 lines
12 KiB
Text
#if 0
|
|
[Code]
|
|
#endif
|
|
|
|
var
|
|
asUninstInf: TArrayOfString; //uninst.inf contents (loaded at start of uninstall, executed at the end)
|
|
|
|
function SplitRegParams(const pInf: String; var oRootKey: Integer; var oKey,oValue: String): Boolean;
|
|
var sRootKey: String;
|
|
d: Integer;
|
|
begin
|
|
Result := False;
|
|
|
|
d := Pos('/',pInf);
|
|
if d = 0 then
|
|
begin
|
|
DebugMsg('SplitRegParams','Malformed line (missing /): ' + pInf);
|
|
exit;
|
|
end;
|
|
|
|
sRootKey := Copy(pInf,1,d - 1);
|
|
oKey := Copy(pInf,d + 1,Length(pInf));
|
|
|
|
if oValue <> 'nil' then
|
|
begin
|
|
d := RevPos('\',oKey);
|
|
if d = 0 then
|
|
begin
|
|
DebugMsg('SplitRegParams','Malformed line (missing \): ' + pInf);
|
|
exit;
|
|
end;
|
|
|
|
oValue := Decode(Copy(oKey,d+1,Length(oKey)));
|
|
oKey := Copy(oKey,1,d-1);
|
|
end;
|
|
|
|
DebugMsg('SplitRegParams','Root: '+sRootKey+', Key:'+oKey + ', Value:'+oValue);
|
|
|
|
case sRootKey of
|
|
'HKCR': oRootKey := HKCR;
|
|
'HKLM': oRootKey := HKLM;
|
|
'HKU': oRootKey := HKU;
|
|
'HKCU': oRootKey := HKCU;
|
|
else
|
|
begin
|
|
DebugMsg('SplitRegParams','Unrecognised root key: ' + sRootKey);
|
|
exit;
|
|
end;
|
|
end;
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
|
|
procedure UninstInfRegKey(const pInf: String; const pIfEmpty: Boolean);
|
|
var sKey,sVal: String;
|
|
iRootKey: Integer;
|
|
begin
|
|
sVal := 'nil';
|
|
if not SplitRegParams(pInf,iRootKey,sKey,sVal) then
|
|
exit;
|
|
|
|
if pIfEmpty then
|
|
begin
|
|
if not RegDeleteKeyIfEmpty(iRootKey,sKey) then
|
|
DebugMsg('UninstInfRegKey','RegDeleteKeyIfEmpty failed');
|
|
end
|
|
else
|
|
begin
|
|
if not RegDeleteKeyIncludingSubkeys(iRootKey,sKey) then
|
|
DebugMsg('UninstInfRegKey','RegDeleteKeyIncludingSubkeys failed');
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure UninstInfRegVal(const pInf: String);
|
|
var sKey,sVal: String;
|
|
iRootKey: Integer;
|
|
begin
|
|
if not SplitRegParams(pInf,iRootKey,sKey,sVal) then
|
|
exit;
|
|
|
|
if not RegDeleteValue(iRootKey,sKey,sVal) then
|
|
DebugMsg('UninstInfREG','RegDeleteKeyIncludingSubkeys failed');
|
|
end;
|
|
|
|
|
|
procedure UninstInfFile(const pFile: String);
|
|
begin
|
|
DebugMsg('UninstInfFile','File: '+pFile);
|
|
|
|
if not DeleteFile(pFile) then
|
|
DebugMsg('UninstInfFile','DeleteFile failed');
|
|
end;
|
|
|
|
|
|
procedure UninstInfDir(const pDir: String);
|
|
begin
|
|
DebugMsg('UninstInfDir','Dir: '+pDir);
|
|
|
|
if not RemoveDir(pDir) then
|
|
DebugMsg('UninstInfDir','RemoveDir failed');
|
|
end;
|
|
|
|
|
|
procedure CreateMessageForm(var frmMessage: TForm; const pMessage: String);
|
|
var lblMessage: TNewStaticText;
|
|
begin
|
|
frmMessage := CreateCustomForm();
|
|
with frmMessage do
|
|
begin
|
|
BorderStyle := bsDialog;
|
|
|
|
ClientWidth := ScaleX(300);
|
|
ClientHeight := ScaleY(48);
|
|
|
|
Caption := CustomMessage('UninstallingAddOnCaption');
|
|
|
|
Position := poScreenCenter;
|
|
|
|
BorderIcons := [];
|
|
end;
|
|
|
|
lblMessage := TNewStaticText.Create(frmMessage);
|
|
with lblMessage do
|
|
begin
|
|
Parent := frmMessage;
|
|
AutoSize := True;
|
|
Caption := pMessage;
|
|
Top := (frmMessage.ClientHeight - Height) div 2;
|
|
Left := (frmMessage.ClientWidth - Width) div 2;
|
|
Visible := True;
|
|
end;
|
|
|
|
frmMessage.Show();
|
|
|
|
frmMessage.Refresh();
|
|
end;
|
|
|
|
|
|
procedure UninstInfRun(const pInf: String);
|
|
var Description,Prog,Params: String;
|
|
Split, ResultCode, Ctr: Integer;
|
|
frmMessage: TForm;
|
|
begin
|
|
DebugMsg('UninstInfRun',pInf);
|
|
|
|
Split := Pos('/',pInf);
|
|
if Split <> 0 then
|
|
begin
|
|
Description := Copy(pInf, 1, Split - 1);
|
|
Prog := Copy(pInf, Split + 1, Length(pInf));
|
|
end else
|
|
begin
|
|
Prog := pInf;
|
|
Description := '';
|
|
end;
|
|
|
|
Split := Pos('/',Prog);
|
|
if Split <> 0 then
|
|
begin
|
|
Params := Copy(Prog, Split + 1, Length(Prog));
|
|
Prog := Copy(Prog, 1, Split - 1);
|
|
end else
|
|
begin
|
|
Params := '';
|
|
end;
|
|
|
|
if not UninstallSilent then //can't manipulate uninstaller messages, so create a form instead
|
|
CreateMessageForm(frmMessage,Description);
|
|
|
|
DebugMsg('UninstInfRun','Running: ' + Prog + '; Params: ' + Params);
|
|
|
|
if Exec(Prog,Params,'',SW_SHOW,ewWaitUntilTerminated,ResultCode) then
|
|
begin
|
|
DebugMsg('UninstInfRun','Exec result: ' + IntToStr(ResultCode));
|
|
|
|
Ctr := 0;
|
|
while FileExists(Prog) do //wait a few seconds for the uninstaller to be deleted - since this is done by a program
|
|
begin //running from a temporary directory, the uninstaller we ran above will exit some time before
|
|
Sleep(UNINSTALL_CHECK_TIME); //it's removed from disk
|
|
Inc(Ctr);
|
|
if Ctr = (UNINSTALL_MAX_WAIT_TIME/UNINSTALL_CHECK_TIME) then //don't wait more than 5 seconds
|
|
break;
|
|
end;
|
|
|
|
end else
|
|
DebugMsg('UninstInfRun','Exec failed: ' + IntToStr(ResultCode) + ' (' + SysErrorMessage(ResultCode) + ')');
|
|
|
|
if not UninstallSilent then
|
|
frmMessage.Free();
|
|
end;
|
|
|
|
(*
|
|
uninst.inf documentation:
|
|
|
|
- Delete Registry keys (with all subkeys):
|
|
RegKey:<RootKey>/<SubKey>
|
|
RootKey = HKCR, HKLM, HKCU, HKU
|
|
SubKey = subkey to delete (warning: this will delete all keys under subkey, so be very careful with it)
|
|
|
|
- Delete empty registry keys:
|
|
RegKeyEmpty:<RootKey>/<SubKey>
|
|
RootKey = HKCR, HKLM, HKCU, HKU
|
|
SubKey = subkey to delete if empty
|
|
|
|
- Delete values from registry:
|
|
RegVal:<RootKey>/<SubKey>/Value
|
|
RootKey = HKCR, HKLM, HKCU, HKU
|
|
SubKey = subkey to delete Value from
|
|
Value = value to delete; \ and % must be escaped as %5c and %25
|
|
|
|
- Delete files:
|
|
File:<Path>
|
|
Path = full path to file
|
|
|
|
- Delete empty directories:
|
|
Dir:<Path>
|
|
|
|
- Run program with parameters:
|
|
Run:<description>/<path>/<params>
|
|
|
|
Directives are parsed from the end of the file backwards as the first step of uninstall.
|
|
|
|
IMPORTANT: From GIMP 2.10.12 onwards (Inno Setup 6 with support for per-user install), Registry keys referring to HKCR will be
|
|
processed by the installer as the first step of install (and the entries will be removed from uninst.inf), since file associations
|
|
code was rewritten.
|
|
|
|
*)
|
|
procedure ParseUninstInf();
|
|
var i,d: Integer;
|
|
sWhat: String;
|
|
begin
|
|
for i := GetArrayLength(asUninstInf) - 1 downto 0 do
|
|
begin
|
|
|
|
DebugMsg('ParseUninstInf',asUninstInf[i]);
|
|
|
|
if (Length(asUninstInf[i]) = 0) or (asUninstInf[i][1] = '#') then //skip comments and empty lines
|
|
continue;
|
|
|
|
d := Pos(':',asUninstInf[i]);
|
|
if d = 0 then
|
|
begin
|
|
DebugMsg('ParseUninstInf','Malformed line: ' + asUninstInf[i]);
|
|
continue;
|
|
end;
|
|
|
|
sWhat := Copy(asUninstInf[i],d+1,Length(asUninstInf[i]));
|
|
|
|
case Copy(asUninstInf[i],1,d) of
|
|
'RegKey:': UninstInfRegKey(sWhat,False);
|
|
'RegKeyEmpty:': UninstInfRegKey(sWhat,True);
|
|
'RegVal:': UninstInfRegVal(sWhat);
|
|
'File:': UninstInfFile(sWhat);
|
|
'Dir:': UninstInfDir(sWhat);
|
|
'Run:': UninstInfRun(sWhat);
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
procedure RestorePointU();
|
|
var
|
|
ResultCode: Integer;
|
|
begin
|
|
UninstallProgressForm.StatusLabel.Caption := CustomMessage('CreatingRestorePoint');
|
|
if not ShellExec('RunAs', 'powershell', '-Command "$job = Start-Job -ScriptBlock { Checkpoint-Computer -Description "GIMP_' + ExpandConstant('{#CUSTOM_GIMP_VERSION}') + '_uninstall" -RestorePointType APPLICATION_UNINSTALL }; Wait-Job $job -Timeout 24; if ($job.State -eq \"Running\") { Stop-Job $job -Confirm:$false }; Receive-Job $job"',
|
|
'', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
|
|
begin
|
|
DebugMsg('RestorePointU','Failed to create restore point. Error code: ' + IntToStr(ResultCode));
|
|
end;
|
|
end;
|
|
|
|
#define API_MAJOR=Copy(GIMP_PKGCONFIG_VERSION,1,Pos(".",GIMP_PKGCONFIG_VERSION)-1)
|
|
|
|
//Taken from https://github.com/GrayWorldFinex/InnoSetupScripts/blob/main/Src%20Code/cdx-gridberd.iss
|
|
Procedure MoveFile(SourceFile, DestFile: {#defined UNICODE ? "Ansi" : ""}String);
|
|
Begin
|
|
FileCopy(SourceFile, DestFile, False);
|
|
End;
|
|
|
|
Function ProcessFiles(S: {#defined UNICODE ? "Ansi" : ""}String): array of {#defined UNICODE ? "Ansi" : ""}String;
|
|
var
|
|
i, k: Integer;
|
|
Begin
|
|
repeat
|
|
i := Pos(',', S);
|
|
k := GetArrayLength(Result);
|
|
SetArrayLength(Result, k + 1);
|
|
If (i > 0) then begin
|
|
Result[k] := Copy(S, 1, i - 1);
|
|
Delete(S, 1, i); end
|
|
else begin
|
|
Result[k] := S;
|
|
SetLength(S, 0);
|
|
end;
|
|
until (Length(S) = 0);
|
|
End;
|
|
|
|
Procedure CopyFiles(SourceDir, DestDir, ExcludeFiles: {#defined UNICODE ? "Ansi" : ""}String);
|
|
var
|
|
FSR, DSR: TFindRec;
|
|
FindResult, Exclude: Boolean;
|
|
SourceFile, DestFile:{#defined UNICODE ? "Ansi" : ""}String;
|
|
i, k: Integer;
|
|
SS: Array of {#defined UNICODE ? "Ansi" : ""}String;
|
|
Begin
|
|
SetArrayLength(SS, 0);
|
|
SS := ProcessFiles(ExcludeFiles);
|
|
k := GetArrayLength(SS);
|
|
If not DirExists(DestDir) Then Begin
|
|
CreateDir(DestDir);
|
|
End;
|
|
FindResult := FindFirst(AddBackslash(SourceDir) + '*.*', FSR);
|
|
Try
|
|
While FindResult Do Begin
|
|
If FSR.Attributes and FILE_ATTRIBUTE_DIRECTORY = 0 Then Begin
|
|
Exclude := False;
|
|
For i := 0 To k - 1 Do Begin
|
|
If AnsiLowercase(SS[i]) = AnsiLowercase(FSR.Name) Then Begin
|
|
Exclude := True;
|
|
Break;
|
|
End;
|
|
End;
|
|
If not Exclude Then Begin
|
|
SourceFile := AddBackslash(SourceDir) + FSR.Name;
|
|
DestFile := AddBackslash(DestDir) + FSR.Name;
|
|
MoveFile(SourceFile, DestFile);
|
|
End;
|
|
End;
|
|
FindResult := FindNext(FSR);
|
|
End;
|
|
FindResult := FindFirst(AddBackslash(SourceDir) + '*.*', DSR);
|
|
While FindResult Do Begin
|
|
If ((DSR.Attributes and FILE_ATTRIBUTE_DIRECTORY) = FILE_ATTRIBUTE_DIRECTORY) and not ((DSR.Name = '.') or (DSR.Name = '..')) Then Begin
|
|
CopyFiles(AddBackslash(SourceDir) + DSR.Name, AddBackslash(DestDir) + DSR.Name, ExcludeFiles);
|
|
End;
|
|
FindResult := FindNext(DSR);
|
|
End;
|
|
Finally
|
|
FindClose(FSR);
|
|
FindClose(DSR);
|
|
End;
|
|
End;
|
|
|
|
function Time(ATimerPtr: integer): integer; external '_time32@ucrtbase.dll cdecl';
|
|
|
|
procedure CurUninstallStepChanged(CurStep: TUninstallStep);
|
|
var
|
|
gimp_directory: String;
|
|
begin
|
|
DebugMsg('CurUninstallStepChanged','');
|
|
case CurStep of
|
|
usUninstall:
|
|
begin
|
|
//Try to make restore point before unninstalling (like before installing, see RestorePoint() on *gimp3264.iss)
|
|
if IsAdminInstallMode() then begin
|
|
RestorePointU();
|
|
UninstallProgressForm.StatusLabel.Caption := FmtMessage(SetupMessage(msgStatusUninstalling),['GIMP']);
|
|
end;
|
|
|
|
LoadStringsFromFile(ExpandConstant('{app}\uninst\uninst.inf'),asUninstInf);
|
|
ParseUninstInf();
|
|
end;
|
|
usPostUninstall:
|
|
begin
|
|
//Offer option to remove %AppData%/GIMP/GIMP_APP_VERSION dir so have a future clean install
|
|
if not IsAdminInstallMode() then begin
|
|
gimp_directory := GetEnv('GIMP{#API_MAJOR}_DIRECTORY');
|
|
if gimp_directory = '' then begin
|
|
gimp_directory := ExpandConstant('{userappdata}\GIMP\{#GIMP_APP_VERSION}');
|
|
end;
|
|
if DirExists(gimp_directory + '\') then begin
|
|
if SuppressibleMsgBox(CustomMessage('UninstallConfig'), mbError, MB_YESNO or MB_DEFBUTTON2, IDNO) = IDYES then begin
|
|
CopyFiles(gimp_directory, ExpandConstant('{userdesktop}\gimp_{#GIMP_APP_VERSION}_backup_' + Format('%d', [Time(0)])), '');
|
|
DelTree(gimp_directory, True, True, True);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure AssociationsCleanUp();
|
|
var i, d, countNew,countUI: Integer;
|
|
asTemp, asNew: TArrayOfString;
|
|
sKey,sVal: String;
|
|
iRootKey: Integer;
|
|
begin
|
|
if FileExists(ExpandConstant('{app}\uninst\uninst.inf')) then
|
|
begin
|
|
DebugMsg('AssociationsCleanUp','Parsing old uninst.inf');
|
|
LoadStringsFromFile(ExpandConstant('{app}\uninst\uninst.inf'),asTemp);
|
|
|
|
countNew := 0;
|
|
countUI := 0;
|
|
SetArrayLength(asNew, GetArrayLength(asTemp));
|
|
SetArrayLength(asUninstInf, GetArrayLength(asTemp));
|
|
|
|
for i := 0 to GetArrayLength(asTemp) - 1 do //extract associations-related entries from uninst.inf
|
|
begin
|
|
|
|
if (Length(asTemp[i]) = 0) or (asTemp[i][1] = '#') then //comment/empty line
|
|
begin
|
|
asNew[countNew] := asTemp[i];
|
|
Inc(countNew);
|
|
continue;
|
|
end;
|
|
|
|
d := Pos(':',asTemp[i]);
|
|
if d = 0 then //something wrong, ignore
|
|
continue;
|
|
|
|
if Copy(asTemp[i],1,3) = 'Reg' then
|
|
begin
|
|
|
|
sVal := 'nil';
|
|
if not SplitRegParams(Copy(asTemp[i], d + 1, Length(asTemp[i])),iRootKey,sKey,sVal) then
|
|
continue; //malformed line, ignore
|
|
|
|
if iRootKey = HKCR then //old association, prepare for cleanup
|
|
begin
|
|
DebugMsg('AssociationsCleanUp','Preparing for cleanup: '+asTemp[i]);
|
|
asUninstInf[countUI] := asTemp[i];
|
|
Inc(countUI);
|
|
continue;
|
|
end;
|
|
|
|
end;
|
|
|
|
//something else, keep for new uninst.inf
|
|
asNew[countNew] := asTemp[i];
|
|
Inc(countNew);
|
|
|
|
end;
|
|
|
|
SetArrayLength(asNew, countNew);
|
|
SetArrayLength(asUninstInf, countUI);
|
|
|
|
SaveStringsToUTF8File(ExpandConstant('{app}\uninst\uninst.inf'), asNew, False); //replace uninst.inf with a cleaned one
|
|
|
|
ParseUninstInf(); //remove old associations
|
|
end;
|
|
end;
|