Inno Setup: Reading a file from installer during uninstallation - inno-setup

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?
My code above builds a custom form with a custom panel. Like here: Inno Setup Uninstall some components only.

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;
Len: Integer;
begin
Len := Length(S);
SetLength(Buffer, (Len div 4) + 1);
Size := Len div 2;
if (CryptStringToBinary(S, Len, CRYPT_STRING_HEX, Buffer, Size, 0, 0) = 0) or
(Size <> Len 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.

Related

Evaluate a collection of data from preprocessor on run time in Inno Setup Pascal Script

I am trying to get Inno Setup define value in Code section but not with {#VersionTool1}. I need to pass defined name dynamically, because there are a lot of them (I want to avoid large switch case). I tried SetupSetting but it's not in Setup section (it's before it). Is there any way to do this?
#define VersionTool1 2019.01.1111
#define VersionTool2 2020.02.2111
...
[Code]
procedure SetSelectedTool(ToolName: String);
var
CurrentTool: string;
begin
...
CurrentTool := 'Version' + ToolName;
CurrentToolVersion := {#CurrentTool};
...
end;
Value of local variable CurrentTool wil for example be 'VersionTool1' and I want to get value of VersionTool1 preprocessor variable which is 2020.02.2111.
It's not possible, see Evaluate preprocessor macro on run time in Inno Setup Pascal Script.
But there are other solutions.
For example:
[Code]
var
ToolNames: TStringList;
ToolVersions: TStringList;
function InitializeSetup(): Boolean;
begin
ToolNames := TStringList.Create;
ToolVersions := TStringList.Create;
#define AddToolVersion(Name, Version) \
"ToolNames.Add('" + Name + "'); ToolVersions.Add('" + Version +"');"
#emit AddToolVersion('Tool1', '2019.01.1111')
#emit AddToolVersion('Tool2', '2020.02.2111')
{ ... }
Result := True;
end;
(of course, the above makes sense only if you actually do not hardcode the version numbers, but use a code that only a preprocessor can do – something like GetStringFileInfo, what I've understood from your comments that you plan to)
And then you can have a function like:
function GetToolVersion(ToolName: string): string;
var
I: Integer;
begin
I := ToolNames.IndexOf(ToolName);
if I >= 0 then Result := ToolVersions[I];
end;
Another similar questions:
Array Variables and dynamic access in [Code] section
Scripting capabilities in the Registry section

Disassembling strings from Inno Setup [Code]

When I compile a Inno Setup project, the [Code] section is also compiled (as Pascal executable or Pascal DLL)?
In other words, if someone unpacks a Inno Setup project, can he see the [Code] section as original source code (damn! :) ) or as compiled executable/DLL (difficult to disassemble)?
I would like to insert in the [Code] section some strings (password and keys) and I don't know if they would be easily recoverable also with little knowledge of reverse engineering.
The code is compiled into some kind of a binary representation (very roughly like .NET CIL or Java bytecode).
There's Inno Setup Unpacker (and others), which can extract the files from .exe generated by Inno Setup. It can extract the binary representation of the code to CompiledCode.bin (if you use -x -m flags).
Then you can use Inno Setup Decompiler project that is able to decompile/disassemble the CompiledCode.bin file into (pseudo) Pascal Script code. But as with a reverse engineering of .NET or Java, it won't give you exact code. The decompiled code possibly won't even compile (at least it was so the last time I tried), but it is good enough to see, what the code does. They seem to have a paid version now, which may be better than the free one I've tried some time ago. (The latest version of Inno Setup Decompiler can even directly extract code from .exe, but it was not updated to the latest version of Inno Setup [5.6.1] yet, so it did not work for me.)
(Inno Setup Decompiler site is gone, but it does not change anything about the fact that it is technically possible to decompile/disassemble the compiled code)
It is rather easy to see literal strings compiled in the code, even in the CompiledCode.bin.
For example these credentials:
Username := 'secretusername';
Password := 'mysupersecretpassword';
can be seen like this in the CompiledCode.bin file:
Of course, you can obfuscate the strings somehow (at least hex-encode them). But as you are hopefully aware, no matter what you do, once the (even compiled) code is on user's machine, there's really no way you can protect it absolutely.
A simple support code to store the string literal hex-encoded:
function CryptStringToBinary(
sz: string; cch: LongWord; flags: LongWord; binary: AnsiString;
var size: LongWord; skip: LongWord; flagsused: LongWord): Integer;
external 'CryptStringToBinaryW#crypt32.dll stdcall';
const
CRYPT_STRING_HEX = $04;
function UnobfuscateString(S: string): string;
var
Size: LongWord;
Buffer: AnsiString;
Len: Integer;
begin
SetLength(Buffer, (Length(S) div 2) + 1);
Len := Length(S);
Size := Len div 2;
if (CryptStringToBinary(S, Len, CRYPT_STRING_HEX, Buffer, Size, 0, 0) = 0) or
(Size <> Length(S) div 2) then
begin
RaiseException('Error unobfuscating string');
end;
Result := Buffer;
end;
#define ObfuscateString(str S) \
Local[0] = AddBackslash(GetEnv("TEMP")) + "ObfuscatedString.pas", \
Local[1] = \
"-ExecutionPolicy Bypass -Command """ + \
"$bytes = [Text.Encoding]::ASCII.GetBytes('" + S + "'); " + \
"$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]), \
"UnobfuscateString(" + Local[3] + ")"
(The code was tested on Unicode version of Inno Setup. Though, it can work with ASCII passwords only.)
With the help of the code above, you can write this (so you have the credentials easily editable in the source code):
Username := {#ObfuscateString("secretusername")};
Password := {#ObfuscateString("mysupersecretpassword")};
But the code will be compiled as:
Username := UnobfuscateString('736563726574757365726E616D65');
Password := UnobfuscateString('6D79737570657273656372657470617373776F7264');
You can verify that by adding this to the end of your .iss script and checking the generated Preprocessed.iss file.
#expr SaveToFile(AddBackslash(SourcePath) + "Preprocessed.iss")
So despite the credentials being readable in the source code, they won't be stored literally to the compiled code:
But again, this is only an obfuscation. Anyone with decent programming skills will be able to unobfuscate (decrypt) the credentials.

Using a standard Inno Setup message in the Run entry description

There is a related question for those who are interested here. I was encouraged to ask one of the questions separately.
So I have this [Run] code:
Filename: "{cmd}"; Parameters: "/C exit"; Description: "Exit Setup"; \
Flags: nowait postinstall runasoriginaluser unchecked skipifsilent; Check: IsWin64
It works well. It shows the following window at the end of the installation:
But the problem is that the wording Exit Setup is hard coded in English. Now, I have looked at the default.isl file and located:
ExitSetupTitle=Exit Setup
ClickFinish=Click Finish to exit Setup.
I don't know if I should be using either of these messages. It makes sense to me to use the ClickFinish because that is what the user will be doing.
But I can't work out how to use that message in the run statement.
Update
I have modified the supplied answer to:
function GetClickFinishSetupMessage(Param: string): string;
var
I: integer;
S1: string;
S2: string;
begin
S1 := SetupMessage(msgClickFinish);
I := Length(S1);
S2 := Copy(S1, I-1, 1);
if(S2 = '.') then
S1 := Delete(S1, I-1, 1);
end;
result := S1;
end;
So I could remove the final period. But it will not compiled. It says there is a mismatch for the third parameter of Delete.
Use SetupMessage support function from a scripted constant:
[Run]
Filename: ...; Description: {code:GetClickFinishSetupMessage}
[Code]
function GetClickFinishSetupMessage(Param: string): string;
begin
Result := SetupMessage(msgClickFinish);
{ Drop trailing dot, if any }
if Copy(Result, Length(Result), 1) = '.' then
SetLength(Result, Length(Result) - 1);
end;

Inno Setup LoadStringFromFile fails when file is open in another process

To check to see when a database (SQL Anywhere) is fired up and ready to receive requests I am outputting the database message window to a log (text) file and then attempting to read this using LoadStringFromFile, which I then search for specific text using Pos. The problem is that this fails (I assume) as the file is in use.
Exec(strInstallPath + '\Bin32\dbeng17.exe', '-n ' + strEngineName + ' "' + strInstallPath + '\Database\Olympus.db" -n ' + strDatabaseName + ' -gdall -xtcpip -ti0 -c25p -ot "' + strTempPath + '\dbeng.log"', '', SW_HIDE,
ewNoWait, intResultCode);
if not LoadStringFromFile(strTempPath + '\dbeng.log', astrDatabaseEngineLog) then
begin
Log('Loading string from file failed.');
end;
I have also tried to copy the log file using FileCopy and attempt to read from the copy of the file, but FileCopy also fails.
if not FileCopy(strTempPath + '\dbeng.log', strTempPath + '\dbengcopy.log', False) then
begin
Log('File copy failed.');
end;
Is there any way to read from a file that is in use or another way to do this?
When spawning the database server, use the dbspawn utility, which is designed for this purpose. You spawn dbspawn along with the dbeng/dbsrv command you want to run, and it starts the server for you. The dbspawn utility doesn't return until the database server is up, running, and ready to accept requests, so there's no guesswork needed and no need to read the console log file.
I don't know what version of SQL Anywhere you're running, but here is the documentation for v17. It should be the same in any other version.
Use the TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone).
In Unicode version of Inno Setup (the only version as of Inno Setup 6), its use is tricky due to a bad interface of the class.
function BufferToAnsi(const Buffer: string): AnsiString;
var
W: Word;
I: Integer;
begin
SetLength(Result, Length(Buffer) * 2);
for I := 1 to Length(Buffer) do
begin
W := Ord(Buffer[I]);
Result[(I * 2)] := Chr(W shr 8); // high byte
Result[(I * 2) - 1] := Chr(Byte(W)); // low byte
end;
end;
function LoadStringFromLockedFile(
const FileName: string; var S: AnsiString): Boolean;
var
Buffer: string;
Stream: TFileStream;
begin
Result := True;
try
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone);
try
SetLength(Buffer, Stream.Size div 2);
Stream.ReadBuffer(Buffer, Stream.Size);
S := BufferToAnsi(Buffer);
finally
Stream.Free;
end;
except
Result := False;
end;
end;
The code is based on TLama's code posted in Read bytes from file at desired position with Inno Setup.

How keep uninstall files inside uninstaller?

Has a any way to make the uninstall files (JPEG, DLL, PNG...) stay inside of the unins000.exe? If so, please show a code.
There is not any way to do this. The Inno uninstall engine cannot contain embedded files.
But on the other hand there's no particular need for it to do so either -- you can just install any files that it requires (into a subfolder of the app folder, if you want to keep things tidy).
The only real reason that the setup files are embedded into the installer is that it makes downloads easier. The uninstaller doesn't have that excuse.
Inno Setup has no native support for embedding files into the uninstaller.
But with some creativity, you can embed the files into the code (in a form of some constant).
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 (with use of a PowerShell code invoked from Inno Setup preprocessor).
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;
L: Integer;
begin
L := Length(S);
SetLength(Buffer, (L div 4) + 1);
Size := L div 2;
if (CryptStringToBinary(S, L, CRYPT_STRING_HEX, Buffer, Size, 0, 0) = 0) or
(Size <> L 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:
'4D5A50000200000004000F00FFFF0000B8000....'
On runtime, you use the hex string with some of the functions WriteBinaryStringToStream, StreamFromBinaryString, LoadBitmapFromBinaryString or SaveBinaryStringToFile.
So for example to load a bitmap to TBitmapImage, use:
LoadBitmapFromBinaryString(
BitmapImage.Bitmap, {#FileToBinaryString("C:\path\WizModernSmallImage.bmp")});
On compile-time, this gets converted to a code like:
LoadBitmapFromBinaryString(
BitmapImage.Bitmap, '4D5A50000200000004000F00FFFF0000B8000....');
To extract a DLL, use:
SaveBinaryStringToFile(
ExpandConstant('{tmp}\InnoCallback.dll'),
{#FileToBinaryString("InnoCallback.dll")});
To use a DLL extracted on runtime, you have to use the delayload flag:
function WrapTimerProc(callback:TTimerProc; paramcount:integer):longword;
external 'wrapcallback#{tmp}\innocallback.dll stdcall delayload';
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).
This solution was initially developed for the question Inno Setup: Reading a file from installer during uninstallation.
The code requires the Unicode version of Inno Setup. With latest Inno Setup 6, it's the only version available anyway. Even if you use Inno Setup 5, 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.

Resources