Search code examples
c#visual-studiovisual-studio-extensionsvspackagevisual-studio-package

Automatically update Visual Studio Extension


I'm trying to make my extension automatically update itself when new versions are pushed to the Visual Studio Gallery. There are a few guides on how one may achieve this, but they are a couple years old and may not apply.

For starters, I'm trying to query the IVsExtensionRepository as follows:

var _extensionRepository = (IVsExtensionRepository)Microsoft.VisualStudio.Shell.Package.GetGlobalService(typeof(SVsExtensionRepository));

var query = _extensionRepository.CreateQuery<VSGalleryEntry>(false, true)
                .OrderByDescending(n => n.Ranking)
                .Skip(0)
                .Take(25) as IVsExtensionRepositoryQuery<VSGalleryEntry>;

query.ExecuteCompleted += Query_ExecuteCompleted;
query.ExecuteAsync();

At Query_ExecuteCompleted I'm receiving an exception from the server: "The remote server returned an error: (400) Bad Request."

A stack trace is provided:

Server stack trace: at System.Runtime.AsyncResult.End[TAsyncResult](IAsyncResult result) at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.End(SendAsyncResult result) at System.ServiceModel.Channels.ServiceChannel.EndCall(String action, Object[] outs, IAsyncResult result) at System.ServiceModel.Channels.ServiceChannelProxy.InvokeEndService(IMethodCallMessage methodCall, ProxyOperationRuntime operation) at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)

The service is hosted at: https://visualstudiogallery.msdn.microsoft.com/services/dev12/extension.svc

Does anyone know how I can create a Visual Studio extension that automatically updates itself from the Visual Studio Gallery? Either through the IVsExtensionRepository or manually?


Solution

  • Edit: Now in Visual Studio 2015 extensions are downloaded automatically.

    So I've completely abandoned querying the IVsExtensionRepository. I'm not sure why, but there must be some internal problem with the queries it constructs. I queried the same service using ErikEJ's suggested project, and it worked fine.

    However, I didn't want to construct the service from the WSDL as it appears SQLCeToolbox has done. Instead I used the IVsExtensionRepository, but avoided the CreateQuery() method.

    Attached is my approach to updating my VSPackage. You'll need to replace any GUIDs or Package specific names with your package's information.

    NOTE There is one Gotcha' in the following code:

    Note that CodeConnectRepositoryEntry only implements DownloadUrl. When updating the VSPackage, this is all one must worry about as it allows us to download the new package. This URL can be found on the VSGallery page for your VSPackage.

    However: You must trim the URL as follows:

    http://visualstudiogallery.msdn.microsoft.com/c0c2ad47-957c-4e07-89fc-20996595b6dd/file/140793/4/CodeConnectAlpha.vsix

    to:

    http://visualstudiogallery.msdn.microsoft.com/c0c2ad47-957c-4e07-89fc-20996595b6dd/file/140793/

    Above, the /4/ represents the fourth upload. By removing it completely, the Visual Studio Gallery will download the latest version.

    internal class CodeConnectUpdater
    {
        IVsExtensionManager _extensionManager;
    
        IVsExtensionRepository _extensionRepository;
    
        //We need only supply the download URL.
        //This can be retrieved from the "Download" button on your extension's page.
        private class CodeConnectRepositoryEntry : IRepositoryEntry
        {
            public string DownloadUpdateUrl
            {
                get; set;
            }
    
            public string DownloadUrl
            {
                get
                {
                    //NOTE: YOU MUST TRIM THE DOWNLOAD URL
                    //TO NOT CONTAIN A VERSION. THIS FORCES 
                    //THE GALLERY TO DOWNLOAD THE LATEST VERSION
                    return "http://visualstudiogallery.msdn.microsoft.com/c0c2ad47-957c-4e07-89fc-20996595b6dd/file/140793/";
                }
                set
                {
                    throw new NotImplementedException("Don't overwrite this.");
                }
            }
    
            public string VsixReferences
            {
                get; set;
            }
        }
    
        //I have been calling this from the VSPackage's Initilize, passing in the component model
        public bool CheckForUpdates(IComponentModel componentModel)
        {
            _extensionRepository = (IVsExtensionRepository)Microsoft.VisualStudio.Shell.Package.GetGlobalService(typeof(SVsExtensionRepository));
            _extensionManager = (IVsExtensionManager)Microsoft.VisualStudio.Shell.Package.GetGlobalService(typeof(SVsExtensionManager));
            //Find the extension you're after.
            var extension = _extensionManager.GetInstalledExtensions().Where(n => n.Header.Name == "Code Connect Alpha").SingleOrDefault();
    
            return CheckAndInstallNewVersion(extension);
        }
    
        private bool CheckAndInstallNewVersion(IInstalledExtension myExtension)
        {
            var needsRestart = false;
            var entry = new CodeConnectRepositoryEntry();
            var newVersion = FetchIfUpdated(myExtension, entry);
            if (newVersion != null)
            {
                Install(myExtension, newVersion);
                needsRestart = true;
            }
    
            return needsRestart;
        }
    
        //Checks the version of the extension on the VS Gallery and downloads it if necessary.
        private IInstallableExtension FetchIfUpdated(IInstalledExtension extension, CodeConnectRepositoryEntry entry)
        {
            var version = extension.Header.Version;
            var strNewVersion = _extensionRepository.GetCurrentExtensionVersions("ExtensionManagerQuery", new List<string>() { "6767f237-b6e4-4d95-9982-c9e898f72502" }, 1033).Single();
            var newVersion = Version.Parse(strNewVersion);
    
            if (newVersion > version)
            {
                var newestVersion = _extensionRepository.Download(entry);
                return newestVersion;
            }
    
            return null;
        }
    
        private RestartReason Install(IInstalledExtension currentExtension, IInstallableExtension updatedExtension)
        {
            //Uninstall old extension
            _extensionManager.Disable(currentExtension);
            _extensionManager.Uninstall(currentExtension);
    
            //Install new version
            var restartReason = _extensionManager.Install(updatedExtension, false);
    
            //Enable the newly installed version of the extension
            var newlyInstalledVersion = _extensionManager.GetInstalledExtension(updatedExtension.Header.Identifier);
            if (newlyInstalledVersion != null)
            {
                _extensionManager.Enable(newlyInstalledVersion);
            }
    
            return restartReason;
        }
    }