Tuesday, September 21, 2010

Inno Setup and recursive installers

We've got a recursive installer in our Inno Setup script. What I mean is that our main setup program installs another setup program (for a viewer), so that the user can copy/install that viewer program at a later date.

Here's how to do that in an Inno Setup script:
Source: {src}\viewer_cd\*; DestDir: {app}\viewer_cd\; Flags: external recursesubdirs replacesameversion
all on one line, of course. I discovered today that Flags: replacesameversion is critical, because it causes the 'setup.exe' file inside that folder to be replaced during debugging. Before I put that flag in, I tested my main installer, then ran the viewer setup.exe, and it ran an old version, with bugs I thought I'd fixed! How annoying. Now setup.exe will get replaced, even if it has the same embedded 'version' info.

Tuesday, September 14, 2010

A switch, taking my own advice

I've taken my own pointer and run with it. Let's go see what
looks like :)

Wednesday, September 8, 2010

Mashable: Run Your Business Online with $10 and a Google Account

HOW TO: Run Your Business Online with $10 and a Google Account:
Seems like a cool way establish an online identity more concretely. I didn't know $10 was all a domain costs for a blogger blog. (blog blog blog ....)
Matt Cutts posts some useful stuff.

Tuesday, September 7, 2010

Review: The Angel's Game, by Carlos Ruiz Zafon

I almost labeled it fantasy. If you start this book, please put aside the time so you can finish it rapidly! I took a week break, and felt lost coming back because of the threads of the plot I hadn't held on to.

It is a fascinating book, worth the time, and it doesn't quite fit where you expect it. The mystical aspects are stronger here than in his first book (The Shadow of the Wind), and that's a negative in my opinion. My wife and I were both somewhat puzzled and dissatisfied with the epilogue/resolution. I enjoyed the characters, the intrigue, and the twisting plot. Overall, recommended.

The Angel's Game at PBSwap.

Building libpano13-2.9.17_rc1 for Hugin

Hugin is great panorama software, but they need people to build it for Windows. I've been occasionally working on it, and I've succeeded in doing complete 32 and 64 bit builds in MSVC 2010. I've almost documented how on the MSVC 2010 wiki page, but it's not quite complete. Thought I'd make note, and invite others to attempt it as well!

Today I compiled the libpano RC, and I'll try the hugin RC soon.

Thursday, September 2, 2010

Inno Setup MSVC vcredist without bothering your users

If your C++ program is compiled with MS Visual Studio 2005 Express, and you link with the DLL versions of the C run-time libraries, you probably already know that you have to run vcredist_x86.exe to install those dependencies on a new computer before your program will run.

Here's how to do that in an Inno Setup script.
First, download vcredist_x86.exe from MSDN, Microsoft Visual C++ 2005 SP1 Redistributable Package (x86)
Notice that's for SP1, the instructions are different for non-SP1, and for the vcredist_x86.exe that comes with Visual Studio Standard or Professional. See the credit link below.

Include this in your script:

Source: {src}\bin\vcredist_x86.exe; DestDir: {app}\bin\;

Filename: {app}\bin\vcredist_x86.exe; Parameters: "/q:a /c:""VCREDI~3.EXE /q:a /c:""""msiexec /i vcredist.msi /qn"""" """; WorkingDir: {app}\bin; StatusMsg: Installing CRT...

