Wix Installer : Setting component condition property when doing a MSIEXEC admin install at command line
Asked Answered
E

3

2

We have three types/flavours of our product, but only one MSI written in WiX. When we build the installer we pass in the flavour via a defined constant:

Call MSBUILD.bat ..\MSIs\CoreProduct\OurProduct.sln /p:DefineConstants="FLAVOUR=%_Flavour%"

and the constant is setup in Visual Studio, under Build -> Define preprocessor variables as FLAVOUR=50. The build process, passes in values 50, 200 or LITE as the flavour.

In the WiX code we have loads of conditions on our components that tell it which file to install based on the flavour; eg

      <Component Id="cmp7F45920B1AA100729BAE37FC846B3FC5" Guid="*">
    <File Id="fil238A776D9294E14671E012472F9F7196"
          KeyPath="yes"
          Source="$(var.MenusPath)\ClientListView 200.r5m"  
    <Condition>$(var.FLAVOUR)=200</Condition>
  </Component>

      <Component Id="cmp8BFF42B232724DC4BA5B8F87994DEF21" Guid="*">
    <File Id="fil808D6428D67248DDB8CA65DBC5978283" 
          KeyPath="yes" 
          Source="$(var.MenusPath)\ClientListView Lite.r5m"
    <Condition>$(var.FLAVOUR)=LITE</Condition>
  </Component>

So the example above will install a file called "ClientListView Lite.r5m" if the FLAVOUR is LITE or it will install a file called "ClientListView 200.r5m" if the FLAVOUR is 200.

This all works as expected and has done for years !!

But now, we have a web version of our product and we need a zip file to contain the folder structure and files that would be installed for each flavour. I discovered that you can run a msi at the command line using MSIEXEC and the /a argument which will then redirect everything that would have been installed into a folder & thought this is exactly what I want ... but alas it's not working as I'd expected.

What it seems to be doing is running the MSI and extracting the files into the target folder, but it is ignoring the flavour and so you end up with both the "ClientListView Lite.r5m" and "ClientListView 200.r5m" files been extracted into the folder; which is obviously not what I want.

Upon reading the docs on MSIEXEC, it seems that you can pass in the value for a Public property eg msiexec.exe /a "C:\Example.msi" MY_PROP="myValue" - so I thought this might help me; so in my WiX code I added the line:

    <Property Id='PRODTYPE' Value="$(var.FLAVOUR)"/>

and then altered my component conditions to be like:

  <Component Id="cmp7F45920B1AA100729BAE37FC846B3FC5" Guid="*">
    <File Id="fil238A776D9294E14671E012472F9F7196"
          KeyPath="yes"
          Source="$(var.MenusPath)\ClientListView 200.r5m"  
    <Condition><![CDATA[PRODTYPE=200]]></Condition>
  </Component>

  <Component Id="cmp8BFF42B232724DC4BA5B8F87994DEF21" Guid="*">
    <File Id="fil808D6428D67248DDB8CA65DBC5978283" 
          KeyPath="yes" 
          Source="$(var.MenusPath)\ClientListView Lite.r5m"
    <Condition><![CDATA[PRODTYPE=LITE]]></Condition>
  </Component>

but although that compiled ok, running it via:

msiexec /a OurProduct.msi /qb PRODTYPE=200 TARGETDIR="C:\InstalledFiles200"

still extracts both files for the 200 & LITE flavours, where I just wanted the one for 200.

So, am I trying to do something that is not possible ... or am I doing something wrong - any help would be gratefully appreciated, because the alternative of mimicking the process in a batch file to create my zip; will be horrendous !!

Cheers,

Chris.

