WiX Changing UI behavior based on registry value - wix

The short version is that I am building an installer package, and I want the “Next” button of my welcome screen to change its behavior based on whether or not a specific registry key exists. It seems like I should be able to modify the conditions of the next button’s actions to get this behavior, but so far no luck. I always either get behavior A or behavior B, I have not gotten behavior that is sensitive to the registry value.
I should note that I can remove the UI completely and run the package via command line to get the desired results, but I am trying to be a little more user friendly by adding the install path dialog when appropriate.
In a bit more detail…
I am working on installers for a series of independent, but related, class libraries. If we install a library on a clean box I want the installer to prompt the user for an install path, and then write this path off to the registry during the install process. However if we install a library on a box which already has one of the other libraries present, I want the installer to read the registry and use the same path as the previous install.
My thought process was to modify the standard WixUI_InstallDir interface to check for the registry search result and skip the InstallDirDlg if it is set. However this does not appear to be working. Here are some snips from the XML:
<Property Id="FOOPATH">
<RegistrySearch Id="PathSet" Type="directory" Root="HKLM" Key="Software\Foo" Name="InstallPath"></RegistrySearch>
</Property>
<Property Id="PATHSET">
<RegistrySearch Id="PathSet" Type="directory" Root="HKLM" Key="Software\Foo" Name="InstallPath"></RegistrySearch>
</Property>
<Directory Id="FOOPATH" Name="Foo">
<Component Id="FooPathReg" Guid="Some Guid">
<RegistryKey Root="HKLM" Key="Software\Foo" Action="createAndRemoveOnUninstall">
<RegistryValue Name="InstallPath" Type="string" Value="[FOOPATH]" KeyPath="yes"></RegistryValue>
</RegistryKey>
</Component>
</Directory>
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="InstallDirDlg">NOT Installed AND NOT PATHSET</Publish>
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg">NOT Installed AND PATHSET</Publish>
If my publish conditions are using the PATHSET property (as shown) then I always get the install directory dialog, if I switch them to use the FOOPATH property then I never get the dialog. I have also tried playing with the InstallUISequence and it does not seem to matter how early I schedule the AppSearch action, I still get the same result.
Is there something simple I am missing? Or do I need to take another approach on this?

Check a verbose log to see if the properties are set as you expect.
Use Orca to see if the ControlEvent is as you expect. UI is additive, so you have to take extra steps when you want to replace the stock UI. For example, see http://neilsleightholm.blogspot.com/2008/08/customised-uis-for-wix.html.

Ok this turned out to be one of those brain dead moments where I missed something in my testing process. I am filling in the details of why it was failing in the hope that it saves someone else some of the frustration I have experienced while working on it.
First it is important to know that I was not actually testing multiple packages, I was trying to get my base structure worked out with the first package, then apply it to the rest when I thought I had it fairly close, this was to help reduce the number of changes I needed to replicate across the group of packages. To do this I was manually adding the registry entry in question, prior to running the package, there by simulating a prior install.
What I neglected to do was actually create the directory referenced by the registry entry. I was walking through the process with a co-worker, and in the course of explaining it I noticed this section of the log:
Action start 8:26:16: AppSearch.
MSI (c) (BC:D4) [08:26:16:505]: Note: 1: 2262 2: Signature 3: -2147287038
MSI (c) (BC:D4) [08:26:16:506]: Note: 1: 2262 2: Signature 3: -2147287038
MSI (c) (BC:D4) [08:26:16:507]: Note: 1: 2262 2: Signature 3: -2147287038
MSI (c) (BC:D4) [08:26:16:507]: PROPERTY CHANGE: Adding NETFRAMEWORK35 property. Its value is '#1'.
Action ended 8:26:16: AppSearch. Return value 1.
And it occurred to me to try creating the directory referenced by the registry value as well as the registry value itself. Once the directory was there, everything started working properly.
Apparently when you tell the RegistrySearcher that the value is a directory, it only sets the value into the property when said directory actually exists. This detail never came up in my searches on the topic, and is not clear in the documentation I have found, though in retrospect I can see where it is implied.

