wix bootstrapper - wix

I have written a managed wix bootstrapper using WPF. The actual installation steps requires chaining of multiple msi's/exe's and batch files.
<Chain>
<MsiPackage SourceFile="xxx"/>
<ExePackage Id="Test" SourceFile="..\TestBatch.bat" Vital="yes"/>
<MsiPackage SourceFile="yyy"/>
</Chain>
During the execution of each package, a message should be displayed (preferably from the bootstrapper UI) indicating which msi/exe package/ batch file is being executed currently.
In short, a ProgressText is needed in the bootstrapper How can I make this happen?
Another question: I do not want all the msi's to be packaged into the bootstrapper exe. This is because: Each time an msi is changed we would like to ship only the updated/modified msi and not the entire bootstrapper exe. Is there a way to do this?

Two answers, one suggestion:
To get messages back during the MsiPackages being installed, handle the BootstrapperCore.ExecuteMsiMessage event. The event args there will contain a Message that contains the data you are looking for.
To configure how the packages are compressed or not, use the Compress attribute. You can either mark the entire Bundle/#Compress='no' or mark each package Compress='no' (or 'yes' if you want to go that way).
--
Suggestion: Be sure to add DetectCondition to the ExePackages so Burn will know if the ExePackages are already present or not.

Related

Wix toolset Bundle Chain installation global condition

