Why is MsiGetFeatureState() returning INSTALLSTATE_ADVERTISED for my un-advertisible feature? - wix

I've got an MSI built in WiX defined as below:
<Feature Id="Core"
Display="0"
Absent="disallow"
ConfigurableDirectory="INSTALLDIR"
AllowAdvertise="no"
Level="1">...</Feature>
I have a 'commit' custom action that loops through all the features of the MSI and determines their install-state. The log file shows this "Core" feature as installed 'Local', but MsiGetFeatureState returns INSTALLSTATE_ADVERTISED. I thought that was impossible given I set:
AllowAdvertise="no"
FWIW, MsiGetFeatureState correctly returns INSTALLSTATE_LOCAL for all other installed features and INSTALLSTATE_ABSENT for all other not-installed features.
Edit for more info:
This occurs during a fresh install.
I do not set the ADVERTISE property (I still don't fully understand what it's for)
The "Core" feature is a parent feature with children that all have the wix attribute InstallDefault="followParent".
The "Core" feature (as well as its children) all have components attached to them.
All the child features are marked as INSTALLSTATE_ADVERTISED as well.
I have a commit custom action (scheduled before InstallFinalize) that queries the installed feature states ([ProductCode] is passed in via CustomActionData). I assumed that a commit action was the right choice since the MSI is officially installed by that point.

AllowAdvertise="no" turns into msidbFeatureAttributesDisallowAdvertise in the Feature table, which says:
Note that this bit works only with features that are listed by the ADVERTISE Property.
IOW, if it became advertised for some other reason, this bit won't stop that.

Found out what the problem was (I think).
I had a custom action that would call MsiSetComponentState() to set a component to INSTALLSTATE_ABSENT if some condition was true. This means that my 'Core' feature was intended to have every attached component set to INSTALLSTATE_LOCAL, but since this one component was manually forced into INSTALLSTATE_ABSENT the 'Core' feature (once installed) is considered to have an install state of 'Advertised'. An additional consequence was that during uninstall all components attached to the 'Core' feature were getting left behind. Their action states were NULL (do nothing) instead of Absent (remove).
Moral of the story, don't use MsiSetComponentState() to turn off a component at install time.

Related

How can I determine the ProductCode for the included products in my WiX ManagedBootstrapperApplication at runtime?

I have a managed bootstrapper application with a bundle that includes four products. Each of the products are defined with "*" for the Id and have a unique UpgradeCode.
In my managed bootstrapper, I need to get the ProductCode (GUID) for the products that are part of my bundle. The detect event handlers' event args (such as DetectPackageCompleteEventArgs) have a PackageId value which is the name of the msi (ex: the "Common.msi" package has a PackageId of "Common.msi").
I also checked the BootstrapperApplicationData.xml file, which has the ProductCode and UpgradeCode of the bundle itself, but not in the WixPackageProperties nodes for the individual products.
In the case of a related package being detected (upgrade), the ProductCode is included in the DetectRelatedMsiPackageEventArgs, but that event doesn't fire when the running the installer for the currently-installed version (ie: clicking "Modify" on Add/Remove Programs)
How can I determine at runtime in my MBA what the ProductCode is for the included products?
Additional info:
Ultimately, I'm using the product code to get the ARPINSTALLLOCATION value using MsiGetProductInfo(productCode, "InstallLocation", strbuffer, len); This is specifically for the case where the user is "modifying" an existing installation (ie: running the same installer version as an already installed bundle -- not an upgrade) and I need to determine the folder to which they installed originally.
Short answer: The product code isn't provided to the BA. But an explicit registry entry (for example, following the "remember property pattern" that Rob discusses) is going to be more reliable and easier to implement than ARPINSTALLLOCATION/MsiGetProductInfo.
Longer answer: Burn runs detection the same way in all "modes" but explicitly doesn't send package-level detection messages when the version of the package in the packages is the same that's installed on the machine.
The place for this kind of static data is in the BA manifest but in v3.8 it doesn't include the product code. Please file a feature request.

Conditional installation of a component that is shared by several features

