Hosting the CLR within a custom action
Today, in order to develop a custom action for a Window Installer setup, you can choose between two options:
1. Writing a Native custom action using the C language
2. Writing a Managed custom action using any .NET compliant language
Both options have different pros and cons, so I will start enumerating some of them:
Native custom actions
Pros:
1. Access to the installer context information.
2. Interop code not required.
Cons:
1. Require some understanding of Windows programming and C language as well.
2. Lack of code protection and memory management, you are responsible to acquire and release all used resources.
3. Find a problem can be a nightmare, for example, a memory leak problem.
4. Hard to debug.
Managed custom actions
Pros:
1. Extremely easy to develop in any CLR compliant language.
2. Easy to debug and find problems.
Cons:
1. Inability to get context information.
2. Only deferred custom actions ( They run at the end, you can't change any condition or property during the install ).
A new option
To overcome the disadvantages of both options, the last week I developed a native custom action which hosts the CRL Runtime and executes a managed custom action. This solution combines the best of both worlds, and you can get many benefits of using it.
It contains three parts, a native custom action, a managed custom action, and a small msi framework to be used within the managed custom action. I will describe each one in detail next.
Native custom action project
The native custom action is a normal custom action written in C, which is responsible to host the CLR runtime and call to the managed custom action.
Basically, this custom action contains code to do the following things:
1. Loads the CLR runtime
2. Gets the managed custom action assembly from the msi binary table
3. Copies the assembly to the user temp folder
4. Loads the assembly within the CLR instance
5. Executes the managed custom action
6. Unloads the CLR runtime
You can find all this code under the CLRHosting folder. This code is always the same, and you won't have to change almost anything of it, except some parameters located in the file "String.h":
// CLR version
static LPCWSTR szCLRVersion = L"v1.1.4322";
// CLR flavor, workstation or server
static LPCWSTR szCLRFlavor = L"wks";
// Application domain name
static LPCWSTR szApplicationName = L"MsiHosting";
// Configuration file assigned to the application domain. You can attach this file to the binary table in the msi
static TCHAR* szApplicationConfigFile = "MsiHosting.config";
// Entry point of the managed custom action
static OLECHAR FAR* szCustomActionMember = L"RunActions";
// Managed custom action assembly
static TCHAR* szAssemblyName = "CustomActionRuntime";
// Managed custom action class name
static TCHAR* szClassName = "CustomActionRuntime.Runtime";
// Key used to locate the assembly file within the msi binary table
static TCHAR* szCustomActionRuntimeBinaryKey = "CustomActionRuntime";
// Key used to locate the configuration file within the msi binary table
static TCHAR* szApplicationConfigFileBinaryKey = "MsiHostingConfig";
Managed custom action project
I wrote the project using C#, it includes the managed custom action and a small framework to manipulate the installer context.
You can find this code under the CustomActionRuntime folder.
The managed custom action should be implemented in "CustomAction.cs", and usually looks like this:
public class CustomAction
{
#region Constructors
public CustomAction()
{
}
#endregion
public void Run( InstallerContext context )
{
// TODO: do something
}
}
The context parameter represents a facade to the msi framework, through this framework you can query and modify different things of the installer context, not to mention the fact of executing dynamic SQL query against the msi tables.
This sample makes use of this framework to query all available properties, it is a useless sample, but helps to show that functionality:
public class CustomAction
{
#region Constructors
public CustomAction()
{
}
#endregion
public void Run( InstallerContext context )
{
View view = context.OpenView( "SELECT * FROM Property" );
RecordCollection records = view.Execute();
foreach( Record record in records )
{
foreach( Field field in record.Fields )
{
string value = field.GetString();
}
}
view.Close();
}
}
Using this custom action within Wix
Until here I described the different parts of this solution, now I will describe how to integrate it in your installer using Wix.
To start I picked up a sample provided in this tutorial, by the way an excellent tutorial to learn Wix.
This sample shows how to validate a PID using a custom action, so I'll modify it a little bit to use this solution.
1. "Including the native custom action"
Go to the custom action definition section, and replace this line :
<CustomAction Id="CheckingPID" BinaryKey="CheckPID" DllEntry="CheckPID" />
For
<CustomAction Id="CheckingPID" BinaryKey="CheckPID" DllEntry="Execute" />
You have to change only the DllEntry attribute, because my solution exports a different dll entry. ( "Execute" instead of "CheckPID" )
2. "Modifying the binary table"
Go to the binaries definition section, and replace this line :
<Binary Id="CheckPID" src="Binary\CheckPID.dll" />
For
<!-- Files required to run the .NET custom action -->
<!-- Native custom action -->
<Binary Id="CheckPID" src="Binary\CLRHosting.dll" />
<!-- Managed custom action assembly file, this key can be changed from Strings.h -->
<Binary Id="CustomActionRuntime" src="Binary\CustomActionRuntime.dll" />
<!-- AppDomain config file, this key can be changed from Strings.h -->
<Binary Id="MsiHostingConfig" src="Binary\MsiHosting.config" />
This change includes the files required to run my solution.
3. "Compiling the solution"
Open the "MSIHosting.sln" solution file with visual studio and compile it. This compilation will produce the binary files required to run the custom action.
4. "Copying the binary files"
Copy the following files to the "Binary" folder:
CLRHosting\Debug\CLRHosting.dll
CustomActionRuntime\bin\debug\CustomActionRuntime.dll
CustomActionRuntime\MsiHosting.config
After all these steps, you will able to compile the wix source file to get the final msi.
Download Source Code