I experienced exactly the same symptom you are describing. There seems to be a problem with WiX toolset. My version of WiX tolset is 3.8, and I also had a custom action, which would not run, and changing its name fixed the problem. The SFX compiler simply compiles the broken DLL without any indication of a problem. What makes the matters worse is that in my case this was a function which was supposed to run on uninstall with Result="ignore", so I wouldn't even have any immediate indication that there is a problem after I run actual installer.
I tried experimenting to understand what exactly is the problem with the name, and did not find any satisfactory explanation. It seems to not matter where in the source code the offending function is, what is it alphabetically (e.g.: functions succeed which are before and after it alphabetically). Loading DLL into depends.exe shows that there is an export, but trying to rundll32 it fails to find that export. Changing its name fixes the problem. Also, sometimes you can add another function, and the failing one would succeed, but the one you just added fails instead.
Here's what I have done to fix the problem: I wrote a C++ program which loads the compiled custom action and verifies the export. Then I wrote a unit test, which verified all of the exports in custom action. This obviously does not fix the issue, but at least you will have a unit test failure and know that your installer is broken. Here's the c++ code if you are interested:
typedef int(__stdcall *CustomActionProc)(HANDLE);
int _tmain(int argc, _TCHAR* argv[])
{
if (argc != 3)
{
_tprintf(_T("Parameters: DLL, EntryPoint
"));
return 1;
}
LPCTSTR dllName = argv[1];
LPCTSTR entryPoint = argv[2];
HMODULE hLib = LoadLibrary(dllName);
if (hLib == NULL)
{
_tprintf(_T("Error loading %s
"), dllName);
return 1;
}
CustomActionProc procAddress =
(CustomActionProc) GetProcAddress(hLib, CStringA(entryPoint));
if (procAddress == NULL)
{
_tprintf(_T("Error locating entrypoint %s
"), entryPoint);
return 1;
}
return 0;
}
And the unit test is:
[TestMethod]
public void TestCustomActionCanBeInvoked()
{
var asm1 = typeof(MyCustomActionsClass).Assembly;
var methods = asm1.GetTypes().SelectMany(t =>
t.GetMethods().Where(m => m.GetCustomAttributes(false)
.Where(a => a.GetType().Name == "CustomActionAttribute").Any()));
var binFolder = (new FileInfo(this.GetType().Assembly.Location)).DirectoryName;
var customActionsSfx = Path.Combine(binFolder, "MyCustomAction.CA.dll");
var testMsiExport = Path.Combine(binFolder, "TestMsiExport.exe");
foreach (var m in methods)
{
Trace.WriteLine("Method Name: " + m.Name);
var p = Process.Start(new ProcessStartInfo()
{
FileName = testMsiExport,
Arguments = """ + customActionsSfx + "" " + m.Name,
UseShellExecute = false,
RedirectStandardOutput = true,
});
p.OutputDataReceived += (s, d) => Trace.WriteLine(d.Data);
p.BeginOutputReadLine();
p.WaitForExit();
if (p.ExitCode != 0)
{
Assert.Fail("Bad Sfx export detected! Export name: " + m.Name);
}
}
}
Hope this helps someone in my situation. It was a very frustrating day trying to nail this down.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…