Wix Burn: How to store the custom InstallFolder for later modifications? - wix

I'm trying to solve this for a while now. I've authored a custom UI for my Bootstrapper Application. Setting a default value for InstallFolder is not the problem, but when the user changes this path, how can I store this path for later changes in add/remove programms, e.g. when another Package in the bundle should be installed by modifying the Bundle?
To write in the Registry could be an option, but the Bootstrapper Application doesn't run elevated all the time, so that it can't write to HKLM. But there should be a way to do this, I saw similar things for Visual Studio...

You should be storing the InstallFolder value in the registry in one (or all depending on how it is authored) of your MSIs that are packaged with the bootstrapper application. On startup you can use a util:RegistrySearch to look for and set the InstallFolder in the bootstrapper.
<Fragment>
<util:RegistrySearch
Id="ServerInstalledCheck"
Root="HKLM"
Key="SOFTWARE\$(var.OEMRegistryRootKeyName)\v7"
Value="ServerPath"
Result="value"
Variable="ServerInstalled"/>
<util:DirectorySearch
Path='[ServerInstalled]'
Variable='InstallFolder'
After='ServerInstalledCheck'
Condition='ServerInstalled' />
</Fragment>
I think you can directly set the variable InstallFolder in the registry search itself and omit the DirectorySearch. The DirectorySearch approach was used just to ensure we only set the InstallFolder to a location that actually exists on the machine. There may be other advantages as well but I can't think of them at the moment.
This will retain your default InstallFolder location on a fresh install and 'remember' the selected install location when running to uninstall/modify/upgrade.
You are right that you cannot rely on writing any registry keys inside your bootstrapper application because it is not guaranteed (and really shouldn't be) run elevated.
This is basically following the 'remember property' pattern which is explained here. Whenever you want to remember a value set in a previous install during modify/upgrade/removal, this is generally the go to.

Related

Wix msi updated property value not working during installation

Working with Wix in Visual Studio. I have a public property defined in Product node of wxs file.
<Property Id="MYPROP" Value="123456789"/>
The property value is passed as commandline argument to a deferred custom action executable. I'm able to receive it in the exe as well. The problem is even if I update the Property using vbs (verified through vbs select as well), when I launch the msi, it still passes the default/original value (123456789) to the custom action executable.
Also tried msiexec.exe /i myinstaller.msi MYPROP=SomeOtherValue
I'm still seeing the original value. What's wrong?
Maybe try this simple thing first:
<Property Id="MYPROP" Secure="yes" Value="123456789"/>
Essentially you need to add a property to the SecureCustomProperties list to have them pass properly to deferred mode in secure desktop environments.
See more information on SecureCustomProperties here. The technical details here are a little bit in flux because of Windows changes, so please just try this first - there could be several other reasons.
How do you use this property? What does it do?
When an msi is run, windows caches the msi file in %windows%\Installer folder. When that msi is run again, windows checks if an msi with identical PackageCode exists in the cache, if so then it uses the cached msi file instead.
PackageCode: identifies each unqiue msi installer package - even if it only has different properties.
In short, when a property is updated using a vbscript etc, then the PackageCode has to be updated as well. This will ensure that after updating msi, the same msi can be used on the same system and windows will not use the cached msi.

How can I conditionally overwrite file during WIX install?

I have two work modes in my installer:
use config files left from previous installation
delete all existing configs and put default configs instead
The mode is determined by the checkbox in the WPF UI of the installer. If second mode is selected, then CustomAction is run, which manually deletes the configs folder from disk:
<InstallExecuteSequence>
<Custom Action="RemoveConfigsFolder" After="RemoveFolders" Overridable="yes">NOT Installed AND DELETESETTINGS=1</Custom>
</InstallExecuteSequence>
I'm using NeverOverwrite attribute:
<ComponentGroup Id="Configs" Directory="INSTALLDIR" >
<Component Id="Configs" Permanent="yes" NeverOverwrite="yes">
<File Id="main.config" Name="main.config" Source=".\Configs\main.config" KeyPath="yes" />
</Component>
</ComponentGroup>
The first mode works fine in this case, but when I try to use second mode it fails and all configs are just deleted and never created again during the installation.
During my research of the issue, I think I've found the reason why this happens: https://community.flexerasoftware.com/showthread.php?96157-The-truth-the-whole-truth-about-quot-Never-overwrite-quot-and-quot-Permanent-quot-files&p=156826#post156826
Actually this is a Windows Installer issue. If you log the uninstall
you will notice that very early in the installation the Installer
decides that the component containing this file will not be installed
because it is marked "Never Overwrite" and a copy of this file already
exists on the target machine. The uninstall happens after that which
removes the existing file. This is because the Installer decides this
when the "CostFinalize" action is launched. This action HAS to be run
before the "RemoveFiles" action.
But how do I fix it?
The problem with settings such as Never Overwrite or Permanent is they look like build settings, but they are not really - they stick to the system attached to the component id. So resetting in the project won't help because it's associated with that id. It's also not clear why setting Never Overwrite might have been a solution to some problem, because by definition patches and overwrite upgrades won't overwrite it, but overwriting it is a requirement of your setup.
Even if you had not set Never Overwrite the Windows Installer rules would not overwrite the file if it was modified after install. So if you had installed it, then it was altered, and then you did an upgrade, the file would not be overwritten (which is another reason why Never Overwrite does not seem needed).
Another issue is that your custom action RemoveConfigsFolder is not marked with an Execute enumeration value, therefore it is immediate, therefore it does not run elevated, therefore it might simply be failing, so without seeing the code it's impossible to say if reports an issue if it can't do the remove. It's also not possible to determine if it explicitly specifies the full path to the folder correctly. So the most likely quick fix to this issue is to mark the custom action as execute deferred, and the DELETESETTINGS value will need to be passed in via CustomActionData.
My initial thought is to remove the 'Never Overwrite' property. Then create a component condition that checks if the file exists. My thought is that your custom action has the condition to correctly remove the config files. If the files do not exist then the components will be selected for install.

What is the usage of TARGETDIR and INSTALLDIR in WiX?

In legacy Visual Studio Deployment Project installers, passing a command-line parameter that specified a value for TARGETDIR allowed me to override the default installation location (most of my installations take place without user interaction, so command-line automation is used heavily). However, the impression I'm getting is that WiX (by default) uses TARGETDIR for something different. While I can (and will) update our command-line tools to change the parameter name, that still leaves all of our existing installations that would need to be touched manually (a non-trivial effort).
Is there any way to override the installation location in a WiX package by specifying TARGETDIR without breaking anything?
After doing more digging, it looks like my previous experience is a result of behavior specific to VSDPROJ's (and possibly InstallShield), wheras WiX is conforming to the Windows Installer.
As I discovered at this link, TARGETDIR is actually supposed to represent the root of the drive with the most free space available (assuming there's more than one). That's why WiX projects have directories nested under there for Program Files, etc. Visual Studio actually adds a custom action that overrides this property to the full installation path.
I was able to accomplish what I wanted by doing two things:
Change all of my components and component groups to install to TARGETDIR instead of INSTALLFOLDER (the default directory that WiX put in there)
Add a custom action that sets the value of the TARGETDIR property to the installation path, assuming one wasn't passed in from the command line.
To do that, I added this under the <Product> tag:
<CustomAction Id="SetTARGETDIR" Property="TARGETDIR"
Value="[ProgramFilesFolder][Manufacturer]\[ProductName]"
Execute="firstSequence" />
And this within the <InstallExecuteSequence> tag:
<Custom Action="SetTARGETDIR" Before="CostFinalize">TARGETDIR=""</Custom>

Wix bootstrapper uninstall shortcut

I am trying to create shortcuts to uninstalling whatever the bootstrapper has installed.
So simply i want to do the same thing as the uninstall does when going to Add and remove programs.
I found that de bootstrapper is installed in package cache{guid}[bootstrappername].exe
One of the msi packages that it installs also installs a shortcut to this bootstrapper /uninstall call.
However problem is that the GUID of the package is regenerated on every build. So i some how have to set it as
a msi property.
But i cannot figure out how to do this, seem to me that the GUID is not known during building but only after build is done?
is there another way to determine the location of the cached bootstrapper ?
If you are use Managed BA you can try this:
In your Bundle.wxs in chain with MsiPackage add MsiProperty like:
<MsiPackage SourceFile="Setup.msi">
<MsiProperty Name="UNINSTALLER_PATH" Value="[UNINSTALLER_PATH]"/>
</MsiPackage>
Somewhere in code (before call install action), you need set value for this variable like this:
Engine.StringVariables["UNINSTALLER_PATH"] = string.Format(#"{0}\{1}\{2}\{3}.exe", Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Package Cache", Engine.StringVariables["WixBundleProviderKey"], ProductName);
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) – path to %systemdir%:\ProgramData
Package Cache- folder name in ProgramData where installing bundle caching
Engine.StringVariables["WixBundleProviderKey"] – name of folder (guid) created by caching bundle
ProductName – name of your bootstrapper “exe”
And finally in your Product.wxs you can create shortcut usual way, but in “Target” attribute you need pass UNINSTALLER_PATH value and “Arguments” set ="/uninstall":
<Shortcut Id="Shortcut1" Name="Uninstall" Description="Uninstall" Target="[UNINSTALLER_PATH]" Arguments="/uninstall" WorkingDirectory="Programmenufolder" />
sorry for my english :)
You can determine the location using the bundle upgradecode you define in your bundle.wxs.
Use the registry path to windows uninstall location of your bundle
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall{upgradecode of your bundle}
or for 64 Bit OS
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall{upgradecode of your bundle}
The value BundleCachePath contains the fullpath including your bootstrapper.exe filename to the package cache where your bundle is cached.
You can also use the value QuietUninstallString which contains the full quiet uninstall command or UninstallString to launch the uninstall in non quiet mode.

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.