Oct

7

Teaser image for blog post

Making a simple installer for your desktop application

We needed an installer for our new Landboss Accounting Exporter but didn't want to deal with the complexity that many installation solutions come with.

A feature of our Landboss product, which runs in the browser, is QuickBooks Desktop integration and when that integration was first built years ago, the technology chosen was ActiveX, a framework created by Microsoft. Unfortunately, ActiveX never took off and so currently only Internet Explorer supports it. When we purchased the product from InterWorks, we knew that we needed to remove that restriction and we determined that the best way to do that would be to decouple the integration from the browser completely. With that in mind, we began working on a modern API for Landboss and a desktop application that would be the link between it and QuickBooks Desktop.

The engine of the Landboss Accounting Exporter utility is a .Net Standard 2.0 assembly with a WPF UI targeting .Net 4.7.2 and our needs for the installation are pretty standard: along with the application assemblies, we want the installer to be able to create a shortcut in the Start Menu, a user settings file in the %appdata% folder, and ensure that 3rd party dependencies like .Net are installed. With those straightforward requirements, we could not justify dealing with the complexity, learning curve, or cost of many of the more popular installation authoring tools.

After some research, we found Inno Setup, a free installer that has many robust features and to help reduce the learning curve, it includes a wizard for creating your installation file.

Running the wizard

When you run the Inno Setup Compiler, you are given the option to create a new empty file, create one with the Script Wizard, or open an existing script file. When you choose the wizard, you are asked several questions about your application, like the name, version, and publisher, which files to include, and which things the user can customize during the install.

Image of Inno Setup Wizard

Customizing the setup script

The wizard does a pretty good job of getting you started, but we found that needed to make a few changes for our use case. For reference, here is the output of the wizard:

; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!

#define MyAppName "Landboss Accounting Exporter"
#define MyAppVersion "1.0"
#define MyAppPublisher "UnitK Software, LLC"
#define MyAppURL "https://landboss.com/"
#define MyAppExeName "Landboss Accounting Exporter.exe"

