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.
Related
I would like to preserve all registry values (all contained within a single key) during an MSI major upgrade, but I would like the values deleted (as usual) when the application is uninstalled.
I believe I need to use RegistrySearch to achieve this, reading the values individually to properties.
I understand this can be achieved using the following markup:
<Property Id="EXISTINGAPIKEY">
<RegistrySearch Id="name_of_dword"
Root="HKLM"
Key="Software\SomeCompany\SomeApp"
Name="api_key"
Type="raw" />
</Property>
I have now saved the value of HKLM:\Software\SomeCompany\SomeApp\api_key to a property called EXISTINGAPIKEY.
The next step is to write this value back to the registry once the upgrade has completed, but before the service (which is installed/updated by the package) is started.
So the questions are:
How can I write this property back to the registry after the update but before the service is started?
How can I ensure this only happens during an upgrade, and that the registry is cleaned during an uninstall?
A good chance that I'm over-thinking this!
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
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
I have an installer that is created with WIX and modifies a config via XmlFile, however I believe that the Wix Util Extension does not perform these actions on repair. This is causing problems when trying to perform a self-healing installer. Is there any way to accomplish what I am looking for
By piecing together a bunch of sources I came up with the following:
<Property Id="REINSTALLMODE" Value="amus"/>
<SetProperty Id="REINSTALL" Value="ALL" After="AppSearch">
<![CDATA[Installed AND REMOVE<>"ALL"]]>
</SetProperty>
Which forces a REINSTALL = ALL if it is not a remove or install
I have a similar scenario. Properties can be edited by the user through the UI, which are stored/loaded via the Registry and written to configuration files. Beyond Justin's answer, Secure="yes" must be set on each property, or MSI will ignore it (the log will show "Ignoring disallowed property").
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.