Related

How to restrict user to change feature in case modify and upgrade in installer?

I have a installer which asks the user to select feature. Whatever user selects, it will never be changed in case of modify and upgrade the installation. For example:
There are three features in my installer which are below:
<Feature Id="Standalone" Title="Standalone" Level="2">
</Feature>
<Feature Id="CentralCase" Title="Central case" Level="2" >
</Feature>
<Feature Id="MiddleEF" Title="Middle Ef" Level="2" Display="expand">
<Feature Id="GUI" Title="Client" Level="3"></Feature>
<Feature Id="AppServer" Title="Application Server" Level="3">
</Feature>
</Feature>
Now suppose user starts the installation and select the first feature which is standalone and install it. Now if user wants to modify, he should not allowed to change feature or even if user wants to upgrade, user should also not allowed to change feature. He can only upgrade what he selected at first time. Is there any way to do this?
ARPNOMODIFY: I guess it depends how critical it is that these features never change. You can set the ARPNOMODIFY in the MSI to
1 and there will be no button to invoke Modify from:
<Property Id="ARPNOMODIFY" Value="1" Secure="yes" />
Disclaimer below. Here be dragons.
msiexec.exe: However, you can still invoke modify by launching the MSI file itself (the default dialog sets should correctly disable the modify button though), but worse: you can go via the msiexec.exe command line and change anything you want:
msiexec /i "MySetup.msi" ADDLOCAL=MyFeature
This might be OK since it would appear to be seldomly used. However, you should be aware that remote management systems often rely on the msiexec.exe command line to handle MSI deployment, and as such the deployment system could be used to change feature state easily (via the deployment tool GUI, no command lines to deal with).
Custom Action: I don't know of an auto-magic way to abort setup if the user tries to modify the feature structure invoked via the msiexec.exe command line, but I suppose you can use a custom action maybe right before InstallInitialize in the InstallExecuteSequence to abort the installation if ADDLOCAL, REMOVE or ADVERTISE are set? If you do not condition this custom action properly, it could cause a package that won't uninstall at all or upgrade properly.
Some unverified conditioning suggestions: How to execute conditional custom action on install and modify only?
MigrateFeatureStates: For a major upgrade the GUI will not run as if it is running modify, but a fresh installation (since the product GUID is new). Hence the original installation GUI is shown and not the modify one. Accordingly you might need to disable some GUI controls or hide whole dialogs to prevent feature selection (not sure in WiX default dialogs). Added a link for that below. The standard action MigrateFeatureStates will take care of preserving the feature installation states between versions, provided you haven't done anything drastic to the feature structure. You enable this standard action to run in the Upgrade table. Should be default to run in WiX MSIs I think.
UPDATE:
Preselected Property: There is a special property called Preselected that is to automatically hide feature selection. You can try to set it or check whether it is set automatically by WiX to see if it hides feature selection. I have honestly never tried it.
Some Further Resources:
Hiding whole dialogs: Wix, custom dialog when previous version exists

Wix: Reschedule RegisrySearch and set property in Merge Module

