Skip to content

Create an InnoSetup Installer

Patrick Heyer edited this page Nov 1, 2024 · 1 revision

Contents

  1. Create an InnoSetup Installer (Windows)
  2. Automatically Generate An InnoSetup File
  3. Note About Installation Location
  4. Note About License Screens
  5. Generate The Installer Program
  6. Example Script File With CMake Customization

Create an InnoSetup Installer (Windows)

To create an InnoSetup installer for distribution on Windows, a setup script needs to be generated. This script file is then provided to InnoSetup's iscc compiler which will create the installer itself.

A basic InnoSetup script is composed of separate sections that contain metadata for the plugin as well as installation instructions.

Setup

[Setup]
AppId=00000000-0000-0000-0000-000000000000

This ID should be a unique identifier for the plugin, generating a version 4 UUID should be sufficient for this.

AppName=my-plugin-for-obs
AppVersion=1.0.0
AppPublisher=My Name
AppPublisherUrl=https://my-plugin.com
AppSupportUrl=https://my-plugin.com/support
AppUpdatesUrl=https://my-plugin.com/updates

These fields contain the metadata for the plugin. To keep the maintenance burden of a new version down, the file (and thus these values) can possibly be automatically filled out by the build system. See below for instructions on this.

DefaultDirName={commonappdata}\obs-studio\plugins\my-plugin
DefaultGroupName=my-plugin-for-obs

These two options set the default installation destination used by the installer as well as the start menu directory ("group") to create. It might also be worth to set the DirExistsWarning directive to no to allow silent updating (so any existing files will be silently overwritten by the installer).

OutputBaseFilename=My-Plugin-For-OBS-Installer

This option sets the base name for the installer application generated by InnoSetup, which would commonly result in an installer named My-Plugin-For-OBS-Installer.exe to be generated.

Compression=lzma
SolidCompression=yes

The compression format lzma has a significantly better compression ratio than zip, but it will also take a bit longer to create an archive.

As this process is not commonly repeated all too often, the compression ratio benefits should outweigh the compression time cost. The SolidCompression directive also instructs InnoSetup to compress all files at once instead of separately, which can improve compression ratio as well.

Languages