CONTEXT: I created a Bootstrapper installer in order to install, if needed, the .net framework 4.8. together with the setup program I want to deliver with it.
Up to here all works nice: my program gets installed always and the .net just if needed.
The thing is that I need to add some more checks to this installer, for example check for a registry key (if another program proper version is installed) and if this does not meet the condition, then the complete installation should be aborted, nothing should be installed.
PROBLEM: the InstallCondition I have added affects just the MsiPackage but the rest of the installation seems to be considered as totally fine and installation finishes successfully, here the interesting part of code:
<util:RegistrySearch Id="OtherProgramVersionId" Root="HKLM" Key="SOFTWARE\XXX\Install::Version" Variable="OtherProgramVersion"/>
<Chain>
<PackageGroupRef Id="NetFx48Redist" />
<MsiPackage
Id="MySuperProgram.Setup"
SourceFile="$(var.MySuperProgram.Setup.TargetPath)"
InstallCondition="OtherProgramVersion >= v10.0"/>
</Chain>
As said before, even the registry key is not found or it does not fulfill the condition, the installation seems to continue "successfully" and I get it in the ControlPanel->Programs as installed... but the main .msi was not really installed! (checking the destination folder, it's empty)
QUESTION: How can I add a global condition in order to stop completely any installation at all and show the user a message with the condition not fulfilled? If possible with a standard dialog.
I have seen (and I am still experiencing) with conditions, but seems they affect just one of the items in the chain... or they seem to break the installation somehow, I have tried adding to the .msi setup creation, file Product.wxs, the condition in order to abort this installation, but when installing I get this not passed condition as a setup error, seems the exit is not clean at all... even able to see the log where I see something like this:
Error 0x80070643: Failed to install MSI package.
Thanks in advance!
If you're using WixStandardBootstrapperApplication, you can use bal:Condition to define bundle-level conditions. The WiX documentation has a sample: https://wixtoolset.org/documentation/manual/v3/howtos/redistributables_and_install_checks/block_stdba_install_on_reg.html

WiX Bootstrapper WPF custom UI

I read and run sample from here.
Some questions:
How I can get actions names during the install/uninstall/remove process?
How I can pass variables and parameters to embedded MSI?
Is any way to get additional information from the embedded MSI (product version, company name etc) as it is done in WixSharp (WpfSetup sample)?
4. How I can get (set) from MSI file INSTALLFOLDER, TARGETDIR and other values?
I'm not sure you can or not. Microsoft.Tools.WindowsInstallerXml.Bootstrapper.BootstrapperApplication will tell you what msi package it is planning or executing, you may also be able to get information about which install action it is executing, check the events that get raised by this during your install process.
2.
In your bootstapper WPF app
//ba is an instance of BootstrapperApplication
this.ba.Engine.StringVariables["ServerInstallLoc"] = "YOUR DATA"
Bundle.wxs
<!-- Install paths provided by the managed bootstrapper interface -->
<Variable Name="ServerInstallLoc" bal:Overridable="yes" Type="string" Value=""></Variable>
And later reference this variable
<MsiPackage Id="MyInstaller" SourceFile="$(var.MyInstallerMsiProjectName.TargetPath)" Compressed="yes" DisplayInternalUI="no">
<!-- Pass wix bundle variable to MSI property -->
<MsiProperty Name="SERVER_INSTALL_OVERRIDE" Value="[ServerInstallLoc]"/>
</MsiPackage>
In your bootstrapper, you can reference properties of the bundled installers. the syntax is: !(bind.packageVersion.PackageName) assuming one of your <MsiPackage> elements is called PackageName. Binder variables reference
For question 4 look at this:
http://www.wrightfully.com/allowing-the-user-to-select-the-install-folder/
You can also look at the Wix managed bootstrapper as I believe it does this as well. You can download the source code here:
https://github.com/wixtoolset/wix3

How to build a bootstrapper for multiple MSI files?

I've got following problem: With WiX 3.7 I have built an installer which creates several localized MSI files, for example:
..\bin\x86\Release\en-us\myProject.msi
..\bin\x86\Release\fr-fr\myProject.msi
..\bin\x86\Release\de-de\myProject.msi
Furthermore, I created a Burn bootstrapper project which should ensure that .NET 4.5 is installed:
...
<Chain>
<PackageGroupRef Id="NetFx45Web"/>
<MsiPackage SourceFile="$(var.myProject.TargetPath)"></MsiPackage>
</Chain>
...
Now I have expected that the Burn bootstrapper project creates:
..\bin\x86\Release\en-us\myProject.exe
..\bin\x86\Release\fr-fr\myProject.exe
..\bin\x86\Release\de-de\myProject.exe
but MsiPackage expects a single file.
Is it generally possible to make the Burn project work as I expect?
If this is not the case, is it possible to get to know how the exact name of the created MSI file? I can not hard code the MSI file name, because the output name can vary.
I don't think that this is possible... If you are using MSBuild or something similar you can build it as you're building your MSI packages.
Or you can use a simple script which will call candle.exe with parameter -dYOUR_VARIABLE=myProject.msi.
See Working with MSBuild for more information.

Copy file using Burn

I am using WiX Burn to make my installer, i am bundling one exe and one msi.
And the exe needs an properties file at the time of install.
Is there a way to copy the file using burn, i tried Payload but it is not working.
Can i know the location throguh any Bundle variable where my file is copied.
Thanks
Ravi S
Make sure you are specifying the properties file as the payload for the exe and not for the bootstrapper. For example, in your bundle, your chain may look something like this:
<Chain>
<MsiPackage SourceFile="MyInstaller.msi" Id="MyInstaller" Cache="yes"/>
<ExePackage SourceFile="MyExe.exe" Id="MyExe" Cache="yes">
<Payload SourceFile="OtherFile.properties" Id="Properties"/>
</ExePackage>
</Chain>
Also, as a sanity check, which version of WiX are you using? If you are using an older build (such as RC0), you could try updating to the latest weekly build.
Update:
In WiX 3.6 it does not appear that you can get the absolute path of a payload file. There are two bugs/feature request open right now regarding the issue that are deferred to WiX 3.7:
Add burn variable to cache path - ID: 3557446
Change working folder to the cache folder - ID: 3538846
One workaround would be to use burn to write your own bootstrapper application and then programmatically determine the working directory and set the appropriate parameters, but that would be a lot of work for this one issue.

Can a .msi file install itself (presumably via a Custom Action)?

I wand to construct an MSI which, in its installation process, will deploy itself along with its contained Files/Components, to the TargetDir.
So MyApp.msi contains MyApp.exe and MyAppBootstrapperEmpty.exe (with no resources) in its File Table.
The user launches a MyAppBootstrapperPackaged.exe (containing MyApp.msi as a resource, obtained from the internet somewhere, or email or otherwise). MyAppBootStrapperPackaged.exe extracts MyApp.msi to a temp folder and executes it via msiexec.exe.
After the msiexec.exe process completes, I want MyApp.msi, MyBootstrapperEmpty.exe (AND MyApp.exe in %ProgramFiles%\MyApp folder so MyApp.exe can be assured access to MyApp.msi when it runs (for creating the below-mentioned packaged content).
MyAppBootstrapper*.exe could try and copy MyApp.msi to %ProgramFiles%\MyApp folder, but would need elevation to do so, and would not allow for its removal via Windows Installer uninstall process (from Add/Remove Programs or otherwise), which should be preserved.
Obviously (I think it's obvious - am I wrong?) I can't include the MSI as a file in my Media/CAB (chicken and egg scenario), so I believe it would have to be done via a Custom Action before the install process, adding the original MSI to the MSI DB's Media/CAB and the appropriate entry in the File table on the fly. Can this be done and if so how?
Think of a content distribution model where content files are only ever to be distributed together with the App. Content is produced by the end user via the App at run time, and packaged into a distributable EXE which includes both the App and the content.
MyApp's installer must remain an MSI, but may be executed by a Bootstrapper EXE. The installed MyApp.exe must have access to both MyApp.msi and EXE is to be "assembled" at runtime by the App from a base (empty) MyAppBootstrapper.exe, which is also installed by the MSI, and the content created by the end-user. The EXE's resource MSI must be the same as that used to install the App which is doing the runtime packaging.
WIX is not to be installed with MyApp.
There can be no network dependencies at run-/packaging- time (i.e. can't do the packaging via a Webservice - must be done locally).
I am familiar with (and using) Custom Actions (managed and unmanaged, via DTF and otherwise).
Add an uncompressed medium to your wxs like this:
<Media Id='2'/>
And then create a component with a File element like this:
<File Source='/path/to/myinstaller.msi' Compressed='no' DiskId='2' />
This will make the installer look for a file called "myinstaller.msi" on the installation medium, in the same folder as the msi that is being installed. The source path above should point to a dummy file, it is only there to appease wix.
Edit: The following sample test.wxs demonstrates that it works. It produces a test.msi file which installs itself to c:\program files\test. Note that you need to put a dummy test.msi file in the same folder as text.wxs to appease wix.
<?xml version='1.0' encoding='utf-8'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
<Product
Name='ProductName'
Id='*'
Language='1033'
Version='0.0.1'
Manufacturer='ManufacturerName' >
<Package
Keywords='Installer'
Description='Installer which installs itself'
Manufacturer='ManufactererName'
InstallerVersion='100'
Languages='1033'
Compressed='yes'
SummaryCodepage='1252'/>
<Media Id='1' Cabinet='test.cab' EmbedCab='yes'/>
<Media Id='2' />
<Directory Id='TARGETDIR' Name="SourceDir">
<Directory Id='ProgramFilesFolder'>
<Directory Id='TestFolder' Name='Test' >
<Component Id="InstallMyself">
<File Source="./test.msi" Compressed="no" DiskId="2" />
</Component>
</Directory>
</Directory>
</Directory>
<Feature
Id='Complete'
Display='expand'
Level='1'
Title='Copy msi file to program files folder'
Description='Test'>
<ComponentRef Id="InstallMyself" />
</Feature>
</Product>
</Wix>
Having one .MSI package launch another .MSI package from "within" itself is called a nested install, and it's bad juju (see Rule 20). Windows Installer has some global data that it uses to manage the current install, and it doesn't handle well multiple installs at the same time. For the same reason, if you start one install and then try to start another while the first is still in progress, you'll usually see a pop-up to the effect of "another install in progress, please wait until it's done".
You can have a program, usually called a bootstrapper (I think that's what you're referring to) which is itself not an install package, but which contains an install package (such as an .MSI or an .EXE) as a resource, possibly compressed. The action of the bootstrapper program is to extract/expand the resource to a file, commonly in a %TEMP% directory, then either launch the extracted .EXE or run MSIEXEC on the extracted .MSI. The bootstrapper can contain multiple resources and extract+install them one by one, if you need to install prerequisites before the main package. Or you can ship multiple packages as separate files, and have the bootstrapper execute/install them directly from the distribution media one by one, or copy them down to the target machine and run the series of install from there, or...
WiX itself does not get installed, no. It's a tool with which .MSI packages can be built. The WiX project has on its wishlist a generic bootstrapper program, but it hasn't been implemented yet. There are other bootstrappers available, e.g. this one.
You won't need a custom action -- in fact, since the bootstrapper isn't itself a Windows Installer installation package, "custom action" has no meaning to it. And, if you're familiar enough with CAs to know about managed/unmanaged/DTF, then you know enough to avoid custom actions whenever you can. (grin)
I think it's much easier for your bootstrapper to extract MSI file to some predefined location rather than to the temp folder. For example, to C:\Documents and Settings\All Users\Application Data\My Company\My Product Install Cache. After installation finishes bootstrapper would leave MSI file sitting there. If at some stage user decides to reinstall your product Windows Installer will be able to locate source MSI file.
Also, add path to this file to RemoveFile table so that it gets deleted on uninstall. You can use RemoveFile element in WiX for that.
So if I understand, then I think I would have the app create a transform (MST) that has the content files and apply that to the base MSI. I'm still not convinced that I understand though. :)
I'd configure the MSI cache path to a known location.
Then at runtime if you need to "edit" the MSI use VBScript or similar.
But still, I ask WHY!?!
I am also working on a way to deploy multiple MSI files. I have a bootstrapper.exe program that bundles the MSI files and runs them one at a time. This solves my problem for most cases.
The case it does not solve is GPO (Global Policy Object) distribution of the install. GPO requires a dot-msi file to run an install.
To do this here's what I did which almost solved the problem (but not quite). I put the dot-msi files in the file table of an installer and put my bootstrapper in the binary table and run it from a custom action inserted after InstallFinalize in the InstallExecuteSequence. Of course the bootstrapper won't be able to run other MSI's because the top level MSI holds the _MSIExecute mutex.
It was pretty easy to get a little further. I made the bootstrapper return control to the top level installer and continute. And then I added a WaitForSingleObject call to wait for the top level install to finish, and the bootstrapper can then continue to finish the install.
My problem is that the GPO installation happens at boot time and the top level install completes before the sub installers are done and GPO reboots the machine.
The toplevel install also returns a success status when the install may actually fail later on.
I'm still looking for a way to block the top level install from completing until after the bootstrapper completes.