├── src
├── Resources
│ ├── logo.png
│ ├── Package.ico
│ ├── preview.png
│ └── License.txt
├── Properties
│ └── AssemblyInfo.cs
├── Guids.cs
├── source.extension.vsixmanifest
├── Updater
│ ├── GalleryEntry.cs
│ ├── UpdateChecker.cs
│ ├── PreEnabledExtensions.cs
│ └── Updater.cs
├── ExtensionUpdaterPackage.cs
├── Settings.cs
├── ExtensionUpdater.vsct
├── ExtensionUpdater.csproj
├── VSPackage.resx
└── Commands.cs
├── artifacts
└── screenshot.png
├── .gitattributes
├── appveyor.yml
├── ExtensionUpdater.sln
├── README.md
└── .gitignore
/src/Resources/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/ExtensionUpdater/master/src/Resources/logo.png
--------------------------------------------------------------------------------
/artifacts/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/ExtensionUpdater/master/artifacts/screenshot.png
--------------------------------------------------------------------------------
/src/Resources/Package.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/ExtensionUpdater/master/src/Resources/Package.ico
--------------------------------------------------------------------------------
/src/Resources/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/ExtensionUpdater/master/src/Resources/preview.png
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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\]/
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Visual Studio Auto Updater
2 |
3 | [](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 | 
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 |
--------------------------------------------------------------------------------
/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/ExtensionUpdater.vsct:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
16 |
22 |
23 |
24 |
25 |
28 |
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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------