oleg-shilo / wixsharp

Framework for building a complete MSI or WiX source code by using script files written with C# syntax.
MIT License
1.08k stars 172 forks source link

Application crashing with unknown error if i click on finish after installation of a WPF Custom UI installer #1551

Closed Aaswin1996 closed 2 weeks ago

Aaswin1996 commented 2 months ago

I have a custom WPF Managed UI installer created from the WPF project extensions . After installation, if I click on the finish button my installer crashes with the following error : image

The Code for the Exit Dialog is given below :

using Caliburn.Micro;
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Media.Imaging;
using WixSharp;
using WixSharp.UI.Forms;
using WixSharp.UI.WPF;
using IO = System.IO;

namespace WixSharp_Setup61
{
    /// <summary>
    /// The standard ExitDialog.
    /// <para>Follows the design of the canonical Caliburn.Micro View (MVVM).</para>
    /// <para>See https://caliburnmicro.com/documentation/cheat-sheet</para>
    /// </summary>
    /// <seealso cref="WixSharp.UI.WPF.WpfDialog" />
    /// <seealso cref="WixSharp.IWpfDialog" />
    /// <seealso cref="System.Windows.Markup.IComponentConnector" />
    public partial class ExitDialog : WpfDialog, IWpfDialog
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ExitDialog"/> class.
        /// </summary>
        public ExitDialog()
        {
            InitializeComponent();
        }

        /// <summary>
        /// This method is invoked by WixSHarp runtime when the custom dialog content is internally fully initialized.
        /// This is a convenient place to do further initialization activities (e.g. localization).
        /// </summary>
        public void Init()
        {
            UpdateTitles(ManagedFormHost.Runtime.Session);

            ViewModelBinder.Bind(new ExitDialogModel { Host = ManagedFormHost }, this, null);
        }

        /// <summary>
        /// Updates the titles of the dialog depending on the success of the installation action.
        /// </summary>
        /// <param name="session">The session.</param>
        public void UpdateTitles(ISession session)
        {
            if (Shell.UserInterrupted || Shell.Log.Contains("User canceled installation."))
            {
                DialogTitleLabel.Text = "[UserExitTitle]";
                DialogDescription.Text = "[UserExitDescription1]";
            }
            else if (Shell.ErrorDetected)
            {
                DialogTitleLabel.Text = "[FatalErrorTitle]";
                DialogDescription.Text = Shell.CustomErrorDescription ?? "[FatalErrorDescription1]";
            }

            // `Localize` resolves [...] titles and descriptions into the localized strings stored in MSI resources tables
            this.Localize();
        }
    }

    /// <summary>
    /// ViewModel for standard ExitDialog.
    /// <para>Follows the design of the canonical Caliburn.Micro ViewModel (MVVM).</para>
    /// <para>See https://caliburnmicro.com/documentation/cheat-sheet</para>
    /// </summary>
    /// <seealso cref="Caliburn.Micro.Screen" />
    internal class ExitDialogModel : Caliburn.Micro.Screen
    {
        public ManagedForm Host { get; set; }
        ISession session => Host?.Runtime.Session;
        IManagedUIShell shell => Host?.Shell;

        public BitmapImage Banner => session?.GetResourceBitmap("WixUI_Bmp_Dialog").ToImageSource();

        public void GoExit()
        {
            MessageBox.Show("Exisitng");
            session.Log("This is failing");

            shell.Exit();
        }

        public void Cancel()
            => shell?.Exit();

        public void ViewLog()
        {
            if (shell != null)
                try
                {
                    string logFile = session.LogFile;

                    if (logFile.IsEmpty())
                    {
                        string wixSharpDir = Path.GetTempPath().PathCombine("WixSharp");

                        if (!Directory.Exists(wixSharpDir))
                            Directory.CreateDirectory(wixSharpDir);

                        logFile = wixSharpDir.PathCombine(Host.Runtime.ProductName + ".log");
                        IO.File.WriteAllText(logFile, shell.Log);
                    }
                    Process.Start("notepad.exe", logFile);
                }
                catch
                {
                    // Catch all, we don't want the installer to crash in an
                    // attempt to view the log.
                }
        }
    }
}

