Multiple instances of managed project

Jan 17 at 7:32 AM
Edited Jan 17 at 7:56 AM
We have a requirement to allow multiple instances of our program. I followed one of the samples and got everything working when instances are invoked from command line. To provide our users with a better experience we want to add a dialog to our project to allow the user to either modify/remove a selected instance or to install a new instance.
So far new instances and updates work perfectly but I’m struggling with the modify. In a previous wix project I got this working by scheduling the dialog before appsearch. But as we are using a managed project the dialogs are embedded and I’m not sure how to schedule a dialog before Appsearch with this approach. So for now I added the dialog in both the modify and the install dialogs, but I need a way to tell the project to use the modify dialogs if the user chooses not to install a new instance.
I also tried launching the dialog in the UIInitialized event, but think If I want to go this route I need to have a different type of UI (not managed).
Can you please advise what the best way is to set instances after launching?
I’ve attached snippets of code, maybe there is something small we are missing.
//managed UI dialogs and sequence
//InstanceSelect is the dialog where the user will choose either a newinstall or a modify
project.ManagedUI = new ManagedUI();      
project.ManagedUI.InstallDialogs.Add<WelcomeDialog>()
                                .Add<InstanceSelect>()                                      
                                .Add<LicenceDialog>()
                                .Add<SetupTypeDialog>()
                                .Add<FeaturesDialog>()
                                .Add<InstallDirDialog>()
                                //.Add<ConfigurationDialog>()
                                .Add<ProgressDialog>()
                                .Add<ExitDialog>();

project.ManagedUI.ModifyDialogs.Add<WelcomeDialog>()
                               .Add<InstanceSelect>()
                               .Add<MaintenanceTypeDialog>()
                               .Add<FeaturesDialog>()
                               .Add<ProgressDialog>()
                               .Add<ExitDialog>();
project.AlwaysScheduleInitRuntime = false;                                

//setup instance transforms for 5 instances
Compiler.WixSourceGenerated += document =>
{
  var instanceTransforms = new XElement("InstanceTransforms", new XAttribute("Property", "INSTANCEID"));
  var guids = new string[] {"B2086842-992F-49AE-AB4A-837C2A21B627", "B3086842-992F-49AE-AB4A-837C2A21B627",
                            "B4086842-992F-49AE-AB4A-837C2A21B627", "B5086842-992F-49AE-AB4A-837C2A21B627",
                            "B6086842-992F-49AE-AB4A-837C2A21B627"};
  var upguids = new string[] {"B2086842-992F-49AE-AB4A-837C2A21B627", "C3086842-992F-49AE-AB4A-837C2A21B627",
                              "D4086842-992F-49AE-AB4A-837C2A21B627", "E5086842-992F-49AE-AB4A-837C2A21B627",
                              "F6086842-992F-49AE-AB4A-837C2A21B627"};
  for (var instance = 1; instance <= 5; instance++)
    instanceTransforms.Add(
      new XElement("Instance",
      new XAttribute("Id", "Instance" + instance),
      new XAttribute("ProductCode", guids[instance - 1]),
      new XAttribute("ProductName", "Our product " + instance),
      new XAttribute("UpgradeCode", upguids[instance - 1])));
  document.Root.Select("Product").Add(instanceTransforms);

};

