ClickTwice, updater, silent mode

Add-in Express™ Support Service
That's what is more important than anything else

ClickTwice, updater, silent mode
ClickTwice, updater, silent mode 
Ionut Adrian Moisii


Guest


Hello,

I have a few questions regarding ClickTwice based setups and the updater program. I created a ClickTwice setup for a PowerPoint COM add-in and want to use automatic, silent updates, because I want the users to always have the latest version, without the need for them to confirm anything. I also added a custom actions dll, based on your http://www.add-in-express.com/files/projects_pub/web_authoring_example.zip project. Here are my configuration screens for updates and updater.

User added an image

User added an image

My use case:
a. Usually all actions will be under a non-admin Windows user account
b. The first install will of course require an admin password, which is fine
c. The updater will run under a non-admin Windows user account
d. I want to use only silent updates (the only ones that don't require admin password)
e. I want to get some info about the installed update version (new features etc.) and present that to the user
f. I want to check for new versions during the first install of the add-in

Questions:
1. From my tests, "Start updater when I start Windows" check doesn't have any influence when "Allow silent updates" is checked. It looks like the updater always starts when Windows starts. Is this correct? If my tests were wrong, how can I start the updater if "Start updater when I start Windows" is not checked?
2. Seems that all the updater preferences are ignored when "Allow silent updates" is checked. Which makes sense, choosing silent updates inhibits all updater notifications, but maybe it would be better to have this written somewhere, or disable the updater preferences screen when "Allow silent updates" is checked.
3. Is there any way to install only the msi (skip prerequisites, so a non-admin user can install) in a non silent mode, with user notifications? I tried to use the
SetupCustomUIClickTwiceModule_OnUpdaterInitialize
method in my custom actions dll and set the SilentMode parameter to false, but that doesn't seem to change the updater behavior (no notifications are shown, even if they are set in the updater configuration screen). This is probably related to question #2.
4. Regarding use case #e, my idea is:
- always log current add-in version in a file when PP close
- use
SetupCustomUIClickTwiceModule_OnUpdaterAfterCheckForUpdates
to get a new features xml file from server
- when PP opens, compare the installed add-in version (which was updated silently by the updater when PP was closed) with the last recorded version and, if different, show the new features xml to the user
Do you think there's any other easier way to do this?
5. Regarding use case #f, is this possible? The scenario I encountered is:
- the user downloads an older version of the setup (let's say v1.0.3). The newest available version on server is v1.0.9
- the user installs v1.0.3 (which has its updater set on silent) and tests the add-in. The updater will update to v1.0.9 only after PP is closed. The user is missing important features, because he has an old version.
- to fix this, I would like to check for updates when the add-in is installed and install the newest available version directly
Is this possible to achieve? Probably using the
SetupCustomUIClickTwiceModule_OnBeforeInstall
of the custom action dll, but can't figure out how.
6. Last question - I promise! :) This is related to question #3, changing the updater behavior on-the-fly:
- the user installs v1.0.1. Its updater is set on silent ("Allow silent updates" is checked).
- I then publish v1.0.2, with its updater set on non silent ("Allow silent updates" is not checked)
- the updater installs v1.0.2 in silent mode. After that, the running updater is in non-silent mode.
- I then publish v1.0.3 (its updater settings are not important)
- the updater detects v1.0.3. Because the updater is running in non silent mode, the user is notified through the tray icon that a new version is available and an admin password is required for installation
I tested the above scenario and it works fine. But my desired use case here is:
- the release versions considered patches should be installed silently
- for major versions I want the user to be notified and allow him to skip the update.
But what happens if, after users install v1.0.1 (its updater is set on silent), I decide to publish v1.2 (my code was great and no patches were needed - yeay!). This version will be installed silently, because the installation mode is based on the previous version updater settings. So it is possible to change the installation mode of the newly detected version on the fly?

I know it's a lengthy message, but I feel like there are a lot of possibilities offered by ClickTwice which are not explored and detailed in the documentation. The Web Authoring Example project is great, but it covers only the downloader part.
Thank you very much for your time!

Adrian
Posted 22 Jun, 2021 04:12:50 Top
Andrei Smolin


Add-in Express team


Posts: 18791
Joined: 2006-05-11
Hello Adrian,

Thank you very much! We are impressed! Very interesting use cases. We'll react to them in subsequent versions.

The main point is: there's no possibility to combine custom setup and silent setup in the current version. To achieve this, we'll have to modify code. We'd suggest that you don't use silent updates and build your setup project around Web Authoring Example.

On your other questions.

> 1. From my tests, "Start updater when I start Windows" check doesn't have any influence when "Allow silent updates" is checked. It looks like the updater always starts when Windows starts. Is this correct? If my tests were wrong, how can I start the updater if "Start updater when I start Windows" is not checked?

A per-machine installation along with 'Allow silent updates' requires installing a Windows service. The service starts when Windows starts; it starts the updater.

> 2. Seems that all the updater preferences are ignored when "Allow silent updates" is checked. Which makes sense, choosing silent updates inhibits all updater notifications, but maybe it would be better to have this written somewhere, or disable the updater preferences screen when "Allow silent updates" is checked.

You can use custom actions to write to any location. We will consider updating the Preference dialog and/or the manual to reflect this moment. Thank you.

> 3. Is there any way to install only the msi (skip prerequisites, so a non-admin user can install) in a non silent mode, with user notifications?

You should choose whether you use silent updates or you use a fully customized setup where you control the MSI UI and show messages to the user.

> 4. Regarding use case #e, my idea is

This is possible. But ClickTwice doesn't have anything specific for this. You need to use the events that ClickTwice provides.

> 5. Regarding use case #f, is this possible? The scenario I encountered is

It isn't possible to install a new version while you uninstall the previous one. This is because the downloader and the MSI are interconnected using the manifest(s) and internal resources of the downloader. You can try to achieve this using a redirect on the web сервере.


Andrei Smolin
Add-in Express Team Leader
Posted 25 Jun, 2021 01:55:30 Top
Ionut Adrian


Guest


Hi Andrei,

Thank you for your answer and appreciations!

Your answer regarding use case "f. I want to check for new versions during the first install of the add-in":
"It isn't possible to install a new version while you uninstall the previous one. This is because the downloader and the MSI are interconnected using the manifest(s) and internal resources of the downloader."

intrigued me. What I wanted is to check for a higher available version at the first install of the add-in and install that higher version instead (although I think this solution would work for any subsequent installation). So if the user runs installer v1.0.1 and v1.0.3 is available on the server, download and install v1.0.3 when v1.0.1 installation starts. After this first install, the updater takes care of the future updates (I'm using automatic, silent updates).
In the end I managed to do this by using the below code in the
OnBeforeInstall
event of theClickTwice module of the custom actions dll. I'm putting the code here, maybe someone gets some inspiration...
My idea was to use the event and do these:
- download the version_info.xml and check if a higher version is available
- if yes, show a message to the user and download that new version (of course, this can be customized, ask user if he wants the newer version etc.)
- launch the downloaded file
- cancel current installation

The
KillUpdaterProcess
and
UninstallUpdaterService
are not really needed in my use case, but I've added them just in case the user starts the installation again, from a setup file, after the add-in was already installed. In this situation, we need to uninstall the updater service and kill the updater process of the installed version (they are already running), because the installer might find another version available and try to install that version updater service and this will fail if an updater service is already running.
I tested this and it works fine, I hope I didn't miss anything.

I would love your input on this approach! Are there any situations when this might not work?

Thank you,
Adrian



using System;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Reflection;
using System.Windows.Forms;
using AddinExpress.Deployment;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Xml.Linq;
using System.Linq;
using System.ServiceProcess;
using System.Configuration.Install;

namespace SetupCustomUI
{
    /// <summary>
    /// Add-in Express ClickTwice deployment module.
    /// </summary>
    [ComVisible(true)]
    public partial class SetupCustomUIClickTwiceModule : AddinExpress.Deployment.ADXClickTwiceModule
    {
        public string InstallationUrl = string.Empty;
        public int LanguageID = 0;
        public string CurrentVersion = string.Empty;

        public SetupCustomUIClickTwiceModule()
        {
            InitializeComponent();
        }
 
        public SetupCustomUIClickTwiceModule(IntPtr parentWindowHandle)
            : base(parentWindowHandle)
        {
            InitializeComponent();
        }

        private void SetupCustomUIClickTwiceModule_OnInitialize(object sender, ADXClickTwiceInitializeEventArgs e)
        {
            this.InstallationUrl = e.InstallationUrl;
            this.LanguageID = e.LanguageID;
            this.CurrentVersion = e.Version;
        }

        private void SetupCustomUIClickTwiceModule_OnBeforeInstall(object sender, ADXClickTwiceBeforeInstallEventArgs e)
        {
            try
            {
                VersionInfoData versionInfoData = GetHighestVersionInfo(this.InstallationUrl);
                var highestVersion = new Version(versionInfoData.HighestVersion);
                var currentVersion = new Version(this.CurrentVersion);

                if (highestVersion > currentVersion)
                {
                    KillUpdaterProcess(versionInfoData.SetupFileName + ".updater");
                    UninstallUpdaterService(versionInfoData.SetupFileName);

                    var response = MessageBox.Show(string.Format("This is version {0}.{1}Version {2} is available and will be installed instead.",
                        this.CurrentVersion, Environment.NewLine, versionInfoData.HighestVersion), "New version available", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    string newSetupFile = DownloadSetupFile(versionInfoData);
                    if (!string.IsNullOrEmpty(newSetupFile))
                    {
                        Process.Start(newSetupFile);
                        e.Cancel = true;
                    }
                    else
                    {
                        MessageBox.Show("New version download failed!", "New version available", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private string DownloadSetupFile(VersionInfoData versionInfoData)
        {
            Uri uri = new Uri(string.Format("{0}/{1}/{2}/{3}", this.InstallationUrl, this.LanguageID, versionInfoData.HighestVersion, versionInfoData.SetupExeFileName));
            string filename = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), string.Format("Temp/new_{0}", versionInfoData.SetupExeFileName));

            try
            {
                if (File.Exists(filename))
                {
                    File.Delete(filename);
                }

                WebClient wc = new WebClient();
                wc.DownloadFile(uri, filename);
                return filename;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                return string.Empty;
            }
        }

        private VersionInfoData GetHighestVersionInfo(string url)
        {
            XDocument doc = XDocument.Load(string.Format("{0}version_info.xml", url));
            string highestVersionValue = doc.Elements("application").Elements("product").Elements("version").Max(x => x.Attribute("name").Value);
            //XElement highestVersion = doc.Elements("application").Elements("product").Elements("version").FirstOrDefault(x => x.Attribute("name").Value == highestVersionValue);
            VersionInfoData versionInfoData = new VersionInfoData
            {
                SetupFileName = doc.Elements("application").FirstOrDefault().Attribute("name").Value,
                HighestVersion = highestVersionValue
            };
            return versionInfoData;
        }

        private void KillUpdaterProcess(string processName)
        {
            Process.GetProcesses()
                        .Where(x => x.ProcessName.StartsWith(processName, StringComparison.OrdinalIgnoreCase))
                        .ToList()
                        .ForEach(x => x.Kill());
        }

        private void UninstallUpdaterService(string serviceName)
        {
            ServiceController service = GetService(serviceName);
            if (service == null)
                return;
            var s = new ServiceInstaller
            {
                Context = new InstallContext(),
                ServiceName = service.ServiceName
            };
            s.Uninstall(null);
        }

        private ServiceController GetService(string serviceName)
        {
            // get list of Windows services
            ServiceController[] services = ServiceController.GetServices();

            // try to find service name
            foreach (ServiceController service in services)
            {
                if (service.ServiceName.ToLower().StartsWith(serviceName))
                    return service;
            }
            return null;
        }
    }

    class VersionInfoData
    {
        public string SetupFileName { get; set; }
        public string SetupExeFileName
        {
            get
            {
                return string.Format("{0}.exe", SetupFileName);
            }
        }
    public string HighestVersion { get; set; }
    }
}
Posted 29 Jun, 2021 08:26:30 Top
Andrei Smolin


Add-in Express team


Posts: 18791
Joined: 2006-05-11
Hello Adrian,

This scenario isn't supported. We haven't tested it and so we can't tell when this approach won't work.


Andrei Smolin
Add-in Express Team Leader
Posted 01 Jul, 2021 04:52:40 Top
Ionut Adrian


Guest


Hi Andrei,

OK, fair enough! :)
I know it's a hack and I understand that I (or whoever wants to try it) should use this code on my own responsibility.

Thank you again!
Posted 01 Jul, 2021 05:34:37 Top
Andrei Smolin


Add-in Express team


Posts: 18791
Joined: 2006-05-11
You are welcome!


Andrei Smolin
Add-in Express Team Leader
Posted 02 Jul, 2021 09:24:47 Top
Ionut Adrian


Guest


Hi Andrei,

I can confirm that my hack approach doesn't work in all tests (i.e. is not working on Win7 x86 with PP2013 x86), so your answer is the correct one when using silent updates.

"You should choose whether you use silent updates or you use a fully customized setup where you control the MSI UI and show messages to the user."


Adrian
Posted 06 Jul, 2021 06:09:13 Top
Andrei Smolin


Add-in Express team


Posts: 18791
Joined: 2006-05-11
Alas!


Andrei Smolin
Add-in Express Team Leader
Posted 06 Jul, 2021 07:43:54 Top