Event viewer gives me this error : image

oleg-shilo commented 2 months ago

Hi @Aaswin1996

I have checked and the default WPF VS WixSharp template for WiX4 seems to work OK.

I suggest that you debug your UI to see what is causing the problem. Then I can guide you. You will find the debugging instructions on wiki.

. . . Unrelated, Your code snippets in the posts need to be enclosed to the pair of ``` . Otherwise it's very difficult to read them. See GitHub markdown info

Aaswin1996 commented 2 months ago

@oleg If I try and access session.Log inside the ExitDialog of default WPF Custom UI Project it throws the same error @oleg-shilo .I also added the failing sample here @oleg-shilo [Uploading WixSharp_Setup61.zip…]()

oleg-shilo commented 2 months ago

It looks like your sample did not finish uploading. It's not available yet.

But it's OK. I have a good guess about the cause of your problem.

You cannot access your session from the exit dialog. It's already terminated. This is one of the strange architectural decisions the MSI made a long time ago and we need to deal with it :o(

Basically, some of the properties/methods of the terminated session object trigger the exceptions and others do not. In this case you cannot call session.Log(string msg) at that point.

The best but ugly thing you can do is to overwrite the log file with updated content. Similar to the ViewLog() implementation.

static public void CustomLog(this Session session, string message)
{
    if (shell != null)
        try
        {
            string logFile = session.LogFile;

            if (logFile.IsEmpty())
            {
                string wixSharpDir = Path.GetTempPath().PathCombine("WixSharp");
                Directory.CreateDirectory(wixSharpDir);
                logFile = wixSharpDir.PathCombine(Host.Runtime.ProductName + ".log");
                IO.File.WriteAllText(logFile, shell.Log);
           }

           var logContent = System.IO.File.ReadAllText(logFile);
           logContent += "\r\n" + message;
           System.IO.File.ReadAllText(logFile, logContent);
       }
       catch
       {
       }
}
. . .

session.CustomLog("test message");
Aaswin1996 commented 2 months ago

@oleg-shilo I was able to work it out now at the exit dialog I have to add entries to the registry (HKLM if its per machine HKCU if its per user ) addition to HKCU works well but addition to HKLM fails as the elevated session is terminated at the exit dialog .Any Workarounds for this ?

oleg-shilo commented 2 months ago

Yeah. You need to perform your access to the registry from the elevated context. IE you can fork an external elevated process or write to the registry from the AfterInstall event, which is already elevated.

Aaswin1996 commented 2 months ago

I cannot use After Install event as the write to registry is dependent on the button I click in the Exit Dialog .Basically my exit dialog has two buttons : Button 1 : on click Add entry to HKLM Button 2 : on click Add entry to HKCU.

Is there a way I can elevate the whole process in one GO .By One Go I mean during the whole process UAC prompt appears only once at the beginning ? One way I can think of Restarting the UI as elevated in case its an admin install @oleg-shilo

oleg-shilo commented 2 months ago

Yes you can do it. Have a look at the SetupEvents sample.

image

The sample does way more than you need but it does show you the right technique. The Project_UIInitialized method restarts itself elevated if it is not already.

Even further simplified code (with process extension method) can be as simple as:

using WixSharp.CommonTasks;
. . .
project.UIInitialized += Project_UIInitialized;
. . .
static void Project_UIInitialized(SetupEventArgs e)
{
    // just an example of restarting the setup UI elevated. Old fashioned but... convenient and reliable.
    if (!WindowsIdentity.GetCurrent().IsAdmin())
    {
        e.Result = ActionResult.Failure;
        "msiexec.exe".StartElevated("/i \"" + e.MsiFile + "\"");
    }
}
Aaswin1996 commented 2 months ago

@oleg-shilo I used it to restart the Installation as an admin .Now I need to skip some WPF dialogs incase of restarts .Any Pointers on how that could be done ?

Aaswin1996 commented 2 months ago

@oleg-shilo I used it to restart the Installation as an admin .Now I need to skip some WPF dialogs incase of restarts .Any Pointers on how that could be done ?

I found out a way to do this using UI_INitialized Events .