[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={{02DF748D-50BB-4EF3-8210-8C332C4C6838}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={autopf}\{#MyAppName}
DisableProgramGroupPage=yes
; Uncomment the following line to run in non administrative install mode (install for current user only.)
;PrivilegesRequired=lowest
OutputBaseFilename=Landboss-Accounting-Exporter
Compression=lzma
SolidCompression=yes
WizardStyle=modern

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

[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked

[Files]
Source: "D:\Code\Landboss\Landboss-Accounting-Exporter\src\Landboss.Accounting.Exporter\bin\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files

[Icons]
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon

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

Getting the product version from the exe

The first thing that we wanted to do was to streamline the version number changes, so instead of hard-coding the version in the setup file and then having to change it each time we do a release, we took advantage of a built-in function supported by InnoSetup called, GetFileVersion.

#define MyAppVersion GetFileVersion('D:\Code\Landboss\Landboss-Accounting-Exporter\src\Landboss.Accounting.Exporter\bin\Release\Landboss Accounting Exporter.exe')

With that change, the script will automatically grab the version number when it builds the installer, leaving us one less thing to worry about when pushing a new release.

Limiting which versions of Windows are supported

When building the UI, we chose .Net Framework 4.7.2 as version 4.8 was only a few months old at the time. Since 4.7.2 is only supported on Windows 7 and newer, we needed to ensure that the installer would not try to run on older versions, just in case and to avoid any unnecessary support calls.

; App uses dotnet 4.7.2 which is supported by Windows 7 SP1 and Server 2008 SP1 and higher
; http://www.jrsoftware.org/ishelp/index.php?topic=isxfunc_getwindowsversionex
MinVersion=6.1.7601

Prepping the AppData directory

The application stores user-specific logs and configs in the user's AppData directory and we wanted the installer to go ahead and create that location for us.

[Dirs]
; Be sure to update [UninstallDelete] to be sure we clean up after ourselves
Name: {userappdata}\UnitK Software\Landboss Accounting Exporter

As you can see from the comment, we needed to make sure that the directory was deleted when uninstalling the application. We'll look at that later in the post.

Leaving behind debug symbols

When the wizard was ran it ended up including all of the files in the release folder, which includes the debug symbols and some other files that we don't want to include in the installer, so we made some changes and additions:

[Files]
Source: "D:\Code\Landboss\Landboss-Accounting-Exporter\src\Landboss.Accounting.Exporter\bin\Release\Landboss Accounting Exporter.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "D:\Code\Landboss\Landboss-Accounting-Exporter\src\Landboss.Accounting.Exporter\bin\Release\*.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "D:\Code\Landboss\Landboss-Accounting-Exporter\src\Landboss.Accounting.Exporter\bin\Release\*.config"; DestDir: "{app}"; Flags: ignoreversion

The first line grabs the exe specifically since we don't want to accidentally distribute just any executable that makes its way into the release folder; tools or utilities for instance. The second line includes all dll's and the final line includes all config's.

Cleaning up after ourselves

Earlier, we asked the script to create a directory for our user logs and configs in the AppData folder, so we need to be sure that location is removed on uninstall in order to not leave behind junk that takes up the user's disk drive.

[UninstallDelete]
Type: filesandordirs; Name: "{userappdata}\UnitK Software\Landboss Accounting Exporter"

Dealing with dependencies

Other than .Net Framework 4.7.2, we also needed to install the QuickBooks Desktop SDK. InnoSetup does not have a built-in 3rd party dependency handling mechanism; however, it does support extension scripts. We quickly found a GitHub project that contained a set of scripts to handle dependency installations in a generic way as well as several scripts for common dependencies, including one for .Net Framework 4.7.2, so that is one dependency out of the way.

; Dependency Installer: https://github.com/domgho/innodependencyinstaller
[CustomMessages]
DependenciesDir=dependencies
WindowsServicePack=Windows %1 Service Pack %2

; shared code for installing the products
#include "scripts\lang\english.iss"
#include "scripts\products.iss"

; helper functions
#include "scripts\products\stringversion.iss"
#include "scripts\products\winversion.iss"
#include "scripts\products\fileversion.iss"
#include "scripts\products\dotnetfxversion.iss"

; actual products
#include "scripts\products\dotnetfx47.iss"

[Code]
function InitializeSetup(): boolean;
begin
    // initialize windows version
    initwinversion();

    dotnetfx47(72); // min allowed version is 4.7.2

    Result := true;
end;

Next we needed to handle the QuickBooks SDK. We added a new dependency script for it.

[CustomMessages]
qbfc13_title=QuickBooks Foundation Class (QBFC) v13.0
qbfc13_size=9.49MB

[Code]
const
    qbfc13_url = 'https://the/source/url/for/QBFC13_0Installer.exe';

procedure qbfc13();
begin
    Log('Checking for QBFC13...');	
    if RegKeyExists(HKLM, 'SOFTWARE\WOW6432Node\Classes\CLSID\{22E885D7-FB0B-49E3-B905-CCA6BD526B52}') then
    begin
        Log('QBFC13 is installed.');		
    end		
    else
    begin
        Log('QBFC13 not installed. Adding it to list of products to be installed.');
        AddProduct('QBFC13_0Installer.exe',
            '',
            CustomMessage('qbfc13_title'),
            CustomMessage('qbfc13_size'),
            qbfc13_url,
            false, false, false);
    end
end;

[Setup]

Next we modified the setup script.

; actual products
#include "scripts\products\dotnetfx47.iss"
#include "scripts\products\qbfc13.iss"

[Code]
function InitializeSetup(): boolean;
begin
    // initialize windows version
    initwinversion();

    dotnetfx47(72); // min allowed version is 4.7.2
    qbfc13();

    Result := true;
end;

Final Script

For reference, below is the final script after all of our changes.

; Script generated by the Inno Setup Script Wizard.
; Manually Modified

#define MyAppName "Landboss Accounting Exporter"
#define MyAppVersion GetFileVersion('D:\Code\Landboss\Landboss-Accounting-Exporter\src\Landboss.Accounting.Exporter\bin\Release\Landboss Accounting Exporter.exe')
#define MyAppPublisher "UnitK Software, LLC"
#define MyAppURL "https://www.landboss.com"
#define MyAppExeName "Landboss Accounting Exporter.exe"

[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={{02DF748D-50BB-4EF3-8210-8C332C4C6838}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={autopf}\UnitK Software\Landboss Accounting Exporter
DefaultGroupName=UnitK Software
AllowNoIcons=yes
; Uncomment the following line to run in non administrative install mode (install for current user only.)
;PrivilegesRequired=lowest
PrivilegesRequiredOverridesAllowed=dialog
OutputDir=D:\Code\Landboss\Landboss-Accounting-Exporter\updates
OutputBaseFilename=Landboss-Accounting-Exporter-v{#MyAppVersion}-Setup
SetupIconFile=D:\Code\Landboss\Landboss-Accounting-Exporter\src\Landboss.Accounting.Exporter\Images\logo.ico
Compression=lzma
SolidCompression=yes
WizardStyle=modern
UninstallDisplayIcon={app}\{#MyAppExeName}
UninstallDisplayName=Landboss Accounting Exporter
SetupLogging=yes

; App uses dotnet 4.7.2 which is supported by Windows 7 SP1 and Server 2008 SP1 and higher
; http://www.jrsoftware.org/ishelp/index.php?topic=isxfunc_getwindowsversionex
MinVersion=6.1.7601

[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked

[Dirs]
; Be sure to update [UninstallDelete] to be sure we clean up after ourselves
Name: {userappdata}\UnitK Software\Landboss Accounting Exporter

[Files]
Source: "D:\Code\Landboss\Landboss-Accounting-Exporter\src\Landboss.Accounting.Exporter\bin\Release\Landboss Accounting Exporter.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "D:\Code\Landboss\Landboss-Accounting-Exporter\src\Landboss.Accounting.Exporter\bin\Release\*.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "D:\Code\Landboss\Landboss-Accounting-Exporter\src\Landboss.Accounting.Exporter\bin\Release\*.config"; DestDir: "{app}"; Flags: ignoreversion
; NOTE: Don't use "Flags: ignoreversion" on any shared system files

[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon

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

[UninstallDelete]
Type: filesandordirs; Name: "{userappdata}\UnitK Software\Landboss Accounting Exporter"

; Dependency Installer: https://github.com/domgho/innodependencyinstaller
[CustomMessages]
DependenciesDir=dependencies
WindowsServicePack=Windows %1 Service Pack %2

; shared code for installing the products
#include "scripts\lang\english.iss"
#include "scripts\products.iss"

; helper functions
#include "scripts\products\stringversion.iss"
#include "scripts\products\winversion.iss"
#include "scripts\products\fileversion.iss"
#include "scripts\products\dotnetfxversion.iss"

; actual products
#include "scripts\products\dotnetfx47.iss"
#include "scripts\products\qbfc13.iss"

[Code]
function InitializeSetup(): boolean;
begin
    // initialize windows version
    initwinversion();

    dotnetfx47(72); // min allowed version is 4.7.2
    qbfc13();

    Result := true;
end;

Depending on your needs, InnoSetup may not be the best solution in your situation, but if you find that it is, we hope that this post helps you get started. Happy developing!

Blog

Words from the team

Get In Touch

Contact Us