clickonce upgrade failure scenario

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

clickonce upgrade failure scenario
windows shutdown fails to complete update/upgrade 
aweber




Posts: 83
Joined: 2013-11-21
We're using ADX 8.1.4350.0 and testing in multiple Office versions (currently 2010 and 2013).

We have found that if you start a clickonce update (using AddinModule.CheckForUpdates) and let it run the update to the point where the message box appears indicating that the application must be restarted for the new version to take effect, then you do a Windows Shutdown/Restart, the update fails to complete.

This has happened in testing and "accidentally" by a user, because they did not notice the completion message box and just shutdown their PC.

What functionally happens is that on restart the previous/old version is still installed. Worse, any further CheckForUpdates does not find the new version (or complete the downloaded version's install). So the user is stuck not able to upgrade without a full uninstall/re-install until a future version is published.

So the installation is not completing if Windows is shutting down the application(s), and there is no identifiable way to recover from that scenario.

Would you please confirm this bug and provide a fix in the next version?

Thanks,
AJ
Posted 23 Sep, 2016 10:00:57 Top
Andrei Smolin


Add-in Express team


Posts: 18829
Joined: 2006-05-11
Hello AJ,

There's no universal solution. You can notify the user that there's a program running (see the code below) but the user may ignore the notification.

       [DllImport("user32.dll", SetLastError = true)]
        public extern static bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string pwszReason);

        [DllImport("user32.dll", SetLastError = true)]
        public extern static bool ShutdownBlockReasonDestroy(IntPtr hWnd);

        private void StopShutdown(string message)
        {
            if (!this.shutdownBlocked)
            {
                try
                {
                    if (!String.IsNullOrEmpty(message) && Environment.Version.Major >= 6)
                    {
                        // string lenght max = MAX_STR_BLOCKREASON = 256
                        if (message.Length > 255)
                            message = message.Substring(0, 252) + "...";

                        if (!Win32.ShutdownBlockReasonCreate(MainWindowHandle, message))
                            throw new Win32Exception(Win32.GetLastError(), "ShutdownBlockReasonCreate failed.");
                    }

                    this.shutdownBlocked = true;
                }
                catch (Exception err)
                {
                    HandleException(err);
                }
            }
        }

        private void ResetShutdown()
        {
            if (this.shutdownBlocked)
            {
                try
                {
                    if (Environment.Version.Major >= 6)
                    {
                        if (!Win32.ShutdownBlockReasonDestroy(MainWindowHandle))
                            throw new Win32Exception(Win32.GetLastError(), "ShutdownBlockReasonDestroy failed.");
                    }
                }
                catch (Exception err)
                {
                    HandleException(err);
                }
                finally
                {
                    this.shutdownBlocked = false;
                }
            }
        }



Andrei Smolin
Add-in Express Team Leader
Posted 26 Sep, 2016 06:08:55 Top
aweber




Posts: 83
Joined: 2013-11-21
Thank you for the good advice.

However, I'm not sure what your example is illustrating with checking Environment.Version.Major? The System.Environment.Version property is for checking the .Net version. Since there is no .Net v6 yet, this won't ever really do anything. You aren't showing your using statements, so I'm not sure what Environment class you're testing against. Can you please clarify?

Thanks again,
AJ

EDIT: I think you were going for OSVersion, so I made my conditional:
if (Environment.OSVersion.Version.Major >= 6)

I think that's what you may have been after.
Posted 26 Sep, 2016 13:18:38 Top
Andrei Smolin


Add-in Express team


Posts: 18829
Joined: 2006-05-11
aweber writes:
I think you were going for OSVersion


Exactly. The code above won't work in Windows XP.


Andrei Smolin
Add-in Express Team Leader
Posted 27 Sep, 2016 08:18:35 Top
aweber




Posts: 83
Joined: 2013-11-21
Andrei,

I am testing this code and the problem is now that the ADXAddinModule.CheckForUpdates() apparently runs asynchronously - in its own thread.

How can I "wrap" the CheckForUpdates() (or the actual update itself) in my "blocking" code? It appears to spawn a thread that I am unaware of, and CheckForUpdates() returns void.

Thanks,
AJ
Posted 29 Sep, 2016 16:30:59 Top
Andrei Smolin


Add-in Express team


Posts: 18829
Joined: 2006-05-11
Hello AJ,

aweber writes:
How can I "wrap" the CheckForUpdates() (or the actual update itself) in my "blocking" code?


You can't do this with ClickOnce. Calling CheckForUpdates initiates the mechanism which is completely outside of your control. I suggest that you use ClickTwice instead.


Andrei Smolin
Add-in Express Team Leader
Posted 30 Sep, 2016 06:06:54 Top
aweber




Posts: 83
Joined: 2013-11-21
I think that means we're back to my original post...

The "ClickOnce" is not MSFT's, it is Add-In Express's version of a ClickOnce paradigm. So the code is under your team's control.

There is an issue where the ClickOnce update will not complete properly and leaves the user's PC in a state where it can not update. Your suggestion to try and force Windows to block shutdown appears valid, but only if we can block the shutdown for the duration of the CheckForUpdates() functionality.

Can the team extend the ClickOnce functionality such that there is a synchronous call (maybe add a "CheckForUpdatesSync()" so legacy code is not impacted)? Or can they return the background thread they started (instead of void) so we can optionally wait on that thread (this, also should not break existing code)?

Or just log the bug with the development team and let them decide how to ensure that the installation/upgrade is left in a consistent state in the case of a user accidentally shutting down the OS during an upgrade?

Thanks again,
AJ
Posted 30 Sep, 2016 07:04:59 Top
Andrei Smolin


Add-in Express team


Posts: 18829
Joined: 2006-05-11
Hello AJ,

aweber writes:
The "ClickOnce" is not MSFT's, it is Add-In Express's version of a ClickOnce paradigm. So the code is under your team's control.


A big part of the ClickOnce functionality is implemented in a Windows service. When you call CheckForUpdates, this ends with a series of actions that you can't control.

aweber writes:
Your suggestion to try and force Windows to block shutdown appears valid, but only if we can block the shutdown for the duration of the CheckForUpdates() functionality.


Note that the next Add-in Express build will include a code analogous to the code fragment in my previous post. But you should understand that you can't block shutdown. You can show a message asking the user to wait for your program to complete. Still, the user may ignore the message.

aweber writes:
Can the team extend the ClickOnce functionality


No, we can't. ClickOnce is a product by Microsoft. Add-in Express develops sort of wrapper that allows a COM add-in developer use the deployment scenarios that ClickOnce supports.

In as much as I wanted to help you, I don't even see a direction to move to.


Andrei Smolin
Add-in Express Team Leader
Posted 03 Oct, 2016 05:47:47 Top