Explosion answered 23/3, 2018 at 12:45 Comment(0)
D
7

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:

  1. 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.

  2. 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).

      • User feature control

        • Users can control features to install interactively via the setup's FeatureTree dialog found in most setups (not all). Usually this involves selecting to do a "Custom" installation.

        • You can also specify what features to install in the setup via the command line kicking off the installation using the ADDLOCAL property and the REMOVE property (and potentially other, similar properties - see link for details).

        • Sometimes you can set a custom property during installation that would trigger a constellation of standard features to installed based on the "edition" specified, which brings us to the next point.

      • Programmatic feature control

        • Adding a link to an answer on programmatic feature control: How to install feature based on the property set in custom action?

        • As stated, you can manipulate what features to install via the command line by using the ADDLOCAL property and the REMOVE property inside the setup while it is running. To do this you can use a custom action.

        • There is also an INSTALLLEVEL property which is relevant for the installation state of features. For each feature there is an install level, and it can be affected by conditions set in the Condition table.

          • So the Condition table can be used to modify the selection state of any entry in the Feature table based on a conditional expression.

          • In other words you can use this to set features to either install or not install by default based on the condition.

        • In addition to the INSTALLLEVEL concept, you can also use custom actions to control the feature selection state.

          • This is strangely more reliable today than the feature condition, since many application packagers routinely max out the INSTALLLEVEL property to force all features to be installed. This is wrong since some features may need to not be installed if they are incompatible with the OS you are running on. I have tried to communicate this to many deaf ears.
        • Using these programmatic constructs your setup can, for example, change the feature selection state for what is to be installed based on a serial key entered by the user.

        • Your setup can also determine that a certain feature should not be installed based on the OS you are running (do not install Tablet OS features, for example).

        • There could be any number of technical and practical reasons for changing feature selection programmatically.

    • 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.

  3. 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.

Delphinium answered 26/3, 2018 at 22:35 Comment(1)
Wow what an answer !! Many thanks for the time you must have spent writing this up, I've learnt a heck of a lot !! I don't use features, because the 3 flavours do roughly the same thing (& there certainly isn't any optional parts that the user can choose not to install) but they have different menu files, different reports etc - but the gist of the code/business logic is the same - well what I mean by that, is that they use the same dlls and there is logic within them, that decides to do different things based on the flavour. But saying that I do like the idea of using include files ! Thx.Explosion
K
3

The /a switch in the msiexec command line is not an install in the usual sense. It is literally just an unpack of the files to a location. If you want an actual install you must use /i or just double-click the MSI file. That'll give you a proper full installation with an entry in Programs&Features and so on.

Choices of what to install are more normally handled by dividing the setup into features, each containing a set of components containing the required functionality. In the WiX UI you can get a dialog to choose the features, and maintenance mode will let you go back and change them. In a command line install you'd just say /i [msi file] ADDLOCAL=Feature1,Feature2 and so on. If you really want to use "flavor" then internally you'd turn it into an ADDLOCAL list.

Kathernkatheryn answered 23/3, 2018 at 18:37 Comment(0)
E
0

Thanks for the info, but I think you miss-understood what I was trying to achieve; I don't want to run the installer via /i because as you said that would actually install the files and change the registry etc. What I wanted to do was run the installer twice - once for flavour 200 then again for flavour Lite; so I would end up with two folders containing the files that "would" be installed, if I ran it with /i. Obviously I can't run it twice with /i, because the second time I run it, it will uninstall the first one.

Anyway, I discovered that conditions within components are ignored during an admin "install" whereas ?If? statements are not ignored - not sure why that is, but I've now removed all my condition statements and replaced them with IF statements - so now I can do what I wanted to do - run it twice with /a and each folder will contain the files unique to that flavour.

Explosion answered 26/3, 2018 at 9:26 Comment(1)
I finished and submitted and answer I previously abandoned. I am not 100% sure I fully understand what you need, but I try to clarify the issue of delivering different flavors of MSI files from the same WiX source. Your approach of using administrative installs may work, but conceptually it does not seem quite OK if you compile a new MSI file each time and then extract to an admin image. Rather you should compile three versions of the same MSI and deliver these to users - admin installations are intended for end users to extract your installation files and make a network installation point.Fluctuate

© 2022 - 2024 — McMap. All rights reserved.