cancel
Showing results for 
Search instead for 
Did you mean: 
Highlighted
ACordner
Occasional contributor

How To: Showing the UI for Chained MSI Packages

For years now I have had an installer that included two chained MSIs. It always bothered me that the UI for the "parent" installer would obscure the UI for the chained MSIs, and there was no way straight-forward way to get around it. In addition, there was also no way of making the progress bar of the "parent" UI indicate the progress of the chained MSIs, and so far as I could tell, no way to specify "Action Text" that woulod be displayed above the progress bar to indicate which chained MSI package was being installed.

I had tried implementing a Custom Action in my chained MSIs to execute a "BringWindowToTop" Windows API call, but that never worked. So finally I decided I was going to figure this out.

Cutting to the chase, what I ended up with was a set of Custom Actions to minimize the "parent" UI right before the chained MSIs were launched, and restore the "parent" UI right after they finished. And now, I finally have what I was always looking for!

So, here's the solution (sorry for the flatness of the code - I can't figure out formatting in this editor)...

Create a Win32 DLL that contains the following code:

using System;
using System.Runtime.InteropServices;

namespace CustomActions
{
public static class WindowUtilities
{
private const int SW_MINIMIZE = 6;
private const int SW_RESTORE = 9;

[DllImport("user32.dll", EntryPoint = "FindWindow")]
public static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

public static void MinimizeWindow(string caption)
{
IntPtr hwnd = FindWindowByCaption(IntPtr.Zero, caption);
if (hwnd != null)
{
ShowWindow(hwnd, SW_MINIMIZE);
}
}

public static void RestoreWindow(string caption)
{
IntPtr hwnd = FindWindowByCaption(IntPtr.Zero, caption);
if (hwnd != null)
{
ShowWindow(hwnd, SW_RESTORE);
}
}
}
}

Now, in your "parent" InstallShield project, set up 4 new Custom Actions:

Name: SetMinimizeUIProperty
Type: Set Property
Property Name: MinimizeUI
Value: [ProductName] - InstallShield Wizard
InstallExecSequence: After ScheduleReboot

Name: MinimizeUI
Type: New Managed Code (I prefer using 'Stored in Binary table')
Assembly File: <reference your DLL>
Method Signature: <reference the MinimizeUI method in the DLL, passing [CustomActionData] as the only argument>
In-script Execution: Deferred Execution
InstallExecSequence: After SetMinimizeUIProperty
Condition: Not Installed

Name: SetRestoreUIProperty
Type: Set Property
Property Name: RestoreUI
Value: [ProductName] - InstallShield Wizard
InstallExecSequence: After InstallInitialize

Name: RestoreUI
Type: New Managed Code (I prefer using 'Stored in Binary table')
Assembly File: <reference your DLL>
Method Signature: <reference the RestoreUI method in the DLL, passing [CustomActionData] as the only argument>
In-script Execution: Commit Execution
InstallExecSequence: After SetRestoreUIProperty
Condition: Not Installed

That's it! At first, it may seem backward that the RestoreUI is called after InstallInitialize and MinimizeUI is called after ScheduleReboot, but the In-script Execution settings determine which pass these CAs will execute on, causing the MinimizeUI to happen right before the chained MSIs are launched, and the RestoreUI to happen right after they complete.

One thing I have not accounted for above is failed/aborted installations. It may also be wise to create a set of "restore" CAs for Rollback Exection as well.

 

Labels (1)
Tags (2)
0 Kudos