I have a merge module which searches for some registry locations to read values and save to Properties. Here is the code segment:
<Fragment Id="RegSearch">
<Property Id="HOST_APP_PATH" >
<RegistrySearch Id="HOST_App"
Root="HKLM"
Key="SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\HOST.exe"
Name="Path"
Type="raw"
/>
</Property>
<Property Id="HOST_ROOT_PATH" >
<RegistrySearch Id="HOST_Root"
Root="HKLM"
Key="SOFTWARE\HostApplication\Installation Info"
Name="HOST_Root"
Type="raw"
/>
</Property>
Windows Installer puts this search in AppSearch custom action.
Problem: AppSearch executes this search very early, before WriteRegistryValues of Host Installer, it won't get any values and properties with this search won't be defined, because registry to search was never written there.
Question 1: Can we reschedule this registry search from merge module after WriteRegistryValues of Host Installer?
Question 2: Is there any other way to search registry after Host Installer executes WriteRegistryValues? Probably with some custom action?
AppSearch is a standard action provided by the windows installer and by design is intended to run very early. This is because it's frequently used by the LaunchConditions standard action to decide if an installation can continue or not. It's also useful for deciding whether features and components should be installed.
MSI is a very opinionated framework. I suspect that there is something wrong with your current design that is going to be incompatible with MSI.
Is host installer the same MSI or a different MSI? Assuming it's the same, why couldn't you just put your data in some MSI properties and use those properties to write to the registry? Then you wouldn't need to read the values back in because you'd already have them in properties.
What do you need these properties for after writing them to the registry? Usually writing them to the registry would be the end game. I'm not sure what else you are doing next.
If host.msi is a different MSI, why are you having one MSI install another MSI? That's not MSI design. In this case you would need a bootstrapper. Host MSI would run first then this MSI. But even then it's kinda strange that a second MSI would depend on properties set by a first MSI. I'd think the bootstrapper UI and Application would gather this information and pass it as secure custom public properties to both MSIs.
To answer question 1: No a merge module can only insert actions into the sequence. It can't reschedule actions. 2: You would have to use a custom action. But as I said above, this feels like the wrong path to me.

How to modify the registry key value on uninstall without removing the key from registry in wix?

