Inno Setup - install files based on feature and automatic update - inno-setup

We are having the following requirement that needs to be done in Inno Setup. We need to support installation of files based on the password and also automatic update of the next versions either via internet update or DVD
Requirement is:
We have several common files for one application
on top of that, we need to install FeatureA or FeatureB or FeatureC depending upon a password per feature. Like this we are having 20 different features.
The user will install only one feature at a time. But they are allowed to add additional features based on a password.
Above scenario is for running setup from the DVD image. In case of int updates, all the features installed by the customer should automatically be updated without password prompt from the user. It should be done silently.
Example:
PC where our software was not installed earlier. Customer using SWVER001 DVD. Install with Feature1PWD => Basic SW + common files + Feature1 files will be installed
Again install using SWVER001 DVD. This time user gives Feature2PWD ==> Feature2 files will be installed. Basic SW + Commonfiles + Feature1 files will be retained and untouched. Main Sw will see both the Feature 1 and Feature 2
User installs SWVER002 using internet update. Here Setup should see both the Basic SW + Common files + Feature1 files + Feature2 files. It should update all the 4 together at once without any input from the user. Silent update would be very good
I am thinking of the following approach
1. Create one main installer with basic/common installation files
2. Then create sub installer per feature
3. Main setup will then call the sub installer based on the passowrd.
4. Main setup will then store the previous features selected in registry/ini file
In case of int update, get the previous feature selected from registry/ini and then do the silent installation automatically without any user input
Any other better suggestions available for configuring above things in the Inno Setup?

It's way easier to create a component per feature and automatically select the component according to entered password.
Something like:
[Components]
Name: "FeatureA"; Description: "Feature A"
Name: "FeatureB"; Description: "Feature B"
Name: "FeatureC"; Description: "Feature C"
[Code]
function TestComponentPassword(
Password: string; Component: string; ComponentPassword: string): Boolean;
var
I: Integer;
begin
Result := (Password = ComponentPassword);
if Result then
begin
for I := 0 to WizardForm.ComponentsList.Items.Count - 1 do
begin
WizardForm.ComponentsList.Checked[I] :=
(WizardForm.ComponentsList.Items[I] = Component);
end;
end;
end;
function CheckPassword(Password: String): Boolean;
begin
Result :=
TestComponentPassword(Password, 'Feature A', 'aaa') or
TestComponentPassword(Password, 'Feature B', 'bbb') or
TestComponentPassword(Password, 'Feature C', 'ccc');
end;
In Inno Setup 6, you can use WizardSelectComponents instead of looking up the component by its name.
For update, create a separate installer for each component/feature. Use the same AppId for all installers, so that they share the uninstall log.

Related

Integrity check of whole Inno Setup installer