//upgrades
project.MajorUpgrade = new MajorUpgrade
{
  AllowSameVersionUpgrades = true, //uncomment this if the the upgrade version is different by only the fourth field
  Schedule = UpgradeSchedule.afterInstallInitialize,
  DowngradeErrorMessage = "A later version of [ProductName] is already installed. Setup will now exit."
};
InstanceSelect dialog snippets:
void dialog_Load(object sender, EventArgs e)
    {
      banner.Image = MsiRuntime.Session.GetEmbeddedBitmap("WixUI_Bmp_Banner");
      Text = "[ProductName] Setup";

      //resolve all Control.Text cases with embedded MSI properties (e.g. 'ProductName') and *.wxl file entries
      base.Localize();

      //load installed instances to select from
      instances = new List<RegistryInstance>();
      RegistryKey rk = Registry.LocalMachine.OpenSubKey(@"software\compname\instance");
      if (rk == null)
        rk = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\compname\instance");
      //look for registry entry and set next available instance
      if (rk != null)
      {
        for(int i = 1; i<=5; i++)
        {
          if (rk.GetValue("Instance" + i) != null && rk.GetValue("Instance" + i).ToString() != "0")
          {
            instances.Add(new RegistryInstance("Instance" + i, rk.GetValue("Instance" + i).ToString()));
            checkedListBox1.Items.Add(instances[i - 1].sDirectory);            
          }
        }        
      }
      //if (instances.Count <= 0)
      //  btnNew.PerformClick();
      
    }

    void cancel_Click(object sender, EventArgs e)
    {
      Shell.Cancel();
    }

    private void btnNew_Click(object sender, EventArgs e)
    {
      int nInstance = 1;
      if (instances.Count > 0)
        nInstance = instances.Count + 1;
      MsiRuntime.Session["INSTANCEID"] = "Instance"+ nInstance;
      MsiRuntime.Session["TRANSFORMS"] = ":" + MsiRuntime.Session["INSTANCEID"] + ";";
      //set default install directory with name of instance id
      MsiRuntime.Session["INSTALLDIR"] = @"C:\Me " + MsiRuntime.Session["INSTANCEID"];
      MsiRuntime.Session["MSINEWINSTANCE"] = "1";
      Shell.GoNext();      
    }

    private void btnModify_Click(object sender, EventArgs e)
    {
      int nInstance = 1;
      if (checkedListBox1.SelectedIndices.Count > 0)
      {
        nInstance = checkedListBox1.SelectedIndices[0] + 1;
        MessageBox.Show("checked instance" + nInstance);
        MsiRuntime.Session["INSTANCEID"] = "Instance" + nInstance;
        MsiRuntime.Session["TRANSFORMS"] = ":" + MsiRuntime.Session["INSTANCEID"] + ";";
        //set default install directory with name of instance id
        MsiRuntime.Session["INSTALLDIR"] = @"C:\Me " + MsiRuntime.Session["INSTANCEID"];
        MsiRuntime.Session["MSINEWINSTANCE"] = "1";
        //added the following section to try and force the maintenace sequence but
        //when trying to remove installed product I get:
        //DEBUG: Error 2755:  Server returned unexpected error 1639 attempting to install package
        Shell.Dialogs.Clear();
        Shell.Dialogs.Add<WelcomeDialog>();
        Shell.Dialogs.Add<InstanceSelect>();
        Shell.Dialogs.Add<MaintenanceTypeDialog>();
        Shell.Dialogs.Add<FeaturesDialog>();
        Shell.Dialogs.Add<ProgressDialog>();
        Shell.Dialogs.Add<ExitDialog>();
        Shell.GoNext();
      } 
    }
  }
Coordinator
Jan 18 at 12:03 PM
"UIInitialized event" is what I wanted to suggest. And you need ManagedProject for this. Exactly what you are already using. So you are good.

I cannot see anything obviously wrong with your code by just reading it. I apologize but I just do not have time for building and running the test case from it. So my assessment is a bit superficial.

If you need any further help/discussion you can also try the GitHub the current home for Wix#.
Jan 19 at 10:51 AM
Thank you for the reply even though you are busy.

Could you maybe explain how I would launch an embedded dialog in the UInitialized event? I tried showdialog, but it complains about the msiruntime and msi properties.

Or is there anyway to reset the setup type that will in effect load another set of dialogs. In other words go from Install dialogs to modify dialogs?
Coordinator
Jan 20 at 3:16 AM
Edited Jan 20 at 9:17 AM
OK. I have managed to test your scenario (at least the UI part of it).
The event you need to hook up to is UILoaded. It is called when UI shell is created and the first dialog is prepared for displaying. UInitialized event is actually not suitable for the task as at this stage the dialogs sequence is not ready.

I also reflected dynamic Dialog sequence creation in the sample here: https://github.com/oleg-shilo/wixsharp/commit/9d6b86d601e89694b3658f1843defb3ca17f0df3
project.UILoaded += Project_UILoaded;
...
static void Project_UILoaded(SetupEventArgs e)
{
    // Simulate analyzing the runtime conditions with the message box.
    // Make a decision to show (or not) Licence dialog by injecting it in the Dialogs collection
    if (MessageBox.Show("Do you want to inject 'Licence Dialog'?", "Wix#", MessageBoxButtons.YesNo) == DialogResult.Yes)
        e.ManagedUIShell.CurrentDialog.Shell.Dialogs.Insert(1, Dialogs.Licence);
}
Your dialog_Load is not much different and should work UI wise. Though keep in mind that in both cases (yours and mine) when the event handler is invoked the first dialog is already displayed so Shell.Dialogs.Clear(); doesn't seems right and I used Dialogs.Insert(1,... instead.

I have tested my sample and the UI part works as expected. The 'Licence' dialog is injected depending on the user MessageBox response.

However you need to be aware that typically your OS shell will invoke your msi (via msiexec.exe) in the install/modify.repair/uninstall mode and the UI sequence reflects this mode one or another way. But I cannot exclude that just changing the way UI looks (dialog sequence) will automatically change the actual mode. The chances are that it will but you will need to test it.
Marked as answer by vstroh on 1/19/2017 at 10:03 PM
Jan 20 at 5:03 AM
Thank you for the assistance