Implement dependency of ExePackage on other MSI so uninstall completes if required MSI was already removed - wix

Background:
I have a large project which consists of a server component, client component and microservice hosting component. Each is packed in its own MSI.
The server component and the microservice hosting component each install a Windows service.
The server components' Windows service depends on the microservice hosting components' Windows service.
Everything is chained together and distributed by a setup bundle *.exe installer. In the installer, a customer can choose if he wants to install only the client, only the server or both (client and server).
At the end of the installation the customer is informed that everything went well (all files copied, all services running) (= success message) or not (= failure message with the possibility to send the log files to our help desk).
Updates are implemented as MajorUpgrades since the setup consists of hundreds of files. Our customers expect that if an update fails, the setup "rolls back" to the previously installed version of the product (= the whole bundle).
What lead to the problem?
Since Wix v3 does not handle rollbacks of bundles the way I expect it (see here) -- only the MSI that triggered the failure is rollbacked, previously installed MSIs in the chain before are only removed -- we had to implement a few workarounds:
install the component, which most likely causes errors on a customer machine, first -- then install the other MSIs.
deal with the Windows service dependencies of the components later (= restart the server component service)
Point 2 is achieved by using ExePackages that start and stop the servers' Windows service. This is done by using the technique described here. The service component MSI does not start the Windows service, only stops it on uninstall.
For architectural reasons, I do not want to configure the service components' Windows service in the microservice hosting service MSI.
This lead to the following Wix source code:
Bundle.wxs
<Chain>
            <PackageGroupRef Id="SqlExpress"/>
            <PackageGroupRef Id="vcredist"/>
            <!-- ... -->
            <PackageGroupRef Id="ServiceHandlingStop" />
            <PackageGroupRef Id="Server"/>
            <PackageGroupRef Id="MicroServiceHost"/>
            <PackageGroupRef Id="ServiceHandlingStart" />
            <PackageGroupRef Id="Client"/>
        </Chain>
Fragment of the "service handling":
The decision of the user what to install is stored inside "InternalInstallType"
<Fragment>
<PackageGroup Id="ServiceHandlingStop">
<ExePackage Id="ServiceHandlingStopCall"
Compressed="yes"
DisplayName="Service Management"
SourceFile="redist\Runner.bat"
InstallCommand="net stop WCFHostService"
UninstallCommand="net start WCFHostService"
DetectCondition="WixBundleInstalled = 1 AND InternalInstallType <> "Client""
InstallCondition="InternalInstallType <> "Client""
Permanent="no"
Vital="no"
PerMachine="yes">
<ExitCode Behavior="success" />
</ExePackage>
</PackageGroup>
<PackageGroup Id="ServiceHandlingStart">
<ExePackage Id="ServiceHandlingStartCall"
Compressed="yes"
DisplayName="Service Management"
SourceFile="redist\Runner.bat"
InstallCommand="net start WCFHostService"
UninstallCommand="net stop WCFHostService"
DetectCondition="WixBundleInstalled = 1 AND InternalInstallType <> "Client""
InstallCondition="InternalInstallType <> "Client""
Permanent="no"
Vital="yes"
PerMachine="yes">
<ExitCode Value="0" Behavior="success" />
<ExitCode Behavior="error" />
</ExePackage>
</PackageGroup>
</Fragment>
What should be achieved by this bunch of code?
"Only if the server component is installed, start the server components' Windows service after the microservice hosting component is installed. On a failed upgrade, start the server components' Windows service after the previous version was installed again. If the user chooses to uninstall, don't care about the server components' Windows service."
The problem
If the setup bundle on uninstallation by a user is killed / closed unexpectedly, it can occur that some of the MSIs mentioned in the chain above are already installed.
Now if the user launches the setup again to uninstall once more, Burn runs the ExePackage ServiceHandlingStartCall again and since is marked as Vital=yes (to inform the user if the service startup succeeded on install, upgrade and rollback and abort if neccessary), the setup fails and the user is unable to remove the product.
Question:
How to implement the dependency between the ExePackage ServiceHandlingStartCall and the service component MSI so uninstallation is possible even though the other MSIs are already gone?
I tried
"InstallCondition" / "DetectCondition"
Dependency I don't find any examples / documentation
Or if someone has an idea of how to handle the dependency of server component and microservice hosting component, my ears are wide open.
Thank you!

