I’ve used MS Installer class to provide custom actions during setup in a few projects(e.g.see Using VS 2005 Web Setup Project with custom actions).
However if you have some complex logic to do as a part of setup, it is possible that some exception will occur.
I don’t like to hide exceptions, and they are shown to the user,causing installation to rollback.
It is not good, becuse sometimes user wants to ignore exception and use installation even if some custom action failed.
I found that moving code from custom action to executable gives more flexibility,as well as allow to use the same program to customize installation later just by running the same exe.
The Visual Studio SetupProject allowes to specify Custom Action as executable,not as Installer Class.
However during Uninstall,Repair etc, Executable Custom Action can be invoke not as programmer initially designed,and it is required to specify conditions for custom actions. Unfortunately, I had a lot of problems trying to set correct conditions(see Custom action condition to run for a new or upgraded product.and Intermittent execution of custom action for installation of product upgrade. )
See also Integration Hurdles for EXE Custom Actions post from Windows Installer Team Blog,
To avoid these problems with Executable Custom Action I desided to return back to Installer Class Custom action, but in Install method the only task is to start executable and executable will do all required customization of installation.
The calling executable should ensure that it is running in foreground and retrieve parameters from CommandLineArgs
‘/ensure that it is foregrownd
Win32Helper.ShowWindow(Me.Handle, Win32Helper.WindowShowStyle.Show)
Win32Helper.SetForegroundWindow(Me.Handle)
Win32Helper.BringWindowToTop(Me.Handle) ‘//13/11/2006
Dim argsColl As New Arguments(Environment.GetCommandLineArgs())
The class StartProcessInstaller is shown below:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Diagnostics;
using System.Text;
using System.Collections.Specialized;
using System.IO;
using System.Collections;
using System.Runtime.InteropServices;
namespace InstallStartCustomAction
{
[RunInstaller(true)]
public partial class StartProcessInstaller : Installer
{
public StartProcessInstaller()
{
InitializeComponent();
}
public override void Install(IDictionary mySavedState)
{
// Trace.WriteLine(“Install started “);
try
{
base.Install(mySavedState);
// Debug.Assert(false);
// Assume that the current assembly located at the same folder as CFSDNNInstall.exe.
FileInfo fileInfo = new FileInfo(System.Reflection.Assembly.GetExecutingAssembly().Location);
string sProgram = Context.Parameters[“Run”]; //”FSDNNInstall.exe”
// string sDir = fileInfo.DirectoryName() + “\”;
sProgram = Path.Combine(fileInfo.DirectoryName, sProgram);
Trace.WriteLine(“Install sProgram= “ + sProgram);
OpenWithStartInfo(sProgram);
}
catch (Exception exc)
{
Context.LogMessage(exc.ToString());
throw;
}
}
/// <summary>
/// Uses the ProcessStartInfo class to start new processes
/// </summary>
void OpenWithStartInfo(string sProgram)
{
// Trace.WriteLine(“OpenWithStartInfo sProgram= ” + sProgram);
ProcessStartInfo startInfo = new ProcessStartInfo(sProgram);
startInfo.WindowStyle = ProcessWindowStyle.Normal ;
string[] ExcludeKeys = new string[] { “run”, “WaitForExit” };
startInfo.Arguments = ContextParametersToCommandArguments(Context, ExcludeKeys);
Trace.WriteLine(“run the program “ + sProgram + startInfo.Arguments);
Process p = Process.Start(startInfo);
//TODO it seems that I should wait until it will be fully opened http://www.xtremevbtalk.com/archive/index.php/t-230805.html
//e.g. p.WaitForInputIdle(30) Method
//or try BringWindowToTop
//I have to use unmanaged ShowWindow http://www.thescripts.com/forum/thread230693.html
ShowWindow(p.MainWindowHandle, WindowShowStyle.Show); //otherwise it is not activated
SetForegroundWindow(p.MainWindowHandle);
BringWindowToTop(p.MainWindowHandle); //13/11/2006
//p.Refresh();
Trace.WriteLine(“the program Responding= “ + p.Responding);
if((Context.IsParameterTrue(“WaitForExit”)) )
{
p.WaitForExit();//Does it required?
}
}
//TODO copy to FSCSharpLib
public static String ContextParametersToCommandArguments(InstallContext context, string[] ExcludeKeys)
{
ExcludeKeys = ToLower(ExcludeKeys);
StringBuilder sb = new StringBuilder();
foreach (DictionaryEntry de in context.Parameters)
{
string sKey = (string)de.Key;
bool bAdd = true;
if (ExcludeKeys != null)
{
bAdd = (Array.IndexOf(ExcludeKeys, sKey.ToLower()) < 0);
}
if (bAdd)
{
AppendArgument(sb, sKey, (string)de.Value);
}
}
return sb.ToString();
}
//TODO copy to FSCSharpLib
public static StringBuilder AppendArgument(StringBuilder sb, String Key, string value)
{
sb.Append(” /”);
sb.Append(Key);
//Note that if value is empty string, = sign is expected, e.g.”/PORT=”
if(value!=null)
{
sb.Append(“=”);
sb.Append(value);
}
return sb;
}
#region “FS library methods”
public static string[] ToLower(string[] Strings)
{
if (Strings != null)
{
for (int i = 0; i < Strings.Length; i++)
{
Strings[i] = Strings[i].ToLower();
}
}
return Strings;
}
#endregion //”FS library methods”
#region “showWindow”
// http://pinvoke.net/default.aspx/user32.BringWindowToTop
[DllImport(“user32.dll”)]
static extern bool BringWindowToTop(IntPtr hWnd);
[DllImport(“user32.dll”)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetForegroundWindow(IntPtr hWnd);
//from http://pinvoke.net/default.aspx/user32.SwitchToThisWindow
[DllImport(“user32.dll”)]
private static extern bool ShowWindow(IntPtr hWnd, WindowShowStyle nCmdShow);
/// <summary>Enumeration of the different ways of showing a window using
/// ShowWindow</summary>
private enum WindowShowStyle : uint
{
/// <summary>Hides the window and activates another window.</summary>
/// <remarks>See SW_HIDE</remarks>
Hide = 0,
/// <summary>Activates and displays a window. If the window is minimized
/// or maximized, the system restores it to its original size and
/// position. An application should specify this flag when displaying
/// the window for the first time.</summary>
/// <remarks>See SW_SHOWNORMAL</remarks>
ShowNormal = 1,
/// <summary>Activates the window and displays it as a minimized window.</summary>
/// <remarks>See SW_SHOWMINIMIZED</remarks>
ShowMinimized = 2,
/// <summary>Activates the window and displays it as a maximized window.</summary>
/// <remarks>See SW_SHOWMAXIMIZED</remarks>
ShowMaximized = 3,
/// <summary>Maximizes the specified window.</summary>
/// <remarks>See SW_MAXIMIZE</remarks>
Maximize = 3,
/// <summary>Displays a window in its most recent size and position.
/// This value is similar to “ShowNormal”, except the window is not
/// actived.</summary>
/// <remarks>See SW_SHOWNOACTIVATE</remarks>
ShowNormalNoActivate = 4,
/// <summary>Activates the window and displays it in its current size
/// and position.</summary>
/// <remarks>See SW_SHOW</remarks>
Show = 5,
/// <summary>Minimizes the specified window and activates the next
/// top-level window in the Z order.</summary>
/// <remarks>See SW_MINIMIZE</remarks>
Minimize = 6,
/// <summary>Displays the window as a minimized window. This value is
/// similar to “ShowMinimized”, except the window is not activated.</summary>
/// <remarks>See SW_SHOWMINNOACTIVE</remarks>
ShowMinNoActivate = 7,
/// <summary>Displays the window in its current size and position. This
/// value is similar to “Show”, except the window is not activated.</summary>
/// <remarks>See SW_SHOWNA</remarks>
ShowNoActivate = 8,
/// <summary>Activates and displays the window. If the window is
/// minimized or maximized, the system restores it to its original size
/// and position. An application should specify this flag when restoring
/// a minimized window.</summary>
/// <remarks>See SW_RESTORE</remarks>
Restore = 9,
/// <summary>Sets the show state based on the SW_ value specified in the
/// STARTUPINFO structure passed to the CreateProcess function by the
/// program that started the application.</summary>
/// <remarks>See SW_SHOWDEFAULT</remarks>
ShowDefault = 10,
/// <summary>Windows 2000/XP: Minimizes a window, even if the thread
/// that owns the window is hung. This flag should only be used when
/// minimizing windows from a different thread.</summary>
/// <remarks>See SW_FORCEMINIMIZE</remarks>
ForceMinimized = 11
}
#endregion //”showWindow”
}
}