I have a product that consists of several features (A, B, C), all of which share a common windows-service component.
The catch is that there is an end case: if only one specific feature is installed (feature C), with a 'legacy-mode' flag (some property that is saved in the registry), then this windows-service component either should not be installed or its startup type should be changed from 'automatic' to 'disabled'. I would prefer the 'not installed' solution to the disabled startup type solution.
In all other cases: when other features (A,B) are installed besides this feature (C), or when the 'legacy-mode' flag is not set - the windows-service component should be installed and its startup type should be set to 'automatic'.
If the product is reconfigured the condition should be reevaluated and acted upon appropriately for both scenarios.
The problem is that it is not possible to use features' install-states in the windows-service component condition, since both features' install-state and component's condition are evaluated in the same CostFinalize action (See: http://www.joyofsetup.com/2008/04/09/feature-states-in-component-conditions/)
Also, using the approach of removing the component in a custom action does not work, because non-transitive components cannot be removed from existing features unless this is a major-upgrade(See http://blogs.msdn.com/b/heaths/archive/2010/02/15/test-your-conditions.aspx)
I could separate the component to a different feature of its own and try to write custom action that sets this feature's install-state based on the other features' install-states after the CostFinalize operation, but that seams to complicate the installation logic for the different scenarios (install, reconfigure, rollback, uninstall) and make it more error-prone.
What would you suggest?
Eventually I created a child feature to feature C that contained only the windows-service component, and I control its install-state with two MsiSetFeatureState custom actions that are scheduled after CostFinalize - one to set the state to absent in case feature C is installed or reconfigured and the 'legacy-mode' flag is set, and another to set state to install-local in case feature C is installed or reconfigured and the 'legacy-mode' flag is not set. The windows-service component is still shared between feature A, feature B and this child feature of feature C. This seems to work just fine!

How to execute custom action present in MSI without invoking installation?

Given:
Wix 3.0 is used to create MSI.
The product consists of multiple feature.
Each feature has few sub features. It’s a standard MSI feature tree.
Each feature or sub feature depend on multiple external components.
E.g. .NET 4, ASP.NET etc
Custom action written in C# using Wix 3.0 SDK processes these
dependency and evaluates if components are present or not for a
given set of features.
At time of install if dependent component is missing for given
selection of features, installation fails.
To achieve:
Ability to execute prerequisite check, which is already done in MSI as custom action during installation, without installing MSI on a given machine.
Failed Attempts:
1)
Custom action have function signature like this
[CustomAction]
public static ActionResult ProcessFeaturePrerequisite(Session session);
In order to get session object I used following API present in Wix 3.0 SDK
Session session = Installer.OpenPackage("Pathto\\Product.msi", true); // true doesn’t install it. Also tried with false, but didn’t work.
When I invoke the above method with above session following things fail.
session.Features["SomeFeature"].CurrentState;
This throws exception.
System.ArgumentException was unhandled by user code
Message=Feature ID not registered. SomeFeature
Source=Microsoft.Deployment.WindowsInstaller
StackTrace:
at Microsoft.Deployment.WindowsInstaller.FeatureInfo.get_CurrentState()
Also below critical API which determines prerequisite status always returns false.
session.EvaluateCondition(prereq);
2)
I know a command line way to specify features to the above MSI and install it. It goes like this
msiexec /i "Product.msi" ADDLOCAL=ALL REMOVE="Foo,Bar "
I couldn’t find any API in SDK which allows me to pass additional params which returns session object without starting installation. My guess is passing such param will make session.Features more valid.
Questions:
So how do I achieve above goal?
Is there
any API in Wix SDK which allows me to call custom action without
invoking installation?
any way to invoke custom action from command line for a given MSI
without installing?
any way to make Wix to change MSI into accepting a command string
containing custom action name which only evaluates the action?
any better way to do the same?
I suppose you're trying to solve the problem with the wrong tool. As far as I understand, you would like to check the installation prerequisites from inside a certain tool, but not from the installation. As long as the functionality is implemented as a custom action in the MSI package, you'd like to utilize that functionality in order not to duplicate the code.
I would choose a different way in your situation:
Extract the functionality which actually checks for prerequisites into a separate assembly, e.g. checkprereq.dll
Refactor your custom action to reference checkprereq.dll. Note that you'll have to add checkprereq.dll to your Binary table as well as the customaction.dll. You should divide the responsibility here: the custom action part works with MSI stuff - in your case, it's defining which prerequisites to check based on the combination of features a user selected - and the functional part - the actual prerequisites verification, which is done by checkprereq.dll
Use checkprereq.dll separately when you need to check prerequisites not triggering the installation process
The attempts you've outlined here demonstrate an important false assumption: the session object at install time is the same as the installation object you get by just opening the MSI database for read only purpose. IT'S NOT TRUE! Actually, I doubt it makes any sense to reference the session object outside the installation transaction. As its name states, it is an installation session, that is, available in process - not a static thing.
The MSI package should be treated just as a database when it is just a file and not a running installation. Hence, only static information living in MSI package can be queried and used when you just open it for reading and not installing. I mean you can query the Feature table, for instance, but don't expect it to contain information which makes sense in installation time only, like whether a user chose a feature for installation or not.
Hope this makes sense and shows you the right direction.

