Elegantly using try / catch with dotnet constants in Inno [Run] section [duplicate] - inno-setup

I have a .NET DLL. It can be registered by RegAsm .NET 3.5 and .NET 4.5.
I use this codes in my setup script:
[Run]
Filename: "{dotnet40}\RegAsm.exe"; Parameters: "my.dll"; WorkingDir: "{app}"; Flags: skipifdoesntexist; StatusMsg: "Registering Controls."
Filename: "{dotnet4064}\RegAsm.exe"; Parameters: "my.dll"; WorkingDir: "{app}"; Flags: skipifdoesntexist; StatusMsg: "Registering Controls."
Filename: "{dotnet20}\RegAsm.exe"; Parameters: "my.dll"; WorkingDir: "{app}"; Flags: skipifdoesntexist; StatusMsg: "Registering Controls."
Filename: "{dotnet2064}\RegAsm.exe"; Parameters: "my.dll"; WorkingDir: "{app}"; Flags: skipifdoesntexist; StatusMsg: "Registering Controls."
It works well if .NET 3.5 and .NET 4.5 is installed on the target machine
I have a function in my script to checking .net in InitializeSetup. So I know one of these versions are installed on the system: v3.5 v4 v4.5
But
we get error in some cases like this: if we have not .NET 3.5 on the target machine
I guess the error reason is:
{dotnet20}
.NET Framework version 2.0 root directory. {dotnet20} is equivalent to {dotnet2032} unless the install is running in 64-bit
mode, in which case it is equivalent to {dotnet2064}.
An exception will be raised if an attempt is made to expand this constant on a system with no .NET Framework version 2.0 present.
My question is how can I handle and ignore this exception and prevent setup rollback:
Internal error: .Net Framework version 2.0 not found.

If you want to stick to the [Run] section and do not write this in a scripting code, then I think you don't have many options to choose from. An exception is raised whenever a constant cannot be expanded, and that is just this case. The only option I can think of is adding a Check function that will attempt to expand the constant in a protected try..except block and prevent the entry to be processed when an exception is raised. Something like follows (based on your code, shortened):
[Run]
Filename: "{dotnet20}\RegAsm.exe"; Parameters: "File.dll"; Check: RegAsmDotNet20Exists
[Code]
function RegAsmDotNet20Exists: Boolean;
begin
try
// process the entry only if the target binary could be found (and so the
// folder constant could have been properly expanded)
Result := FileExists(ExpandConstant('{dotnet20}\RegAsm.exe'));
except
// this is the fallback in case the folder constant could not be expanded,
// or something unexpected happened when checking if the binary file to be
// executed exists; in such case, don't process the entry
Result := False;
end;
end;
Another, quite cleaner and a bit safer option would be doing your assembly registration purely from [Code] section in some of the postinstall events. Even though you'd still need to catch exceptions when using those constants, you would get more control over that tool (e.g. you could get the exit code to obtain an error reason if that tool uses it).

Related

How to control addition of file in Inno Setup 6 script using the presence of a substring in a defined string

I have two executables which do the same thing, but one is built to target a DEV environment, and the other a PROD environment.
my-app-dev.exe
my-app-prod.exe
A VERSION parameter is always passed into the Inno build script which contains the build version in one of the following two formats:
"1.0.0"
"1.0.0-DEV"
In the [Files] section, how can I include the my-app-dev.exe if the current value of VERSION contains the DEV suffix, but include the my-app-prod.exe if not?
I figured a Check parameter would be the way to resolve this, but I can't even get the simplest case to work.
For example, the following adds the file to the build despite the Check function clearly returning False.
[Files]
Source: "my-app-dev.exe"; DestDir: {app}; Check: ShouldIncludeDev
function ShouldIncludeDev: Boolean;
begin
Result := False;
end;
Perhaps I must be missing something fundamental here...
Based on this answer (Inno Setup IDE and ISCC/ISPP passing define) you could do this:
Pass the value via command line parameter:
/DargDEV="DEV"
In your [Files] section you can then do this:
#ifdef argDEV
Source: "my-app-dev.exe"; DestDir: {app};
#else
Source: "my-app-prod.exe"; DestDir: {app};
#endif
Notes
It uses the #ifdef pre-processor directive.

