In my inno setup RUN selection I force silent install of MSVCRT. I wonder how to make it install itself only if not yet installed?
This is what I call now:
Filename: {tmp}\vcredist_x86.exe; Parameters: "/passive /Q:a /c:""msiexec /qb /i vcredist.msi"" "; StatusMsg: Installing 2010 RunTime...
Since you don't want to tell what minimal version of Visual C++ redistributable package you require, here you have the code sample from which you can build this by your own. Please notice that I have no clue, what versions are compatible with what and what lower versions must be installed, I'll keep this upon you.
The only thing I can tell you is that you should definitely use Check conditional parameter, the solutions attempting to install the framework or runtime libraries when the wizard is being opened are wrong. This conditional parameter works as when you return True to it, the file is being installed, if False it is skipped. So you need to return True to VCRedistNeedsInstall function when you want to install your runtime libraries, False if not. The helper function VCVersionInstalled which uses constants beginning with the VC_ here returns True when the package is installed, False otherwise.
As the source of this I've used the following sources:
How to detect the presence of the VC 8.0 runtime redistributable package
How to detect the presence of the VC 9.0 runtime redistributable package
How to detect the presence of the VC 2010 runtime redistributable package
The following code should be compatible with Unicode and ANSI versions of Inno Setup thanks to kobik's idea to use the conditional define.
Here is the code:
[Files]
Source: "vcredist_x86.exe"; DestDir: {tmp}; Flags: deleteafterinstall
[Run]
; add the Parameters, WorkingDir and StatusMsg as you wish, just keep here
; the conditional installation Check
Filename: "{tmp}\vcredist_x86.exe"; Check: VCRedistNeedsInstall
[Code]
#IFDEF UNICODE
#DEFINE AW "W"
#ELSE
#DEFINE AW "A"
#ENDIF
type
INSTALLSTATE = Longint;
const
INSTALLSTATE_INVALIDARG = -2; { An invalid parameter was passed to the function. }
INSTALLSTATE_UNKNOWN = -1; { The product is neither advertised or installed. }
INSTALLSTATE_ADVERTISED = 1; { The product is advertised but not installed. }
INSTALLSTATE_ABSENT = 2; { The product is installed for a different user. }
INSTALLSTATE_DEFAULT = 5; { The product is installed for the current user. }
VC_2005_REDIST_X86 = '{A49F249F-0C91-497F-86DF-B2585E8E76B7}';
VC_2005_REDIST_X64 = '{6E8E85E8-CE4B-4FF5-91F7-04999C9FAE6A}';
VC_2005_REDIST_IA64 = '{03ED71EA-F531-4927-AABD-1C31BCE8E187}';
VC_2005_SP1_REDIST_X86 = '{7299052B-02A4-4627-81F2-1818DA5D550D}';
VC_2005_SP1_REDIST_X64 = '{071C9B48-7C32-4621-A0AC-3F809523288F}';
VC_2005_SP1_REDIST_IA64 = '{0F8FB34E-675E-42ED-850B-29D98C2ECE08}';
VC_2005_SP1_ATL_SEC_UPD_REDIST_X86 = '{837B34E3-7C30-493C-8F6A-2B0F04E2912C}';
VC_2005_SP1_ATL_SEC_UPD_REDIST_X64 = '{6CE5BAE9-D3CA-4B99-891A-1DC6C118A5FC}';
VC_2005_SP1_ATL_SEC_UPD_REDIST_IA64 = '{85025851-A784-46D8-950D-05CB3CA43A13}';
VC_2008_REDIST_X86 = '{FF66E9F6-83E7-3A3E-AF14-8DE9A809A6A4}';
VC_2008_REDIST_X64 = '{350AA351-21FA-3270-8B7A-835434E766AD}';
VC_2008_REDIST_IA64 = '{2B547B43-DB50-3139-9EBE-37D419E0F5FA}';
VC_2008_SP1_REDIST_X86 = '{9A25302D-30C0-39D9-BD6F-21E6EC160475}';
VC_2008_SP1_REDIST_X64 = '{8220EEFE-38CD-377E-8595-13398D740ACE}';
VC_2008_SP1_REDIST_IA64 = '{5827ECE1-AEB0-328E-B813-6FC68622C1F9}';
VC_2008_SP1_ATL_SEC_UPD_REDIST_X86 = '{1F1C2DFC-2D24-3E06-BCB8-725134ADF989}';
VC_2008_SP1_ATL_SEC_UPD_REDIST_X64 = '{4B6C7001-C7D6-3710-913E-5BC23FCE91E6}';
VC_2008_SP1_ATL_SEC_UPD_REDIST_IA64 = '{977AD349-C2A8-39DD-9273-285C08987C7B}';
VC_2008_SP1_MFC_SEC_UPD_REDIST_X86 = '{9BE518E6-ECC6-35A9-88E4-87755C07200F}';
VC_2008_SP1_MFC_SEC_UPD_REDIST_X64 = '{5FCE6D76-F5DC-37AB-B2B8-22AB8CEDB1D4}';
VC_2008_SP1_MFC_SEC_UPD_REDIST_IA64 = '{515643D1-4E9E-342F-A75A-D1F16448DC04}';
VC_2010_REDIST_X86 = '{196BB40D-1578-3D01-B289-BEFC77A11A1E}';
VC_2010_REDIST_X64 = '{DA5E371C-6333-3D8A-93A4-6FD5B20BCC6E}';
VC_2010_REDIST_IA64 = '{C1A35166-4301-38E9-BA67-02823AD72A1B}';
VC_2010_SP1_REDIST_X86 = '{F0C3E5D1-1ADE-321E-8167-68EF0DE699A5}';
VC_2010_SP1_REDIST_X64 = '{1D8E6291-B0D5-35EC-8441-6616F567A0F7}';
VC_2010_SP1_REDIST_IA64 = '{88C73C1C-2DE5-3B01-AFB8-B46EF4AB41CD}';
{ Microsoft Visual C++ 2012 x86 Minimum Runtime - 11.0.61030.0 (Update 4) }
VC_2012_REDIST_MIN_UPD4_X86 = '{BD95A8CD-1D9F-35AD-981A-3E7925026EBB}';
VC_2012_REDIST_MIN_UPD4_X64 = '{CF2BEA3C-26EA-32F8-AA9B-331F7E34BA97}';
{ Microsoft Visual C++ 2012 x86 Additional Runtime - 11.0.61030.0 (Update 4) }
VC_2012_REDIST_ADD_UPD4_X86 = '{B175520C-86A2-35A7-8619-86DC379688B9}';
VC_2012_REDIST_ADD_UPD4_X64 = '{37B8F9C7-03FB-3253-8781-2517C99D7C00}';
{ Visual C++ 2013 Redistributable 12.0.21005 }
VC_2013_REDIST_X86_MIN = '{13A4EE12-23EA-3371-91EE-EFB36DDFFF3E}';
VC_2013_REDIST_X64_MIN = '{A749D8E6-B613-3BE3-8F5F-045C84EBA29B}';
VC_2013_REDIST_X86_ADD = '{F8CFEB22-A2E7-3971-9EDA-4B11EDEFC185}';
VC_2013_REDIST_X64_ADD = '{929FBD26-9020-399B-9A7A-751D61F0B942}';
{ Visual C++ 2015 Redistributable 14.0.23026 }
VC_2015_REDIST_X86_MIN = '{A2563E55-3BEC-3828-8D67-E5E8B9E8B675}';
VC_2015_REDIST_X64_MIN = '{0D3E9E15-DE7A-300B-96F1-B4AF12B96488}';
VC_2015_REDIST_X86_ADD = '{BE960C1C-7BAD-3DE6-8B1A-2616FE532845}';
VC_2015_REDIST_X64_ADD = '{BC958BD2-5DAC-3862-BB1A-C1BE0790438D}';
{ Visual C++ 2015 Redistributable 14.0.24210 }
VC_2015_REDIST_X86 = '{8FD71E98-EE44-3844-9DAD-9CB0BBBC603C}';
VC_2015_REDIST_X64 = '{C0B2C673-ECAA-372D-94E5-E89440D087AD}';
function MsiQueryProductState(szProduct: string): INSTALLSTATE;
external 'MsiQueryProductState{#AW}#msi.dll stdcall';
function VCVersionInstalled(const ProductID: string): Boolean;
begin
Result := MsiQueryProductState(ProductID) = INSTALLSTATE_DEFAULT;
end;
function VCRedistNeedsInstall: Boolean;
begin
{ here the Result must be True when you need to install your VCRedist }
{ or False when you don't need to, so now it's upon you how you build }
{ this statement, the following won't install your VC redist only when }
{ the Visual C++ 2010 Redist (x86) and Visual C++ 2010 SP1 Redist(x86) }
{ are installed for the current user }
Result := not (VCVersionInstalled(VC_2010_REDIST_X86) and
VCVersionInstalled(VC_2010_SP1_REDIST_X86));
end;
Update by chuckleplant:
Added VC++ 2012 product codes. Notice that the redistributable package installs two things, the minimum runtime and the additional runtime. It should be enough for you to look for the minimum runtime. Testing against the Microsoft Visual C++ 2012 Redistributable product code will give INSTALLSTATE_UNKNOWN, use the Minimum Runtime codes instead. It's the same situation for VC++ 2013 and 2015.
You can find product codes for other Visual Studio versions in HKEY_CLASSES_ROOT\Installer\Dependencies\, for example HKEY_CLASSES_ROOT\Installer\Dependencies\Microsoft.VS.VC_RuntimeAdditionalVSU_amd64,v14
Here's a version that uses the Microsoft recommended way of determining whether the VC Redistributable is installed. I only needed VC 2015 so you will need to adapt values and parameters for other versions:
[Files]
Source: ".\vc-redist\vc-redist\win64\bin\vc_redist.x64.exe"; DestDir: "{tmp}"; Flags: deleteafterinstall; Check: Is64BitInstallMode
; FlexLM requires the VC 2015 redistributables so run the installer if this
; or a later 2015 version is not already present
[Run]
Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/install /passive"; StatusMsg: "{#VCmsg}"; Check: IsWin64 and not VCinstalled
[Code]
function VCinstalled: Boolean;
// Function for Inno Setup Compiler
// Returns True if same or later Microsoft Visual C++ 2015 Redistributable is installed, otherwise False.
var
major: Cardinal;
minor: Cardinal;
bld: Cardinal;
rbld: Cardinal;
key: String;
begin
Result := False;
key := 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64';
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, key, 'Major', major) then begin
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, key, 'Minor', minor) then begin
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, key, 'Bld', bld) then begin
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, key, 'RBld', rbld) then begin
Log('VC 2015 Redist Major is: ' + IntToStr(major) + ' Minor is: ' + IntToStr(minor) + ' Bld is: ' + IntToStr(bld) + ' Rbld is: ' + IntToStr(rbld));
// Version info was found. Return true if later or equal to our 14.0.24212.00 redistributable
// Note brackets required because of weird operator precendence
Result := (major >= 14) and (minor >= 0) and (bld >= 24212) and (rbld >= 0)
end;
end;
end;
end;
end;
Much easier:
#define VCmsg "Installing Microsoft Visual C++ Redistributable...."
[Run]
Filename: "vc_redist.x86.exe"; StatusMsg: "{#VCmsg}"; Check: not IsWin64 and not VCinstalled
Filename: "vc_redist.x64.exe"; StatusMsg: "{#VCmsg}"; Check: IsWin64 and not VCinstalled
[Code]
function VCinstalled: Boolean;
// By Michael Weiner <mailto:spam#cogit.net>
// Function for Inno Setup Compiler
// 13 November 2015
// Returns True if Microsoft Visual C++ Redistributable is installed, otherwise False.
// The programmer may set the year of redistributable to find; see below.
var
names: TArrayOfString;
i: Integer;
dName, key, year: String;
begin
// Year of redistributable to find; leave null to find installation for any year.
year := '';
Result := False;
key := 'Software\Microsoft\Windows\CurrentVersion\Uninstall';
// Get an array of all of the uninstall subkey names.
if RegGetSubkeyNames(HKEY_LOCAL_MACHINE, key, names) then
// Uninstall subkey names were found.
begin
i := 0
while ((i < GetArrayLength(names)) and (Result = False)) do
// The loop will end as soon as one instance of a Visual C++ redistributable is found.
begin
// For each uninstall subkey, look for a DisplayName value.
// If not found, then the subkey name will be used instead.
if not RegQueryStringValue(HKEY_LOCAL_MACHINE, key + '\' + names[i], 'DisplayName', dName) then
dName := names[i];
// See if the value contains both of the strings below.
Result := (Pos(Trim('Visual C++ ' + year),dName) * Pos('Redistributable',dName) <> 0)
i := i + 1;
end;
end;
end;
I know is very old question but i encounter this situation and since i couldn't do winregistry checking i did this approach,
based on this list of files : https://matthew-brett.github.io/pydagogue/_sources/python_msvc.rst.txt
i was able to just check if the file exist , for example VCRedist 2015/2013 x86/x64:
2015
C:/Windows/System32/msvcp140.dll - x64
C:/Windows/SysWOW64/msvcp140.dll" - x86
2013
C:/Windows/System32/msvcp120.dll - x64
C:/Windows/SysWOW64/msvcp120.dll" - x86
I'm dealing with the Visual C++ 2015-2019 redistributable package and with possible 32bit installations on 64bit OS.
I modified user3433200 solution as follows:
function VCinstalled(const regKey: string): Boolean;
{ Function for Inno Setup Compiler }
{ Returns True if same or later Microsoft Visual C++ 2015 Redistributable is installed, otherwise False. }
var
major: Cardinal;
minor: Cardinal;
bld: Cardinal;
rbld: Cardinal;
begin
Result := False;
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, regKey, 'Major', major) then begin
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, regKey, 'Minor', minor) then begin
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, regKey, 'Bld', bld) then begin
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, regKey, 'RBld', rbld) then begin
Log('VC 2015-2019 Redist Major is: ' + IntToStr(major) + ' Minor is: ' + IntToStr(minor) + ' Bld is: ' + IntToStr(bld) + ' Rbld is: ' + IntToStr(rbld));
{ Version info was found. Return true if later or equal to our 14.23.27820.0 redistributable }
{ Note brackets required because of weird operator precendence }
Result := (major >= 14) and (minor >= 23) and (bld >= 27820) and (rbld >= 0)
end;
end;
end;
end;
end;
function VCRedistNeedsInstall: Boolean;
begin
if NOT IsWin64 then
Result := not (VCinstalled('SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\X86'))
else if Is64BitInstallMode then
Result := not (VCinstalled('SOFTWARE\WOW6432Node\Microsoft\VisualStudio\14.0\VC\Runtimes\x64'))
else
Result := not (VCinstalled('SOFTWARE\WOW6432Node\Microsoft\VisualStudio\14.0\VC\Runtimes\x86'));
end;
Tested on Win 7 32bit and Win10 64bit.
Comsci's answer (which Martin's answer adapted) is close but doesn't perform the version check correctly. (It will fail if there is a bump in the minor version and the build number resets to zero, for example.) Here's a corrected version of that answer, for both the 32-bit and 64-bit Visual Studio 2015-2019 packages.
I've found that the current 64-bit package breaks the runtime if it is already installed, so checking before installing it is necessary (until this is fixed by Microsoft). Edit: This was probably fixed in Visual Studio 16.10 (release notes).
As in the original answer, this is based on the Microsoft recommended way of determining whether the VC Redistributable is installed.
[Files]
Source: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Redist\MSVC\v142\vcredist_x86.exe; DestDir: {tmp}; Flags: deleteafterinstall ignoreversion
Source: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Redist\MSVC\v142\vcredist_x64.exe; DestDir: {tmp}; Flags: deleteafterinstall ignoreversion; Check: IsWin64
[Run]
Filename: {tmp}\vcredist_x86.exe; Parameters: /quiet /norestart; StatusMsg: "{#VCmsg32}"; Check: not VCRuntime32Installed
Filename: {tmp}\vcredist_x64.exe; Parameters: /quiet /norestart; StatusMsg: "{#VCmsg64}"; Check: IsWin64 and not VCRuntime64Installed
[Code]
function VCRuntime32Installed: Boolean;
var
required_major: Cardinal;
required_minor: Cardinal;
required_bld: Cardinal;
required_rbld: Cardinal;
major: Cardinal;
minor: Cardinal;
bld: Cardinal;
rbld: Cardinal;
key: String;
begin
required_major := 14;
required_minor := 29;
required_bld := 30037;
required_rbld := 0;
Result := False;
key := 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\X86';
if RegQueryDWordValue(HKLM32, key, 'Major', major) then begin
if RegQueryDWordValue(HKLM32, key, 'Minor', minor) then begin
if RegQueryDWordValue(HKLM32, key, 'Bld', bld) then begin
if RegQueryDWordValue(HKLM32, key, 'Rbld', rbld) then begin
Log('vcruntime (x86) version: ' + IntToStr(major) + '.' + IntToStr(minor) + '.' + IntToStr(bld) + '.' + IntToStr(rbld));
Result := (major > required_major) or ((major = required_major) and ((minor > required_minor) or ((minor = required_minor) and ((bld > required_bld) or ((bld = required_bld) and (rbld >= required_rbld))))))
end;
end;
end;
end;
end;
function VCRuntime64Installed: Boolean;
var
required_major: Cardinal;
required_minor: Cardinal;
required_bld: Cardinal;
required_rbld: Cardinal;
major: Cardinal;
minor: Cardinal;
bld: Cardinal;
rbld: Cardinal;
key: String;
begin
required_major := 14;
required_minor := 29;
required_bld := 30037;
required_rbld := 0;
Result := False;
key := 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\X64';
if RegQueryDWordValue(HKLM64, key, 'Major', major) then begin
if RegQueryDWordValue(HKLM64, key, 'Minor', minor) then begin
if RegQueryDWordValue(HKLM64, key, 'Bld', bld) then begin
if RegQueryDWordValue(HKLM64, key, 'Rbld', rbld) then begin
Log('vcruntime (x64) version: ' + IntToStr(major) + '.' + IntToStr(minor) + '.' + IntToStr(bld) + '.' + IntToStr(rbld));
Result := (major > required_major) or ((major = required_major) and ((minor > required_minor) or ((minor = required_minor) and ((bld > required_bld) or ((bld = required_bld) and (rbld >= required_rbld))))))
end;
end;
end;
end;
end;
Related
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
My requirement is to check for previous installation of SQL native Client 11, before installation and uninstall the previous version. I have been able to check for the previous installation with no problems, however, I am unable to uninstall the same.
I used the solution mentioned in the How to detect old installation and offer removal?
During run time, I am getting the following error
Exception: Internal error: Unknown constant "A22EED3F-6DB6-4987-8023-6C6B7030E554".
(where the constant is the GUID of the native client) during the execution of the line
Exec(ExpandConstant(sUnInstallString), '', '', SW_SHOW, ewWaitUntilTerminated, iResultCode);
The sUnInstallString is
MsiExec.exe /I{A22EED3F-6DB6-4987-8023-6C6B7030E554}
Thanks in advance.
That's not a (Inno Setup) constant. That's a GUID. Remove the ExpandConstant call.
And you need to split the uninstall string to a program path and its parameters.
var
P: Integer;
UninstallPath: string;
UninstallParams: string;
begin
// ...
// In case the program path is quoted, because it contains spaces.
// (it's not in your case, but it can be, in general)
if Copy(sUnInstallString, 1, 1) = '"' then
begin
Delete(sUnInstallString, 1, 1);
P := Pos('"', sUnInstallString);
end
else P := 0;
if P = 0 then
begin
P := Pos(' ', sUnInstallString);
end;
UninstallPath := Copy(sUnInstallString, 1, P - 1);
UninstallParams :=
TrimLeft(Copy(sUnInstallString, P + 1, Length(sUnInstallString) - P));
Exec(UninstallPath, UninstallParams, '', SW_SHOW, wWaitUntilTerminated,
iResultCode);
// ...
end;
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;
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.
When I compile the code below, it completes without errors, but when I attempt to run the setup file I get a type mismatch error. Can anyone tell me what might be causing it? (exact error message is "Runtime Error (at 1:66): Type Mismatch.")
[Setup]
DefaultDirName={code:AppDir}\MyApp
[Code]
function AppDir(Param: String): String;
var
Check: Integer;
begin
Check := GetWindowsVersion();
if Check = 6.0 then
Result := ExpandConstant('{userdocs}')
else
Result := ExpandConstant('{pf}');
end;
Quoting from the Inno Setup documentation for GetWindowsVersion():
Returns the version number of Windows packed into a single integer. The upper 8 bits specify the major version; the following 8 bits specify the minor version; the lower 16 bits specify the build number. For example, this function will return $05000893 on Windows 2000, which is version 5.0.2195.
You can't compare with a floating point value, you need to extract the parts of the version number, like so:
function AppDir(Param: String): String;
var
Ver: Cardinal;
VerMajor, VerMinor, BuildNum: Cardinal;
begin
Ver := GetWindowsVersion();
VerMajor := Ver shr 24;
VerMinor := (Ver shr 16) and $FF;
BuildNum := Ver and $FFFF;
if VerMajor >= 6 then
Result := ExpandConstant('{userdocs}')
else
Result := ExpandConstant('{pf}');
end;
Note that you should never check VerMajor for equality, as this would fail for either lower or higher Windows versions. Always use <= or >= instead.