We use Inno Setup for our installer. Recently a user reported the following error during installation:
An error occurred while trying to copy a file: the source file is corrupted
This was due to a setup file that was indeed corrupted somehow.
Ideally the setup EXE would have performed some kind of check upon initialization to see if the entire EXE was valid or not. But apparently it only did this on a file-by-file basis. Is it possible to get InnoSetup to do that?
I looked in Inno Setup documentation for keywords like 'check', 'hash', etc. but didn't see anything - perhaps I missed it.
Quite similar question (from about 10 years ago - though specifically asking about MD5): How to implement MD5 check into Inno Setup installer to get 'like NSIS integrity check'? . That question seemed to state that such a check should already be happening. So perhaps the issue is not whether or not the setup EXE is validated but when this information is used / shown to the user. Also the accepted answer seemed quite manual, ideally I'd like Inno to do this itself.
Similar question with a different error message: "Source file corrupted: SHA-1 hash mismatch" error from Inno Setup
Add a checksum to the installer and verify it when the installer is starting. A standard (and recommended) way to do that is to sign the installer using code signing certificate. It's a must anyway these days.
Easy way to verify the signature is using PowerShell Get-AuthenticodeSignature. You need PowerShell 5.1 for that. It is bundled with Windows 10 Build 14393 (August 2016) and newer. The following code uses that (and skips the check on older versions of Windows).
function InitializeSetup(): Boolean;
var
WindowsVersion: TWindowsVersion;
S: string;
ResultCode: Integer;
begin
Result := True;
GetWindowsVersionEx(WindowsVersion);
Log(Format('Windows build %d', [WindowsVersion.Build]));
// TODO: Better would be to check PowerShell version
if WindowsVersion.Build < 14393 then
begin
Log('Old version of Windows, skipping certificate check');
end
else
begin
S := ExpandConstant('{srcexe}');
if (Pos('''', S) > 0) or (Pos('"', S) > 0) then
RaiseException('Possible code injection');
S := 'if ((Get-AuthenticodeSignature ''' + S + ''').Status -ne ''Valid'') ' +
'{ exit 1 }';
if ExecAsOriginalUser(
'powershell', '-ExecutionPolicy Bypass -command "' + S + '"',
'', SW_HIDE, ewWaitUntilTerminated, ResultCode) and
(ResultCode = 0) then
begin
Log('Installer signature is valid');
end
else
begin
S := 'Installer signature is not valid. Are you sure you want to continue?';
Result := (MsgBox(S, mbError, MB_YESNO) = IDYES);
end;
end;
end;
If you need to support older versions of Windows, you will have to use more complicated methods, like:
Bundling signtool with the installer (not sure about license here);
Using X509Certificate class.
See How to check if a file has a digital signature.

Installing AutoCAD vlx files with AutoCAD registry keys and Inno Setup

I have some vlx files to add to my AutoCAD folder. I would like to make install package with Inno Setup which will install my vlx files at post install.I got the registry key of AutoCAD 2014.
HKEY_LOCAL_MACHINE\Software\Autodesk\AutoCAD\R19.1\ACAD-D001:409
Please show me inno setup script for the above case.
I found this article which states:
Below is a Inno Setup/Pascal code example of how to loop through AutoCAD versions and create the relevant LspLoad registry entries for an imaginary application called "MyCADApp". It is purely to demonstrate the mechanism. It is stripped down to just do 64bit ACAD on 64bit Windows to demonstrate the principle.
Once you've got a handle on the pascal scripting, it should be relatively straight forward to modify/expand it to add tests for 32bit CAD on 64bit Windows and 32bit CAD on 32bit Windows and of course do similar for Bricscad. All the additional functions you may require eg "IsWin64" can be found in the "Support Function Reference" in the Inno Setup Help files.
procedure AddACADRegKey (Release: String); {eg 'R19.0 for ACAD 2013'}
var
RunTimeNum: String; {eg '19'}
AllProductID: TArrayOfString;
ProductID: String; {eg 'ACAD-9001:409'}
KeyPathShort: String;{eg 'SOFTWARE\Autodesk\AutoCAD\'}
KeyPathLong: String; {eg 'SOFTWARE\Autodesk\AutoCAD\R18.1\ACAD-9001:409\Applications\MyCADApp'}
KeyStringShort: String; {eg c:\Program Files\MyCADApp\LspLoad.'}
KeyStringLong: String; {eg 'c:\Program Files\MyCADApp\LspLoad.19.x64.arx'}
I: Integer;
begin
RunTimeNum := Copy (Release, 2, 2);
KeyStringShort := ExpandConstant('{pf}\MyCADApp\LspLoad.');
KeyPathShort := 'SOFTWARE\Autodesk\AutoCAD\';
if RegGetSubkeyNames(HKLM64, KeyPathShort + Release, AllProductID)
then begin
for I := 1 to GetArrayLength(AllProductID) do begin
ProductID := AllProductID[I-1];
KeyPathLong := KeyPathShort + Release + '\' + ProductID + '\Applications\MyCADApp';
KeyStringLong := KeyStringShort + RunTimeNum + '.x64.arx';
RegWriteDWordValue (HKLM64, KeyPathLong,'LOADCTRLS', 2);
RegWriteStringValue (HKLM64, KeyPathLong,'LOADER',KeyStringLong);
end;
end;
end;
An example of its useage - to test for ACAD 2012 and ACAD 2013 and add the registry entries triggered by an end installation event you would add the following code:
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep=ssPostInstall
then begin
AddAcadRegKey ('R18.2');{ACAD 2012}
AddAcadRegKey ('R19.0');{ACAD 2013}
end;
end;
You will find additional information in that discussion thread.
Here is one example I am examining in the registry:
Now, the code I showed you creates those keys:
RegWriteDWordValue (HKLM64, KeyPathLong,'LOADCTRLS', 2);
RegWriteStringValue (HKLM64, KeyPathLong,'LOADER',KeyStringLong);
Have you actually tried it? Why don't you manually add your VLX to the startup suite and then look at the changes made to the registry? Then you know what you need to do.
There is also information here which I found with one search in Google:
Load a .NET assembly
After you have determined the build type of your .NET assembly, you must determine how it will be loaded into AutoCAD. A .NET assembly file can be loaded manually or with demand loading.
Manually - Use the NETLOAD command at the Command prompt or within an AutoLISP file.
Demand load - Define a key specific to the application you want to load when AutoCAD starts up. The key must be placed under the Application key for the specific release of AutoCAD that you want your application to be loaded in.
The key for the application can contain the following keys:
DESCRIPTION
Description of the .NET assembly and is optional.
LOADCTRLS
Controls how and when the .NET assembly is loaded.
1 - Load application upon detection of proxy object
2 - Load the application at startup
4 - Load the application at start of a command
8 - Load the application at the request of a user or another application
16 - Do not load the application
32 - Load the application transparently
LOADER
Specifies which .NET assembly file to load.
MANAGED
Specifies the file that should be loaded is a .NET assembly or ObjectARX file. Set to 1 for .NET assembly files.
You should use the autoloader bundle format. Then you don't have to mess with registry keys, and your Inno Setup script is just installing to the Autodesk\ApplicationPlugins folder under the user data folder, Program Data, or Program Files.

Can I put setup command line parameters in a file which is called during installation instead?

After creating my setup.exe I have to pack it for various software deployment tools. Therefore I can't call the setup.exe with parameters, instead I have placed my own parameters in a setup.ini file next to the setup.exe
[Code]
var
MyIniFile: String;
function InitializeSetup(): Boolean;
var
LoadFromIniFile: String;
begin
Result := true;
MyIniFile := ExpandConstant('{srcexe}'); //writes the full path of the setup.exe in "MyIniFile"
MyIniFile := Copy(MyIniFile, 1, Length(MyIniFile) - Length(ExtractFileExt(MyIniFile))) + '.ini'; //changes the ".exe" into ".ini"
if FileExists(MyIniFile) then LoadFromIniFile := MyIniFile; //checks wether there is a ini-file
if LoadFromIniFile <> '' then begin
MyLogFile := GetIniString('Setup', 'Log', MyLogFile , LoadFromIniFile);
ProductName := GetIniString('Setup', 'ProductName', ProductName, LoadFromIniFile);
end;
end;
Now I want to also place the so called "Setup Command Line Parameters" (listed on the Inno Setup Help site) in my ini-file. I think that there is a way for the /Dir="x:\dirname parameter, which I did not figure out yet. But I also want to have the /SILENT parameter in there, do you think there is a way to do this? If yes, how would you do this? If not, can you please give me a hint why not?
So customize your installer for different products, I'd recommend you to use a pre-processor and automatically build the installer for each product (with different "defines"), instead of using an external INI file.
For example to be able to change application name and resulting executable when building the installer, use a script like:
[Setup]
AppName={#AppName}
OutputBaseFilename={#BaseFilename}
Now you can create two different installers automatically using command-line:
ISCC.exe Example1.iss /dAppName=App1 /dBaseFilename=SetupApp1
ISCC.exe Example1.iss /dAppName=App2 /dBaseFilename=SetupApp2
Regarding the implicit silent installation:
There's no API other than the command-line /SILENT switch to trigger silent installation.
But you can create a near-silent installation by disabling most installer pages:
[Setup]
DisableWelcomePage=true
DisableDirPage=true
DisableProgramGroupPage=true
DisableReadyPage=true
DisableFinishedPage=true
Actually the above example disables all default pages. But Inno Setup compiler will ignore the DisableReadyPage=true, if all other previous pages are disabled.
You may want to choose a different page to show instead. For example a Welcome page (by omitting DisableWelcomePage=true, but keeping the DisableReadyPage=true).
If you do not mind about using external files (as you already use an external INI file), you can of course wrap the installer to a batch file and call the installer with the /SILENT switch.

Inno setup and DefaultDirName

Is there some way to set the DefaultDirName by code depending on some decission a user did on installtion?
Let me comment:
I have some code which is build for two different systems (using different interops/ocx's and such stuff). My input files are stored in two directories input\A and input\B.
I want to have only one setup-file for both systems.
In the setup file i use CreateInputOptionPage with 2 options to determin which files to install (using Check on each file). This works okay.
But i do also have some ShellExec on finish of setup, which at the moment uses {app} to e.g. register some .Net classes and ShellExec to unregister the .Net classes on InitializeUninstall (also uses {app})
The setup must install the software on two different locations (depending on the selection of the user (eg. c:\software_a or c:\software_b). Can't change this.
So is there some way to specify the DefaultDirName before the files get copied to the system, so i can use the same ShellExec on install and uninstall? I could of course add the same ShellExec for both systems on installtation and use an if to check which files to register (depending on the user selection) but on uninstall i would not have this information (user selection), so i can not unregister the .Net classes.
thanks
In your CreateInputOptionPage code section, you might be able to set a value then use that value in the code snippet below. I haven't tested it but it might work.
[Setup]
DefaultDirName={code:getpath}
[Code]
function GetPath( Default: string ): string;
begin
if (CreateInputOptionPageValue1) then
Result := ExpandConstant({sd}) + '\path1';
else
Result := ExpandConstant({sd}) + '\path2';
end;
If you need to change the install folder after the DefaultDirName has been initialized, this was working for me quite well:
procedure CurPageChanged(CurPageID: Integer);
begin
{ updates the install path depending on the install type or the entered suffix }
if CurPageID = wpSelectDir then begin
WizardForm.DirEdit.Text := ExpandConstant('{pf}') + '\MyAppName' + GetAppSuffix('');
end;
end;
Cheers
Chris

How to configure Inno Setup to uninstall everything?

I am new to Inno Setup. Stuck on one issue ~ how to configure the uninstall piece to remove all files, folders, subfolders, and even new files/folders etc. created by application (in other words, a 100% removal of the application and associated files).
I hunted around here and also on their forum, and came up empty. Can anyone point me to a document, FAQ etc. regarding how to do this?
UPDATE
Thanks for all the feedback so far (very awesome). So it looks like I can delete everything using the {app}*.* directive in the uninstall section. Also looks like everyone is advising against it. So the question now becomes (I am wondering if this should be a totally new question) is there a way during the uninstall that we can ask the user 'Do you want to remove all project files associated with this application?' and if they answer YES, to run the uninstall {app}*.* piece?
Thanks -
I think the recommended approach is to specify what to remove in the uninstall section. The reasoning is that what if for whatever reason the user decided to put their own files in your installation directory that they didn't want removed, or saved data that they might want to keep around (maybe they uninstall is to install a newer version?)
That being said, I don't know offhand what the script is, but if you use ISTool (highly recommend) just got to the Uninstall Delete section and add things you want removed. It should present all the possible options in a nice GUI and generate the script for you.
Edit: An example from the Inno Setup documentation:
[UninstallDelete]
Type: files; Name: "{win}\MYPROG.INI"
But they strongly you don't do something like
[UninstallDelete]
Type: files; Name: "{app}\*.*"
NOTE: Don't be tempted to use a wildcard here to delete all files in
the {app} directory. I strongly
recommend against doing this for two
reasons. First, users usually don't
appreciate having their data files
they put in the application directory
deleted without warning (they might
only be uninstalling it because they
want to move it to a different drive,
for example). It's better to leave it
up to the end users to manually remove
them if they want. Also, if the user
happened to install the program in the
wrong directory by mistake (for
example, C:\WINDOWS) and then went to
uninstall it there could be disastrous
consequences. So again, DON'T DO THIS!
You should probably have made this a totally new question, but I'll answer your updated question here as well. Have a look at the section "Pascal Scripting: Uninstall Code" in the Inno Setup Documentation.
To give an example how to conditionally delete data files as part of the uninstallation process:
[Code]
procedure DeleteBitmaps(ADirName: string);
var
FindRec: TFindRec;
begin
if FindFirst(ADirName + '\*.*', FindRec) then begin
try
repeat
if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY <> 0 then begin
if (FindRec.Name <> '.') and (FindRec.Name <> '..') then begin
DeleteBitmaps(ADirName + '\' + FindRec.Name);
RemoveDir(ADirName + '\' + FindRec.Name);
end;
end else if Pos('.bmp', AnsiLowerCase(FindRec.Name)) > 0 then
DeleteFile(ADirName + '\' + FindRec.Name);
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end;
end;
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
if CurUninstallStep = usUninstall then begin
if MsgBox('Do you want to delete all data files?', mbConfirmation,
MB_YESNO) = IDYES
then begin
DeleteBitmaps(ExpandConstant('{app}'));
end;
end;
end;
But depending on the amount of stuff you need to clean up you might be better off to create a special helper program that is part of the installation, and which can be executed during the uninstallation of the app (using an entry in the [UninstallRun] section).
There are cases to want to delete files which were not initially written to the user's disk at time of installation. One of these cases is when you have an application that updates itself when it is started. New files can be added to the disk in this manner which are not a part of the uninstaller.
For this case I suggest that you create a "patch manifest" file which keeps a running record of what files should be in the {app} directory. Find below a sample of code that reads from a file in the {app} dir called 'patch_manifest.txt'
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
var
i: Integer;
arrayLen: Longint;
item: String;
itemsToDelete: Array of String;
begin
case CurUninstallStep of
usUninstall:
begin
LoadStringsFromFile(ExpandConstant('{app}') + '\patch_manifest.txt', itemsToDelete);
arrayLen := GetArrayLength(itemsToDelete);
for i := 0 to arrayLen-1 do
begin
item := ExpandConstant('{app}') + '\' + itemsToDelete[i];
if FileExists(item) then
DeleteFile(item);
if DirExists(item) then
RemoveDir(item);
end;
end;
end;
end;
and a sample of the patch_manifest.txt
data/something_here.dat
data/moredatahere.dat
data/
Launcher.exe
patch_manifest.txt
Note: The order of the lines in the patch_manifest is important. All files within a directory should first be listed followed by the directory - directories which are not empty cannot be delete.
Your application should be shipped with a patch_manifest and the patch_manifest should be updated with every patch. Make this part of your build process so you don't forget to update it!
It is very important that you do not delete by wildcard (.) even if you prompt the user. Uninstaller's have elevated privileges which could potentially destroy a user's computer. Take the case of a user who accidentally installed your application to C:\Windows\ or C:\Program Files.
Another good idea is to verify that the file is in fact "your file" by performing an MD5 check prior to deleting it. In this case your patch_manifest.txt would not only include the relative path to the file but also the MD5 checksum.
You can't use InnoSetup to uninstall anything it didn't install, and you shouldn't want to do so. I for one would be very unhappy if I installed an application, entered a lot of data, and then decided to use something else instead that would read that data from your app. If your uninstall killed all of the work I'd already done, I might be tempted to come looking for you. And probably not to buy you a cup of coffee.
Or consider the case where I install your application in the process of evaluating several. I try the apps, and keep going back to yours because I like it a little better, and each time I enter more data. Then I decide not to do anything for a while, so I remove all the test apps. In a week, I decide I need to use your app now after all, and I reinstall it. Oops! All of the work I did testing it that I now wanted to use is gone.
The above is why, when you uninstall an application, it leaves behind anything you created in the folders, like configuration files, data files, etc. It doesn't know what you want it to do with them, so it leaves them alone.
This should do the trick:
[Dirs]
Name: "{app}"; Flags: uninsalwaysuninstall
Add delete file/folder item in Inno Setup studio. Or directly use the script as follows.
[Generated code sample]
[UninstallDelete]
Type: filesandordirs; Name: "{app}\unnecessary_files"
I wanted to delete gData.dat which created on run time in installed folder
and was working fine for me
[UninstallDelete]
Type: files; Name: "{app}\gData.dat"
Isn't that the default if your don't specify "uninsneveruninstall" for an entry?
edit - Sorry I hadn't realised you were talking about newly created data files.
To delete everything I use this :
[UninstallDelete]
Type:files; Name: "{app}"

Resources