Add support for checkin policies for VS2017 and VS2019

Visual Studio versions >= 2017 (or is it actually 2015 ?) store the
necessary information for e.g. CheckinPolicies no longer in the registry.
Instead, they use a private registry usually found in

  C:\Users\<USER>\AppData\Local\Microsoft\VisualStudio\15.0_xxxxxx\privateregistry.bin

where the xxxxxx value is differing between the different installed Visual
Studio editions and/or minor versions.

This commit introduces the ExternalSettingsManager which loads this private registry
and we can then add the VS extension folders gathered from the SettingsStore to
our assembly search path.

Sadly, this only works for 32bit process and crashes in a 64bit process as soon
as a method tries to create an ExternalSettingsManager instance. Therefore
this is put behind a check so we only do it in a 32bit process.

This implies that the checkin policies are only working in 32bit mode!

In addition to the search patsh gathered from the ExternalSettingsManager, add
  <vsInstallDir>\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer
to the search path, as otherwise some of the checkin policies used in my dev
environment here didn't load because a library in that folder wasn't
found. This indicated to me that we still are not 100% compatibile to
what Visual Studio does, but at least this fixes all the custom checkin
policies here, so it can't be that bad.
This commit is contained in:
Peter Baumann
2020-11-02 22:45:51 +01:00
parent b8bedf5666
commit a515071ed8
@@ -11,9 +11,11 @@ using GitTfs.Core.TfsInterop;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Common;
using Microsoft.TeamFoundation.Server;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.VisualStudio.Services.Client;
using Microsoft.VisualStudio.Settings;
using Microsoft.VisualStudio.Setup.Configuration;
using StructureMap;
@@ -42,6 +44,17 @@ namespace GitTfs.VsCommon
private readonly int myMajorVersion;
/// <summary>
/// Loading the ExternalSettingsManager and then GetReadOnlySettingsStore ensures
/// that also the private Visual Studio registry hive which is usually found
/// in a path looking similar to
/// C:\Users\USER\AppData\Local\Microsoft\VisualStudio\15.0_xxxxxx\privateregistry.bin
/// is loaded.
/// Without loading that private VS registry hive, private CheckinPolicies will not work,
/// as the assemblies are simply not found.
/// </summary>
private ExternalSettingsManager myExternalSettingsManager;
public TfsHelperVS2017Base(TfsApiBridge bridge, IContainer container, int majorVersion)
: base(bridge, container)
{
@@ -51,10 +64,52 @@ namespace GitTfs.VsCommon
myAssemblySearchPaths = new List<string>();
if (!string.IsNullOrEmpty(myVisualStudioInstallationPath))
{
// Calling LoadAssemblySearchPathFromVisualStudioPrivateRegistry would immediately
// crash with BadImageException in a 64Bit process therefore put it behind a check
if (!Environment.Is64BitProcess)
{
var devenvPath = Path.Combine(myVisualStudioInstallationPath, @"Common7\IDE\devenv.exe");
LoadAssemblySearchPathFromVisualStudioPrivateRegistry(devenvPath);
}
myAssemblySearchPaths.Add(Path.Combine(myVisualStudioInstallationPath, myPrivateAssembliesFolder));
}
}
/// <summary>
/// Loads the Visual Studio private registry, which is implicitly done when creating
/// a new ExternalSettingsManager. The private registry contains the search paths to the
/// extensions which is required for the Check-In Policies to work.
///
/// Calling this method on a 64bit process will not work, as a BadImageException is thrown.
/// </summary>
/// <param name="devenvPath">Path to the Visual Studio installation for which the private registry shall be loaded</param>
private void LoadAssemblySearchPathFromVisualStudioPrivateRegistry(string devenvPath)
{
Trace.WriteLine($"Loading VS private registry for '{devenvPath}");
myExternalSettingsManager = ExternalSettingsManager.CreateForApplication(devenvPath);
Trace.WriteLine("ApplicationExtensions:" + myExternalSettingsManager.GetApplicationDataFolder(ApplicationDataFolder.ApplicationExtensions));
Trace.WriteLine(myExternalSettingsManager.GetApplicationDataFolder(ApplicationDataFolder.Configuration));
Trace.WriteLine(myExternalSettingsManager.GetApplicationDataFolder(ApplicationDataFolder.Documents));
Trace.WriteLine(myExternalSettingsManager.GetApplicationDataFolder(ApplicationDataFolder.LocalSettings));
Trace.WriteLine(myExternalSettingsManager.GetApplicationDataFolder(ApplicationDataFolder.RoamingSettings));
Trace.WriteLine("UserExtensions :" + myExternalSettingsManager.GetApplicationDataFolder(ApplicationDataFolder.UserExtensions));
foreach (string searchPath in myExternalSettingsManager.GetCommonExtensionsSearchPaths()) {
Trace.WriteLine("CommonExtensionsPath :" + searchPath);
}
SettingsStore store = myExternalSettingsManager.GetReadOnlySettingsStore(SettingsScope.UserSettings);
var propNames = store.GetPropertyNames(@"ExtensionManager\EnabledExtensions");
myAssemblySearchPaths.AddRange(myExternalSettingsManager.GetCommonExtensionsSearchPaths());
string userExtensions = myExternalSettingsManager.GetApplicationDataFolder(ApplicationDataFolder.UserExtensions);
if (!userExtensions.IsNullOrEmpty())
{
myAssemblySearchPaths.Add(Path.Combine(myVisualStudioInstallationPath, userExtensions));
}
myAssemblySearchPaths.Add(Path.Combine(myVisualStudioInstallationPath, myTeamExplorerFolder));
}
protected override bool HasWorkItems(Changeset changeset)
{
return Retry.Do(() => changeset.AssociatedWorkItems.Length > 0);