This blog is used as a memory dump of random thoughts and interesting facts about different things in the world of IT. If anyone finds it useful, the author will be just happy! :-)

Thursday, February 24, 2011

Moving to dotNetInstaller: the odd Basic UI

In the previous post, I’ve outlined how to emulate the launch conditions behavior in dotNetInstaller. In that article I have also emphasized the importance of turning the UI into the Basic mode. It is necessary in order to avoid extra dialogs which require user interaction. If you followed the scenario I described, you might notice a strange behavior of the BasicUI mode: the message boxes disappear without any user participation. I thought it’s be a kind of a bug, but it was done on purpose. Take a look at this code (taken from dotNetInstaller sources):

int DniMessageBox::Show(const std::wstring& p_lpszText, UINT p_nType /*=MB_OK*/, UINT p_nDefaultResult /*=MB_OK*/, UINT p_nIDHelp /*=0*/)
{
int result = p_nDefaultResult;
switch(InstallUILevelSetting::Instance->GetUILevel())
{
// basic UI, dialogs appear and disappea
case InstallUILevelBasic:
{
g_hHook = SetWindowsHookEx(WH_CBT, CBTProc, NULL, GetCurrentThreadId());
CHECK_WIN32_BOOL(NULL != g_hHook, L"Error setting CBT hook");
result = AfxMessageBox(p_lpszText.c_str(), p_nType, p_nIDHelp);
CHECK_BOOL(0 != result, L"Not enough memory to display the message box.");
if (result == 0xFFFFFF) result = p_nDefaultResult;
}
break;

// silent, no UI
case InstallUILevelSilent:
result = p_nDefaultResult;
break;

// full UI
case InstallUILevelFull:
default:
result = AfxMessageBox(p_lpszText.c_str(), p_nType, p_nIDHelp);
break;
}

return result;
}

So, as you can see, in Basic mode is shows the message box, and after some time (if you didn’t catch the moment to press any button), it automatically emulates the pressing of default choice button. I was quite surprised when I understood it was designed to work like this – that’s because I’ve never seen such a UI behavior…

But, anyway, I suspect that a user would like to know why the installation terminated - a certain prerequisite is not installed. As long as the mentioned behavior is hard-coded, the only option is to create a custom build of dotNetInstaller. It’s obvious that the fix is trivial here – make the case for InstallUILevelBasic go the same branch as InstallUILevelFull, that is, just show the message box. Next step is to build the solution – see “Contributing to Source Code” chapter of dotNetInstaller.chm for instructions how to build.

Finally, install the custom build instead of the official one and make sure your setup project picks the changes up. That’s it!

As usual, I would appreciate any comments and notes!


Friday, February 18, 2011

Moving to dotNetInstaller: launch conditions

In the previous post I’ve described how to implement the simplest use case of a bootstrapper: create a single EXE file and run the actual installation after extraction. Today I’d like to go further and illustrate more production-like situation.

Ok, imagine that you’d like to add some checks to your installation package, and run the actual installation only if all those checks pass. This scenario has its own term: adding launch conditions. Launch condition is basically a statement which evaluates to either true, or false. In case it’s false, and the check is critical for the further installation, you terminate the installation process, as a rule. Otherwise, you let it do the job.

The dotNetInstaller has a conception called Installed Checks. It can check various areas, like system registry, files or directories. It is only allowed to place installed checks under components. In the simplest scenario we avoided using components, relying just on the install complete command. Components refer to separate independent parts of your installation package. There are various types of components – dotNetInstaller help file explains them all pretty good. So, my first guess was to add a single component of type “exe”, move my embedded files there and add a number of installed checks to it for various prerequisites I require. Something like this:

DNI_prerequisite_wrong

But my assumption was not correct. The trick is that installed check (or a combination of those) placed under a component defines if this very component is installed. In other words, the most “supported” use case of dotNetInstaller is when you add all the components you need into your final package, and each of them verifies its own presence on the target machine. As a result of such verification, a component decides whether to install or not.

A quick search on codeplex.com discussions gave me a link to the appropriate feature request, which proved my assumption it’s not supported out of the box today. However, there is a workaround.

For each of the launch conditions a separate component should be declared. The trick is such components won’t actually install anything, so we’ll call them “fake” components. A component has a property called “failed_exec_command_continue”. It contains a message to be shown to the user in case a component failed to install, so put the appropriate message there, for instance, “.NET 3.5 SP1 is not installed. The installation program will terminate”. Make sure that both “allow_continue_on_error” and “default_continue_on_error” are set to False – otherwise a user will be presented with a prompt box, instead of a simple message box. Finally, put non-existing executable to the “executable” property, e.g. “fake.exe”. Now it’s time to add a required number and combination of installed checks to this fake component, which will actually do the job. Here’s what we get at the end of this shaman dancing:

DNI_prerequisite_right

So, how does this work? The dotNetInstaller starts the installation from the .NET (3.5 SP1) component and the first thing it evaluates the installed checks. If the evaluation succeeds, in our sample this means that the .NET 3.5 SP1 is present on the target machine. In terms of dotNetInstaller, this means that a component we called “.NET (3.5 SP1)” is installed and we do not trigger its installation. Otherwise, if the evaluation fails, this means that the component is not present and dotNetInstaller starts its installation. It will try to call “fake.exe”, which does not exist, and will show a message. As long as we forbad the rest of the installation to continue, it will terminate. Exactly what we need!

Note however, that the described behavior looks that good in Basic UI mode. The error of failed component is just logged to the log file, and no more annoying dialogs are displayed.

If you try this out, you’ll notice one strange little thing with message boxes. In the next blog post I’ll tell you what it is, and how to handle it. And this will be the end of the trilogy. :-)