.netvb.netvstoclickonceoffice-addins

Programatically updating ClickOnce deployment (VSTO) then results in ApplicationDeployment.IsNetworkDeployed = False


I have an Excel VSTO add-in, which updates every 24 hours via ClickOnce. This works fine.

I would like to provide a button where the user can manually check for an update, immediately. I followed the instructions provided in the documentation. My code looks like this: (ignore the commented section for the moment)

Sub TryUpdateApp()

    If (ApplicationDeployment.IsNetworkDeployed) Then

        Dim Deployment As ApplicationDeployment = ApplicationDeployment.CurrentDeployment
        Dim Info As UpdateCheckInfo = Nothing

        'Try
        '    Dim AppIdentity As New ApplicationIdentity(Deployment.UpdatedApplicationFullName)
        '    Dim UnrestrictedPerms As New Security.PermissionSet(Security.Permissions.PermissionState.Unrestricted)
        '    Dim AppTrust As New Security.Policy.ApplicationTrust(AppIdentity) With {
        '          .DefaultGrantSet = New Security.Policy.PolicyStatement(UnrestrictedPerms),
        '          .IsApplicationTrustedToRun = True,
        '          .Persist = True
        '          }
        '    Security.Policy.ApplicationSecurityManager.UserApplicationTrusts.Add(AppTrust)
        'Catch ex As Exception
        '    'log error
        'End Try

        Try
            Info = Deployment.CheckForDetailedUpdate()
        Catch dde As DeploymentDownloadException
            MsgBox($"The new version of App cannot be downloaded at this time.{vbNewLine}Please check your network connection, or try again later. Error: {dde.Message}", vbExclamation Or vbOKOnly)
            Exit Sub
        Catch ioe As InvalidOperationException
            MsgBox($"This application cannot be updated. It is likely not a ClickOnce application. Error: {ioe.Message}", vbCritical Or vbOKOnly)
            Exit Sub
        End Try

        Try

            If (Info.UpdateAvailable) Then
                Try
                    Deployment.Update()
                    MsgBox("App has been upgraded. Please restart Excel to apply changes.", vbInformation Or vbOKOnly)
                Catch dde As DeploymentDownloadException
                    MsgBox($"Unable to install the latest version of App: download failed.{vbNewLine}Please check your network connection, or try again later.", vbCritical Or vbOKOnly)
                    Exit Sub
                Catch tnge As TrustNotGrantedException
                    MsgBox("Unable to install the latest version of App: trust not granted.", vbExclamation Or vbOKOnly)
                    Exit Sub
                End Try
            Else
                MsgBox("The latest version of App is already installed.", vbInformation Or vbOKOnly)
            End If

        Catch ex As Exception

            MsgBox("Unable to install the latest version of App: unknown error.")
            Exit Sub

        End Try

    Else

        Throw New ApplicationException("Application is not network deployed.")

    End If

End Sub

Whilst it would accurately indicate when "The latest version of App is already installed.", it would fail to update if necessary, throwing a TrustNotGrantedException: User has refused to grant required permissions to the application..

The first funny thing is that this exception is captured by "Unable to install the latest version of App: unknown error.", not "Unable to install the latest version of App: trust not granted.", as one would expect.

I thend found this thread, which corresponds to the commented part of the code above. When I uncomment and run the sub, it seems to work as I get "App has been upgraded. Please restart Excel to apply changes". However, when I then restart Excel and run the Sub once again, I get "Application is not network deployed".

How could I resolve this? (any C# code is fine)


Solution

  • After scratching my head for a while, I manage to find out why it was behaving that way and how to properly start an update manually.

    The answer lies in this other SO answer, which links a MSDN forum post which itself links to an old Microsoft blog post.

    I'll summarize the findings here, in case the links disappear.

    The main thing to understand is that, while we can deploy VSTO as a ClickOnce app, it is actually NOT a fully compliant ClickOnce app since it is not an application, but an addin to an Office application! But it's not explicitly said anywhere in the docs! VSTO ClickOnce uses only a specific core part of ClickOnce, but not all!

    I think this is the one thing that is bringing ton of confusion to everyone and explain why it's not simple to find an answer online. We are looking at how performing an update with ClickOnce and implementing it exactly as everyone is doing, but it's not working! Even though we clearly took the exact same code as in the docs or the myriad blog posts about it.

    This is simply a case of mislabelling and reminds me how difficult it is to actually name things. If only Microsoft had named it "ClickOnceForOffice" or something, we may not have this issue.

    Anyway.

    The gist to get it working is twofold:

    Here is the helper class I am using:

    internal static class VstoHelper
    {
        // WARNING : VstoInstaller path actually depends on 
        // Office bitness : use "Program Files (x86)" if 32 bit Office
        // Office version : use 10.0 for Office 2019 (not tested for other version)
        private static string VstoInstallerPath = @"C:\Program Files\Common Files\Microsoft Shared\VSTO\10.0\VSTOInstaller.exe";
    
        public static bool ApplyUpdate(bool applySilently = false)
        {
            if (!ApplicationDeployment.IsNetworkDeployed)
            {
                throw new InvalidOperationException();
            }
    
            using (var process = new Process())
            {
                process.StartInfo = new ProcessStartInfo()
                {
                    FileName = VstoInstallerPath,
                    Arguments = $"{(applySilently ? "/S" : "")} /I \"{ApplicationDeployment.CurrentDeployment.UpdateLocation}\"",
                    UseShellExecute = false,
                    RedirectStandardOutput = false,
                    RedirectStandardError = false,
                };
    
                process.Start();
                process.WaitForExit();
    
                return process.ExitCode == 0;
            }
        }
    
        public static UpdateCheckInfo CheckForUpdate()
        {
            if (!ApplicationDeployment.IsNetworkDeployed)
            {
                return null;
            }
    
            var deployment = ApplicationDeployment.CurrentDeployment;
            var appId = new ApplicationIdentity(deployment.UpdatedApplicationFullName);
            var unrestrictedPerms = new PermissionSet(PermissionState.Unrestricted);
            var appTrust = new ApplicationTrust(appId)
            {
                DefaultGrantSet = new PolicyStatement(unrestrictedPerms),
                IsApplicationTrustedToRun = true,
                Persist = true
            };
            ApplicationSecurityManager.UserApplicationTrusts.Add(appTrust);
    
            var info = deployment.CheckForDetailedUpdate();
            return info;
        }
    }
    

    I hope this will help someone in the future :-) Good luck!