How do I add C# methods to an existing large wix script
We have an existing wix script that is pretty complex & long. All the CustomActions are performed with inline vbscript.
I want to switch some of those actions from vbscript to C#. All the examples everywhere start with "create a wix project in VisualStudio...". Is there any example out there about how to add in C# code to an existing wix project? One where it is built using the old school wix command line apps?
Solution 1:
A shameless promotion of C++ custom actions first! :-).
And: "WiX Quick Start" (some pointers to good WiX and MSI resources).
Step-By-Step: I'll give it a try, please try this (you might want to skip to the bottom source if you are sort of done with these preliminary steps - this is step-by-step for real and very slow to get to the action - you might get what you need straight from the WiX source):
-
In WiX Visual Studio solution,
right click solution node at top
=>Add
=>New Project...
-
Expand WiX Toolset node, select v3 (provided that is the version of WiX you use)
-
Double click
"C# Custom Action Project for WiX v3"
-
Right click
"References"
in WiX project (not in C# project) =>Add Reference...
-
Go
"Projects"
and add a reference to theC# project
(double click and OK) -
Do a test build. Provided there were no errors before there should be none now.
-
You should see something like
"CustomAction1.CA.dll"
in the build Output window. The suffix*.CA.dll
is added to a win32 wrapper dll for the original managed code dll. All of this is handled by WiX itself - or actually the Votive Visual Studio integration for WiX - just know the difference:-
"CustomAction1.dll"
- managed code dll. -
"CustomAction1.CA.dll"
- native win32 wrapper dll containing the native one and several other components.Include this version in your MSI
.
-
-
Add the following snippet:
<Binary Id="CustomActions" SourceFile="$(var.CustomAction1.TargetDir)\$(var.CustomAction1.TargetName).CA.dll" />
-
The above should compile the actual C# dll into the MSI. You can open the MSI in Orca and see in the Binary table.
-
It is not great, but I like to add a reference to
System.Windows.Forms
and use aMessageBox.Show
to show a dialog from within the custom action to ensure it is running as expected. I also add the application debugger launch command for dlls built in debug mode. That way Visual Studio will be automatically invoked (if all works correctly) so the code can be stepped through. -
Add the reference to
"System.Windows.Forms"
by right clicking the C# project's Reference node and then add"System.Windows.Forms"
. Also add"using System.Windows.Forms;"
to the top of the source file - see full source below. The key is to remember to reference"System.Windows.Forms"
at a project level. -
Now add this as test code to the custom action project's
"CustomAction1"
custom action code snippet (see code section towards bottom for full source):// will launch the debugger for debug-build dlls #if DEBUG System.Diagnostics.Debugger.Launch(); #endif MessageBox.Show("hello world");
-
To get a standard setup GUI (for others who read this), add a reference to
WiXUIExtension
as explained here (that is a step-by-step for creating a basic WiX project that compiles and has a GUI), and then inject this into your source:<UIRef Id="WixUI_Mondo" />
-
I like to change
<MediaTemplate />
to<MediaTemplate EmbedCab="yes" />
to avoid external source cab files (with this change cabs are compiled into the MSI). -
If you don't have any components added, you can add this to include
notepad.exe
in your MSI for test installation under directoryINSTALLFOLDER
(just a trick to install something without having source files available - a source path that should resolve on any machine) - replace the whole "TODO" section - see full source below:<Component Feature="ProductFeature"> <File Source="$(env.SystemRoot)\notepad.exe" /> </Component>
-
Now we need to declare the actual custom action and insert it into an installation sequence. Let's add this underneath the previous
<Binary> element
:<CustomAction Id="CA1" BinaryKey="CustomActions" DllEntry="CustomAction1"/> <InstallUISequence> <Custom Action="CA1" After="CostFinalize" /> </InstallUISequence> <InstallExecuteSequence> <Custom Action="CA1" After="CostFinalize" /> </InstallExecuteSequence>
-
Now build and test run the MSI. You should get numerous
"hello world"
messages. -
That is the overall
"heartbeat"
of a C# / managed code custom action - the way I sometimes use them.
WiX Source Actual: And now, the synthesis - remember to replace all GUIDs!:
The construct:
$(env.SystemRoot)
- in the WiX source below - gets the environment variable%SystemRoot%
- which resolves toC:\
on most systems (to list environment variables open acmd.exe
and typeset
and pressEnter
). The below source should hence compile on all systems without modifications:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="DemoCA" Language="1033" Version="1.0.0.0" Manufacturer="test" UpgradeCode="0adf972a-5562-4a6f-a552-dd1c16761c55">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate EmbedCab="yes" />
<UIRef Id="WixUI_Mondo" />
<!-- START CUSTOM ACTION CONSTRUCTS -->
<Binary Id="CustomActions" SourceFile="$(var.CustomAction1.TargetDir)\$(var.CustomAction1.TargetName).CA.dll" />
<CustomAction Id="CA1" BinaryKey="CustomActions" DllEntry="CustomAction1"/>
<InstallUISequence>
<Custom Action="CA1" After="CostFinalize" />
</InstallUISequence>
<InstallExecuteSequence>
<Custom Action="CA1" After="CostFinalize" />
</InstallExecuteSequence>
<!-- END CUSTOM ACTION CONSTRUCTS -->
<Feature Id="ProductFeature" Title="AddingCSharpCustomActions" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
</Product>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="AddingCSharpCustomActions"/>
</Directory>
</Directory>
</Fragment>
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<Component Feature="ProductFeature">
<File Source="$(env.SystemRoot)\notepad.exe" />
</Component>
</ComponentGroup>
</Fragment>
</Wix>
Steps-in-brief: short summary of changes needed:
- Set manufacturer field to something.
- Add and modify the custom action constructs as indicated above.
- Add the Component / File elements towards bottom replacing the "TODO" section there.
- Set the
MediaTemplate
to use embedded cabs as described above (optional, not necessary for the sample to work).
Custom Action Code: And finally the actual C# custom action test code - updated with Debugger.Launch which will launch the debugger for a debug-build DLL. You can then attach the debugger to the correct source project and step-through the code:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Deployment.WindowsInstaller;
using System.Windows.Forms;
namespace CustomAction1
{
public class CustomActions
{
[CustomAction]
public static ActionResult CustomAction1(Session session)
{
#if DEBUG
System.Diagnostics.Debugger.Launch();
#endif
MessageBox.Show("hello world");
session.Log("Begin CustomAction1");
return ActionResult.Success;
}
}
}
Links:
- Debugging MSI Custom Actions
- C++ Custom Action and Common Problems
- WiX resource links of all kinds
- Forgot this one from earlier - simpler and might be clearer
- C# custom actions - Windows Installer (Advanced Installer video)