Prevent custom actions from running during patch - wix

I am authoring a very small patch to a very large package, it's sole purpose is to update a single file and add four smaller ones.
Using the WiX help as a guide I am able to generate the MSP file.
However, a patch runs apparently it runs the original package in reinstall mode, with all the custom actions and whatnot that go along with it, which is not what I want.
Further research turned up the OptimizeCA property of the MsiPatchMetadata table, and its WiX equivalent OptimizeCustomActions which allows custom actions to be skipped when applying a patch.
That sounded exactly what I wanted but unfortunately it did not work as expected. The original package has a bunch of XML config file change custom actions, and looking at the log it appears to be erroring out when it hits the XmlFile CAs:
MSI (s) (58!74) [14:53:24:928]: PROPERTY CHANGE: Adding ExecXmlFileRollback property. Its value is (stuff deleted)
MSI (s) (58:74) [14:53:24:928]: Doing action: ExecXmlFileRollback
Action 14:53:24: ExecXmlFileRollback.
Action start 14:53:24: ExecXmlFileRollback.
MSI (s) (58:74) [14:53:24:928]: Skipping Action: ExecXmlFileRollback. It is being skipped as per the value provided for OptimizeCA in MsiPatchMetadata table of an applicable patch
Action ended 14:53:24: ExecXmlFileRollback. Return value 0.
SchedXmlFile: Error 0x8007065a: Failed MsiDoAction on deferred action
SchedXmlFile: Error 0x8007065a: failed to schedule ExecXmlFileRollback for file: (file)
SchedXmlFile: Error 0x8007065a: failed to begin file change for file: (file)
Action ended 14:53:24: SchedXmlFile. Return value 3.
You can see that the OptimizeCA property is being respected for the ExecXmlFileRollback action, as it is being skipped, but something goes wrong when it tries to schedule it and that causes the whole thing to crash the installer.
This is the relevant part of my WiX patch authoring:
<OptimizeCustomActions
SkipAssignment="no"
SkipImmediate="no"
SkipDeferred="yes">
</OptimizeCustomActions>
I'm really having a hard time figuring out what to try next. I found another person having the exact same issue as me on wix-users but there was no response to the query.

A condition including "Not PATCH" should prevent the CA from running during patch install.
Alternatively, "Not Installed" would prevent it from running if the product is already installed, because it looks like you'd have a similar issue during a repair.

Related

Confused with meaning of the CostFinalize action

I want my custom action to be performed after a user viewed and modified features, which are to be installed, but before execute installing.
I tried to call my action before PublishProduct (it seems to me that it's the right point), but I got a linkage errors from WiX.
<InstallUISequence>
<Custom Action="ModifyConfigBeforeStartService" Before="PublishProduct"/>
</InstallUISequence>
The error is:
error LGHT0094: Unresolved reference to symbol 'WixAction:InstallUISequence/PublishProduct' in section 'Product:*'
Then I tried to call my action after CostFinalize.
From the documentation:
The CostFinalize action queries the Condition table to determine which features are scheduled to be installed.
But (from the same doc):
The CostFinalize action must be executed before starting any user interface sequence which allows the user to view or modify Feature table selections
How installer detects which features are to be installed, if user interface wasn't displayed yet?
PublishProduct exists in the execute sequence not the UI sequence. The name of your custom action implies it should be scheduled before StartServices (also in the execute sequence) not the PublishProduct action.
File costing is another important concept for you to learn but not really relevant here. I'd start with this required reading:
InstallSite: Installation Phases and In-Script Execution

How to run custom code as part of WiX burn MBA on install-complete and support rollback

I need to embed, invoke and run some custom code as part of my custom managed bootstrapper application, as a post-install step. This custom code is within a class library that I have included as a reference in my MBA project. So, right after the state becomes InstallationState.Applied I plan on invoking this custom code. However, I am unable to figure out how I could tie in a failed state of this custom code to initiate the bootstrapper to rollback, since the progress callback would have been completed by now. Any ideas?
As per WiX & Burn's lead dev Rob Mensching here, point-2, it seems any custom code that needs to facilitate rollback in the setup process should be run as a custom action, and not as part of the bootstrapper application, as I wanted to. I went ahead and did as Rob suggested, and everything works as expected. However, since the custom action runs significant code, I may in future put that in a WiX extension.

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.

SetARPINSTALLLOCATION wrong value

<Custom Action="SetARPINSTALLLOCATION" After="InstallValidate">NOT (REMOVE="ALL" or REMOVE="ProgramFiles")</Custom>
I have this custom action called in InstallSequence, action that must populate in upgrade INSTALLDIR from registry, and this custom action is called in fresh install also.
In majority of cases this action, for fresh install, return one location from local machine, and the setup works without issues, but on a customer machine the value returned is \SomeDir\ and the setup faills.
How could I use this custom action to work correctly?
After some analysis I found that the functions works correctly, but the system contained an invalid registry entry, and for that value the result of SetARPINSTALLLOCATION was a wrong value.
The wrong value in registry was generated by the existence of record InstallLocation directly on uninstall registry key, without any guid like parent.
Thanks.

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.