Thoughts about setup and deployment issues, WiX, XNA, the .NET Framework and Visual Studio
All postings are provided AS IS with no warranties, and confer no rights. Additionally, views expressed herein are my own and not those of my employer, Microsoft.
A while back I wrote an article about Windows Installer and the resiliency feature. Anyone who has seen a small dialog appear saying "Configuring XXX....please wait" has seen resiliency in action. I wanted to talk in more detail about a specific type of problem I have seen that ends up inadvertantly triggering resiliency repairs at very random times.
According to the help documentation for Windows Installer, the Class table contains COM server-related information that must be generated as a part of the product advertisement. Each row may generate a set of registry keys and values. The associated ProgId information is included in this table.
In practice, what happens during setup is that for each entry in the Class table, a set of registry data is created under HKEY_CLASSES_ROOT\CLSID\{GUID} for the COM object in question. The {GUID} in this case is the value of the CLSID column of each row in the Class table of the MSI. This registry data allows the COM object to be instantiated on this machine by applications or custom script code later on.
There is also some additional data that advertises this COM object and associates it to any MSI that includes it in the Class table. If you look under the InprocServer32 subkey, you will see a REG_MULTI_SZ value that is also named InprocServer32. This collection of strings represents an encoded list of features that are associated with this COM object. Whenever an instance of this COM object is instantiated (with CoCreateInstance or WScript.CreateObject for example), Windows Installer recognizes that this COM object is advertised and proceeds to perform a feature health check for each of the features listed in the REG_MULTI_SZ value named InprocServer32 for this COM object. If this feature health check returns false for any reason, then you will see a small Windows Installer dialog and a resiliency repair, even if the information needed for this COM object is functioning perfectly fine.
I have run into this problem numerous times while debugging resiliency repairs that have popped up for internal users who installed daily builds of Visual Studio 2005. Using the debugging techniques in my previous blog article and also here have led me to components in Visual Studio that were being reported as broken. I had been meaning to write about this for a while, but it got put back in the forefront of my mind yesterday while I helped someone on the Visual Studio team figure out why they were seeing a repair dialog appear when they tried to build a setup/deployment project in the Visual Studio IDE. In this case, building a setup/deployment project was calling CoCreate on the MSM.Merge object exposed by mergemod.dll, and since this COM object was installed via the Class table of the Visual Studio MSI, it was advertised and a health check was triggered. The health check happened to fail due to a known bug in the VS MSI that is scheduled to be fixed soon.
This isn't that big of a deal for daily development work because there are experts within Microsoft that can look at the problem and also because the resiliency repair is exposing valid bugs (in several previous cases they were bugs in fusion related to new assembly attributes that were being treated as invalid because they were not recognized, see this blog item for more details if you're interested).
However, it becomes a big deal for end users who are trying to install and use shipped products and encountering repair dialogs for unknown reasons - and worse yet are being asked to insert a CD or browse to an inaccessible network location so that Windows Installer can access source files to try to repair them. In some products, especially large products such as Visual Studio, the feature that ends up being advertised in the registry by the Class table contains many components. A resiliency repair will be triggered even if some component that is completely unrelated to the functionality of the COM object being instantiated fails the Windows Installer health check.
Fortunately there is a way to avoid this issue for setup authors. I recommend using the standard MSI registry, file and component tables to install and register COM objects instead of using the Class table (and the companion ProgId table).
I'm having this resiliency problem, but it seems the problem is an interop assembly in a _parent_ feature.
Two issues:
1. The event log doesn't say what component is reason for reinstall (only the first event is generated - creating a COM object using AppId).
2. The interop assembly has been installed to GAC_MSIL, while MSI looks for it in GAC only...
How should I fix the mismatch with installing to \Windows\Assembly\GAC_MSIL, but MSI resiliency looks in \Windows\Assembly\GAC
Hope you can help.
Regards,
Rune.Christensen at visma dot com.
Hi Runec - I've never heard of a case where a repair was triggered by Windows Installer resiliency but yet there was not information in the application event log indicating what component(s) were being repaired. Can you double-check to see if there are any warnings reported from the source named MsiInstaller that you might have missed? It might help to clear out the log and start fresh in order to do this. Also, you can enable Windows Installer verbose logging (using steps like the ones I posted at http://blogs.msdn.com/astebner/archive/2005/03/29/403575.aspx) and reproduce the problem and the verbose log might help you narrow this down as well.
If you have an assembly that was installed to GAC_MSIL, but your MSI is looking for it in the GAC folder instead, this is likely caused by some missing attributes for the assembly in the MsiAssemblyName table. In cases I've seen like this in the past, the MsiAssemblyName table did not have a processorArchitecture attribute listed for the assembly, which caused Windows Installer to look in the old .NET Framework 1.0/1.1 GAC instead of the new .NET 2.0 GAC_32, GAC_64 or GAC_MSIL. You might need to add processorArchitecture = Neutral to your MSI for this issue.
Thanks for your reply!
I've checked the event viewer log again, and the only entry is:
Detection of product '{09001326-D530-40C6-98C9-B127A47498E2}', feature 'ClientTools' failed during request for component '{544FC311-F9E9-4FB7-889B-FC855AAF9BA3}'
{544FC311-F9E9-4FB7-889B-FC855AAF9BA3} is a COM server (advertised) being instatiated using prog ID.
Analysing the log file I found that components in the ClientTools were reinstalled, plus Visma.Interop.Crm.dll, the interop .NET assembly installed to GAC. the Visma.Interop.Crm.dll component belongs to ProgramFiles, the parent feature of ClientTools. This is the component that has been installed to GAC_MSIL, but resiliency process looks for in GAC.
The records in the MsiAssemblyName table for Visma.Interop.Crm.dll are
Visma.Interop.Crm.dll Name Visma.Interop.Crm
Visma.Interop.Crm.dll Version 1.0.0.0
Visma.Interop.Crm.dll Culture neutral
Visma.Interop.Crm.dll FileVersion 7.15.4013
Visma.Interop.Crm.dll PublicKeyToken D15E281808C3A4BB
When I try to add the "Visma.Interop.Crm.dll processorArchitecture Neutral" to the MsiAssemblyName table, I get this error in verbose log:
MSI (s) (94:D8) [13:39:34:605]: Assembly Error:The given assembly name or codebase, '%1', was invalid.
MSI (s) (94:D8) [13:39:34:605]: Note: 1: 1935 2: {BB52BE7F-4807-4C70-A93C-9E83420DEA28} 3: 0x80131047 4: 5: CreateAssemblyNameObject 6: Visma.Interop.Crm,Version="1.0.0.0",processorArchitecture="neutral",Culture="neutral",FileVersion="7.15.4013",PublicKeyToken="D15E281808C3A4BB"
{BB52BE7F-4807-4C70-A93C-9E83420DEA28} is Visma.Interop.Crm.dll
I tried "Visma.Interop.Crm.dll processorArchitecture x86" instead. Then installation succeeds, but the re-install issue is still present.
As a current "fix", I've moved the Visma.Interop.Crm.dll component from ProgramFiles feature into a leaf feature...
Rune Christensen
Hi RuneC - I'm sorry, but it looks like I mistyped when I suggested using processorArchitecture = Neutral. Can you please try to set processorArchitecture = MSIL instead and see if that will work in this scenario?
Also, you can use the gacutil.exe utility in the .NET Framework SDK to display the full assembly identity for your assembly. What I typically do is run gacutil.exe /i <path to assembly DLL> and then run gacutil.exe /u <assembly DLL name without the .dll extension>. Doing that from a cmd prompt will display text like the following:
Assembly: <assembly name>, Version=6.0.6000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL
Uninstalled: <assembly name>, Version=6.0.6000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL
You'll need to make sure that the MsiAssemblyName table of your MSI contains values for all parts of the assembly identity in order for Windows Installer to be able to correcty find it during this type of resiliency scenario.
Also, in general, I'd recommend leaving your advertised components in leaf features like you describe above. Repairs should be non-existent or very rare after fixing the assembly attributes as described above, but authoring your features this way will ensure that any repairs that might be triggered will be minimal and not take as long of a time for the user.
Hope this helps....
Now it finds Visma.Interop.Crm.dll during resiliency repair - thanks!
Is there a way to decode the [HKEY_CLASSES_ROOT\CLSID\{GUID}]"InprocServer32" value?