Are you able to check whether the individual compoments (MSIs) are installed?
It seems you're only checking if the entire bundle is installed or not (WixBundleInstalled = 1). Even though the InternalInstallType variable holds the "decision of the user what to install", that might not reflect the actual state of the system if the installer was previously interrupted, as you're describing.
If you could make every component (MSI) mark if it is installed or not (in the registry, for example) you could then do a RegistrySearch in Wix and only try to uninstall the component if it was detected.
I've implemented the same thing recently. This code check if the WebView2 Runtime is already installed, before attempting to install it:
Bundle.wxs (Bootstrapper)
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:bal="http://schemas.microsoft.com/wix/BalExtension"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<Bundle ...>
... code removed for brevity
<!-- Check if WebView2 Runtime already installed -->
<util:RegistrySearch
Root="HKLM"
Key="SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"
Value="pv"
Variable="WV2RuntimeMachineVersion" />
<util:RegistrySearch
Root="HKCU"
Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"
Value="pv"
Variable="WV2RuntimeUserVersion" />
<Chain>
<!--This will only be installed when INSTALLING or REPAIRING and the WebView2 Runtime is not already installed-->
<ExePackage Id="TheWebView2Runtime"
DisplayName="WebView2 Runtime"
Vital="no"
Cache="no"
Permanent="yes"
SourceFile="..\..\data\bundle\MicrosoftEdgeWebView2RuntimeInstallerX64.exe"
InstallCommand="/silent /install"
DetectCondition="NOT (WixBundleAction = 5 OR WixBundleAction = 7) OR (WV2RuntimeMachineVersion >= v102.0.1245.22 OR WV2RuntimeUserVersion >= v102.0.1245.22)" />
</Chain>
</Bundle>
</Wix>
Note that in my case, I don't want to uninstall the dependency (WebView2 Runtime) ever, but that could easily be changed by removing the NOT (WixBundleAction = 5 OR WixBundleAction = 7) of the DetectCondition.
Do you think this approach might work for you?

Related

Wix Installer: Bundled MsiPackage causing install bundle to attempt to run again?

I have the following very basic WIX 3.11 bundle defined at the minute and I have removed the execpackages that fire before the MSI is installed as the problem I'm about to describe only occurs with the MSIPackage command and the specific Third Party MSI I'm using.
<?xml version="1.0"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
<Bundle Name="ACME APP 32Bit" Manufacturer="ACME CORP" Version="1.0.0.0" UpgradeCode="0B736949-AE50-46B0-A534-42C9672FAF1F" IconSourceFile='..\Common Files\Images\icon.ico'>
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLargeLicense">
<bal:WixStandardBootstrapperApplication
LicenseFile="..\Common Files\Documents\EULA.rtf"
ShowVersion="yes"
LogoFile="..\Common Files\Images\logo-64x64.png"
LogoSideFile="..\Common Files\Images\logo-64x64.png"
/>
</BootstrapperApplicationRef>
<Chain>
<MsiPackage Id="TP32BIT" SourceFile="ThirdParty.msi" Visible="no" />
</Chain>
</Bundle>
</Wix>
What is happening is after the bundle deploys the MSI and whilst the Installation completed successfully dialog is present , the modify setup dialog again (repair uninstall cancel ).
It only appears to happen with the third party MSI that I need to install. I don't have control over this MSI and nor can I get support on changing from the manufacturer at this time.
I've replaced the MSI with another random product and it doesn't result in the same issue. Its deployed without effectively attempting to run the bundle installer again.
I've run the Third Party MSI from the command line and checked it's return code on instillation and it returns 0.
I'm baffled as to whats causing the installer to think it needs to run itself again on completion of this MSI. Non of the UUIDs are in conflict and I don't think there's anything wrong in my xml.
If anyone can shed some light onto this I'd appreciate it. Currently the only thing I can think of doing is attempting to deploy this specific MSI to the platform via a execpackage approach that runs the msiexec from command line but that completely negates the reason I'm using a bundle in the first place.
Thanks in advance.
I am a little bit confused reading this.
Does the problem manifest itself if you run ThirdParty.msi interactively with full setup GUI outside the Burn Bundle?
In other words a regular installation, that is not invoked via command line but run by double clicking the MSI and then clicking through the setup GUI.
I suppose it is possible that some fancy event in the setup complete dialog kicks off a custom action that does something crazy. Is this an MSI that we can take a look at? Can you provide a download location? (no promises though).
When the setup is run in silent mode, the GUI sequence does not run - which could explain why things work in silent mode - if this is indeed the case.
Turns out it's a known bug in WIX that is triggered by the 3rd Party MSI. github.com/wixtoolset/issues/issues/5266 This MSI can't be changed and has to deploy it's content using this mechanism. I've been able to create a work around to resolve the issue as the installer starts 2 new instances of Wix after the MSI completes so I'm tracking the process IDs and killing anything "unknown" –

Wix ExePackage Side by Side Install without Uninstall