Wix installer (3.0) - how do I write to the Registry after success or fail of the installation?

I have an application that will be installed by another program (basically a wrapper that installs multiple applications and reports pass/fail for each). The requirement from the wrapper development team is that my application must write either Success or Fail to a specific registry key after the installation completes.
For my solution, I was thinking I could initilize the registry key to Success when the installtion begins, and update the value to Fail only if the installation fails (or the other way around).
Based on reading examples, browsing through the Wix Help, and searching for similar issues, I'm pretty certain I need to use a Custom Action, something like
<Custom Action="SetInstallationStatusFail" After="InstallFinalize">NOT Installed</Custom>
The place I'm stuck at now is that I don't know what code I need to write for the SetInstallationStatusFail in order to update the registry key. I'm also not sure what Parent element to stck it under. I think it should be something simple like this
<RegistryValue Action="write" Root="HKLM" Key="SOFTWARE\$(var.RegistryCompanyName)\$(var.RegistryProductName)\InstallStatus" Name="install" Type="string" Value="Fail" />
If you think I'm trying to do something that isn't valid, or if you know of a better solution for wrting to the registry after install, please let me know. Thank you.
It's a bad requirement because the registry value would be "orphaned" with nothing to clean it up. MSI already provides a way to determine success or failure: the return code. See "Error Codes" in the MSI SDK for a list.
MSI doesn't provide a way to write to the HKLM registry after failure, either natively or via custom action. (There are actions that can be triggered on error but they don't have privileges to write to HKLM.)
Have a chat to the wrapper guys and see if they are willing to consider other options - even if they can't accept return codes and want to check somewhere else there are still other ways to do this using the Windows Installer API.
For example, here's a really simple VBScript to loop through the currently installed products looking to see if an application is installed by name.
productName = "My Application"
Set installer = Wscript.CreateObject("WindowsInstaller.Installer")
For Each productCode In installer.Products
If LCase(installer.ProductInfo(productCode, "ProductName")) = LCase(productName) Then Exit For
Next
If IsEmpty(productCode) Then
Wscript.Echo "Couldn't find " & productName
Else
Wscript.Echo "Found " & productName
End If
Another option would be to test if the value in the registry exists.
Your installer writes a value to the registry, the value can be Success. If the value exists in the registry, then installation was successful; if it does not, then installation failed.
Here is a thought. You can write registry value in both cases in the success case or failure case. Write a custom action to write success value. Write and schedule a rollback custom action to write failed value. If install fails it will be rolled back and the rollback custom action will write failed. If it succeeds you write value success in the custom action with condition not installed.
In either case These registry values can be deleted during uninstall. This may not be an elegant solution but if this is your requirement then you can fulfill it.
Perhaps Bob Arnson can comment upon this solution.

How can I get WiX to call a method in a .NET assembly as part of the installation process?

I'm migrating some existing products to use WiX 3.5 (I'm using the Votive VS integration). Some of the items I'm installing need to be registered with a third-party framework. The requirement is that I must call a Register() method in a third party .NET assembly to inform it of the presence of the items I'm installing. It expects a COM ProgID.
I can't figure out how to get WiX to do this. I thought about creating a binary Custom Action, but I can't find a way of passing a parameter (a string containing the ProgID) into that custom action. I don't want to hard-code it because I need this to be re-usable code. I can't see a way to do this declaratively because the Register() function is a 'black box'.
Man this is a steep learning curve. What's my best approach here?
Look at the Deployment Tools Foundation (DTF) for WIX. There is a DTF.chm file with the WIX installation with lots of information.
Assuming you installation process is something like
Setup installation, input parameters/ProgID, do validation, etc.
Begin actual installation of files
Call registration methods
You'll need two Custom actions (ignoring rollback and uninstallation)
SetupRegistration
DoRegistration
SetupRegistration should be an immediate custom action fired either from the UI or late in the setup phase. It grabs the ProgID and any other data needed, uses a CustomActionData object and assigns that to a property named "DoRegistration" (Important, the property name must be the same as the second custom action)
The DoRegistration is a deferred custom action and needs to be scheduled in the InstallExecuteSequence probably after InstallFiles, but that depends. It pulls the Session.CustomActionData property and gets the ProgID out, then calls whatever registration method you need.
Am using a sort of what you have described.
I use to call CustomAction(events) when required. Like on clicking button you can call a method which will do work for you.
Calling custom action like:
<Custom Action="ActionName" After="InstallFinalize">CONDITION = "1"</Custom>
Or calling custom action based on specific button click:
<CustomAction Id="TestConnection" BinaryKey="SetupCustomActions" DllEntry="TestConnection" Execute="immediate" Return="check" />