Constant defined using Inno Setup preprocessor #define is not recognised in ExpandConstant

I'm trying to set up an install file that (optionally) installs .NET 5 if it's not already installed.
However, I'm having trouble defining the version of .NET to install in a constant.
My script is set up like this
#define DotNetVersion "5"
...
[Tasks]
Name: "dotnet"; Description: "{cm:DotNet}"; GroupDescription: "{cm:Prerequisites}"
...
[Files]
Source: "..\Dependencies\{#DotNetInstallFile}"; DestDir: {tmp}; \
Flags: deleteafterinstall; AfterInstall: InstallDotNet; \
Check: NetNotInstalled(ExpandConstant('{DotNetVersion}')); Tasks: "dotnet"
When I try running the resulting install file I get the following error:
Internal error: Expression error 'Internal error: Unknown constant "DotNetVersion"'
The function NetNotInstalled works correctly if I replace ExpandConstant('{DotNetVersion}') with '5', but I want to easily be able to change this without modifying more than the defined constants.
I don't get what's wrong here. The Inno Setup docs state that this should be valid.
Using the same constant for any other function seems to work flawlessly.
A variable defined using Inno Setup preprocessor is not Inno Setup constant. Calling ExpandConstant function on it has no effect.
To expand preprocessor variable (or any expression), you can use {#VariableOrExpression} syntax. It's an inline preprocessor directive call, where, when no directive is explicitly specified, the emit is implied. So the {#VariableOrExpression} is the same as {#emit VariableOrExpression}. And as every preprocessor construct, it's evaluated on compile time (contrary to ExpandConstant).
You actually do that correctly already with {#DotNetInstallFile}, so do the same with DotNetVersion:
Source: "..\Dependencies\{#DotNetInstallFile}"; \
DestDir: {tmp}; Flags: deleteafterinstall; AfterInstall: InstallDotNet; \
Check: NetNotInstalled('{#DotNetVersion}'); Tasks: "dotnet"
See also How to use variables \ macros with Inno Setup?

How to get current setup directory for the script section?

Under the [Run] section I want to change the working directory to the directory from which the installer was executed. For example, if the setup was executed from the Desktop, I want the working directory to point to the Desktop:
Filename: "{app}\setup.exe"; WorkingDir: "{app}"; MinVersion: 0.0,6.0; Flags: skipifsilent
Use the {src} constant. The documentation describes it as:
{src}
The directory in which the Setup files are located. For example: If
you used {src}\MYPROG.EXE on an entry and the user is installing from
"S:\", Setup will translate it to "S:\MYPROG.EXE".
In your case you can use it like:
[Run]
Filename: "{app}\setup.exe"; WorkingDir: "{src}"; MinVersion: 0.0,6.0; Flags: skipifsilent

Installing MS SQL Server Express 2017 with Inno Installer

I'm desperately trying to install SQL Server Express 2017 with Inno Installer.
Within my installer I include the extracted installer files.
That means that I already executed the common SQLEXPR_x64_ENU.exe, to avoid the "extract-temp-folder" prompt while my installer is running.
I execute the following on the cmd:
{somePath}\SQLEXPR_x64_ENU\setup.exe /ACTION=Install /Q /SKIPRULES=RebootRequiredCheck /SUPPRESSPRIVACYSTATEMENTNOTICE=1 /IAcceptSQLServerLicenseTerms=1 /SECURITYMODE=SQL /SAPWD=secretPW /ConfigurationFile=ConfigurationFileExpr.ini
The install succeeds.
But when I do the same within my InnoInstaller-File like this:
...
[Files]
Source: "SQLEXPR_x64_ENU\*"; DestDir: "{tmp}\SQLEXPR_x64_ENU"; Check: not SQLExpress_Check; Flags: recursesubdirs;
[Run]
Filename: "{tmp}\SQLEXPR_x64_ENU\setup.exe"; Description: "Installing SQL Server Express 2017..."; StatusMsg: "Installing SQL Server Express 2017..."; \
Parameters: "/ACTION=Install /Q /SKIPRULES=RebootRequiredCheck /SUPPRESSPRIVACYSTATEMENTNOTICE=1 /IAcceptSQLServerLicenseTerms=1 /SECURITYMODE=SQL /SAPWD=secretPW /ConfigurationFile=ConfigurationFileExpr.ini"; Check: not SQLExpress_Check; Flags: runascurrentuser;
...
SQL Installer fails with the following error:
Exception type: System.MissingMethodException
Message:
Method not found: 'Void Microsoft.SqlServer.Chainer.Infrastructure.RoleService.Initialize(Microsoft.SQL.Chainer.Product.RolesType)'.
HResult : 0x80131513
Data:
DisableWatson = true
Stack:
at Microsoft.SqlServer.Configuration.BootstrapExtension.InitializeRoleServiceAction.ExecuteAction(String actionId)
at Microsoft.SqlServer.Chainer.Infrastructure.Action.Execute(String actionId, TextWriter errorStream)
at Microsoft.SqlServer.Setup.Chainer.Workflow.ActionInvocation.<>c__DisplayClasse.<ExecuteActionWithRetryHelper>b__b()
at Microsoft.SqlServer.Setup.Chainer.Workflow.ActionInvocation.ExecuteActionHelper(ActionWorker workerDelegate)
Is this a permission error?
I do not have a clue.
On cmd-shell it works, but not on InnoInstaller.
Thanks in advance for your efforts and have a nice day.
Solution for me was provided by Gavin Lambert on the Inno Setup Forum :
If you're [installing from the directory of unpacked files], you need to use {sd}\shortname as the DestDir (usually combined with deleteafterinstall) -- you can't put the files in {tmp} or any similar path as the files are very deeply nested and the db installer ends up failing to access some files because the path is too long.
If you use an unpacked installer file, here is what should work absolutely perfect.
SQLEXPR_x64_ENU.exe /x:%temp%\SQLEXPR_x64_ENU\ /QS /ACTION=Install /SKIPRULES=RebootRequiredCheck /SUPPRESSPRIVACYSTATEMENTNOTICE=1 /IAcceptSQLServerLicenseTerms=1 /SECURITYMODE=SQL /SAPWD=secretPW /ConfigurationFile=ConfigurationFileExpr.ini
In the above command, /x:%temp%\SQLEXPR_x64_ENU\ is the very important switch where it describes the extraction location and with combination to /QS it will show you the progress on screen but will not ask for any input.
You may have to change %temp% to appropriate command to grab a windows temporary folder in your installer. The command I have posted is good for command-line execution.
Enjoy! :)

Inno Setup: Asking for directory page if a task checked

Under the task section I have
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; \
GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
Name: "installFolder"; Description: "Install project folder."; \
GroupDescription:" folder";
and in the Files section is this particular folder
Source: "C:\\Output\LEA\*.*"; DestDir: {code:GetDataDir}; \
Flags: createallsubdirs recursesubdirs ignoreversion;
My aim is to test the for the checked button and then have a window to ask for the directory to install the folder to.
if WizardForm.TasksList.Checked[3] then
GetDataDir;
Can this be done without the need to create pages or the one page to get the directory?
Also, is this a good way to handle extra files that are optional and will be installed to a different location than the default {app} location?
The confusing part for me thus far is when it's all compiled, the GetDataDir is being called before the page to select Tasks. So I choose my directory and then I'm asked whether I want to install it or not. I don't know how to go about getting the GetDataDir to occur afterwards.
The wizard model in Inno Setup means you should always create the wizard pages, but you can skip the ones that don't need to be shown.
This can be done in the ShouldSkipPage() event function by calling IsTaskSelected():
function ShouldSkipPage(PageID: Integer): Boolean;
begin
if (PageID = InstallFolderPage.ID) and not IsTaskSelected('installFolder') then
Result := True
else
Result := False
end;
In this case, with only a single check, it can be shortened to:
function ShouldSkipPage(PageID: Integer): Boolean;
begin
Result := (PageID = InstallFolderPage.ID) and not IsTaskSelected('installFolder')
end;
As TLama said, you don't need to do anything special in the {code:...} functions, just return the appropriate value directly.
You simply need to add '; Tasks: installFolder' at the end of your Source... line, then it won't be called unless the task as been selected.
Source: "C:\\Output\LEA\*.*"; DestDir: {code:GetDataDir}; Flags: createallsubdirs recursesubdirs ignoreversion; Tasks: installFolder

Resources