I have two products which I'm trying to write an installer for. Both products are wix bundles which both have a third product bundle as a requirement.
Ideally what I want to happen when you install one is:
If Product3 is not installed then it should be.
If Product3 is installed and installed version is higher or the same do nothing.
If Product3 is installed and installed version is lower then upgrade.
If uninstalling and product1 or 2 is still installed do not uninstall product3.
So far I was able to conquer some of these points but not all at the same time.
Originally I tried getting the installed version using a ProductSearch and using InstallCommand. However I ran into problems with uninstalling when doing side by side installs because the installcommand is more like "requested install state" and so if I don't want it to install when it detects the same version it actually starts uninstalling it.
I took a look at this similar answer:
How to avoid uninstalling previously installed ExePackage (redistributables) while installing a WiX Bundle?
which suggested using the provideskey and requires elements but I cannot find any useful documentation on them whatsoever. I tried experimenting with it but it doesn't seem to do anything at all.
I've looked at RelatedBundle but I'm not sure it's what I'm after. Seems more targeted at hotfixing systems.
I was hoping there was a way of doing this without having to resort to custom actions since that seems a bit extreme for what seems to be rather simple functionality.
After a lot of trial and error I figured out how to get the DependencyExtension working.
In the example below ProductC is a Wix Bundle executable. This bundle includes an MSI file. I use a productSearch to look for the upgrade code of that MSI file (NOT THE BUNDLE) to detect if it's already installed.
In your bundle file for project A and B:
<Wix xmlns:dep="http://schemas.microsoft.com/wix/DependencyExtension"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<Bundle>
<util:ProductSearch Id="ProductCInstallSearch"
UpgradeCode="{ProductC_MSI_UpgradeCode_GUID}"
Variable="ProductCInstalled"
Result="state"/>
<dep:Requires Id="ProductCRequired" ProviderKey="ProductC"/>
<Chain>
<PackageGroupRef Id="ProductC_pkg"/>
</Chain>
</Bundle>
<Fragment Id="Required_Pkgs">
<PackageGroup Id="ProductC_Pkg">
<ExePackage Id="ProductC_Bundle"
Permanent="no"
DetectCondition="NOT ProductCInstalled = 2"
InstallCommand="/quiet"
UninstallCommand="/uninstall /quiet">
<dep:Provides Key="ProductC"
Version="0.0.0.0">
<dep:RequiresRef Id="ProductCRequired"/>
</dep:Provides>
</ExePackage>
</PackageGroup>
</Fragment>
</Wix>
Now you can install A and B in any arrangement and when uninstalling product C will only be removed when the last one is uninstalled.

WiX - How to uninstall the bundle when uninstall the msi

Im using WiX to install my .msi, I´m generating a WiX Bundle using the Bundle Element.
I try to not show the Bundle on "Add/Remove programs" so i set the properties of the Bundle element like this:
<Bundle Name="$(var.ProductName)" Version="!(bind.packageVersion.MSIPackage)"
Manufacturer="$(var.ProductManufacturer)" UpgradeCode="$(var.UpgradeCode)"
DisableRemove="yes" DisableModify="yes" DisableRepair="yes">
DisableRemove, DisableModify and DisableRepair to "yes" made the Bundle be hidden under "Add/Remove programs".
My problem is that when i Uninstall my application, the application is uninstalled correctly but the Bundle remains Hidden, so it cause some problems when i try to install other version of the App, for example the new Bundle detects that there are other Bundle installed and performs some versioning check and so on.
So my question is: is possible to when the application in uninstalled from the "Add/Remove programs" uninstall the Hidden Bundle as well?
To expand on Tom's answer, if you remove the Disables from your Bundle tag
<Bundle Name="$(var.ProductName)" Version="!(bind.packageVersion.MSIPackage)"
Manufacturer="$(var.ProductManufacturer)" UpgradeCode="$(var.UpgradeCode)">
You can modify your MsiPackage tag to hide the MSI from Add/Remove Programs
<MsiPackage
Id="YOUR-ID"
Vital="yes"
DisplayName="$(var.ProductName)"
SourceFile="$(var.Source.TargetPath)">
<MsiProperty Name="ARPSYSTEMCOMPONENT" Value="1"/>
</MsiPackage>
This will leave just one entry in Add/Remove Programs. Your Bundle will now handle the UI of the install and uninstall, and will correctly allow other versions of the bundle to be installed.
Well, you could use a custom action in the msi but don't.
You have inverted the designed relationship between bundles and packages. I suggest that you hide the package and show the bootstrapper in ARP.
The bootstrapper engine ("burn") is a package manager that collaborates with Windows Installer. Together they handle upgrades and uninstallation of packages. If, after understanding how it works, you don't want what it does then you may want a self-extractor instead of burn. (Some projects that do use burn are Visual Studio and WiX itself.)
Use -repair option when running the installer every time. It's a hack but it works. The problem is that the bundle uninstall is hidden, and when running uninstall you are only removing the package inside, not the bundle.
This causes the issue when you want to run the installation again after uninstalling the package inside. The installer thinks that the bundle is still installed. By using the -repair option (every time you install the bundle), you are telling it to either install the bundle if no bundle is present. or repair it if the package was removed.
-repair = repair (or install if not installed)

