What is Inno Setup?
Inno Setup is a free script-driven installation system software for creating Windows app installer. Inno Setup is open source, the best non-MSI alternative installer by Jordan Russel and Martijn Laan. It is first introduced in 1997, Inno Setup today rivals and even surpasses many commercial installers in feature set and stability. Visit Inno Setup official website to learn more about it.
Get started with Inno Setup
You can download and install Inno Setup software from its download page here. Unicode version is recommended. If you need a better intuitive GUI software for Inno Setup, you can download and install Inno Script Studio.
For syntax highlighting when editing Inno Setup scripts, you can install following extensions:
Integrate Inno Setup in your automated build system
If you have automated build system such as CI/CD setup that builds your app source code, you can integrate Inno Setup Compiler into your build system to compile the Inno Setup script file (*.iss
). There is a file called ISCC.exe
for the command-line compiler.
Example CLI for compiling your Inno Setup script:
cd "C:\Program Files (x86)\Inno Setup 5"
iscc "path\to\myinstaller.iss"
Note: Type
iscc /?
to learn more on what command-line options that are available.
Example screenshot

Example of Inno Setup project structure
I used Inno Setup for one of my apps called Exif Reader and this is how the project structure looked like.
.\
├── basic.iss
└── basic\
├── app\
| ├── Resources\
| | ├── map.xml
| | └── res.ini
| ├── ExifReader.exe
| ├── ICSharpCode.SharpZipLib.dll
| ├── MetadataExtractor.dll
| ├── Ookii.Dialogs.Wpf.dll
| └── XmpCore.dll
├── appconfig\
| └── config.ini
├── doc\
| ├── EULA.rtf
| ├── Readme.rtf
| └── Requirements.rtf
└── res\
├── app.ico
├── TopBanner.bmp
└── VerticalBanner.bmp
Inno Setup script for Exif Reader project
This is how my Inno Script looked like. Sometimes, I keep re-using this script as a boilerplate and make some modifications depending on the requirement of the project.
; Preprocessor variables
#define MyAppName "EXIF Reader"
#define MyAppVersion "0.0.1"
#define MyAppPublisher "Heiswayi Nrird"
#define MyAppURL "https://heiswayi.nrird.com/exifreader"
#define MyAppExeName "ExifReader.exe"
#define MyAppCopyright "Copyright (C) 2018 Heiswayi Nrird"
#define IncludeFramework true
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{41045341-CB76-446F-8180-6489D9474F1F}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppVerName={#MyAppName}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
AppCopyright={#MyAppCopyright}
; Default location of installation
DefaultDirName={pf}\{#MyAppPublisher}\{#MyAppName}
DefaultGroupName={#MyAppPublisher}\{#MyAppName}
; Installer name
OutputBaseFilename="ExifReaderSetup"
; Installer icon
SetupIconFile=.\basic\res\app.ico
Compression=lzma
SolidCompression=yes
LicenseFile=.\basic\doc\EULA.rtf
InfoBeforeFile=.\basic\doc\Requirements.rtf
InfoAfterFile=.\basic\doc\Readme.rtf
; Uninstaller icon
UninstallDisplayIcon={app}\{#MyAppExeName}
DisableWelcomePage=no
; Installer vertical banner, max resolution: 164x314 px
WizardImageFile=.\basic\res\VerticalBanner.bmp
; Installer top logo, max resolution: 55x58 px
; I'm not using this as I'm using top banner instead (see below), so I commented it out.
;WizardSmallImageFile=TopLogo.bmp
; Installer FileVersion
VersionInfoVersion=1.0.0.0
;CreateUninstallRegKey=no
PrivilegesRequired=admin
; Installer output is the same as source (.iss file)
OutputDir=.
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1
[Files]
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
Source: ".\basic\app\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: ".\basic\doc\*"; DestDir: "{app}\Docs"; Flags: ignoreversion
; These files will not be removed during uninstallation
; {userdocs} = My Documents folder
Source: ".\basic\appconfig\*"; DestDir: "{userdocs}\{#MyAppName}"; Flags: ignoreversion uninsneveruninstall
; Customized top banner, max resolution: 499x58 px
Source: ".\basic\res\TopBanner.bmp"; Flags: dontcopy
[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
[Messages]
; Customized license agreement checkbox messages
LicenseAccepted=&Agree
LicenseNotAccepted=&Do Not Agree
[Code]
// I'm using horizontal top banner instead of logo
procedure InitializeWizard();
var
BitmapImage: TBitmapImage;
begin
ExtractTemporaryFile('TopBanner.bmp');
BitmapImage := TBitmapImage.Create(WizardForm);
BitmapImage.Parent := WizardForm.MainPanel;
BitmapImage.Width := WizardForm.MainPanel.Width;
BitmapImage.Height := WizardForm.MainPanel.Height;
BitmapImage.Stretch := True;
BitmapImage.AutoSize := False;
BitmapImage.Bitmap.LoadFromFile(ExpandConstant('{tmp}\TopBanner.bmp'));
WizardForm.WizardSmallBitmapImage.Visible := False;
WizardForm.PageDescriptionLabel.Visible := True;
WizardForm.PageNameLabel.Visible := True;
WizardForm.PageDescriptionLabel.Color := TColor($E8E8E8);
WizardForm.PageNameLabel.Color := TColor($E8E8E8);
WizardForm.PageDescriptionLabel.Width :=
WizardForm.PageDescriptionLabel.Width - ScaleX(60);
WizardForm.PageNameLabel.Width :=
WizardForm.PageNameLabel.Width - ScaleX(60);
end;
Meaning of some common Flags under [Files]
section
ignoreversion
- replace existing files regardless of their version numberonlyifdoesntexist
- only install the file if it doesn’t already exist on the user’s systemrecursesubdirs
- use this when you use wildcard for theSource
as shown in the script abovecreateallsubdirs
- create all subdirectories, and must be combined withrecursesubdirs
uninsneveruninstall
- never remove the file during uninstallationdeleteafterinstall
- delete file once the installation is completed/aborted
For other available flags, you can refer to Inno Setup documentation here for more details.
How to implement a components-based installation in your installer
Example screenshot

Example of Inno Setup script
If you want to design your app installer using a components-based installation, you may need to modify the Inno Setup script and add two extra sections called [Types]
and [Components]
. Check the provided example script below which taken from Components.iss
file that is located in C:\Program Files (x86)\Inno Setup 5\Examples
folder once you have installed the Inno Setup software in your Windows system.
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
DefaultGroupName=My Program
UninstallDisplayIcon={app}\MyProg.exe
OutputDir=userdocs:Inno Setup Examples Output
[Types]
Name: "full"; Description: "Full installation"
Name: "compact"; Description: "Compact installation"
Name: "custom"; Description: "Custom installation"; Flags: iscustom
[Components]
Name: "program"; Description: "Program Files"; Types: full compact custom; Flags: fixed
Name: "help"; Description: "Help File"; Types: full
Name: "readme"; Description: "Readme File"; Types: full
Name: "readme\en"; Description: "English"; Flags: exclusive
Name: "readme\de"; Description: "German"; Flags: exclusive
[Files]
Source: "MyProg.exe"; DestDir: "{app}"; Components: program
Source: "MyProg.chm"; DestDir: "{app}"; Components: help
Source: "Readme.txt"; DestDir: "{app}"; Components: readme\en; Flags: isreadme
Source: "Readme-German.txt"; DestName: "Liesmich.txt"; DestDir: "{app}"; Components: readme\de; Flags: isreadme
[Icons]
Name: "{group}\My Program"; Filename: "{app}\MyProg.exe"
The meanings of the Flags used in the script above:
fixed
- usually used in the main program file where user cannot unselect that componentexclusive
- user only can select one of the exclusive components, mostly used for localized files
How to create prerequisites for your installer
.NET Framework as the primary prerequisite
If your app requires particular .NET Framework to be installed as part of the prerequisites, there are few ways you can do with Inno Setup script. You either can;-
- include or package together the offline installer of .NET Framework into your app installer, or
- just check and if not installed, inform the user to download and install particular .NET Framework manually, and then abort your app installer.
You can use the Check parameter to run check if particular .NET Framework is installed.
Note: Inno Setup scripting is based on Pascal programming language which adds lots of new possibilities to customize your Setup or Uninstall at run-time.
Include .NET Framework offline installer into your installer
Add a new Source
under the [Files]
section:
[Files]
; .NET Framework v4.7.1 offline installer
Source: ".\basic\dependencies\NDP471-KB4033342-x86-x64-AllOS-ENU.exe"; DestDir: {tmp}; Flags: deleteafterinstall; AfterInstall: InstallFramework; Check: FrameworkIsNotInstalled
Add these two code under [Code]
section:
[Code]
// Check for installed .NET Framework 4.7.1 in the registry
// MSDN page: https://msdn.microsoft.com/en-us/library/hh925568.aspx
function FrameworkIsNotInstalled: Boolean;
var
readVal: cardinal;
success: Boolean;
begin
success := RegQueryDWordValue(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', readVal);
success := success and (readVal < 461308);
Result := not success;
end;
// Create progress dialog for installing .NET Framework 4.7.1
procedure InstallFramework;
var
StatusText: string;
ResultCode: Integer;
begin
StatusText := WizardForm.StatusLabel.Caption;
WizardForm.StatusLabel.Caption := 'Installing .NET Framework 4.5.2. This might take a few minutes...';
WizardForm.ProgressGauge.Style := npbstMarquee;
try
Exec(ExpandConstant('{tmp}\NDP471-KB4033342-x86-x64-AllOS-ENU.exe'), '/passive /norestart', '', SW_SHOW, ewWaitUntilTerminated, ResultCode)
if ResultCode <> 0 then
begin
MsgBox('.NET installation failed with code: ' + IntToStr(ResultCode) + '.' + #13#10 + #13#10 + 'Setup will now terminate.', mbError, MB_OK);
DeleteFile(ExpandConstant('{tmp}\NDP471-KB4033342-x86-x64-AllOS-ENU.exe'));
Exterminate;
end
else
begin
WizardForm.StatusLabel.Caption := StatusText;
WizardForm.ProgressGauge.Style := npbstNormal;
end;
finally
DeleteFile(ExpandConstant('{tmp}\NDP471-KB4033342-x86-x64-AllOS-ENU.exe'));
end;
end;
var
ForceClose: Boolean;
procedure Exterminate;
begin
ForceClose:= True;
WizardForm.Close;
end;
procedure CancelButtonClick(CurPageID: Integer; var Cancel, Confirm: Boolean);
begin
Confirm:= not ForceClose;
end;
Implement checking and inform user to download and install if the required .NET Framework is not installed
Another way is to check the required .NET Framework at the beginning of installation, if not installed, inform the user to download and install the required .NET Framework. Check the code below how to implement it. The code below is taken from http://www.kynosarges.de/DotNetVersion.html.
[Code]
function IsDotNetDetected(version: string; service: cardinal): boolean;
// Indicates whether the specified version and service pack of the .NET Framework is installed.
//
// version -- Specify one of these strings for the required .NET Framework version:
// 'v1.1' .NET Framework 1.1
// 'v2.0' .NET Framework 2.0
// 'v3.0' .NET Framework 3.0
// 'v3.5' .NET Framework 3.5
// 'v4\Client' .NET Framework 4.0 Client Profile
// 'v4\Full' .NET Framework 4.0 Full Installation
// 'v4.5' .NET Framework 4.5
// 'v4.5.1' .NET Framework 4.5.1
// 'v4.5.2' .NET Framework 4.5.2
// 'v4.6' .NET Framework 4.6
// 'v4.6.1' .NET Framework 4.6.1
// 'v4.6.2' .NET Framework 4.6.2
// 'v4.7' .NET Framework 4.7
// 'v4.7.1' .NET Framework 4.7.1
// 'v4.7.2' .NET Framework 4.7.2
//
// service -- Specify any non-negative integer for the required service pack level:
// 0 No service packs required
// 1, 2, etc. Service pack 1, 2, etc. required
var
key, versionKey: string;
install, release, serviceCount, versionRelease: cardinal;
success: boolean;
begin
versionKey := version;
versionRelease := 0;
// .NET 1.1 and 2.0 embed release number in version key
if version = 'v1.1' then begin
versionKey := 'v1.1.4322';
end else if version = 'v2.0' then begin
versionKey := 'v2.0.50727';
end
// .NET 4.5 and newer install as update to .NET 4.0 Full
else if Pos('v4.', version) = 1 then begin
versionKey := 'v4\Full';
case version of
'v4.5': versionRelease := 378389;
'v4.5.1': versionRelease := 378675; // 378758 on Windows 8 and older
'v4.5.2': versionRelease := 379893;
'v4.6': versionRelease := 393295; // 393297 on Windows 8.1 and older
'v4.6.1': versionRelease := 394254; // 394271 before Win10 November Update
'v4.6.2': versionRelease := 394802; // 394806 before Win10 Anniversary Update
'v4.7': versionRelease := 460798; // 460805 before Win10 Creators Update
'v4.7.1': versionRelease := 461308; // 461310 before Win10 Fall Creators Update
'v4.7.2': versionRelease := 461808; // 461814 before Win10 April 2018 Update
end;
end;
// installation key group for all .NET versions
key := 'SOFTWARE\Microsoft\NET Framework Setup\NDP\' + versionKey;
// .NET 3.0 uses value InstallSuccess in subkey Setup
if Pos('v3.0', version) = 1 then begin
success := RegQueryDWordValue(HKLM, key + '\Setup', 'InstallSuccess', install);
end else begin
success := RegQueryDWordValue(HKLM, key, 'Install', install);
end;
// .NET 4.0 and newer use value Servicing instead of SP
if Pos('v4', version) = 1 then begin
success := success and RegQueryDWordValue(HKLM, key, 'Servicing', serviceCount);
end else begin
success := success and RegQueryDWordValue(HKLM, key, 'SP', serviceCount);
end;
// .NET 4.5 and newer use additional value Release
if versionRelease > 0 then begin
success := success and RegQueryDWordValue(HKLM, key, 'Release', release);
success := success and (release >= versionRelease);
end;
result := success and (install = 1) and (serviceCount >= service);
end;
function InitializeSetup(): Boolean;
begin
if not IsDotNetDetected('v4.7.1', 0) then begin
MsgBox('EXIF Reader requires Microsoft .NET Framework 4.7.1.'#13#13
'Please use Windows Update to install this version,'#13
'and then re-run the EXIF Reader setup program.', mbInformation, MB_OK);
result := false;
end else
result := true;
end;
How to prevent the installation if newer version already installed
In case you want to prevent the installation of your app if newer version already installed in user PC, you can apply this code within InitializeSetup
function, so your installer wouldn’t downgrade existing installation.
Add a custom message under [CustomMessages]
section:
[CustomMessages]
english.NewerVersionExists=A newer version of {#AppName} is already installed.%n%nInstaller version: {#AppVersion}%nCurrent version:
Apply this code under [Code]
section to check for existing installation version:
[Code]
// Check for existing installation version
function InitializeSetup: Boolean;
var Version: String;
begin
if RegValueExists(HKEY_LOCAL_MACHINE,'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#AppId}_is1', 'DisplayVersion') then
begin
RegQueryStringValue(HKEY_LOCAL_MACHINE,'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#AppId}_is1', 'DisplayVersion', Version);
if Version > '{#AppVersion}' then
begin
MsgBox(ExpandConstant('{cm:NewerVersionExists} '+Version), mbInformation, MB_OK);
Result := False;
end
else
begin
Result := True;
end
end
else
begin
Result := True;
end
end;
How to automatically uninstall previous installed version
Based on the solution from Craig McQueen at StackOverflow, your installer can check if the user already installed your app or not and if found, your installer will grab the UninstallString
from the registry and run the uninstallation command in silent mode.
[Code]
function GetUninstallString(): String;
var
sUnInstPath: String;
sUnInstallString: String;
begin
sUnInstPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1');
sUnInstallString := '';
if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then
RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString);
Result := sUnInstallString;
end;
function IsUpgrade(): Boolean;
begin
Result := (GetUninstallString() <> '');
end;
function UnInstallOldVersion(): Integer;
var
sUnInstallString: String;
iResultCode: Integer;
begin
// Return Values:
// 1 - uninstall string is empty
// 2 - error executing the UnInstallString
// 3 - successfully executed the UnInstallString
// default return value
Result := 0;
// get the uninstall string of the old app
sUnInstallString := GetUninstallString();
if sUnInstallString <> '' then begin
sUnInstallString := RemoveQuotes(sUnInstallString);
if Exec(sUnInstallString, '/SILENT /NORESTART /SUPPRESSMSGBOXES','', SW_HIDE, ewWaitUntilTerminated, iResultCode) then
Result := 3
else
Result := 2;
end else
Result := 1;
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if (CurStep=ssInstall) then
begin
if (IsUpgrade()) then
begin
UnInstallOldVersion();
end;
end;
end;
How to kill existing running services before re-install (or upgrade)
To kill existing running services, what you need to do is to apply BeforeInstall parameter at the service file you’re going to (re)install. Take a look on the example script below:
[Files]
Source: ".\bin\Service1.exe"; DestDir: "{app}"; Flags: ignoreversion; BeforeInstall: TaskKill('Service1.exe')
Source: ".\bin\Service2.exe"; DestDir: "{app}"; Flags: ignoreversion; BeforeInstall: TaskKill('Service2.exe')
[Code]
procedure TaskKill(FileName: String);
var
ResultCode: Integer;
begin
Exec(ExpandConstant('taskkill.exe'), '/f /im ' + '"' + FileName + '"', '', SW_HIDE,
ewWaitUntilTerminated, ResultCode);
end;
How to associate a program with an extension during installation
Add a property as per below in [Setup]
section:
[Setup]
ChangesAssociations = yes
This will tell Windows Explorer to refresh its file associations information at the end of the installation, and similarly for the uninstallation. Then, add these following registry keys:
[Registry]
; Associate file extension ".exif" to my app program
Root: HKCR; Subkey: ".exif"; ValueData: "{#MyAppName}"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""
Root: HKCR; Subkey: "{#MyAppName}"; ValueData: "Program {#MyAppName}"; Flags: uninsdeletekey; ValueType: string; ValueName: ""
Root: HKCR; Subkey: "{#MyAppName}\DefaultIcon"; ValueData: "{app}\{#MyAppExeName},0"; ValueType: string; ValueName: ""
Root: HKCR; Subkey: "{#MyAppName}\shell\open\command"; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; ValueType: string; ValueName: ""
If you don’t want to use MSI-based installer for your Windows app, but you want something that is good and free, then look for no other, Inno Setup is the best and easy to use. There are a lot of example scripts out there that can help you build a great installer for your app. This is my personal preference and I’m really recommended it!