[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"

The installer program itself can be localized via the Languages section. Each language requires its own 3rd-party definition files as InnoSetup only ships with English default files. More information can be found in InnoSetup's documentation.

Files

[Files]
Source: "<Path to plugin files>\*"; DestDir: "{app}"; Flags: recursesubdirs createallsubdirs
Source: "<Path to other plugin file>"; DestDir: "{app}"

The Files section contains information about where to put files contained in the installer (and also which files to ignore). Each Source can either be a single file or a wildcard expression to match all files in a directory. If relative paths are provided, then the current working directory of the process invoking the iscc compiler is used as a root for these paths.

The built-in constant {app} represents the destination directory chosen by the user as part of the installation process.

If a wildcard is used, additional Flags can be provided to influence InnoSetup's file discovery behavior:

  • recursesubdirs tells InnoSetup to recursively discover all directories and their sub-directories found in the source location.
  • createallsubdirs tells InnoSetup to not skip empty sub-directories it may encounter during the recursive discovery and also recreate these empty directories at the install destination.

As no DestName directive is provided, all file names will be kept as-is. Optionally the ignoreversion flag could be used to instruct InnoSetup to ignore any and all version information found in source and destination files.

Icons

[Icons]
Name: "{group}\{cm:ProgramOnTheWeb,my-plugin-for-obs}"; Filename: "https://my-plugin.com"

This section defines the start menu entries that should be created as part of the installation. In this example only a web link to the plugin's web site is added.

Important

In the past the installer script also created a start menu entry for the uninstall program, similar to this:

Name: "{group}\{cm:UninstallProgram,my-plugin-for-obs}"; Filename: {uninstallexe}

This is bad practice - Microsoft's own guidance since Windows 95 has been for developers to not add such an entry to the start menu, as users are supposed to use the corresponding "Add/Remove Programs" setting provided by Windows itself. In fact, Windows 11 has started to automatically hide these entries if they are added.

The {cm:<...>} entry uses a template name from the default language file which takes a name argument. As this example only provides an English language file, such entries will use the English template strings.

Automatically Generate An InnoSetup File

Many values used by the InnoSetup script are kept in the buildspec.json file and are thus known to the build system already. So instead of writing the InnoSetup file manually, one could use the build system to generate the file.

Example Using CMake

When using CMake as the build system generator, this can be achieved via the configure_file command and a CMake template file which commonly uses the final output name with an .in suffix (e.g. my-template-installer.iss.in).

To avoid repetitions inside the script file, CMake variables can be defined at the very top of it (before any other section):

#define MyAppName "@CMAKE_PROJECT_NAME@"
#define MyAppVersion "@CMAKE_PROJECT_VERSION@"
#define MyAppPublisher "@PLUGIN_AUTHOR@"
#define MyAppURL "@PLUGIN_WEBSITE@"

When configure_file is called with this template file as its first argument, CMake will replace any strings of the format @VARIABLE_NAME@ with the value of the variable. Note that it is not strictly necessary to use #define, as CMake will replace any other occurrences directly, e.g.:

AppId={{@UUID_APP@}

This will put a CMake variable called UUID_APP into a string enclosed by curly braces and assign it to the AppId variable (note the double curly braces at the beginning - these are necessary to make InnoSetup use the actual opening curly brace character and not interpret it as an InnoSetup constant).

Important

The UUID_APP variable needs to be provided to CMake either via the preset or directly on the CMake command line.

By default CMake will put files generated by configure_file into the binary output directory. If another (more controlled) output directory is desired, provide it as the second argument to the function.

Note About Installation Location

In a now obsolete variant of the plugin template the InnoSetup script file was generated automatically. This was removed in favor of this documentation because of the maintenance burden to keep a valid and working InnoSetup script as part of the template's code base.

This variant used a [Code] section in the installer script to detect the installation location of OBS Studio to be able to copy plugins directly into the application directories.

This practice is now discouraged and no example code is provided for it:

  • This approach required a version of OBS Studio that was installed via its own installation program. Any version not "installed" (but instead just extracted in some other location) would not be discovered.
  • It also lead to a mix of 1st-party and 3rd-party plugins in the application directory, which - coupled with the unfortunate practice of naming plugins obs-your-plugin - made it unnecessarily hard for users to distinguish between OBS Studio's own modules and 3rd party plugins

Plugins should be installed in %PROGRAMDATA\obs-studio\plugins instead, which this guide assumes as well.

Note About License Screens

The obsolete variant of the InnoSetup script provided by the template also used the LICENSE file to create a license approval screen as part of the installer. There is no need to display such a license screen to users and it is considered an anti-pattern to do so (because the GPL is not an EULA). The Free Software Foundation (FSF) has the following to say about this practice:

Merely agreeing to the GPL doesn't place any obligations on you. You are not required to agree to anything to merely use software which is licensed under the GPL. You only have obligations if you modify or distribute the software. If it really bothers you to click through the GPL, nothing stops you from hacking the GPLed software to bypass this.

Thus this part of the installation script is not used by this guide either.

Generate The Installer Program

To generate the actual installer program, the iscc compiler is used:

iscc <Path to generated .iss file> /O<Output path for generated installer>

Be aware that the compiler will use the current working directory to resolve any relative links that might have been used for Source directives in the Files section.

Build Installer Program on GitHub Actions

To build the installer program as part of the GitHub Actions CI workflow, the Windows packaging script Package-Windows.ps1 needs to be modified to include the iscc compilation steps. The script file can be found in the scripts sub-directory of the .github directory.

As iscc is an external program, Invoke-External (a custom PowerShell function used as part of the build scripts) should be used to allow for proper error handling in the PowerShell script. This example will generate an installer using the release sub-directory of the project root as its output directory and will also override the output name of the script:

# Declare the location of the InnoSetup setup file
$IsccFile = "${ProjectRoot}/build_${Target}/<NAME OF YOUR GENERATED INNOSETUP SCRIPT FILE>"

# Throw an error if the provided path is invalid
if ( ! ( Test-Path -Path $IsccFile ) ) {
    throw 'InnoSetup install script not found. Run the build script or the CMake build and install procedures first.'
}

Log-Information 'Creating InnoSetup installer...'

# Push the current location on the "BuildTemp" directory stack for easier return later
Push-Location -Stack BuildTemp

# Change to "release" sub-directory of the project root directory
Ensure-Location -Path "${ProjectRoot}/release"

# Copy the directory for the specified configuration (e.g. "Release") to a new directory named "Package"
Copy-Item -Path ${Configuration} -Destination Package -Recurse

# Invoke the InnoSetup iscc compiler with the specified setup file and the sub-directory "release" in 
# the project root directory as the output directory
Invoke-External iscc ${IsccFile} /O"${ProjectRoot}/release" /F"${OutputName}-Installer"

# Remove the copied "Package" directory and its contents
Remove-Item -Path Package -Recurse

# Pop the location stored in the "BuildTemp" directory stack earlier
Pop-Location -Stack BuildTemp

Log-Group

This example expects CMake to have used the Visual Studio generator, which is a multi-config generator. As explained in CMake Build System Guide this means that the configuration type is not fixed at the point of time the build system is generated and thus is not available when the InnoSetup script file is generated.

Instead the PowerShell script will receive the configuration that was used to build the plugin and copies the files into a generic directory called Package which is then used by the installer script. This corresponds to the Source directive using ..\release\Package\* in the script.

Note

If the generated installer is placed in a directory called release in the project's root (next to the generated .zip archive), it will be automatically discovered by the GitHub Actions workflows and added to a GitHub release.

Example Script File With CMake Customization

#define MyAppName "@CMAKE_PROJECT_NAME@"
#define MyAppVersion "@CMAKE_PROJECT_VERSION@"
#define MyAppPublisher "@PLUGIN_AUTHOR@"
#define MyAppURL "@PLUGIN_WEBSITE@"

[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={{@UUID_APP@}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={commonappdata}\obs-studio\plugins\my-plugin
DefaultGroupName={#MyAppName}
OutputBaseFilename={#MyAppName}-{#MyAppVersion}-Windows-Installer
Compression=lzma
SolidCompression=yes
DirExistsWarning=no

[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"

[Files]
Source: "..\release\Package\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files

[Icons]
Name: "{group}\{cm:ProgramOnTheWeb,{#MyAppName}}"; Filename: "{#MyAppURL}"