While updating at a client site a hugely out of date Inno Setup directory tree and instructions combo (docs mentioning isetup-2.0.19.exe, isxsetup2.exe, istool-3.0.0.exe but using ispack-5.3.10.exe) I made a few notes:
- isxsetup2.exe (part of “oldfiles”) is not needed (by long!) any more as they have been integrated into Inno Setup 4.
- SourceDir and OuputDir under the [Setup] section can themselves – like regular source references – be relative paths (see for instance GameDev.net – The install script)
- If OutputDir is relative, then it is relative to SourceDir
- Need to research why it expects Setup.exe to be present under OutputDir
- Windows seems to create Thumbs.db virtually everywhere; Inno Setup includes them when they are not having the hidden attribute (similar things can hold for desktop.ini and .DS_Store files)
- You can exclude these on a per
Source:
entry in the[Files]
section like using anExclude
parameter like this:
Source: Manuals\files\*.*; Excludes: "Thumbs.db"; DestDir: {app}\manuals\files
- Some more links on this topic (major tip: ensure the various
Source:
entries do *not* overlap!):
- You can exclude these on a per
- I’ve opted to set compression settings in the
[Setup]
section to- use
lzma2/ultra64
without specifying dictionary size (to it’s 64 megabyte) which takes a lot of memory during compression (about 700 megabytes) but greatly reduces Setup.exe size. - do not use
SolidCompression
as every installation uses only a subset of the files
- use
Source files I need to figure out if the are needed, where they originally come from and which actual version should be used:
- System\msvcrt.dll
- System\borlndmm.dll
- System\gds32.dll
- (try to make your software use fbclient.dll;
if that is impossible, copyfbclient.dll
togds32.dll
or better read section3/ Supporting legacy applications and drivers
inFireBird\doc\README.Win32LibraryInstallation.txt
about usinginstclient.exe
f to make a proper conversion offbclient.dll
intogds32.dll
)
- (try to make your software use fbclient.dll;
The vcredist_x86_2010.exe was actually the Visual C++ 2010 SP1 one with version 10.0.40219.1, not the RTM one with version 10.0.30319.1.
- Download Microsoft Visual C++ 2010 Redistributable Package (x86) from Official Microsoft Download Center
- Download Microsoft Visual C++ 2010 Service Pack 1 Redistributable Package MFC Security Update from Official Microsoft Download Center
I need to figure out this error message that occurs every now and then:
--------------------------- Error --------------------------- ShellExecuteEx failed; code 1460. This operation returned because the timeout period expired. --------------------------- OK ---------------------------
I need to catch up on many things having to do with the [Code]
section:
- Mirality Systems: Inno Setup Tips & Tricks which has a very good introduction to the underlying Pascal Script (by RemObjects) language.
- Inno Setup Help – Pascal Scripting: Support Functions Reference – functions you can call from your scripts.
- Inno Setup Help – Pascal Scripting: Setup event functions as lots of functions you can define that are being called by the Inno Setup engine.
- Inno Setup Help – Pascal Scripting: Scripted Constants like
{code:myStringFunction}
that refer to a string function namedmyStringFunction
. - Inno Setup Help – Constants constants like
{pf}
for theProgram Files
directory which you can use everywhere. - Inno Setup Preprocessor Help – Directives on
#define value,
using{#value}
and other kinds of directives like#emit
. - Inno Setup Preprocessor Help – Functions on all sorts of functions you can use within the pre-procesor
It pays off to split your [Code]
section in at least three parts:
- A part having the
Setup event functions
- A part having the
Pascal Scripting: Scripted Constants
functions - A part having your own utility functions
There is no {code:...}
way of getting the value of OutputBaseFileName
, but you can use
ExpandConstant
followed byChangeFileExt
to change the extension of the resulting EXE filename into INI which usually is good enough.- The preprocessor to pass in a value to both the
OutputBaseFileName
and your code.
Not all places can use {code:...}
expansion, so you might want to use the preprocessor ispp (which stands for Inno Setup Preprocessor).
It was a bit hard to find if/when ispp
was available as that has changed over the years as it used to be a separate product. From some Inno Setup 4.x or 5.x version up, it is available in the core product, possibly enabled by default (reading Inno Setup Help – Script Format Overview I’m still not sure) but to make sure it is enabled, just add this line at the start of your script files:
#preproc ispp
With the pre-processor, you can do things ike this.
Without the pre-processor, this will fail in the [Files
] section with an error containig “unknown filename prefix”:
Source: Service\{code:GetServiceExe}; DestDir: {app}; ... BeforeInstall: DoBeforeInstallForService({code:GetServiceName})
With the pre-processor, you can replace it with this:
#preproc ispp #define cServiceExe = "SomeWeirdExeName.exe" #define cServiceName = "SomeWeirdServiceName" ... Source: Service\{#cGetServiceExe}; DestDir: {app}; ... BeforeInstall: DoBeforeInstallForService('{#cServiceName}')
If you forget the single quotes around {#cServiceName}
then you get this very weird error for which Googling “Can only call function” “ExpandConstant” “within parameter lists.” will return no satisfactory results:
[Window Title] Error [Main Instruction] Compiler Error [Content] Line 91: Directive or parameter "BeforeInstall" expression error: Can only call function "ExpandConstant" within parameter lists. [OK]
Of course the pre-processor syntax is different from the Pascal Script syntax, so this won’t work:
#define cVersion="1.2.3.4"
#define cOutputDir="..\Output-{#cVersion}"
It needs to be this (via Inno Setup – #define directive – how to use previously defined variable? – Stack Overflow):
#define cOutputDir="..\Output-"+cVersion
Importing Windows functions from DLLs
Now that there is both an Ansi and Unicode version of Inno Setup, lots of scripts you find on the interwebz need modification: they import ANSI versions from various DLLs but now need to check the Inno Setup Pre-Processor pre-defined variable UNICODE.
Those predefined variables are listed here: Inno Setup Preprocessor: Predefined Variables
You use it like in the CodeDll.iss example:
//importing a Windows API function, automatically choosing ANSI or Unicode (requires ISPP)
function MessageBox(hWnd: Integer; lpText, lpCaption: String; uType: Cardinal): Integer;
#ifdef UNICODE
external 'MessageBoxW@user32.dll stdcall';
#else
external 'MessageBoxA@user32.dll stdcall';
#endif
I learned this the hard way inheriting a bunch of code that would install services and failing on one service manager call with a GetLastError code ERROR_INVALID_NAME a.k.a. 123 (0x7B). I found it was the first OpenSCManager API call but since the code did not have any error handling at all tracking that down took quite some effort that failed. It would not with the documented ERROR_ACCESS_DENIED a.k.a. 5 (0x5) and ERROR_DATABASE_DOES_NOT_EXIST a.k.a. 1065 (0x429) codes.
Of course OpenSCManager ServicesActive 0x0000007B nor OpenSCManager Error 123 didn’t return meaningful pages.
There were some mentions of invalid registry keys but those didn’t make sense to me at that time. Only after fiddling a lot I found the ROpenSCManagerW that mentioned Unicode, the ERROR_INVALID_NAME and ERROR_SHUTDOWN_IN_PROGRESS a.k.a. 1115 (0x45B). Apparently the lpDatabaseName
parameter wasn’t interpreted correctly. Thad made sense as passing the 'ServicesActive'
as Unicode string where the the import uses Ansi will see the string as an alternating series of ANSI character bytes and null bytes and stop after the first S
.
The fix was easy: apply the above #ifdef UNICODE
logic and import the function either using W@
or A@
depending on the mode.
Later I found out the code was borrowed without attribution nor mentioning the ANSI limitation from installation – upgrading windows service using inno setup – Stack Overflow. This all the more illustrates that when you borrow code from the internet you should attribute it and ensure the limitations are mentioned near your code.
Logging
Logging involves a few things:
- Call the
Log
method: Inno Setup Help – Pascal Scripting: Log - Enable logging using either
- The
SetupLogging
entry in the[Setup]
section: Inno Setup Help – [Setup]: SetupLogging - The
/LOG
parameter on the setup command-line: Inno Setup Help – Setup Command Line Parameters
- The
- Inspect the log file in your
%TEMP%
directory (files are named likeSetup Log 2016-07-12 #001.log
) - Note that I wish there was a
Log
function with parameters similar toFormat
, but since the underlying Pascal Script language does not allow overloads, I tried to introduce aLogFormat
function instead but found out that Pascal Script doesn’t likearray of const
parameters (the code below fails with anidentifier expected
error on theconst
keyword) for which I asked if I can report a bug:
There is an undocumented UsingWinNT
function originating from the non-NT era that is sometimes used for detecting Windows versions (2K, XP, Vista, 7, 8, 8.1, 10, etc) and for fiddling with Windows Services with or without using the ServicesActive
database name.
Luckily these functions exist:
Format
to format strings with a mask just like in DelphiDLLGetLastError
wrapper around the WindowsGetLastError
functionMsgBox
so you don’t need to import aMessageBox
function yourself and you can decipher the Windows System error codes.Abort
to silently exit from fatal errors.
Exiting and rolling prematurely
There are various ways the interwebz suggest you to exit an Inno Setup script prematurely, but most of them do not do a proper rollback/cleanup of the install.
These are bad (don’t cleanup/rollback):
- Don’t use ExitProcess as it fails to rollback and leaves temporary files around
- Don’t post a WM_CLOSE message to the WizardForm as it basically is calling WizardForm.Close or pressing the Close button on the form anyway
Note Abort
only works in these events (thanks Iepe):
InitializeSetup
InitializeWizard
CurStepChanged(ssInstall)
InitializeUninstall
CurUninstallStepChanged(usAppMutexCheck)
CurUninstallStepChanged(usUninstall)
Mahris has answered a nice workaround in installer – Inno Setup: How to Abort/Terminate Setup During Install? – Stack Overflow:
[Files] Source: "MYPROG.EXE"; DestDir: "{app}"; AfterInstall: MyAfterInstall [Code] var CancelWithoutPrompt: boolean; function InitializeSetup(): Boolean; begin CancelWithoutPrompt := false; result := true; end; procedure MyAfterInstall(); begin (Do something) if BadResult then begin MsgBox('Should cancel because...',mbError,MB_OK) CancelWithoutPrompt := true; WizardForm.Close; end; end; procedure CancelButtonClick(CurPageID: Integer; var Cancel, Confirm: Boolean); begin if CurPageID=wpInstalling then Confirm := not CancelWithoutPrompt; end;
x64 versus x86
Since Inno Setup supports both Win32 and Win64, you can use it to install the right flavour of dependencies, for instance installer – Install correct version of Firebird (32bit or 64bit) with Inno Setup – Stack Overflow
–jeroen
Filed under: Development, Encoding, Inno Setup ISS, Installer-Development, Software Development, Unicode