I added a registry key value into the registry on install.And I want to modify that registry key value on uninstall but I don't want to removing that from registry.
1)I tried with custom actions execution on uninstall to modify the registry value. But the registry values are removing from the registry on uninstall.
2)If I make the component to permenent then it is not modifying the key value at the time of uninstall.
<Component Id="SampleRegComp"
Guid="3865FE52-F8EE-4E29-B321-BDF0FD6D3F58"
Permanent="yes">
<RegistryKey Action="create"
Key="SOFTWARE\Microsoft\Notepad"
Root="HKCU">
<RegistryValue Name="StatusBar" Type="integer" Value="1" />
</RegistryKey>
</Component>
<CustomAction
Id="ModifyOutlookRegInitSign_12"
Return="ignore"
Directory="TARGETDIR"
ExeCommand= ""[SystemFolder]reg.exe" ADD "HKCU\SOFTWARE\Microsoft\Notepad" /v StatusBar /t integer /d 0 /f" >
</CustomAction>
Installed
The short answer is to create the registry entry with your code and modify it with your code, then it's clear that you are managing those entries, not both you and the MSI setup.
An alternative is to put them in a component by itself with a null component guid, then MSI won't manage it at all after it's been installed.
Phil has already answered, but maybe I can add that you can use (REMOVE="ALL" AND NOT UPGRADINGPRODUCTCODE) for the custom action to be executed during uninstallation. Then just set the registry component permanent (HKCU registry data shouldn't really be uninstalled either - a matter of some debate though). This still won't fix the HKCU data for users who are not logged on at the time of the uninstall though. This is a much more involved task, probably possible to do with ActiveSetup.
Adding NOT UPGRADINGPRODUCTCODE ensures uninstalls performed as part of major upgrades do not update the registry since the product is then due to be reinstalled. Shouldn't be necessary for your case, but ensures the custom action doesn't run unnecessarily.
I would suggest scheduling the custom action right before InstallFinalize in the InstallExecuteSequence. I haven't tested this location, but it is then one of the last actions to run during the uninstall, and should have the desired effect.
See the condition lists for detecting different installation modes at the bottom of this thread.

MSIEXEC using command line REINSTALL not using original INSTALLDIR

I am trying to configure Wix to build my msi to only perform build versions (1.0.x) of my product in conjunction with the REINSTALL property, my problem is that when I run the command line: MSIEXEC.exe /i my.msi /l*vx build-inst.log REINSTALL=ALL REINSTALLMODE=vamus it fails to do anything.
I have checked the msi log and found that it is looking for the existing product in the default folder (.\program files (x86)...\myproduct) yet when I installed it the first time I actually used a custom path (c:\myproduct). It was my impression that using REINSTALL the installer would use the installed path of the original product.
Is this actually the case? Should I be specifying the INSTALLDIR on my command line? I would rather not as this is meant for use by clients and I cannot guarantee I will know where the product was installed.
This method of performing "build" upgrades has been suggested in a couple of places but I can not find anything explaining any need to specify the INSTALLDIR
Is there any way to configure this in Wix?
Thanks
Kieran
The easiest solution would be to store the installation directory in the registry and look it up upon reinstalling.
To look up your registry value, you'd use something of the sort:
<Property Id="INSTALLDIR">
<RegistrySearch Id="InstallLocation" Root="HKCU"
Key="SOFTWARE\Company\Product" Name="Location" Type="raw" />
</Property>
If the registry value isn't found, the INSTALLDIR property will be set to your directory structure.
Rob has a complete solution on his blog for when you specify such a property from the command line.
Normally the original entries in the directory table are stored for reinstall without that you store them yourself.
So there is something "special" in your MSI, if this doesn't work. If you have a custom action which sets directory properties like INSTALLDIR, you should not use it. E.g. give them a condition "Not Installed".
I found out that the problem was due to using a wildcard for the product id, so every time a new msi was built it created a new product id.
By fixing this it seemed to resolve the problem, though I have also implemented the registry key option as it will help for upgrades where I do want to change the product id.
Thanks

Having difficulty installing a device driver (packaged in .exe) from my WiX installer

I've struggled through building a new WiX installer, and I'm really happy with it, except that I also need to install a device driver.
Ideally, I'd like to be able to detect the presence of this driver by using RegistrySearch, e.g.
<Property Id="DRIVERINSTALLED">
<RegistrySearch Id="DriverInstalledSearch" Root="HKLM" Key="SOFTWARE\DriverCompany\Settings" Name="InstallPath" Type="raw" />
</Property>
If there is a value for the InstallPath registry key, then I'd like to move ahead with the device driver installation. I haven't figured that part out yet, but that is secondary right now, because I can't even get the device driver to install every time.
There are various examples floating around on the web, and the one corresponding to WiX v3.5 seems to follow this pattern:
<CustomAction Id="InstallDeviceDrivers" Execute="deferred" Directory="INSTALLLOCATION" ExeCommand="setup.exe" Return="check" />
.
.
.
<InstallExecuteSequence>
<Custom Action="InstallDeviceDrivers" After="InstallFiles" />
</InstallExecuteSequence>
Whenever I run my msi, I get the error "a program required for this install to complete could not be run".
setup.exe is copied to my INSTALLLOCATION, and I have verified this by looking in that folder when my msi fails.
I played with the value of the After attribute, but InstallFiles seems to be the right one since it maintains elevated user privileges. I had originally tried InstallFinalize, but that failed and I figured that it wasn't running with elevated privileges. The problem is, none of the other Actions I have tried work.
I then ran my installer with msiexec /i installer.msi /l*v install.log and looked over the output file. That's where I saw a slightly more specific error:
MSI (s) (74:CC) [14:06:10:098]: Executing op: ActionStart(Name=InstallDeviceDrivers,,)
Action 14:06:10: InstallDeviceDrivers.
MSI (s) (74:CC) [14:06:10:098]: Executing op: CustomActionSchedule(Action=InstallDeviceDrivers,ActionType=1058,Source=C:\Program Files\MyCompany\MyProduct\,Target=setup.exe,)
MSI (s) (74:CC) [14:06:10:108]: Note: 1: 1721 2: InstallDeviceDrivers 3: C:\Program Files\MyCompany\MyProduct\ 4: setup.exe
MSI (s) (74:CC) [14:06:10:108]: Note: 1: 2205 2: 3: Error
MSI (s) (74:CC) [14:06:10:108]: Note: 1: 2228 2: 3: Error 4: SELECT `Message` FROM `Error` WHERE `Error` = 1721
Error 1721. There is a problem with this Windows Installer package. A program required for this install to complete could not be run. Contact your support personnel or package vendor. Action: InstallDeviceDrivers, location: C:\Program Files\MyCompany\MyProduct\, command: setup.exe
MSI (s) (74:CC) [14:06:11:800]: Note: 1: 2205 2: 3: Error
MSI (s) (74:CC) [14:06:11:800]: Note: 1: 2228 2: 3: Error 4: SELECT `Message` FROM `Error` WHERE `Error` = 1709
MSI (s) (74:CC) [14:06:11:800]: Product: Installer -- Error 1721. There is a problem with this Windows Installer package. A program required for this install to complete could not be run. Contact your support personnel or package vendor. Action: InstallDeviceDrivers, location: C:\Program Files\MyCompany\MyProduct\, command: setup.exe
Based off of some searching, it sounded like I just needed to run as admin, but my installer already triggers UAC... and sure enough, running the installer from an elevated command prompt didn't help.
Can anyone recommend a next course of action for me to debug this? If you additionally have information on how to conditionally install based on the presence of a registry key, that would be really great, too. Thank you!
EDIT -- I have run my installer on Windows XP 32bit and Windows 7 32bit, and it fails on both. Yet another reason to believe it's not permissions-related.
EDIT #2 -- I don't know why I didn't try this before, but I changed from setup.exe to notepad.exe, and Notepad launched. So obviously the CustomAction works. I'll try again with Process Monitor to see where it's looking for setup.exe... or perhaps I just can't run an installer from within another installer?
I'd like to revise my answer to cover what I have learned that is relevant to what I think is a typical scenario:
You want to customize the bitmaps in the dialogs
You also might want to skip the licensing agreement.
You want to detect presence of some prerequisite, like the .NET Framework 4.0.
You want to write an installer that can install some other software, like a device driver, at the end of the installation process.
You want to use the presence of a registry key to make the previously-mentioned checkbox invisible
You might need to include a merge module, like the VC++ 2010 redistributable
First things first:
All of the information is on the Internet. I just spent hours wading through info and trying stuff.
Be patient, and don't give up!
Don't just rely on Google. The WiX documentation (in the .chm) has an enormous wealth of information. I suggest that you search there as well, if not first.
Did I say it already, "be patient, and don't give up!"
Skeleton WXS
Each of the sections below only have the information directly related to themselves. At the very end of the answer, I will post a skeleton (partially complete) .wxs.
Detecting the .NET Framework 4.0
First, you'll need to add a project reference to WixNetFxExtension. Then add this XML to your <Product> node: (search .chm for ".NET")
<PropertyRef Id="NETFRAMEWORK40FULL" />
<Condition Message ="This application requires .NET Framework 4.0. Please install the .NET Framework and then run this installer again.">
<![CDATA[Installed OR NETFRAMEWORK40FULL]]>
</Condition>
Customizing the Bitmaps
Found in Customizing Build-in WixUI Dialog Sets in the .chm. I only changed the following:
WixUIBannerBmp
WixUIDialogBmp
Just set them under the <Product> element:
<WixVariable Id="WixUIBannerBmp" Value="$(var.ProjectDir)\Bitmaps\mybanner.bmp"/>
<WixVariable Id="WixUIDialogBmp" Value="$(var.ProjectDir)\Bitmaps\mydialog.bmp"/>
I just create a Bitmaps folder in my WiX installer project in Visual Studio 2010.
Skipping the EULA Dialog & Setting the Installation Location
There are several built-in ways to define your installer's look and flow, but for those of us that are lazy, you'll only care about WIXUI_MINIMAL and WIXUI_INSTALLDIR. The former is only for the extremely lazy (this is what I originally sent to people!), but it doesn't allow the user to do anything but click Yes to install the program. It won't tell you that the installation is complete, either. WIXUI_INSTALLDIR strikes a nice balance, IMO. You get the typical welcome dialog, click Next through more typical dialogs, and get a Finish button at the end of the process.
Add a project reference to WixUIExtension, then use the following XML:
Creating an installer using Wix v3.0, Votive, and Visual Studio 2005/2008 - Part 2, the GUI (The Code Project)
How to build a minimal WiX Installer UI without a license page? (Stack Overflow)
WiX installer guidance (Stack Overflow)
1
1
Installing Device Driver at the End
If you want to install a device driver at the end of your installation, I think the easiest way is to just ensure that the driver's setup.exe is copied to your installation location and then executed from there. At least, that's what I did. Then you just need to use a CustomAction:
Custom Action Types (MSDN)
How To: Run the Installed Application After Setup (WiX on SourceForge)
But what if you want to allow the user to decide whether or not to install the driver? Then you do this:
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="Install device drivers when the installer exits." />
and your <UI> node will now look like this:
<UI>
<UIRef Id="WixUI_InstallDir" />
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="InstallDirDlg" Order="2">1</Publish>
<Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2">1</Publish>
<Publish Dialog="ExitDialog" Control="Finish" Order="1" Event="DoAction" Value="InstallDeviceDrivers">WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed</Publish>
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLLOCATION" />
</UI>
The logic added above just checks to see if the checkbox is checked, and the "and NOT Installed" is there to ensure that this operation only runs when the installer is installing your application, not uninstalling.
Using a Registry Key to Control Availability of the Checkbox
Just add the following XML to Product:
<Property Id="DRIVERINSTALLED">
<RegistrySearch Id="DriverInstalledSearch" Root="HKLM" Key="SOFTWARE\DriverCompany\Settings" Name="SomeRegistryKeyThatMustBePresentIfInstalled" Type="raw" />
</Property>
and then use <SetProperty> instead of <Property>, as previously shown:
How do I create a conditional property in WiX? (Almost like an If-Then)
<SetProperty Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="Install device drivers when the installer exits." After="CostFinalize">
<![CDATA[NOT DRIVERINSTALLED]]>
</SetProperty>
Merge Module
Finally, what if you need to add the VC++ 2010 Redistributable package? I wasn't sure of the best way to do this, and although my application worked when I did this (where it didn't before), I found it strange that Add/Remove Programs didn't mention anything about it actually being installed. So for this part, YMMV.
Since I run a continuous integration (CI) server that doesn't necessarily have Visual Studio 2010 installed, I put the merge module file into my WiX installer project. I made a folder called MergeModules and put it there. The file is located in Program Files\Common Files\Merge Modules, and is called Microsoft_VC100_CRT_x86.msm. Now put the following line in your <Directory> node:
How To: Install the Visual C++ Redistributable with your installer (WiX on SourceForge)
<Merge Id="VCRedist" SourceFile="$(var.ProjectDir)\MergeModules\Microsoft_VC100_CRT_x86.msm" DiskId="1" Language="0"/>
and the following line in your <Feature> node:
<Feature Id="VCRedist" Title="Visual C++ 10.0 Runtime" AllowAdvertise="no" Display="hidden" Level="1">
<MergeRef Id="VCRedist"/>
</Feature>
Skeleton WXS
I'm providing an incomplete skeleton .wxs below. It's incomplete because I'm only providing the element names for the stuff that isn't directly associated with this post. The missing information is easy to find.
May I suggest a different approach. Use a bootstrapper such as WiX Burn to check if the driver is installed and if not, install it as a prerequisite. Then launch your MSI installer. The WiX bundle would look something like:
<Fragment>
<util:RegistrySearch
Variable="DriverInstalled"
Root="HKLM,SOFTWARE\Microsoft\MyProduct\[UniqueId]\Setup"
Key="InstallPath"
Result="Exists" />
<PackageGroup Id="DriverPackage">
<ExePackage
SourceFile="Path_To_Driver\Setup.exe"
InstallCondition="InstallPath" />
</PackageGroup>
<PackageGroup Id="MainMsi">
<MsiPackage
SourceFile="Path_To_Msi\Installer.msi"
After="DriverPackage" />
</PackageGroup>
</Fragment>
Note: I did not test the above, but that should get you started if you go that route. If you're interested I would recommend looking up WiX bootstrapper bundles and Define Searches Using Variables explains how you can use the util extension to search the registry in your bootstrapper, similar to how you do it in the installer. This approach has the benefit of not mucking up your installer with custom actions.