How to deploy 64-bit and a 32-bit Windows Installer package as a single setup?

I need to deploy a software setup targeting both, Windows 64bit and 32bit. I have two separate Windows Installer databases (created with WiX) for each platform, and I am using dotNetInstaller to combine both into a single installation bootstrapper executable.
I'm currently using version 1.10 of dotNetInstaller and set auto_close_if_installed=True, because I want to comletely hide the bootstrapper from the user. Still, dotNetInstaller insists on displaying a sill progress bar window while my installer is running, and doesn't really auto-close. The user needs to confirm a dialog box telling him that the application was successfully installed. But the real deal-breaker is that it doesn't support Windows 8 (yet).
Upgrading to a later version of dotNetInstaller seems to break auto_close_if_installed, so it's even worse.
So my question is: what is the current state of the art to deploy both setups in a single executable. Would Wix Burn be an option?
I know that in an ideal world, I simply provide my customers with separate installers for either platform. But they happen to be completely unaware of such subtleties, most of them don't even know what platform they are using.
I would definitely use Burn in this scenario. Something akin to the following:
<Wix>
<Bundle...>
<BootstrapperApplicationRef Id='WixStandardBootstrapperApplication.HyperlinkLicense' />
<Chain>
<MsiPackage InstallCondition='NOT VersionNT64' SourceFile='path\to\x86.msi' />
<MsiPackage InstallCondition='VersionNT64' SourceFile='path\to\x64.msi' />
</Chain>
</Bundle>
</Wix>
This is exactly one of the scenarios Burn was designed to handle.
You can do it in a single Wix via Conditions and Features.
<Feature Id='X86' Level='1'>
<ComponentRef Id='X86Feature1' />
<Condition Level="1">NOT VersionNT64</Condition>
</Feature>

Bundling Apple's Windows Bonjour installer into our msi

I've been asked to bundle Apple's Bonjour installer into our own msi installer, so that Bonjour automatically gets installed alongside our software. Anyone done this before? It doesn't seem to be trivial, as an msi installer cannot include and kick off another one. I assume I'd need some kind of batch file to run the two installers sequentially?
You'll need to use a bootstrapper to chain the Bonjour install with your installer. If you are using WiX 3.6 or later, using Burn to create a package bundle is a good option.
I found the Bonjour installer by downloading the Bonjour SDK and opening it in 7-zip, though I'm sure installing the SDK would provide access to it as well.
The way I typically like to do this is to add a new source file to your setup project for each dependency package to keep that logic separate from the main application setup.
The Bonjour package can be listed as a remote payload to retrieve on the fly, or build it into your setup. In this case, it seems more likely to build it in (Compressed="yes"). If you need to add any extra dependencies related to bonjour or parameters to pass into it, you could define them here as well.
<Fragment>
<!-- if a web link actually exists, I didn't find it... -->
<?define BonjourWebLink = "http://path/to/Bonjour.msi"?>
<PackageGroup Id="BonjourWeb">
<MsiPackage Id="BonjourWeb"
Compressed="no"
DownloadUrl="$(var.BonjourWebLink)">
</MsiPackage>
</PackageGroup>
<PackageGroup Id="Bonjour">
<MsiPackage Id="Bonjour"
Compressed="yes"
SourceFile="path\to\Bonjour.msi"/>
</PackageGroup>
</Fragment>
In your main bundle you just need to add a reference to the correct package group.
<Chain>
<PackageGroupRef Id="Bonjour"/>
<MsiPackage SourceFile="path\to\YourProduct.msi"/>
</Chain>
Since Bonjour uses MSI instead of an executable, you don't need to do anything to detect whether it is present or not; Burn will handle that for you. Actually, since WiX harvests most of the information your bundle needs from the MSI, this might be overkill, and you could just put in the MsiPackage element in your chain directly.
Don't forget to carefully check Apple's terms for doing this.
This would be a bit more work, and is prone to issues with upgrading, but you can take the Bonjour MSI and decompile it using dark. Convert the decompiled MSI into a Merge module that can be included with your installer, and you will have a single install. I have done this with some driver installs in the past, and it is usually not that complicated.
You need a bootstrapper; there are several freely available out there, including one being developed in WiX called Burn.
Wix Burn is relatively stable now.I`m using Wix 3.8.
If you are allowed to redistribute Bonjour Installer,you can chain the installer in Wix Burn. You can even put a condition to specify Bonjour as prerequesite for your installer.If it is not present,then Bonjour will be deployed, else it can be skipped.
You can check this link for understanding Burn.
http://wixtoolset.org/documentation/manual/v3/bundle/