Inno Setup: Reading a file from installer during uninstallation

后端 未结 1 1918
独厮守ぢ
独厮守ぢ 2021-01-03 14:47

Using the following code during uninstall

BitmapImage := TBitmapImage.Create(InstallTopPanel);
BitmapImage.AutoS         


        
相关标签:
1条回答
  • 2021-01-03 15:45

    Correct, the ExtractTemporaryFile extracts files from the installer. Therefore it cannot work in the uninstaller as the installer is not available anymore.

    Also note that you cannot extract the file referenced to by the WizardSmallImageFile directive from the installer anyway. You have to add your own copy.


    If you need to use some file during uninstallation, you have to install it in the installer and then use the installed copy in the uninstaller.

    [Files]
    Source: "WizardSmallImageFile.bmp"; DestDir: "{app}";
    
    [Code]
    
    function InitializeUninstall(): Boolean;
    begin
      ...
      BitmapImage := TBitmapImage.Create(...);
      ...
      BitmapImage.Bitmap.LoadFromFile(ExpandConstant('{app}\WizardSmallImageFile.bmp'));
      ...
    end;
    

    If you want to do without installing the file, you can embed the image data into the code.

    Unfortunately the Unicode Inno Setup is quite limited when dealing with binary data as it tends to try to convert everything to UTF-8. But after numerous tries I've ended up with some working code.

    Note that the code uses a PowerShell code invoked from Inno Setup preprocessor - The PowerShell is needed on compile-time only, not on run/install-time.

    Add this code somewhere to the front of your [Code] section:

    function CryptStringToBinary(
      sz: string; cch: LongWord; flags: LongWord; binary: string; var size: LongWord;
      skip: LongWord; flagsused: LongWord): Integer;
      external 'CryptStringToBinaryW@crypt32.dll stdcall';
    
    const
      CRYPT_STRING_HEX = $04;
    
    procedure WriteBinaryStringToStream(S: string; Stream: TStream);
    var
      Buffer: string;
      Size: LongWord;
    begin
      SetLength(Buffer, (Length(S) div 4) + 1);
      Size := Length(S) div 2;
      if (CryptStringToBinary(S, Length(S), CRYPT_STRING_HEX, Buffer, Size, 0, 0) = 0) or
         (Size <> Length(S) div 2) then
      begin
        RaiseException('Error decoding binary string');
      end;
    
      Stream.WriteBuffer(Buffer, Size);
    end;  
    
    function StreamFromBinaryString(S: string): TStream;
    begin
      Result := TStringStream.Create('');
      WriteBinaryStringToStream(S, Result);
      Result.Position := 0;
    end;
    
    procedure LoadBitmapFromBinaryString(Bitmap: TBitmap; S: string);
    var
      Stream: TStream;
    begin
      Stream := StreamFromBinaryString(S);
      try
        Bitmap.LoadFromStream(Stream);
      finally
        Stream.Free;
      end;
    end;
    
    procedure SaveBinaryStringToFile(FileName: string; S: string);
    var
      Stream: TStream;
    begin
      Stream := TFileStream.Create(FileName, fmCreate);
      try
        WriteBinaryStringToStream(S, Stream);
      finally
        Stream.Free;
      end;
    end;
    
    #define FileToBinaryString(str FileName) \
      Local[4] = ExtractFileName(FileName), \
      Local[0] = AddBackslash(GetEnv("TEMP")) + Local[4] + ".pas", \
      Local[1] = \
        "-ExecutionPolicy Bypass -Command """ + \
        "Write-Host 'Generating code for " + Local[4] + "'; " + \
        "$bytes = [System.IO.File]::ReadAllBytes('" + FileName + "'); " + \
        "$s = '''' + (($bytes | foreach { $_.ToString('X2') }) -join '') + ''''; " + \
        "Set-Content -Path '" + Local[0] + "' -Value $s;" + \
        """", \
      Exec("powershell.exe", Local[1], SourcePath, , SW_HIDE), \
      Local[2] = FileOpen(Local[0]), \
      Local[3] = FileRead(Local[2]), \
      FileClose(Local[2]), \
      DeleteFileNow(Local[0]), \
      Local[3]
    

    And then you can use the FileToBinaryString preprocessor macro to convert a file on compile-time (or more precisely, when pre-processing) to a hex string like:

    '4D5A50000200000004000F00FFFF0000B800000....'
    

    On runtime, you use the hex string with some of the functions WriteBinaryStringToStream, StreamFromBinaryString, LoadBitmapFromBinaryString or SaveBinaryStringToFile.

    In your case you will use:

    LoadBitmapFromBinaryString(
      BitmapImage.Bitmap, {#FileToBinaryString("C:\path\WizModernSmallImage.bmp")});
    

    On compile-time, this gets converted to a code like:

    LoadBitmapFromBinaryString(
      BitmapImage.Bitmap, '4D5A50000200000004000F00FFFF0000B800000....');
    

    The pre-processor/Pascal compiler has a limit of about 100M characters for a string. Though you will actually first hit a [compile-time] memory limit of the PowerShell script for files larger than about 20-30 MB. Though even for smaller sizes (larger than few MBs), the compile-time time performance of the PowerShell script is bad. The script can be optimized significantly though.

    Due to the hex encoding, the size of the installer increases twice as much. This could be improved by using some more efficient encoding, like Base64 (CRYPT_STRING_BASE64). The code section is not even compressed too, comparing to files included with the [Files] section (not a problem for images as these are compressed already, but makes a difference with DLLs for example).

    The code requires the Unicode version of Inno Setup. You should not use the Ansi version anyway, in the 21st century. Though ironically, implementing this in the Ansi version would be way easier. See my answer to Writing binary file in Inno Setup for a use of the CryptStringToBinary that's compatible with both Ansi and Unicode version of Inno Setup. Though in the Ansi version you can actually do with a binary string, instead of a hex string.

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