InnoSetup: don't uninstall changed files

后端 未结 2 2007
滥情空心
滥情空心 2021-02-06 03:17

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: whe

相关标签:
2条回答
  • 2021-02-06 03:53

    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;
    
    0 讨论(0)
  • 2021-02-06 04:04

    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.

    0 讨论(0)
提交回复
热议问题