开发者

Managing Dependency Hell with WiX and C#

We are on the eve of product launch, and at the last minute I am being bombarded with crash reports that appear to be related to our installer, which is a WiX3 project with separate outputs for x86 and x64 builds. These have been an ongoing problem that I always thought were fixed, only to find out that they were still lurking.

The product itself is a collection of binaries that communicate with each other via .Net remoting, including a Windows Service and a small COM component that is loaded as an addon in another app. The service runs as SYSTEM, the COM piec开发者_如何学Pythone runs in a low-rights context, while the other pieces run in normal user contexts. Other pieces include an third-party COM object library DLL and a shared DLL with the .net Remoting interfaces.

I've observed flat-out weird behavior with MSI, particularly on version upgrades. Between MS' anal strong-name implementation (specifically, the exact version check before loading a given assembly), a documented WiX/MSI bug that sees critical files erased on upgrades (essentially, if a file in the upgrade MSI has the same version number as the existing install, that file is deleted)(edit: having trouble producing said documentation...), and having to work around Wow64 virtualization (x86 MSI can only write to registry/HD locations via Wow64, yet x64 MSIs cannot run on x86 computers...), I am about ready to trash the whole thing and port it over to a different install system.

What I am looking for on tips + tricks, techniques, or suggestions on how to properly do things so that I am not fighting with Windows Installer's twisted sense of logic. I am tired of fighting with WiX/MSI/Windows Installer. All it needs to do is place files and registry keys where I tell it to, upgrade them when appropriate, and don't delete anything until the user uninstalls. Instead, dependencies are deleted willy-nilly, bringing up a whole bunch of uncatchable exceptions (can't wrap a try{} block around function declarations) and GPF'ing the whole app.

I am particularly interested in 'best practices' and examples regarding shared and dependency DLLs, and any tips on making sure if a file needs to go to GAC, that it actually goes to the GAC and stays there until it is appropriate to remove it.

Thanks!

Tom


Start off by reading The Definitive Guide to Windows Installer.

Done? Great. Now think about scrapping your existing install, starting from scratch and filing an application bug for everything you currently have to "workaround" in setup. For nearly every single bit of "twisted logic" that you've been fighting with is there for a purpose, to make your installation more reliable and repairable.

If you don't want the reliability and resiliency of Windows Installer, or if you're trying to bypass it in any way then use something else. Windows Installer does much, much more than simply just "place files and registry keys where I tell it to".

When you're writing an MSI package, you define how you want the target system to look. That's the way it looks after setup, the way it should automatically repair to if a file is deleted or a key data file is corrupted. You define how the system should roll back to if a user later cancels during an upgrade from 1.0 to 2.0. Windows Installer is 100% data driven. From a development point of view this is the hardest concept to understand, you can't just edit a config file or write some more data and expect it to persist. Once you get your head around this, Windows Installer becomes a real piece of cake and you design your programs and features to work within it's limitations, rather than trying to break free of them :)

A few other useful links...

  • Tao of the Windows Installer, Part 1
  • Understanding UAC in MSI


Assuming you're on at least Wix 3.0, you can make use of MakeSfxCA.exe to package dependencies in a single DLL. (This was an add-in from the DFT -- Deployment Tools Foundation.) Basically, start off by making sure project is copying your dependent DLLs. Make a CustomAction.config file. Test with a simple .bat file like:

REM MyMakeSfxCA.bat - Run under $(TargetDir); abs. paths reqd. "%WIX%\SDK\MakeSfxCA" ^ %cd%\Managed_custom_action_pkg.dll ^ "%WIX%\SDK\x86\sfxca.dll" ^ %cd%\Managed_custom_action.dll ^ %cd%\Dependent1.dll ^ %cd%\Dependent2.dll ^ %cd%\Microsoft.Web.Administration.dll ^ %cd%\Microsoft.Deployment.WindowsInstaller.dll ^ %cd%\CustomAction.config

Once that works, convert into a Post-Build Event: "$(WIX)\SDK\MakeSfxCA" ^ $(TargetDir)\Managed_custom_action_pkg.dll ^ "$(WIX)\SDK\x86\sfxca.dll" ^ $(TargetDir)\Managed_custom_action.dll ^ $(TargetDir)\Dependent1.dll ^ $(TargetDir)\Dependent2.dll ^ $(TargetDir)\Microsoft.Web.Administration.dll ^ $(TargetDir)\Microsoft.Deployment.WindowsInstaller.dll ^ $(TargetDir)\CustomAction.config

In your .wxs file, your Binary Key will look like: < Binary Id="Managed_custom_action_CA_dll" SourceFile="$(var.Managed_custom_action.TargetDir)$(var.Managed_custom_action.TargetName)_pkg.dll" / >

For they CustomAction.config, you can find examples online such as

This is the best way that I've found when working with managed code.


I'm kind of afraid to step into this one but the limitations are only there if you haven't followed best practices upstream.

Take the "delete files" "bug". I haven't seen this one in a while, but if I recall, this is typically caused by the following scenario:

build A: file 1: 1.0.0.0

build B: file 1: 1.0.0.0

Now you go do a major upgrade where you have RemoveExistingProducts scheduled after CostFinalize. FindRelated Products detects the ProductCode to be removed, Costing says oh I see I have 1.0.0.0 but 1.0.0.0 is already installed so nothing for me to do here and then RemoveExistingProducts comes by and removes the old version thereby deleting file1.

I always worked around this one by scheduling RemoveEXistingProducts way earlier then suggested by Microsoft. It's been working for me for years.

As for dependency hell, I manage a software product line that consists of hundreds of features expressed by hundreds of merge modules and thousands of files ( 15,000 + ) consumed by dozens of installers on dozens of various feature, integration, main, maintenance and maintenance_integ branches.

How do I do it? One fragment at a time and lot's of automation, SCM and virtual machines to make sure that everything does actually work the way it's intended.

Being this close to your product shipping, the only "answer" I can offer truely offer you is to let you know people like myself are always available for hire. You'd be suprised how fast some of us can turn projects around and get software shipping.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