Alternative for x64 ( I haven't tested this):
Filename: {app}\bin\vcredist_x64.exe; Parameters: "/q:a /c:""VCREDI~2.EXE /q:a /c:""""msiexec /i vcredist.msi /qn"""" """; WorkingDir: {app}\bin; StatusMsg: Installing CRT...

Note all those quotes. Also you can see that I include vcredist in a 'bin' subdirectory, and I install it along with the other files my app needs. That last param can be /qb! to show progress, or /qn for no interface at all.

I tracked this down from a helpful MSDN blog post. Go check it for the variants needed for other flavors of vcredist.....

Inno Setup custom data directory location

In my last post, I forgot to point you to Inno Setup; go get the Inno Setup QuickStart Pack to get going.
I showed how to copy a directory full of data, like tutorials or sample data, that might change depending on the customer. That means the files are not known when the installer is compiled.

Here's what it looked like:
Source: {src}\data\*; DestDir: C:\MyCompany\data;
   Flags: external recursesubdirs skipifsourcedoesntexist onlyifdoesntexist uninsneveruninstall;
   Permissions: users-modify

This time, I 'm going to show how to let the user choose where this directory is located, and whether to install the contents of the directory at all.

First, let's show an obvious choice:

Source: {src}\data\*; DestDir: {userdocs}\MyCompany\data; Flags: [as above....]

That new constant will put the data in a subdirectory of My Documents, for the user that installs the program. This might be fine for you, if each user of your program is going to install it themselves, and generate their own data. This is exactly what Microsoft had in mind with the My Documents folder.

However, it doesn't work for us. Often, an IT person will install the program, and several users on the computer with different logins will use it and need to access the same data. Our data is also big, so we don't want multiple copies made.

Here's what we actually use:

Source: {src}\data\*; DestDir: {code:GetDataDir}; Check: InstallSampleData; Flags: [as above...]

We use two 'code' pieces to make this work. First we as the user where to put the data, and the function GetDataDir retrieves that value. Second, we ask the user whether to install the data we provide, and InstallSampleData retrieves that answer. Here's what those two routines look like, in the [Code] section of our script:

function GetDataDir(Param: String): String;
  { Return the selected DataDir }
  //MsgBox('GetDataDir.', mbError, MB_OK);
  Result := DataDirPage.Values[0];

function InstallSampleData(): Boolean;
  { Return the value of the 'install' radiobutton }
  //MsgBox('InstallSampleData.', mbError, MB_OK);
  Result := SampleDataPage.Values[0];

Pretty simple - they are just retrieving the answers and returning them. The commented MsgBox calls can let you know when these routines are being called when you run your installer, for the curious.

But where are we getting these values? Custom wizard pages. We need some answers that don't really fit the normal flow of the installer, but can be inserted pretty easily. I followed the CodeDlg.iss example provided with Inno Setup pretty closely, so it's worth looking at that, too. I'm actually writing this blog post because there isn't an explanation to go along with some of the Inno Setup examples.

// global vars
  DataDirPage: TInputDirWizardPage;
  SampleDataPage: TInputOptionWizardPage;
  DataDirVal: String;

// custom wizard page setup, for data dir.
procedure InitializeWizard;
  { Taken from CodeDlg.iss example script }
  { Create custom pages to show during install }

  DataDirPage := CreateInputDirPage(wpSelectDir,
    '{#ShortAppName} Data Directory', '',
    'Please select a location for {#AppName} data. (We recommend the default.)',
    False, '');

  { Set default values, using settings that were stored last time if possible }
  if RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\MyCompanyProg',
     'DataDir', DataDirVal) then begin
    DataDirPage.Values[0] := DataDirVal;
  end else
    DataDirPage.Values[0] := 'C:\
// you might replace the previous with this, which is a per-user way
// to retrieve the previous value:
//   DataDirPage.Values[0] := GetPreviousData('DataDir', 'C:\MyCompany\data\');

  SampleDataPage := CreateInputOptionPage(DataDirPage.ID,
    'Install Sample Data', 'The sample scene is used in our tutorials and training.',
    'Should the sample scene be installed?   ( Recommended for new users, ~ XX GB )',
    True, False);
  SampleDataPage.Add('Do not install');

  SampleDataPage.Values[0] := True;

// This is needed only if you use
GetPreviousData above.
procedure RegisterPreviousData(PreviousDataKey: Integer);
  SetPreviousData(PreviousDataKey, 'DataDir', DataDirPage.Values[0]);

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

Whew! As you can see, it's starting to get long. Basically, I'm created two pre-formatted wizard pages, that let the use choose a directory, and choose between two (or more) options with a radio button. When they are display, their vals will get changed by the user. Then during the installation phase, those values are retrieved and used to create and copy the data directory. We are also using Inno Setup PreProcessor values for the application name in the dialog, so check that out in ISPPExample1.iss.

But wait, there's one more thing:

; If this directory already exists, it sets permissions on all files inside.
; this can take a LONG time (~5 min) so only install if it doesn't already exist.
Name: {code:GetDataDir}; Check: not DataDirExists; Flags: uninsneveruninstall; Permissions: users-modify

The directory won't get created if we don't put anything in it, so we need to include an entry in the Dirs section. And as the comment says, it's worth it to not touch it if it already exists, because setting permissions on a lot of files can take a LONG time, and the installer appears to hang while it's doing that.

That's it for a custom, shared data directory and the Inno Setup script to make it happen. Please let me know if you found this useful, or if it need fixes. Thanks!

Wednesday, September 1, 2010

Inno Setup user data directory

One of the requirements we have for an installer is to create a user data directory. We'd like the installer to create C:\MyCompany\data\, and copy any data we decide to give this customer on their install DVD into that new folder. VS 2005 Install projects couldn't do it without a custom action. Here's how in Inno Setup:

Source: {src}\data\*; DestDir: C:\MyCompany\data;
   Flags: external recursesubdirs skipifsourcedoesntexist onlyifdoesntexist uninsneveruninstall;
   Permissions: users-modify

I've put that on multiple lines so it's easier to read, but it's all one line in the .iss file. Let's take a look at what each of those pieces mean.
  • First, that '*' in the Source: field is magic. It means it will go and find whatever is there in the data\ directory next to your setup.exe. It must be combined with the Flags: external, so that Inno Setup knows not to look for it when your script is compiled.
  • DestDir will be created by the installer, and files copied in. I've hard-coded the directory name in this example, but what we actually do I will discuss in my next Inno Setup post - we let the user choose.
  • Flags: external means the files should be present when the installer runs, not when it is compiled.
  • recursesubdirs means get all the sub-directories and their contents, too. 
  • skipifsourcedoesntexist means don't show a message if there are no files in the source data/ directory to be found.
  • onlyifdoesntexist means don't replace files if they are already on the user's system. This is useful for updating data - you don't want to over-write files the user may have worked on and modified. 
  • uninsneveruninstall means don't remove these files when your app is un-installed. During an upgrade, you might uninstall the previous version, and you don't want sample/tutorial data files to disappear!
  • Permissions: users-modify is also very cool - since we are not installing into My Documents (because our data is big and we want everyone to share the data), we have to make sure user-level accounts (i.e. non-administrator) can edit and save the data. Just tack that on, and every file/directory copied will be modifiable by all the users on this computer, even if it's installed by an admin.
Note that I'm working with ISTool and Inno Setup version 5.3, so double-check if your version is different.

Next time: Allowing the user to customize where this data goes, and whether to install it or not.
Edit: It's up