外部工具 Visual Studio

Gre*_*reg 2 .net c# registry visual-studio

我创建了一个要运行的应用程序,但是从 Visual Studio 的应用程序构建了一个外部工具。直到 15.4.0 应用程序运行良好,但他们修改了注册表。根据 Microsoft 的更改,外部工具保存在此子项下:

SOFTWARE\Microsoft\VisualStudio\14.0_Config\External Tools

当您观察密钥时,您会清楚地看到 Visual Studio 中包含的工具、Guid、错误查找和其他几个文件夹(子密钥)。但问题是,如果您在 Visual Studio 中手动创建工具,密钥信息将直接转储到根外部工具密钥。

因此,如果我执行以下任一方法:

var user = RegistryKey.OpenBaseKey(RegistryHive.CurrenUser, RegistryView.Default);
var tools = user.OpenSubKey(@"SOFTWARE\Microsoft\VisualStudio\14.0_Config\External Tools", true);
var path = Environment.GetCommandLineArgs()[0];

tools.SetValue("ToolArg", "$(ItemPath)");
tools.SetValue("ToolCmd", path);
tools.SetValue("ToolDir", String.Empty);
tools.SetValue("ToolOpt", 0x12);
tools.SetValue("ToolsSourceKey", String.Empty);
tools.SetValue("ToolTitle", "My Tool");
Run Code Online (Sandbox Code Playgroud)
    var user = RegistryKey.OpenBaseKey(RegistryHive.CurrenUser, RegistryView.Default);
    var tools = user.OpenSubKey(@"SOFTWARE\Microsoft\VisualStudio\14.0_Config\External Tools", true);
var sub = tools.CreateSubKey("MyTool");
    var path = Environment.GetCommandLineArgs()[0];

sub.SetValue("ToolArg", "$(ItemPath)");
sub.SetValue("ToolCmd", path);
sub.SetValue("ToolDir", String.Empty);
sub.SetValue("ToolOpt", 0x12);
sub.SetValue("ToolsSourceKey", String.Empty);
sub.SetValue("ToolTitle", "My Tool");
Run Code Online (Sandbox Code Playgroud)

该工具不会出现在列表或工具栏中。Visual Studio 2017 15.5.* 是否有不同之处,使其不再通过代码运行?更糟糕的是,在 Visual Studio 2017 中手动创建时,密钥并不总是出现。

And*_*y S 5

在 Visual Studio 2017 中,外部工具存储在用户本地应用程序数据文件夹中的私有注册表配置单元中。如果您运行 Sysinternals Process Monitor 工具,您将看到 Visual Studio 读取/写入以开头的键\REGISTRY\A\- 这就是您知道它是私有注册表配置单元的方式。要更新它们,您需要通过 P/InvokingRegLoadAppKey并附加到结果句柄来加载该注册表配置单元。可以在此处找到一个示例:

RegLoadAppKey 在 32 位操作系统上工作正常,在 64 位操作系统上失败,即使两个进程都是 32 位

标题乍一看似乎具有误导性,但问题中给出的示例确切地显示了如何调用RegLoadAppKey和打开下面的子键。

接下来您必须解决的问题是找到私有注册表配置单元。Visual Studio 将私有注册表配置单元存储在用户本地应用程序数据文件夹的子文件夹中。子文件夹名称将以 开头,Microsoft\VisualStudio\15.0_然后是 32 位十六进制值。我不太确定那个值是什么,或者如何优雅地发现它。每个用户都不同。我的方法是选择以“15.0”开头的最新文件夹并假设它是正确的。如果有人有更好的方法来识别这个文件夹,我很乐意看到它。

我将版本号和十六进制字符串的组合称为“版本标签”。您需要跟踪它,因为版本标记将再次用作私有注册表配置单元中的子键。

综上所述,我创建了一个VisualStudioContext用于定位私有注册表配置单元并加载根密钥的类。

public class VisualStudioContext : IDisposable
{
    public string VersionTag { get; }
    public string UserFolder { get; }
    public string PrivateRegistryPath { get; }
    public SafeRegistryHandle RegistryHandle { get; }
    public RegistryKey RootKey { get; }

    private static readonly Lazy<VisualStudioContext> LazyInstance = new Lazy<VisualStudioContext>(() => new VisualStudioContext());

    public static VisualStudioContext Instance => LazyInstance.Value;

    private VisualStudioContext()
    {
        try
        {
            string localAppDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
            string vsFolder = $"{localAppDataFolder}\\Microsoft\\VisualStudio";
            var vsFolderInfo = new DirectoryInfo(vsFolder);
            DateTime lastDateTime = DateTime.MinValue;

            foreach (DirectoryInfo dirInfo in vsFolderInfo.GetDirectories("15.0_*"))
            {
                if (dirInfo.CreationTime <= lastDateTime)
                    continue;

                UserFolder = dirInfo.FullName;
                lastDateTime = dirInfo.CreationTime;
            }

            if (UserFolder == null)
                throw new Exception($"No Visual Studio folders found in \"{vsFolder}\"");
        }
        catch (Exception ex)
        {
            throw new Exception("Unable to open Visual Studio folder", ex);
        }

        VersionTag = Path.GetFileName(UserFolder);

        PrivateRegistryPath = $"{UserFolder}\\privateregistry.bin";
        int handle = RegistryNativeMethods.RegLoadAppKey(PrivateRegistryPath);

        RegistryHandle = new SafeRegistryHandle(new IntPtr(handle), true);
        RootKey = RegistryKey.FromHandle(RegistryHandle);
    }

    public void Dispose()
    {
        RootKey?.Close();
        RegistryHandle?.Dispose();
    }

    public class Exception : ApplicationException
    {
        public Exception(string message) : base(message)
        {
        }

        public Exception(string message, Exception innerException) : base(message, innerException)
        {
        }
    }

    internal static class RegistryNativeMethods
    {
        [Flags]
        public enum RegSAM
        {
            AllAccess = 0x000f003f
        }

        private const int REG_PROCESS_APPKEY = 0x00000001;

        // approximated from pinvoke.net's RegLoadKey and RegOpenKey
        // NOTE: changed return from long to int so we could do Win32Exception on it
        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern int RegLoadAppKey(String hiveFile, out int hKey, RegSAM samDesired, int options, int reserved);

        public static int RegLoadAppKey(String hiveFile)
        {
            int hKey;
            int rc = RegLoadAppKey(hiveFile, out hKey, RegSAM.AllAccess, REG_PROCESS_APPKEY, 0);

            if (rc != 0)
            {
                throw new Win32Exception(rc, "Failed during RegLoadAppKey of file " + hiveFile);
            }

            return hKey;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以使用它打开外部工具键,如下所示:

using (var context = VisualStudioContext.Instance)
{
    RegistryKey keyExternalTools = 
        context.RootKey.OpenSubKey($"Software\\Microsoft\\VisualStudio\\{context.VersionTag}\\External Tools", true);

    // Do something interesting here
}
Run Code Online (Sandbox Code Playgroud)