Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

InnoSetup: don't uninstall changed files

Tags:

inno-setup

How to tell InnoSetup to not uninstall (text) files which had been changed by the user (== are different from those installed by InnoSetup)?

Or maybe more difficult: when installing a new version over an existing, InnoSetup should ask the user whether to overwrite the changed file, but on a pure uninstall, it should uninstall it without asking.

like image 376
Mot Avatar asked May 21 '12 13:05

Mot


2 Answers

I recently had a similar problem. This was my solution to detect if a text file (profile) has been changed from the one installed during the last installation run:

Use ISPP (Inno Setup Pre-Processor) to create the list of text files and their hashes at compile time:

[Files]
; ...
#define FindHandle
#define FindResult
#define Mask "Profiles\*.ini"
#sub ProcessFoundFile
   #define FileName "Profiles\" + FindGetFileName(FindHandle)
   #define FileMd5 GetMd5OfFile(FileName)
   Source: {#FileName}; DestDir: {app}\Profiles; Components: profiles; \
      Check: ProfileCheck('{#FileMd5}'); AfterInstall: ProfileAfterInstall('{#FileMd5}');
#endsub
#for {FindHandle = FindResult = FindFirst(Mask, 0); FindResult; FindResult = FindNext(FindHandle)} ProcessFoundFile

At the top of the "Code" section I define some useful things:

[Code]
var
   PreviousDataCache : tStringList;

function InitializeSetup() : boolean;
begin
   // Initialize global variable
   PreviousDataCache := tStringList.Create();
   result := TRUE;
end;

function BoolToStr( Value : boolean ) : string;
begin
   if ( not Value ) then
      result := 'false'
   else
      result := 'true';
end;

In the "Check" event handler I compare the hashes of previous install and current file:

function ProfileCheck( FileMd5 : string ) : boolean;
var
   TargetFileName, TargetFileMd5, PreviousFileMd5 : string;
   r : integer;
begin
   result := FALSE;
   TargetFileName := ExpandConstant(CurrentFileName());
   Log('Running check procedure for file: ' + TargetFileName);

   if not FileExists(TargetFileName) then
   begin
      Log('Check result: Target file does not exist yet.');
      result := TRUE;
      exit;
   end;

   try
      TargetFileMd5 := GetMd5OfFile(TargetFileName);
   except
      TargetFileMd5 := '(error)';
   end;
   if ( CompareText(TargetFileMd5, FileMd5) = 0 ) then
   begin
      Log('Check result: Target matches file from setup.');
      result := TRUE;
      exit;
   end;

   PreviousFileMd5 := GetPreviousData(ExtractFileName(TargetFileName), '');
   if ( PreviousFileMd5 = '' ) then
   begin
      r := MsgBox(TargetFileName + #10#10 + 
         'The existing file is different from the one Setup is trying to install. ' + 
         'It is recommended that you keep the existing file.' + #10#10 +
         'Do you want to keep the existing file?', mbConfirmation, MB_YESNO);
      result := (r = idNo);
      Log('Check result: ' + BoolToStr(result));
   end
   else if ( CompareText(PreviousFileMd5, TargetFileMd5) <> 0 ) then
   begin
      r := MsgBox(TargetFileName + #10#10 + 
         'The existing file has been modified since the last run of Setup. ' +
         'It is recommended that you keep the existing file.' + #10#10 +
         'Do you want to keep the existing file?', mbConfirmation, MB_YESNO);
      result := (r = idNo);
      Log('Check result: ' + BoolToStr(result));
   end
   else
   begin
      Log('Check result: Existing target has no local modifications.');
      result := TRUE;
   end;
end;

In the "AfterInstall" event handler I mark the file hash to be stored in Registry later. Because in my tests the event was triggered even if the file move failed (target file is read-only) I compare the hash again to find out if the file move was successful:

procedure ProfileAfterInstall( FileMd5 : string );
var
   TargetFileName, TargetFileMd5 : string;
begin
   TargetFileName := ExpandConstant(CurrentFileName());
   try
      TargetFileMd5 := GetMd5OfFile(TargetFileName);
   except
      TargetFileMd5 := '(error)';
   end;
   if ( CompareText(TargetFileMd5, FileMd5) = 0 ) then
   begin
      Log('Storing hash of installed file: ' + TargetFileName);
      PreviousDataCache.Add(ExtractFileName(TargetFileName) + '=' + FileMd5);
   end;
end;

procedure RegisterPreviousData( PreviousDataKey : integer );
var
   Name, Value : string;
   i, n : integer;
begin
   for i := 0 to PreviousDataCache.Count-1 do
   begin
      Value := PreviousDataCache.Strings[i];
      n := Pos('=', Value);
      if ( n > 0 ) then
      begin
         Name := Copy(Value, 1, n-1);
         Value := Copy(Value, n+1, MaxInt);
         SetPreviousData(PreviousDataKey, Name, Value);
      end;
   end;
end;
like image 59
blerontin Avatar answered Nov 06 '22 15:11

blerontin


Inno can't do this check natively.

To not replace changed files during install, you'll need to use custom [Code] to do a checksum and compare against a known good value that is precomputed or saved from the previous install.

To avoid removing them during uninstall, you'll need to disable Inno's own uninstall for that file and check against the same checksum before removing them, again in [Code].

Note that it's better to keep any files the user can edit outside of the setup to handle this situation better and to correctly adhere to the application guidelines.

like image 24
Deanna Avatar answered Nov 06 '22 15:11

Deanna