Configurable Data Directories via Inno Setup

Recently, I’ve been relying on Inno Setup installation scripts to build setup executables for my Windows installs. I’ve given up on .NET deployment projects, and even InstallShield Express (why does my brain insist on reading ‘Express’ as ‘CrippleWare‘?) given their limitations.

The two big wins with Inno Setup for me are 1) it’s freeware, and 2) if I need to do something funky, I can roll my own behaviour into the script with its Pascal scripting option.

Today’s post is how I wrote an installer wizard that prompts the user for a “Data Directory” that receives data that I don’t want sitting in the “Program Files” area of Windows, and a configuration file so I can re-install to a different data-directory and allow the program toggle between them via config file modifications.

I do this because a very wise man once pointed out to me that:

Data that evolves at different rates should be designed to evolve separately.

Programming tends to evolve more slowly than input data, which can typically evolve more slowly than output data. This approach is a tip of the hat to his wisdom. Also, as I’ve learned several times:

The programs can come and go, but if you let it, the data will outlive them.

So, back to Inno Script and getting it to generate a config-file at install time that points at the Data-directory we will store our data to. I’ve settled on INI files, given their ease of understanding for my target users, and Inno Setup’s favouring of them.

First, I define a directory under the [Dirs] section. The name of the directory is delegated to being resolved via a call to some code I define later called GetDataDir(). I check that the directory doesn’t exist. I ask that it not be uninstalled with the software, and I grant the user permission to modify the directory contents:

[Dirs]
Name: {code:GetDataDir}; Check: not DataDirExists; Flags: uninsneveruninstall; Permissions: users-modify

Then in the [Files] section, I identify the files I want (here an entire directory I’ve pre-defined with the constant {#DataDir}. I ask that it confirms any over-write that it might have to do of pre-existing data, to recurse through any sub-directory and again, that the contents are to be left intact on an uninstall of the software:

[Files]
Source: "{#DataDir}\*"; DestDir: "{code:GetDataDir}";  Flags: confirmoverwrite recursesubdirs uninsneveruninstall

Next, I write some Pascal code to prompt the user for a data directory. If an INI file doesn’t exist, it will initially supply text based on the default location for the user’s application data. If the file does exist, the wizard will use the value it finds in the file instead.

A warning I should point out here is that turned out to be a bit tricky to get working as intended.

Because I want the wizard to prompt the user for a data directory directly after they’ve been prompted for the program directory, I’ve learned that program directory constant {app} isn’t available yet when this code runs. I need to reach under the hood, and pluck the value out of the running Delphi API code via a call to WizardDirDrive(), which returns whatever the user accepted as the program directory in the install wizard’s previous step.

[Code]
// global vars
var
  DataDirPage: TInputDirWizardPage;
  SampleDataPage: TInputOptionWizardPage;
  DataDirVal: String;

function GetDataDir(Param: String): String;
begin
  { Return the selected DataDir }
  Result := DataDirPage.Values[0];
end;

function GetDefaultDataDirectory() : String;
begin
  Result := ExpandConstant('{localappdata}\{#MyShortAppName}');
end;

function GetIniFilename() : String;
begin
    Result :=  WizardDirValue() + '\{#MyShortAppName}.ini';
end;

  // custom wizard page setup, for data dir.
procedure InitializeWizard;
var
  myLocalAppData: String;
begin
  DataDirPage := CreateInputDirPage(
    wpSelectDir,
    '{#MyLongAppName} Data Directory',
    '',
    'Please select a directory to install {#MyShortAppName} data to.',
    False,
    '{#MyShortAppName}'
  );
  DataDirPage.Add('');

  DataDirPage.Values[0] := GetIniString('{#MyShortAppName}', '{#INI_DataDirKey}', GetDefaultDataDirectory(), GetIniFilename());
end;

function DataDirExists(): Boolean;
begin
  { Find out if data dir already exists }
  Result := DirExists(GetDataDir(''));
end;

Finally, whatever the user chose via the scripted code will ned to be stored in this INI file. I add an entry to the [INI] section telling the script to write the DataDirectory value to the INI file:

[INI]
Filename: "{app}\{#MyShortAppName}.ini"; Section: "{#MyShortAppName}"; Key: "DataDirectory"; String: "{code:GetDataDir}"; Flags: createkeyifdoesntexist

And there we have it. Inno Setup. It’s powerful, it’s free, and it allows me to tack hard against default behaviour if I need something non-standard. If you’re programming for Windows, and need a a top-notch installer that’s free, take it for a spin.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s