├── .gitattributes ├── .gitignore ├── ExtensionUpdater.sln ├── README.md ├── appveyor.yml ├── artifacts └── screenshot.png └── src ├── Commands.cs ├── ExtensionUpdater.csproj ├── ExtensionUpdater.vsct ├── ExtensionUpdaterPackage.cs ├── Guids.cs ├── Properties └── AssemblyInfo.cs ├── Resources ├── License.txt ├── Package.ico ├── logo.png └── preview.png ├── Settings.cs ├── Updater ├── GalleryEntry.cs ├── PreEnabledExtensions.cs ├── UpdateChecker.cs └── Updater.cs ├── VSPackage.resx └── source.extension.vsixmanifest /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # Build results 11 | [Dd]ebug/ 12 | [Dd]ebugPublic/ 13 | [Rr]elease/ 14 | [Rr]eleases/ 15 | x64/ 16 | x86/ 17 | build/ 18 | bld/ 19 | [Bb]in/ 20 | [Oo]bj/ 21 | 22 | # Roslyn cache directories 23 | *.ide/ 24 | 25 | # MSTest test Results 26 | [Tt]est[Rr]esult*/ 27 | [Bb]uild[Ll]og.* 28 | 29 | #NUNIT 30 | *.VisualState.xml 31 | TestResult.xml 32 | 33 | # Build Results of an ATL Project 34 | [Dd]ebugPS/ 35 | [Rr]eleasePS/ 36 | dlldata.c 37 | 38 | *_i.c 39 | *_p.c 40 | *_i.h 41 | *.ilk 42 | *.meta 43 | *.obj 44 | *.pch 45 | *.pdb 46 | *.pgc 47 | *.pgd 48 | *.rsp 49 | *.sbr 50 | *.tlb 51 | *.tli 52 | *.tlh 53 | *.tmp 54 | *.tmp_proj 55 | *.log 56 | *.vspscc 57 | *.vssscc 58 | .builds 59 | *.pidb 60 | *.svclog 61 | *.scc 62 | 63 | # Chutzpah Test files 64 | _Chutzpah* 65 | 66 | # Visual C++ cache files 67 | ipch/ 68 | *.aps 69 | *.ncb 70 | *.opensdf 71 | *.sdf 72 | *.cachefile 73 | 74 | # Visual Studio profiler 75 | *.psess 76 | *.vsp 77 | *.vspx 78 | 79 | # TFS 2012 Local Workspace 80 | $tf/ 81 | 82 | # Guidance Automation Toolkit 83 | *.gpState 84 | 85 | # ReSharper is a .NET coding add-in 86 | _ReSharper*/ 87 | *.[Rr]e[Ss]harper 88 | *.DotSettings.user 89 | 90 | # JustCode is a .NET coding addin-in 91 | .JustCode 92 | 93 | # TeamCity is a build add-in 94 | _TeamCity* 95 | 96 | # DotCover is a Code Coverage Tool 97 | *.dotCover 98 | 99 | # NCrunch 100 | _NCrunch_* 101 | .*crunch*.local.xml 102 | 103 | # MightyMoose 104 | *.mm.* 105 | AutoTest.Net/ 106 | 107 | # Web workbench (sass) 108 | .sass-cache/ 109 | 110 | # Installshield output folder 111 | [Ee]xpress/ 112 | 113 | # DocProject is a documentation generator add-in 114 | DocProject/buildhelp/ 115 | DocProject/Help/*.HxT 116 | DocProject/Help/*.HxC 117 | DocProject/Help/*.hhc 118 | DocProject/Help/*.hhk 119 | DocProject/Help/*.hhp 120 | DocProject/Help/Html2 121 | DocProject/Help/html 122 | 123 | # Click-Once directory 124 | publish/ 125 | 126 | # Publish Web Output 127 | *.[Pp]ublish.xml 128 | *.azurePubxml 129 | # TODO: Comment the next line if you want to checkin your web deploy settings 130 | # but database connection strings (with potential passwords) will be unencrypted 131 | *.pubxml 132 | *.publishproj 133 | 134 | # NuGet Packages 135 | *.nupkg 136 | # The packages folder can be ignored because of Package Restore 137 | **/packages/* 138 | # except build/, which is used as an MSBuild target. 139 | !**/packages/build/ 140 | # If using the old MSBuild-Integrated Package Restore, uncomment this: 141 | #!**/packages/repositories.config 142 | 143 | # Windows Azure Build Output 144 | csx/ 145 | *.build.csdef 146 | 147 | # Windows Store app package directory 148 | AppPackages/ 149 | 150 | # Others 151 | sql/ 152 | *.Cache 153 | ClientBin/ 154 | [Ss]tyle[Cc]op.* 155 | ~$* 156 | *~ 157 | *.dbmdl 158 | *.dbproj.schemaview 159 | *.pfx 160 | *.publishsettings 161 | node_modules/ 162 | 163 | # RIA/Silverlight projects 164 | Generated_Code/ 165 | 166 | # Backup & report files from converting an old project file 167 | # to a newer Visual Studio version. Backup files are not needed, 168 | # because we have git ;-) 169 | _UpgradeReport_Files/ 170 | Backup*/ 171 | UpgradeLog*.XML 172 | UpgradeLog*.htm 173 | 174 | # SQL Server files 175 | *.mdf 176 | *.ldf 177 | 178 | # Business Intelligence projects 179 | *.rdl.data 180 | *.bim.layout 181 | *.bim_*.settings 182 | 183 | # Microsoft Fakes 184 | FakesAssemblies/ 185 | 186 | # ========================= 187 | # Operating System Files 188 | # ========================= 189 | 190 | # OSX 191 | # ========================= 192 | 193 | .DS_Store 194 | .AppleDouble 195 | .LSOverride 196 | 197 | # Thumbnails 198 | ._* 199 | 200 | # Files that might appear on external disk 201 | .Spotlight-V100 202 | .Trashes 203 | 204 | # Directories potentially created on remote AFP share 205 | .AppleDB 206 | .AppleDesktop 207 | Network Trash Folder 208 | Temporary Items 209 | .apdisk 210 | 211 | # Windows 212 | # ========================= 213 | 214 | # Windows image file caches 215 | Thumbs.db 216 | ehthumbs.db 217 | 218 | # Folder config file 219 | Desktop.ini 220 | 221 | # Recycle Bin used on file shares 222 | $RECYCLE.BIN/ 223 | 224 | # Windows Installer files 225 | *.cab 226 | *.msi 227 | *.msm 228 | *.msp 229 | 230 | # Windows shortcuts 231 | *.lnk 232 | -------------------------------------------------------------------------------- /ExtensionUpdater.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExtensionUpdater", "src\ExtensionUpdater.csproj", "{545865B3-DB94-4CB4-B5F1-7A22092B334F}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{04473293-3BC3-42FA-90F4-105F6592990E}" 9 | ProjectSection(SolutionItems) = preProject 10 | .gitignore = .gitignore 11 | appveyor.yml = appveyor.yml 12 | README.md = README.md 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | CI|Any CPU = CI|Any CPU 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {545865B3-DB94-4CB4-B5F1-7A22092B334F}.CI|Any CPU.ActiveCfg = CI|Any CPU 23 | {545865B3-DB94-4CB4-B5F1-7A22092B334F}.CI|Any CPU.Build.0 = CI|Any CPU 24 | {545865B3-DB94-4CB4-B5F1-7A22092B334F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {545865B3-DB94-4CB4-B5F1-7A22092B334F}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {545865B3-DB94-4CB4-B5F1-7A22092B334F}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {545865B3-DB94-4CB4-B5F1-7A22092B334F}.Release|Any CPU.Build.0 = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(SolutionProperties) = preSolution 30 | HideSolutionNode = FALSE 31 | EndGlobalSection 32 | EndGlobal 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Visual Studio Auto Updater 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/61vaxxkrbdmtmql0?svg=true)](https://ci.appveyor.com/project/madskristensen/extensionupdater) 4 | 5 | [Download the extension on the VS Gallery](https://visualstudiogallery.msdn.microsoft.com/14973bbb-8e00-4cab-a8b4-415a38d78615) 6 | or get the [nightly build](https://ci.appveyor.com/project/madskristensen/extensionupdater/build/artifacts) 7 | 8 | This extension allows you to specify which of your Visual Studio extensions 9 | you want to automatically update when a new version is released. 10 | 11 | ### How it works 12 | 13 | Every time Visual Studio opens, a background process checks for updates 14 | to the extensions you have specified for auto updating. 15 | 16 | If it finds any, it will silently install them in the background, 17 | so that next time you open Visual Studio, you'll have the latest versions 18 | of your favorite extensions already installed. 19 | 20 | You specify which extensions to auto update in a flyout menu located in the 21 | `Tools` menu. 22 | 23 | ![Screenshot](https://raw.githubusercontent.com/madskristensen/ExtensionUpdater/master/artifacts/screenshot.png) 24 | 25 | The first item in the menu is `Enable Automatic Updates`. That's the master switch for 26 | this feature. If it is unchecked, no auto updating will take place for any extension. 27 | 28 | Not all extensions are listed. Only the ones that can be auto updated, which excludes 29 | 30 | 1. Extensions installed by MSIs. 31 | 2. Extensions that require admin permissions to update. 32 | 3. Extensions that are shipped as part of Visual Studio. 33 | 34 | Some extensions are enabled for automatic updates by default. These 35 | extensions are typically smaller extensions that are safe to update. 36 | You can always turn off automatic updating of those extensions, 37 | but they have been classified as "safe" to update. 38 | 39 | For instance, Web Essentials is not on the list because there are 40 | people that prefer earlier versions over the latest. 41 | 42 | ### Extension writers 43 | 44 | If you have written an extension and want to add it to the list 45 | of extensions that are automatically updated by default, then you 46 | can easily do that. 47 | 48 | Just send a pull request with your extension's Product ID/guid 49 | to the list found here: 50 | [PreEnabledExtensions.cs](https://github.com/madskristensen/ExtensionUpdater/blob/master/src/Updater/PreEnabledExtensions.cs) 51 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | 3 | install: 4 | - ps: (new-object Net.WebClient).DownloadString("https://raw.github.com/madskristensen/ExtensionScripts/master/AppVeyor/vsix.ps1") | iex 5 | 6 | before_build: 7 | - ps: Vsix-IncrementVsixVersion | Vsix-UpdateBuildVersion 8 | - ps: Vsix-TokenReplacement src\ExtensionUpdaterPackage.cs 'Version = "([0-9\\.]+)"' 'Version = "{version}"' 9 | 10 | build_script: 11 | - msbuild /p:configuration=Release /p:DeployExtension=false /p:ZipPackageCompressionLevel=normal /v:m 12 | 13 | after_build: 14 | - ps: Vsix-PushArtifacts | Vsix-PublishToGallery 15 | 16 | before_deploy: 17 | - ps: Vsix-CreateChocolatyPackage -packageId vsautoupdater 18 | 19 | deploy: 20 | - provider: Environment 21 | name: Chocolatey 22 | on: 23 | branch: master 24 | appveyor_repo_commit_message_extended: /\[release\]/ -------------------------------------------------------------------------------- /artifacts/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionUpdater/25fe41065596974593d45979835eafeb87532cd2/artifacts/screenshot.png -------------------------------------------------------------------------------- /src/Commands.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.ExtensionManager; 2 | using Microsoft.VisualStudio.Shell; 3 | using Microsoft.VisualStudio.Shell.Interop; 4 | using Microsoft.Win32; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.ComponentModel.Design; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Text; 11 | 12 | namespace MadsKristensen.ExtensionUpdater 13 | { 14 | class Commands 15 | { 16 | private static bool _hasLoaded = false; 17 | private bool _isImportProcessing = false; 18 | private string[] _toInstallExtensions = new string[] { }; 19 | 20 | private IVsExtensionRepository _repository; 21 | private IVsExtensionManager _manager; 22 | private OleMenuCommandService _mcs; 23 | private IVsOutputWindowPane _outputPane; 24 | 25 | public Commands(IVsExtensionRepository repo, IVsExtensionManager manager, OleMenuCommandService mcs, IVsOutputWindowPane outputPane) 26 | { 27 | _repository = repo; 28 | _manager = manager; 29 | _mcs = mcs; 30 | _outputPane = outputPane; 31 | } 32 | 33 | public void Initialize() 34 | { 35 | CommandID menuCommandID = new CommandID(GuidList.guidExtensionUpdaterCmdSet, (int)PkgCmdIDList.cmdEnableAutoUpdate); 36 | OleMenuCommand command = new OleMenuCommand(MasterSwitch, menuCommandID); 37 | command.BeforeQueryStatus += (s, e) => { SetVisibility(command); }; 38 | _mcs.AddCommand(command); 39 | 40 | CommandID checkAllCommandID = new CommandID(GuidList.guidExtensionUpdaterCmdSet, (int)PkgCmdIDList.cmdCheckAll); 41 | OleMenuCommand checkAll = new OleMenuCommand(CheckAll, checkAllCommandID); 42 | _mcs.AddCommand(checkAll); 43 | 44 | CommandID importCommandID = new CommandID(GuidList.guidImportExportCmdSet, (int)PkgCmdIDList.cmdImport); 45 | OleMenuCommand import = new OleMenuCommand(Import, importCommandID); 46 | _mcs.AddCommand(import); 47 | 48 | CommandID exportCommandID = new CommandID(GuidList.guidImportExportCmdSet, (int)PkgCmdIDList.cmdExport); 49 | OleMenuCommand export = new OleMenuCommand(Export, exportCommandID); 50 | _mcs.AddCommand(export); 51 | } 52 | 53 | private void WriteToOutputPane(string message) 54 | { 55 | _outputPane.OutputString(message + Environment.NewLine); 56 | _outputPane.Activate(); 57 | } 58 | 59 | #region Import / export 60 | 61 | private void Import(object sender, EventArgs e) 62 | { 63 | if (_isImportProcessing) 64 | { 65 | WriteToOutputPane("Extensions import ignored - one is currently already running."); 66 | return; 67 | } 68 | 69 | var openFileDialog = new OpenFileDialog() 70 | { 71 | AddExtension = true, 72 | DefaultExt = ".vsextensionslist", 73 | CheckPathExists = true, 74 | Filter = "Extensions List (.vsextensionslist)|*.vsextensionslist", 75 | FilterIndex = 1, 76 | //InitialDirectory = "%userprofile%", 77 | }; 78 | 79 | var userClickedOK = openFileDialog.ShowDialog() ?? false; 80 | if (!userClickedOK) 81 | { 82 | return; 83 | } 84 | 85 | if (string.IsNullOrWhiteSpace(openFileDialog.FileName)) 86 | { 87 | WriteToOutputPane("Extensions not imported - please select a file."); 88 | return; 89 | } 90 | 91 | _isImportProcessing = true; 92 | WriteToOutputPane("Importing extensions..."); 93 | 94 | string[] importFileLines = null; 95 | try 96 | { 97 | importFileLines = File.ReadAllLines(openFileDialog.FileName).Where(l => !String.IsNullOrWhiteSpace(l)).Select(l => l.Trim()).ToArray(); 98 | } 99 | catch 100 | { 101 | WriteToOutputPane("Error accessing/reading import file."); 102 | _isImportProcessing = false; 103 | return; 104 | } 105 | 106 | if (!importFileLines.Any()) 107 | { 108 | WriteToOutputPane("No extensions were found in the import file."); 109 | _isImportProcessing = false; 110 | return; 111 | } 112 | 113 | // Get extensions not already installed 114 | var _installedExtensions = Commands.GetExtensions(_manager).ToDictionary(ie => ie.Header.Identifier, ie => ie.Header.Name); 115 | _toInstallExtensions = importFileLines.Where(l => _installedExtensions.All(ie => ie.Key != l)).ToArray(); 116 | 117 | if (!_toInstallExtensions.Any()) 118 | { 119 | WriteToOutputPane("You've already got all the extensions listed in the import file."); 120 | _isImportProcessing = false; 121 | return; 122 | } 123 | 124 | // Query for the complete new extension objects 125 | var query = _repository.CreateQuery(false, true, "ExtensionManagerQuery") 126 | .Where(entry => _toInstallExtensions.Contains(entry.VsixID)) 127 | .OrderBy(entry => entry.Name) 128 | .Skip(0) 129 | .Take(500) 130 | as IVsExtensionRepositoryQuery; 131 | 132 | WriteToOutputPane( 133 | string.Format("Looking up {0} potentially new extension/s in the gallery after skipping {1} already installed extension/s...", 134 | _toInstallExtensions.Length, 135 | importFileLines.Length - _toInstallExtensions.Length 136 | ) 137 | ); 138 | 139 | query.ExecuteCompleted += Query_ExecuteCompleted; 140 | query.ExecuteAsync(); 141 | } 142 | 143 | private void Query_ExecuteCompleted(object sender, ExecuteCompletedEventArgs e) 144 | { 145 | if (e.Error != null) 146 | { 147 | WriteToOutputPane("Error looking up new extension/s in the gallery..."); 148 | _isImportProcessing = false; 149 | return; 150 | } 151 | 152 | // Extract results of found extensions 153 | var foundExtensions = e.Results.Cast().ToArray(); 154 | var installableExtensions = foundExtensions.Where(entry => _toInstallExtensions.Any(l => l != entry.VsixID)).ToArray(); 155 | var missingExtensions = _toInstallExtensions.Except(foundExtensions.Select(fe => fe.VsixID)).ToArray(); 156 | 157 | if (!installableExtensions.Any()) 158 | { 159 | WriteToOutputPane("Couldn't find any of the new extension/s in the gallery."); 160 | _isImportProcessing = false; 161 | return; 162 | } 163 | 164 | if (missingExtensions.Any()) 165 | { 166 | WriteToOutputPane("Couldn't find " + missingExtensions.Length + " of the new extension/s in the gallery."); 167 | } 168 | 169 | // Download and install the new ones 170 | WriteToOutputPane("Installing new extension/s:"); 171 | var wasAnInstallSuccessful = false; 172 | foreach (var installableExtension in installableExtensions) 173 | { 174 | var msg = string.Format(" - '{0}' ", installableExtension.Name); 175 | 176 | IInstallableExtension extension = null; 177 | try 178 | { 179 | extension = _repository.Download(installableExtension); 180 | _manager.Install(extension, false); 181 | msg += "installed."; 182 | wasAnInstallSuccessful = true; 183 | } 184 | catch (Exception ex) 185 | { 186 | msg += "install failed. " + ex.Message; 187 | } 188 | 189 | WriteToOutputPane(msg); 190 | } 191 | 192 | if (wasAnInstallSuccessful) 193 | { 194 | WriteToOutputPane(Environment.NewLine + Environment.NewLine + "Please restart for changes to take affect."); 195 | } 196 | 197 | WriteToOutputPane("Extensions imported."); 198 | 199 | // Reset 200 | _isImportProcessing = false; 201 | _toInstallExtensions = new string[] { }; 202 | } 203 | 204 | private void Export(object sender, EventArgs e) 205 | { 206 | var saveFileDialog = new SaveFileDialog() 207 | { 208 | AddExtension = true, 209 | DefaultExt = ".vsextensionslist", 210 | CheckPathExists = true, 211 | Filter = "Extensions List (.vsextensionslist)|*.vsextensionslist", 212 | FilterIndex = 1, 213 | //InitialDirectory = "%userprofile%", 214 | }; 215 | 216 | var userClickedOK = saveFileDialog.ShowDialog() ?? false; 217 | if (!userClickedOK) 218 | { 219 | return; 220 | } 221 | 222 | if (string.IsNullOrWhiteSpace(saveFileDialog.FileName)) 223 | { 224 | WriteToOutputPane("Extensions not exported - please choose a filename."); 225 | } 226 | 227 | WriteToOutputPane("Exporting your extensions..."); 228 | 229 | var installedExtensions = Commands.GetExtensions(_manager); 230 | var sbInstalledExtensions = new StringBuilder(installedExtensions.Count() * 50); 231 | foreach (var ext in installedExtensions) 232 | { 233 | sbInstalledExtensions.AppendLine(ext.Header.Identifier); 234 | } 235 | 236 | try 237 | { 238 | File.WriteAllText(saveFileDialog.FileName, sbInstalledExtensions.ToString()); 239 | WriteToOutputPane("Extensions exported."); 240 | } 241 | catch 242 | { 243 | WriteToOutputPane("Problem exporting extensions."); 244 | } 245 | } 246 | 247 | #endregion 248 | 249 | private void CheckAll(object sender, EventArgs e) 250 | { 251 | foreach (var extension in GetExtensions(_manager)) 252 | { 253 | Settings.ToggleEnabled(extension.Header.Identifier, true); 254 | } 255 | } 256 | 257 | private void MasterSwitch(object sender, EventArgs e) 258 | { 259 | OleMenuCommand command = (OleMenuCommand)sender; 260 | Settings.Enabled = !command.Checked; 261 | 262 | if (!command.Checked) // Not checked means that it is checked. 263 | { 264 | Updater updater = new Updater(_repository, _manager); 265 | updater.CheckForUpdates(); 266 | } 267 | } 268 | 269 | private void SetVisibility(OleMenuCommand master) 270 | { 271 | master.Checked = Settings.Enabled; 272 | 273 | if (_hasLoaded) 274 | return; 275 | 276 | int num = 0; 277 | 278 | foreach (var extension in GetExtensions(_manager).OrderBy(e => e.Header.Name)) 279 | { 280 | num++; 281 | CommandID commandId = new CommandID(GuidList.guidExtensionUpdaterCmdSet, (int)PkgCmdIDList.cmdEnableAutoUpdate + num); 282 | OleMenuCommand command = PrepareMenuItem(extension, commandId); 283 | 284 | _mcs.AddCommand(command); 285 | } 286 | 287 | _hasLoaded = true; 288 | } 289 | 290 | private OleMenuCommand PrepareMenuItem(IInstalledExtension extension, CommandID commandId) 291 | { 292 | OleMenuCommand command = new OleMenuCommand(ToggleAutoUpdating, commandId); 293 | command.Text = extension.Header.Name; 294 | command.ParametersDescription = extension.Header.Identifier; 295 | command.Checked = Settings.IsEnabled(extension.Header.Identifier); 296 | command.BeforeQueryStatus += (x, y) => 297 | { 298 | OleMenuCommand c = (OleMenuCommand)x; 299 | c.Enabled = Settings.Enabled; 300 | c.Checked = Settings.IsEnabled(c.ParametersDescription); 301 | }; 302 | 303 | return command; 304 | } 305 | 306 | private void ToggleAutoUpdating(object sender, EventArgs e) 307 | { 308 | OleMenuCommand command = (OleMenuCommand)sender; 309 | Settings.ToggleEnabled(command.ParametersDescription, !command.Checked); 310 | } 311 | 312 | public static IEnumerable GetExtensions(IVsExtensionManager manager) 313 | { 314 | return from e in manager.GetInstalledExtensions() 315 | where !e.Header.SystemComponent && !e.Header.AllUsers && !e.Header.InstalledByMsi && e.State == EnabledState.Enabled 316 | select e; 317 | } 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /src/ExtensionUpdater.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(VisualStudioVersion) 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | Program 7 | $(DevEnvDir)\devenv.exe 8 | /rootsuffix Exp 9 | true 10 | Normal 11 | 12 | 13 | bin\CI\ 14 | TRACE 15 | true 16 | pdbonly 17 | AnyCPU 18 | prompt 19 | MinimumRecommendedRules.ruleset 20 | False 21 | 22 | 23 | 24 | Debug 25 | AnyCPU 26 | 2.0 27 | {545865B3-DB94-4CB4-B5F1-7A22092B334F} 28 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 29 | Library 30 | Properties 31 | MadsKristensen.ExtensionUpdater 32 | ExtensionUpdater 33 | v4.5 34 | 35 | 36 | true 37 | full 38 | false 39 | bin\Debug\ 40 | DEBUG;TRACE 41 | prompt 42 | 4 43 | 44 | 45 | pdbonly 46 | true 47 | bin\Release\ 48 | TRACE 49 | prompt 50 | 4 51 | 52 | 53 | 54 | 55 | False 56 | $(DevEnvDir)\PrivateAssemblies\Microsoft.VisualStudio.ExtensionManager.dll 57 | 58 | 59 | 60 | 61 | 62 | 63 | True 64 | 65 | 66 | True 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | {00020430-0000-0000-C000-000000000046} 82 | 2 83 | 0 84 | 0 85 | primary 86 | False 87 | False 88 | 89 | 90 | 91 | 92 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | true 107 | VSPackage 108 | Designer 109 | 110 | 111 | 112 | 113 | Designer 114 | 115 | 116 | 117 | 118 | Menus.ctmenu 119 | Designer 120 | 121 | 122 | 123 | 124 | true 125 | 126 | 127 | true 128 | 129 | 130 | 131 | true 132 | 133 | 134 | 135 | 136 | true 137 | 138 | 139 | 140 | 147 | -------------------------------------------------------------------------------- /src/ExtensionUpdater.vsct: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Auto Update Extensions 14 | 15 | 16 | 17 | 18 | 19 | Import/Export Extensions 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 44 | 45 | 51 | 52 | 58 | 59 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/ExtensionUpdaterPackage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using System.Runtime.InteropServices; 4 | using Microsoft.VisualStudio.ExtensionManager; 5 | using Microsoft.VisualStudio.Shell; 6 | using Microsoft.VisualStudio.Shell.Interop; 7 | 8 | namespace MadsKristensen.ExtensionUpdater 9 | { 10 | [PackageRegistration(UseManagedResourcesOnly = true)] 11 | [ProvideAutoLoad(UIContextGuids80.NoSolution)] 12 | [ProvideAutoLoad(UIContextGuids80.SolutionExists)] 13 | [InstalledProductRegistration("#110", "#112", Version, IconResourceID = 400)] 14 | [ProvideMenuResource("Menus.ctmenu", 1)] 15 | [Guid(GuidList.guidExtensionUpdaterPkgString)] 16 | public sealed class ExtensionUpdaterPackage : ExtensionPointPackage 17 | { 18 | public const string Version = "1.6"; 19 | 20 | protected override void Initialize() 21 | { 22 | base.Initialize(); 23 | 24 | var repository = (IVsExtensionRepository)GetService(typeof(SVsExtensionRepository)); 25 | var manager = (IVsExtensionManager)GetService(typeof(SVsExtensionManager)); 26 | var mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService; 27 | var outWindow = GetService(typeof(SVsOutputWindow)) as IVsOutputWindow; 28 | 29 | if (repository == null || manager == null || mcs == null || outWindow == null) 30 | { 31 | return; 32 | } 33 | 34 | // Get a unique output window pane 35 | var paneGuid = new Guid("233a6542-251a-42b1-b435-c80a3d70340e"); 36 | outWindow.CreatePane(ref paneGuid, "Extension Updater", 1, 1); 37 | IVsOutputWindowPane customOutputPane; 38 | outWindow.GetPane(ref paneGuid, out customOutputPane); 39 | 40 | // Initialize settings 41 | Settings.Initialize(this); 42 | 43 | // Setup the menu buttons 44 | Commands commands = new Commands(repository, manager, mcs, customOutputPane); 45 | commands.Initialize(); 46 | 47 | // Check for extension updates 48 | Updater updater = new Updater(repository, manager); 49 | updater.CheckForUpdates(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Guids.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MadsKristensen.ExtensionUpdater 4 | { 5 | static class GuidList 6 | { 7 | public const string guidExtensionUpdaterPkgString = "cb8900a0-79fb-4c76-a6d6-f4e88d3384d2"; 8 | 9 | public const string guidExtensionUpdaterCmdSetString = "c1703d5f-c150-4977-a4a7-d4a6094412bf"; 10 | public const string guidFlyoutMenuString = "c1703d5f-c150-4977-a4a2-d4a6094412ba"; 11 | 12 | public const string guidImportExportCmdSetString = "c1703d5f-c150-4977-a4a7-d4a6094412be"; 13 | public const string guidFlyoutImportExportMenuString = "c1703d5f-c150-4977-a4a2-d4a6094412bb"; 14 | 15 | public static readonly Guid guidExtensionUpdaterCmdSet = new Guid(guidExtensionUpdaterCmdSetString); 16 | public static readonly Guid guidFlyoutMenu = new Guid(guidFlyoutMenuString); 17 | 18 | public static readonly Guid guidImportExportCmdSet = new Guid(guidImportExportCmdSetString); 19 | public static readonly Guid guidFlyoutImportExportMenu = new Guid(guidFlyoutImportExportMenuString); 20 | } 21 | 22 | static class PkgCmdIDList 23 | { 24 | public const uint cmdEnableAutoUpdate = 0x100; 25 | public const uint cmdCheckAll = 0x200; 26 | public const uint cmdSearch = 0x300; 27 | 28 | public const uint cmdImport = 0x400; 29 | public const uint cmdExport = 0x500; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using MadsKristensen.ExtensionUpdater; 2 | using System; 3 | using System.Reflection; 4 | using System.Resources; 5 | using System.Runtime.InteropServices; 6 | 7 | [assembly: AssemblyTitle("Extension Updater")] 8 | [assembly: AssemblyDescription("Configure Visual Studio to automatically install updates to the extensions you are using")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("Mads Kristensen")] 11 | [assembly: AssemblyProduct("Extension Updater")] 12 | [assembly: AssemblyCopyright("")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | [assembly: ComVisible(false)] 16 | [assembly: CLSCompliant(false)] 17 | [assembly: NeutralResourcesLanguage("en-US")] 18 | 19 | [assembly: AssemblyVersion(ExtensionUpdaterPackage.Version)] 20 | [assembly: AssemblyFileVersion(ExtensionUpdaterPackage.Version)] -------------------------------------------------------------------------------- /src/Resources/License.txt: -------------------------------------------------------------------------------- 1 | Copyright 2014 Mads Kristensen 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /src/Resources/Package.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionUpdater/25fe41065596974593d45979835eafeb87532cd2/src/Resources/Package.ico -------------------------------------------------------------------------------- /src/Resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionUpdater/25fe41065596974593d45979835eafeb87532cd2/src/Resources/logo.png -------------------------------------------------------------------------------- /src/Resources/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionUpdater/25fe41065596974593d45979835eafeb87532cd2/src/Resources/preview.png -------------------------------------------------------------------------------- /src/Settings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.VisualStudio.Settings; 5 | using Microsoft.VisualStudio.Shell.Settings; 6 | 7 | namespace MadsKristensen.ExtensionUpdater 8 | { 9 | class Settings 10 | { 11 | private const string _name = "Extension Updater"; 12 | private const string _identifierKey = "ExtensionIdentifiers"; 13 | private const string _separator = "__|--"; 14 | 15 | private static SettingsManager _manager; 16 | private static SettingsStore _readStore; 17 | private static WritableSettingsStore _writeStore; 18 | 19 | public static void Initialize(IServiceProvider provider) 20 | { 21 | _manager = new ShellSettingsManager(provider); 22 | _readStore = _manager.GetReadOnlySettingsStore(SettingsScope.UserSettings); 23 | _writeStore = _manager.GetWritableSettingsStore(SettingsScope.UserSettings); 24 | 25 | if (!_writeStore.CollectionExists(_name)) 26 | { 27 | _writeStore.CreateCollection(_name); 28 | 29 | string defaults = string.Join(_separator, PreEnabledExtensions.List); 30 | _writeStore.SetString(_name, _identifierKey, defaults); 31 | } 32 | } 33 | 34 | public static bool Enabled 35 | { 36 | get 37 | { 38 | return _readStore.GetBoolean(_name, "IsEnabled", true); 39 | } 40 | set 41 | { 42 | _writeStore.SetBoolean(_name, "IsEnabled", value); 43 | } 44 | } 45 | 46 | public static bool IsEnabled(string identifier) 47 | { 48 | string raw = _readStore.GetString(_name, _identifierKey, string.Empty); 49 | 50 | if (string.IsNullOrEmpty(raw)) 51 | return false; 52 | 53 | string[] identifiers = raw.Split(new[] { _separator }, StringSplitOptions.RemoveEmptyEntries); 54 | 55 | return identifiers.Contains(identifier); 56 | } 57 | 58 | public static void ToggleEnabled(string identifier, bool isEnabled) 59 | { 60 | string raw = _readStore.GetString(_name, _identifierKey, string.Empty); 61 | 62 | IEnumerable ids = raw.Split(new[] { _separator }, StringSplitOptions.RemoveEmptyEntries); 63 | 64 | if (isEnabled) 65 | { 66 | ids = ids.Union(new[] { identifier }); 67 | } 68 | else 69 | { 70 | ids = ids.Where(i => i != identifier); 71 | } 72 | 73 | string newValue = string.Join(_separator, ids); 74 | _writeStore.SetString(_name, _identifierKey, newValue); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Updater/GalleryEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.ExtensionManager; 3 | 4 | namespace MadsKristensen.ExtensionUpdater 5 | { 6 | /// 7 | /// This class replicates the Microsoft.VisualStudio.ExtensionManager.UI.VsGalleryEntry in Microsoft.VisualStudio.ExtensionsManager.Implementation.dll. 8 | /// We do so to avoid dependency on Implementation.dll assembly, which is a private assembly of VS. 9 | /// 10 | public class GalleryEntry : IRepositoryEntry 11 | { 12 | private Version _nonNullVsixVersion; 13 | 14 | public string VsixID { get; set; } 15 | public string DownloadUrl { get; set; } 16 | public string DownloadUpdateUrl { get; set; } 17 | public string VsixReferences { get; set; } 18 | public string VsixVersion { get; set; } 19 | public string Name { get; set; } 20 | public int Ranking { get; set; } 21 | 22 | public Version NonNullVsixVersion 23 | { 24 | get 25 | { 26 | if (_nonNullVsixVersion == null) 27 | { 28 | if (!Version.TryParse(VsixVersion, out _nonNullVsixVersion)) 29 | { 30 | _nonNullVsixVersion = new Version(); 31 | } 32 | 33 | } 34 | 35 | return _nonNullVsixVersion; 36 | } 37 | } 38 | 39 | public override string ToString() 40 | { 41 | return Name; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/Updater/PreEnabledExtensions.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace MadsKristensen.ExtensionUpdater 3 | { 4 | static class PreEnabledExtensions 5 | { 6 | /// 7 | /// A list of extensions that are safe to auto update by default. 8 | /// 9 | /// 10 | /// You can find the guid/ID for any extension by looking in the .vsixmanifest file. 11 | /// The ID is located in the attribute. 12 | /// 13 | /// It's important that auto-updating extensions are considered "safe" to update, 14 | /// so that the users don't get a bad experience. 15 | /// 16 | public static string[] List = new string[] 17 | { 18 | GuidList.guidExtensionUpdaterPkgString, // This extension 19 | "4c1a78e6-e7b8-4aa9-8812-4836e051ff6d", // Trailing Whitespace Visualizer 20 | "27dd9dea-6dd2-403e-929d-3ff20d896c5e", // Add New File 21 | "6c799bc4-0d4c-4172-98bc-5d464b612dca", // File Nesting 22 | "aaa8d5c5-24d8-4c45-9620-9f77b2aa6363", // Package Intellisense 23 | "0798393f-f7b0-4283-a36e-c57a73f031c4", // Error Watcher 24 | "cced4e72-2f8c-4458-b8df-4934677e4bf3", // GruntLauncher 25 | "4156516b-f6e6-40f2-aecb-ff99cded5f8a", // Open from Azure Websites 26 | "0e313dfd-be80-4afb-b5e9-6e74d369f7a1", // SQL Server Compact / SQLite Toolbox 27 | "f4ab1e64-5d35-4f06-bad9-bf414f4b3bbb", // Open Command Line 28 | "e6e2a48e-387d-4af2-9072-86a5276da6d4", // SideWaffle 29 | "bf95754f-93d3-42ff-bfe3-e05d23188b08", // Image Optimizer 30 | "96559c66-f326-40e2-95c1-449a80387524", // Flatten Packages 31 | "T4Toolbox.12", // T4 Toolbox for Visual Studio 2013 32 | }; 33 | } 34 | } -------------------------------------------------------------------------------- /src/Updater/UpdateChecker.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.VisualStudio.ExtensionManager; 3 | 4 | namespace MadsKristensen.ExtensionUpdater 5 | { 6 | class UpdateChecker 7 | { 8 | private readonly IVsExtensionRepository _repository; 9 | private readonly IVsExtensionManager _manager; 10 | 11 | public UpdateChecker(IVsExtensionRepository extensionRepository, IVsExtensionManager extensionManager) 12 | { 13 | _manager = extensionManager; 14 | _repository = extensionRepository; 15 | } 16 | 17 | public bool CheckForUpdate(IInstalledExtension extension, out IInstallableExtension update) 18 | { 19 | // Find the vsix on the vs gallery 20 | // IMPORTANT: The .AsEnumerble() call is REQUIRED. Don't remove it or the update service won't work. 21 | GalleryEntry entry = _repository.CreateQuery(includeTypeInQuery: false, includeSkuInQuery: true, searchSource: "ExtensionManagerUpdate") 22 | .Where(e => e.VsixID == extension.Header.Identifier) 23 | .AsEnumerable() 24 | .FirstOrDefault(); 25 | 26 | // If we're running an older version then update 27 | if (entry != null && entry.NonNullVsixVersion > extension.Header.Version) 28 | { 29 | update = _repository.Download(entry); 30 | return true; 31 | } 32 | else 33 | { 34 | update = null; 35 | return false; 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Updater/Updater.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Microsoft.VisualStudio.ExtensionManager; 5 | 6 | namespace MadsKristensen.ExtensionUpdater 7 | { 8 | class Updater 9 | { 10 | private readonly IVsExtensionRepository _repository; 11 | private readonly IVsExtensionManager _manager; 12 | private readonly UpdateChecker _checker; 13 | 14 | public Updater(IVsExtensionRepository extensionRepository, IVsExtensionManager extensionManager) 15 | { 16 | _manager = extensionManager; 17 | _repository = extensionRepository; 18 | _checker = new UpdateChecker(extensionRepository, extensionManager); 19 | } 20 | 21 | public void CheckForUpdates() 22 | { 23 | Task.Run(() => { Update(); }); 24 | } 25 | 26 | private void Update() 27 | { 28 | try 29 | { 30 | DownloadAndInstall(); 31 | } 32 | catch (Exception ex) 33 | { 34 | System.Diagnostics.Debug.WriteLine(ex.Message); 35 | } 36 | } 37 | 38 | private void DownloadAndInstall() 39 | { 40 | IEnumerable extensions = Commands.GetExtensions(_manager); 41 | 42 | foreach (IInstalledExtension extension in extensions) 43 | { 44 | if (!Settings.IsEnabled(extension.Header.Identifier)) 45 | continue; 46 | 47 | IInstallableExtension update; 48 | bool updateAvailable = _checker.CheckForUpdate(extension, out update); 49 | 50 | if (updateAvailable && update != null) 51 | { 52 | _manager.Disable(extension); 53 | _manager.Uninstall(extension); 54 | _manager.InstallAsync(update, false); 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/VSPackage.resx: -------------------------------------------------------------------------------- 1 |  2 | 12 | 13 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | text/microsoft-resx 120 | 121 | 122 | 2.0 123 | 124 | 125 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 126 | 127 | 128 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 129 | 130 | 131 | 132 | Extension Updater 133 | 134 | 135 | Configure Visual Studio to automatically install updates to the extensions you are using 136 | 137 | 138 | Resources\Package.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 139 | 140 | -------------------------------------------------------------------------------- /src/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Visual Studio Auto Updater 6 | Configure Visual Studio to automatically install updates to the extensions you are using 7 | Resources\License.txt 8 | Resources\logo.png 9 | Resources\preview.png 10 | extensions, 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | --------------------------------------------------------------------------------