Your Pre-processor Use
I was half-way answering with a suggestion to use the pre-processor constructs (?if?
et al) when I realized you only wanted a single MSI (at least at a time - it seems), so I skipped it. I normally use such constructs to compile flavors of MSI files from the same WiX source. I have dumped what I wrote below with some rehashing - without too much review. I will check in on it later.
I might be missing something in your case, but I do not see how an ?if?
statement can work at installation time - it is a compile time construct. It pre-processes your WiX source file before compilation and linking. As such it sounds like you actually have compiled three different versions of your MSI file and then run admin install on each of them? If this is the case then the use of the admin image is irrelevant, because your whole MSI does not contain anything but the components that you have included with ?if?
- there is no need to pass in any edition property specification.
Setup Flavors
The normal technical approaches I use when I need to create different setup flavors or product editions are as follows:
Preprocessor constructs: used to compile different flavors or editions (also different language editions) of the MSI at compile time. One such construct is the ?if?
statement you are mentioning. There are several other ones, as seen in the link provided directly above. The relevant ones for you would be the conditional statements (if
, ifdef
, ifndef
, else
, elseif
, endif
) and possibly the include
statement.
So the overall message is that preprocessor constructs allow you to build several different flavors of MSI files from a single WiX XML source.
In your case this would yield a single WiX XML source and three MSI files - each for a different version of the application. All similar, but with only the components they need.
Running installation via /i
or administrative image (file extract) via /a
would yield only the components you added to that setup flavor, but you do not have a single MSI as you say - but three different ones depending on how you compiled.
I prefer to use ?include?
statements to include WiX fragment files rather than to just condition components directly with ?if?
as you mention. There is a sample of the difference at the bottom in the "Preprocessor Constructs" section.
MSI Features (as PhilDW answered): you can also rely on MSI features to deliver a single setup in different flavors. Features are used to segregate the MSI into various user-selectable installation items (some of which you can make mandatory).
- Features allow a single MSI to be installed in different flavors - whilst containing all components needed by each flavor. Conditioning components can achieve similar effects.
- Conditioning components resembles feature manipulation, but works on a more primitive and fundamental level. These are not user-selectable installation units, but fundamental and atomic bits and pieces of your product to install and hidden from the user's view - whilst features are user-selectable and generally visible for the user.
What features are to be installed can be adjusted by the user or manipulated programmatically via a custom action (to override user specifications and enforce a technical design auto-magically - for example).
It is important to note that the overall result is that the feature selection can be set via the command line or by the user, and then your setup makes some "under the hood" modifications for technical reasons.
And just to point it out: the features that are manipulated for installation programmatically are typically hidden from view (but still possible to override via the command line - which can be a problem).
More on features, components and customization of setups for installation here: How to make better use of MSI files.
Setup.exe launcher: a "modern" way to do complex deployment in different flavors can be to use WiX's Burn feature to compile smaller MSI setups that are installed in different "sets" to yield a different installation state.
- I find this to be too complicated for general use, but it is certainly possible. I think some find it easier, since there is less feature manipulation. Maybe I just lack experience with it.
- The benefit is smaller MSI files that install quicker, and you can update a single MSI file and create a new setup.exe wrapper and then do total QA on your whole solution without rebuilding all setups.
- In my world a single, updated MSI requires a full QA anyway, so I am not always buying these "simplicity arguments" myself. Every release cycle has risks, and hence adds to total risk. It can be great, though, to be able to rebuild a tiny setup and keep your large one stable.
Practical Use
I prefer to make as few setup flavors as possible, but most of the time I end up using preprocessor constructs to create different setups for different language versions (English, German, Russian) and also for different product editions (Enterprise, Professional, Standard). I usually set them all related sharing an upgrade code and not capable of installing side-by-side.
I feel that a single MSI gets more QA resources and will hence be tested better. However, this single MSI approach typically breaks down if the setups are very large - in which case I like to split them. I also like to make separate setups for each language for business- and real-world practical reasons explained here. Practical and legal requirements (licensing) can also make it imperative to compile special versions of the same MSI setup. I have also been asked to build new MSI flavors for OEM vendors.
And now a surprising conclusion (that you never asked for :-) ): with all that said, in an ideal world I like to install all features without any technical fuss in the setup, and use the serial key to determine what application functionality and modules should be activated in the application. I also like to put the serial key validation inside the application, and not in the setup either. The reason? I want setups as primitive as possible to ensure the application is properly installed without a high deployment error percentage. Therefore I hunt for complexity to kill in deployment to ensure less deployment support issues. Reliable deployment is crucial to the success of the product. Once you are installed, your application can launch in a much more debuggable and predictable context (no conditioning, impersonation or sequencing issues) and report any errors interactively and meaningfully to the user - and they can call support with more hope of successfully resolving any issues than what would be the case if the setup just bombed out with a mysterious non-interactive error message (in the system's event log).
Once you got a good, simple deployment solution, your job as release manager and setup developer should expand into taking care of the application's launch sequence, file configuration and overall configuration and also in my opinion support and debugging features you might want to add to the application (self-inspection of the application and logging and auto-gathering of information to send to support). Try to expand your responsibility and influence with the application itself in order to keep deployment "as primitive as possible". Anything that can be coded in the application rather than in the setup will be more reliable in the long run (and much easier to debug).
Back in reality: for real deployment you will get a bunch of files with irregular versioning scheme delivered to you via a slow network share with a bunch of incoherent instructions from a developer who is offshore and paid hourly (you can be sure he feels the pain too :-) ). And they will have a bunch of partially insane requirements for your setup to do magic to cover up their less-than-ideal design (which is all they were paid to make). This is not a rant, it is reality - sad to say (OK, it is a rant). We must work to better an application's overall design to make deployment less error prone. Magic can indeed be done in a setup, but it will trigger higher error percentages for deployment due to the unavoidable and unruly complexity of deployment (most significantly: 1)
errors are cumulative - each release adds risks and opportunities to break the serviceability of your installation (sometimes you have to pull everything back and start over - at great cost), 2)
target systems are in extremely diverse and unpredictable states (any state, any language, any OS edition, any hardware, any malware, etc...), 3)
debugging is very difficult when you don't have access to the problem systems interactively - see towards bottom of linked answer for more details. And I will add a 4th issue
falling out of the 2nd one: can you guarantee deployment on a machine choke-full of malware? Such systems are impossible to support and debug - just take your pick to find a critical problem you can not fix. If your package fails on such malware systems it is a natural error percentage that is unavoidable.
The overall point is obvious: we can't account for these unknowns - garbage in, garbage out (with apologies for how that sounds) - there will be an error percentage - every time you deploy - even for perfect packages. Be sure to grab all available and always valuable QA-guys and teach them about setup testing (testing all installation modes, testing uninstall and interaction with other applications, testing real-world upgrade scenarios, testing specific advanced setup features such as license checking, etc...). Get all the help you can get and value their contributions. If nothing else, as accomplices :-). Seriously: failing to utilize available QA-resources effectively and help train them in deployment testing is high on my list as my biggest neglects as a release manager and setup developer. It is a safe assumption you will be owing your QA-guys a bottle of Horílka in no time - or green tea or Rooibos tea - depending on where you are in the world :-).
Pre-processor Constructs
I would probably use the preprocessor constructs ?if?
and ?include?
to selectively include different sections of WiX XML source code / markup based on your defined edition in the ?define?
statement at the top of the sample:
<?define EditionType = “LITE” ?>
<!-- You can put your edition-specific components in an include file -->
<?if $(var.EditionType) = "200" ?>
<?include "200Features.wxi" ?>
<?endif ?>
<?if $(var.EditionType) = "LITE" ?>
<?include "LiteFeatures.wxi" ?>
<?endif ?>
Instead of using include files, you can also do this inline in your main source (this might be what you have done):
<?if $(var.EditionType) = "200" ?>
<Component>
<File Source="$(var.MenusPath)\ClientListView 200.r5m" />
</Component>
<?endif?>
<?if $(var.EditionType) = "LITE" ?>
<Component>
<File Source="$(var.MenusPath)\ClientListView Lite.r5m" />
</Component>
<?endif?>
Of the options above, I would prefer to keep the edition-specific components in a separate include file (first option) - then there is less "line noise" in your WiX source (fewer repetitions of the preprocessor constructs).
The include files are basically just WiX XML files that are included just like C++ header files into the parent WiX XML file at compile time.
Like Phil says I would use different feature names for the different editions, so you define a feature 200Features
in the 200Features.wxi include file, but you do not need to use features - though strongly encouraged.
Also be aware of the WiX concept of the fragment element. Essentially a way to cross-reference content in the WiX source file. An example here.
I should mention that the original concept of merge modules in MSI provides a different way to share components between different setups, but I like the include file approach better. Merge modules feel like COM objects somehow (binary blob included as a versioned whole), whereas include files seem more dynamic in that you get the latest files linked to for every build without any merge module rebuild and versioning. Some people will undoubtedly find this very wrong.