Integrity check of whole Inno Setup installer - inno-setup

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.

Related

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.

InnoTools Downloader not working with Inno 5.5

On the recommendation of several posts here on SO, I've been working with the InnoTools Downloader to try to install a 3rd party dependency for our app during the Install script in Inno setup.
Unfortunately the InnoTools Downloader hasn't been updated in a few years, and is starting to look like it's incompatible with the current Inno Tools setup (5.5.2 (u) on my machine at present). The PChar parameters in ITD have been replaced by PAnsiChar parameters, and when I try to run the various ITD_xxx procedures it gives me varying degrees of fail:
ITD_DownloadFiles gives a type mismatch error and won't compile in Inno Setup
ITD_DownloadFile compiles, but the file that shows up is 6KB in length and not runnable.
Has anyone gotten ITP to run with newer Inno (post-5.3.0) unicode versions? Or should I look around for another solution?
EDIT
Just to clarify things a bit, I have tried going into the it_download.iss file and replacing all instances of PChar with PAnsiChar. This got me past the compile errors when I first tried to integrate ITD with my setup script.
Here is a sample section of the Inno script:
[Code]
procedure InitializeWizard();
begin
ITD_Init; // initialize the InnoTools Downloader
// install 3rd party tool (ex. Git) from the internet.
if ITD_DownloadFile('http://git-scm.com/download/win',expandconstant('{tmp}\GitInstaller.exe'))=ITDERR_SUCCESS then begin
MsgBox(expandconstant('{tmp}\GitInstaller.exe'), mbInformation, MB_OK);
Exec(ExpandConstant('{tmp}\GitInstaller.exe'), '', '', SW_SHOW, ewWaitUntilTerminated, tmpResult);
end
end;
When this is run, it will bring up a dialog stating where it "downloaded" and stored the file -- on my machine it's in a subdirectory of c:\Users\\AppData\Local\Temp. This file is 6KB, as opposed to the file downloaded from http://git-scm.com/download/win, which is currently 15,221KB.
The ITP_DownloadAfter method gives a similar result.
Except replacing all PChar type occurrences with PAnsiChar you will need to replace all occurrences of string type with AnsiString in the it_download.iss file. The next problem is the URL you're trying to get. The size of a file differs from expectation, because you are downloading a HTML document instead of the binary file on which that site redirects. So, if you have your ITD ready for Unicode, change the URL in your script to a direct binary URL. Note, that I didn't used HTTPS because ITD doesn't currently support SSL. A code proof may look like this:
[Code]
const
GitSetupURL = 'http://msysgit.googlecode.com/files/Git-1.8.4-preview20130916.exe';
procedure InitializeWizard;
var
Name: string;
Size: Integer;
begin
Name := ExpandConstant('{tmp}\GitInstaller.exe');
ITD_Init;
if ITD_DownloadFile(GitSetupURL, Name) = ITDERR_SUCCESS then
begin
if FileSize(Name, Size) then
MsgBox(Name + #13#10 + 'Size: ' + IntToStr(Size) + ' B',
mbInformation, MB_OK)
else
MsgBox('FileSize function failed!', mbError, MB_OK);
end;
end;

Inno setup : Custom messages

We package few other thirdparty softwares along with our installer,and also install them during
the installation of our product. We install them in silent mode and capture their exit codes,
so sotimes, they get installed successfully and give exitcode as "3010" which is reboot required.
So, in those cases we want to show reboot page at the last but want to give a custom message.
what is the best way to show the custom message on the finish page?
[Messages]
#if FileExists("c:\RebootFile.txt")==0
FinishedRestartLabel=To complete the installation of ConditionalMessageOnWizard, Setup must restart your computer. Would you like to restart now?
#else
FinishedRestartLabel=Reboot Required
#endif
I am using the above code, but i am unable to use the dynamic paths like {sd} or {tmp} for fileexists function.
Can anyone help?
After clarification of your problem, we've found that you actually want to check if a certain file exixts and conditionally change the FinishedLabel caption at runtime.
The #emit, or in short # starting statements are used by preprocessor. And preprocessing runs right before the compilation. It allows you to conditionally modify the script, which is, after this process is done, compiled. So, with your above script you are in fact checking, if the file c:\RebootFile.txt exists on the machine where the setup is being compiled and depending on the result it then chooses value of the FinishedRestartLabel message. But it never compiles both texts into the setup binary.
You can modify the FinishedLabel caption from code e.g. this way. There you can expand constants without any problem:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Code]
function NeedRestart: Boolean;
begin
Result := True;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if not FileExists(ExpandConstant('{sd}\RebootFile.txt')) then
WizardForm.FinishedLabel.Caption := 'RebootFile NOT found. Restart ?'
else
WizardForm.FinishedLabel.Caption := 'RebootFile WAS found. Restart ?';
end;

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

Resources