Inno Setup: Reading a file from installer during u

2019-02-11 02:29发布

Using the following code during uninstall

BitmapImage := TBitmapImage.Create(InstallTopPanel);
BitmapImage.AutoSize := True;
BitmapImage.Bitmap.LoadFromFile(ExpandConstant( '{tmp}\WizardSmallImageFile.bmp') );
BitmapImage.Parent := InstallTopPanel;
BitmapImage.Top := (InstallTopPanel.ClientHeight - 58) / 2;
BitmapImage.Left := InstallTopPanel.ClientWidth - 55 - 10;

I get an error:

Exception : can not open file. C:\users\xyz\AppData\Local\Temp\is-U3Q8P.tmp\WizardSmallImageFile.Bmp. File not found.

I tried also to use ExtractTemporaryFile before I call LoadFromFile which is not supported during uninstall.

ExtractTemporaryFile('WizardSmallImageFile.bmp');

So, the question, how to view an image or specifically WizardSmallImageFile during uninstall?

标签: inno-setup
1条回答
ゆ 、 Hurt°
2楼-- · 2019-02-11 02:44

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.

查看更多
登录 后发表回答