Hello,
I have build a ManagedUI installer, it looks like this sample
With an exception, the user of the installer can select the features he want install at the first install via an features selection dialog.
The update strategy is initialized as follows
project.MajorUpgradeStrategy = MajorUpgradeStrategy.Default
project.MajorUpgradeStrategy.RemoveExistingProductAfter = Step.InstallInitialize
When we detect an Update installation we skipping the most dialogs with following code
private static void ManagedUIShell_OnCurrentDialogChanged(IManagedDialog dialog)
{
dialog.MsiRuntime().Session.Log("ManagedUIShell_OnCurrentDialogChanged");
var installedVersion = AppSearch.GetProductVersionFromUpgradeCode("{" + Constants.ProductCode + "}");
var installerVersion = new Version(dialog.MsiRuntime().Session["InstallerVersion"]);
var isUpgrade = installedVersion != null && installerVersion > installedVersion;
// We check now whether we are in an upgrade mode, so we can skip some dialogs
if (dialog.GetType() == typeof(LicenceDialog) && isUpgrade)
{
dialog.Shell.GoTo(dialog.Shell.Dialogs.IndexOf(typeof(ProgressDialog)));
}
}
When I now double click a new installer it detects the previous version.
It uninstalls the previous installer und installs the new installer with the same features as before.
My problem is now, I want to install the msi with the silent options.
I use following command
msiexec /i foo.msi /qn /l*e c:/install.log
When I use the silent option the installer detects again the previous one and uninstalls it.
But in the install phase of the new installer it installs all available features not only the selected features.
I hava checked in project.BeforeInstall the features list with
string itemsToInstall = e.Session.Features
.Select(x => x.Name + "-" + x.CurrentState.ToString())
.Join(",");
string itemsToInstall2 = e.Session.Features
.Select(x => x.Name + "-" + x.RequestState.ToString())
.Join(",");
e.Session.Log(itemsToInstall);
e.Session.Log(itemsToInstall2);
This shows that the RequestState of every feature has the state Local, CurrentState is for every feature Absend
I have logged the things on a double click installation. In this case the RequestState has the state Local only for the selected features. Everything else has the state Absend.
The CurrentState is for every feature Absend again.
So I am a bit confused. Should I initialize something, in a silent update situation, which will normally initialized through the UI steps?
Any help would be great!
Best Regards
Markus
Should I initialize something...
Unfortunately 'yes' :)
Major upgrade effectively is a sequence of independent uninstall followed by a fresh install action. And since you have no UI you are effectively forcing your setup to install all default features. Which are ALL the features in your case.
Thus you need to detect and note at startup the installed state of individual features of currently installed product if found. This state you will need to set to the ADDLOCAL MSI property. See FeaturesDIalog from the CustomUI template for details.
Thx for the fast answer.
Ok so I have to implement this in the project.Load method?
But what is the best way to fetch the current config?
If I try to fetch the config via
string itemsToInstall = e.Session.Features
.Select(x => x.Name + "-" + x.CurrentState.ToString())
.Join(",");
then I get an exception with feature not configured or initialized.
THX for your help.
Yeah, at that time your session was not initialized. And if it was, its state would represent the state of the started setup but not the features installed with the previous version setup. That's why you will not find this information from any runtime objects of your active setup.
Thus your task is simple and difficult at the same time.
Simple: You need to analyses the target system without meeting any precondition (e.g. the need to be in the installing process).
Difficult: I don't know how to perform this analysis. Most likely if you dig in MSI documentation you will find some magic registry value(s) that hold this information. But I cannot be sure.
And, of course, you can always resort to something inelegant but reliable as storing user feature selection in custom registry value at the end of install and reading it at the start of the upgrade setup.
Ok really strange that this work for non silent installations.
So I will look deeper maybe I will find something, if not I will store it in the registry.
I will post my results!
THX
I found this Stackoverflow
and changed it to this.
var productInstallation = ProductInstallation.GetRelatedProducts("{" + Constants.ProductCode + "}");
if(productInstallation.Count() > 0)
{
var selected = productInstallation.ElementAt(0);
e.Session.Log(selected.ProductName + " " + selected.ProductVersion);
foreach (var feature in selected.Features)
{
e.Session.Log("{0} is {1}", feature.FeatureName, feature.State.ToString());
}
e.Session.Log("Finished");
}
When I run this code in the project.Load method, I get the installed features.
I will post the complete result when I am back in a week
Fantastic thank you for sharing
I have checked and turned out that WixSharp already implements this technique. Below is the code for FeatureItem of FeturesDialog:
```C#
static InstallState DetectFeatureState(Session session, string name)
{
var productCode = session["ProductCode"];
var installedPackage = new Microsoft.Deployment.WindowsInstaller.ProductInstallation(productCode);
if (installedPackage.IsInstalled)
return installedPackage.Features
.First(x => x.FeatureName == name)
.State;
else
return InstallState.Absent;
}
Now it's clear why it works for UI. You simply will need to initialize `ADDLOCAL` to the features state of the currently installed product by analyzing the features as above one by one.
I do consider it as a part of WixSharp functionality so I will aim to implement it as an extension method. Something like that:
```C#
static void InitFeaturesFromCurrentInstallation(this Session session)
{
var productCode = session["ProductCode"];
var installedPackage = new Microsoft.Deployment.WindowsInstaller.ProductInstallation(productCode);
if (installedPackage.IsInstalled)
{
var installedFeatures = installedPackage.Features
.Where(x => x.State == InstallState.Local)
.Select(x => x.FeatureName)
.Join(",");
session["ADDLOCAL"] = installedFeatures;
}
}
You can implement this extension in your codebase. Call it on project_load but only if UI is disabled.
If you are confident in your implementation share it and I will release it with the very next version.
Anyway, making this issue into "enhancement"
So I had some time to test it.
My code in project_load looks like this
```C#
// Is this a silent installation?
if (e.ManagedUIHandle == IntPtr.Zero)
{
e.Session.Log("msi_Load Silent Install");
e.Session.InitFeaturesFromCurrentInstallation();
}
I had to change your code to this
```C#
static void InitFeaturesFromCurrentInstallation(this Session session)
{
var upgradeCode = session["UpgradeCode"];
var installedPackage = ProductInstallation.GetRelatedProducts(upgradeCode).FirstOrDefault();
if (installedPackage != null)
{
var installedFeatures = installedPackage.Features
.Where(x => x.State == InstallState.Local)
.Select(x => x.FeatureName)
.Join(",");
session["ADDLOCAL"] = installedFeatures;
}
}
The upgrade code is the GUID which does not change over every version.
The next problem was that new ProductInstallation(upgradeCode) always initialize an empty object. Thats why I changed this.
With this changes my installer installs the same features in a silent upgrade install.
Thx a lot for your help
Makes perfect sense. Thanks.
Added to the codebase