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

Installer welcome dialog
Installer welcome dialog

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

; 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.)
; Default location of installation
; Installer name
; Installer icon
; Uninstaller icon
; Installer vertical banner, max resolution: 164x314 px
; 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.
; Installer FileVersion
; Installer output is the same as source (.iss file)

Name: "english"; MessagesFile: "compiler:Default.isl"

Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1

; 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

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

Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent

; Customized license agreement checkbox messages
LicenseNotAccepted=&Do Not Agree

// I'm using horizontal top banner instead of logo
procedure InitializeWizard();
  BitmapImage: TBitmapImage;
  BitmapImage := TBitmapImage.Create(WizardForm);
  BitmapImage.Parent := WizardForm.MainPanel;
  BitmapImage.Width := WizardForm.MainPanel.Width;
  BitmapImage.Height := WizardForm.MainPanel.Height;
  BitmapImage.Stretch := True;
  BitmapImage.AutoSize := False;

  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);

Meaning of some common Flags under [Files] section

  • ignoreversion - replace existing files regardless of their version number
  • onlyifdoesntexist - only install the file if it doesn’t already exist on the user’s system
  • recursesubdirs - use this when you use wildcard for the Source as shown in the script above
  • createallsubdirs - create all subdirectories, and must be combined with recursesubdirs
  • uninsneveruninstall - never remove the file during uninstallation
  • deleteafterinstall - 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

Components-based installation dialog
Components-based installation dialog

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.

AppName=My Program
DefaultDirName={pf}\My Program
DefaultGroupName=My Program
OutputDir=userdocs:Inno Setup Examples Output

Name: "full"; Description: "Full installation"
Name: "compact"; Description: "Compact installation"
Name: "custom"; Description: "Custom installation"; Flags: iscustom

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

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

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 component
  • exclusive - 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:

; .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:

// 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;
  readVal: cardinal;
  success: Boolean;
  success := RegQueryDWordValue(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', readVal);
  success := success and (readVal < 461308);
  Result := not success;

// Create progress dialog for installing .NET Framework 4.7.1
procedure InstallFramework;
    StatusText: string;
    ResultCode: Integer;
   StatusText := WizardForm.StatusLabel.Caption;
   WizardForm.StatusLabel.Caption := 'Installing .NET Framework 4.5.2. This might take a few minutes...';
   WizardForm.ProgressGauge.Style := npbstMarquee;

       Exec(ExpandConstant('{tmp}\NDP471-KB4033342-x86-x64-AllOS-ENU.exe'), '/passive /norestart', '', SW_SHOW, ewWaitUntilTerminated, ResultCode)
       if ResultCode <> 0 then
           MsgBox('.NET installation failed with code: ' + IntToStr(ResultCode) + '.' + #13#10 + #13#10 + 'Setup will now terminate.', mbError, MB_OK);
           WizardForm.StatusLabel.Caption := StatusText;
           WizardForm.ProgressGauge.Style := npbstNormal;

    ForceClose: Boolean;

procedure Exterminate;
    ForceClose:= True;

procedure CancelButtonClick(CurPageID: Integer; var Cancel, Confirm: Boolean);
    Confirm:= not ForceClose;

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.

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
    key, versionKey: string;
    install, release, serviceCount, versionRelease: cardinal;
    success: boolean;
    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';

    // .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

    // 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);

    // .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);

    // .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);

    result := success and (install = 1) and (serviceCount >= service);

function InitializeSetup(): Boolean;
    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;

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:

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:

// Check for existing installation version
function InitializeSetup: Boolean;
var Version: String;
  if RegValueExists(HKEY_LOCAL_MACHINE,'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#AppId}_is1', 'DisplayVersion') then
      RegQueryStringValue(HKEY_LOCAL_MACHINE,'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#AppId}_is1', 'DisplayVersion', Version);
      if Version > '{#AppVersion}' then
          MsgBox(ExpandConstant('{cm:NewerVersionExists} '+Version), mbInformation, MB_OK);
          Result := False;
          Result := True;
      Result := True;

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.

function GetUninstallString(): String;
  sUnInstPath: String;
  sUnInstallString: String;
  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;

function IsUpgrade(): Boolean;
  Result := (GetUninstallString() <> '');

function UnInstallOldVersion(): Integer;
  sUnInstallString: String;
  iResultCode: Integer;
  // 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
      Result := 2;
  end else
    Result := 1;

procedure CurStepChanged(CurStep: TSetupStep);
  if (CurStep=ssInstall) then
    if (IsUpgrade()) then

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:

Source: ".\bin\Service1.exe"; DestDir: "{app}"; Flags: ignoreversion; BeforeInstall: TaskKill('Service1.exe')
Source: ".\bin\Service2.exe"; DestDir: "{app}"; Flags: ignoreversion; BeforeInstall: TaskKill('Service2.exe')

procedure TaskKill(FileName: String);
  ResultCode: Integer;
    Exec(ExpandConstant('taskkill.exe'), '/f /im ' + '"' + FileName + '"', '', SW_HIDE,
     ewWaitUntilTerminated, ResultCode);

How to associate a program with an extension during installation

Add a property as per below in [Setup] section:

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:

; 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!