创建您自己的 SQL Server Management Studio 17 (SSMS) 扩展






4.91/5 (26投票s)
关于如何开始创建 SSMS 扩展的简短教程。
引言
这将是一份面向绝对初学者的指南,介绍如何开始创建 SQL Server Management Studio 17 (SSMS) 扩展。如果你不知道从何开始创建自己的扩展,那么你来对地方了。它将涵盖从头开始开发 SSMS 扩展所需开发环境的设置基础知识。
如果你想跳过我的悲惨故事以及我创建本教程的原因,你可以直接跳到要求部分,开始项目。
注意:本教程是为扩展最新版本的 SQL Server Management Studio 而创建的,在撰写本文时,最新版本是 17.6。扩展旧版本 SSMS 的过程应该也类似。
背景
我当时只想创建一个简单的 SQL Server Management Studio (SSMS) 扩展,以简化每周重复几次的一个简单操作。我心想,这能有多难……这只是一个简单的操作。你在菜单的某个地方点击一个按钮,然后就会发生一些简单的事情。我以为这会很容易,而且直接了当。这会是一个有趣的项目。我知道 SSMS 扩展的存在。虽然数量不多,远不及 Visual Studio 提供的扩展数量,但它们确实存在。一定有一些不错的初学者教程……一定有一些体面的文档解释了所有细节,并提供了一些示例代码。我只需要谷歌搜索一下,几个小时后我就可以开始工作了。
我真是大错特错!网上什么都没有。关于创建 SSMS 扩展的文档不存在。现有的教程寥寥无几,而且不幸的是,其中大部分都太旧了,讲解的是不再受支持的旧的 Add-in 系统,所以它们大多是无用的。所有能找到的只是零散的知识和散布在互联网上的经验记录。
好吧,我想,回到原点。我知道 Visual Studio 有很多扩展,而且我知道 SSMS 17 是基于 Visual Studio 2015 隔离 Shell 的。那么,也许 Visual Studio 扩展的教程可以帮助我入门。嗯,也不完全是。一旦你开始并运行了,它们会有帮助,但没有任何教程能向我解释如何启动一个 SSMS 扩展项目。
希望,对其他人来说,这就是那个教程……
正如你下面将看到的,这个过程远非轻松或直观,而且会很快变得非常棘手。那么,我们开始吧。
要求
你开始所需的一切就是安装了 Visual Studio 和 Visual Studio 可扩展性工具。这将使你在创建新的 Visual Studio 项目时能够使用 VSIX 项目模板。如果你已安装 Visual Studio 但没有可扩展性工具,可以通过控制面板中的“程序和功能”将它们添加到你当前的安装中。
我推荐使用 Visual Studio 2015 来完成这个任务,因为我在使用 Visual Studio 2017 时遇到了引用和依赖项方面的问题。由于 SSMS 17.X 是基于 Visual Studio 2015 隔离 Shell 的,我认为这可能是这类项目更兼容的版本,所以我决定坚持使用它。不过,如果你是经验丰富的用户,使用 Visual Studio 2017 应该也可以。
文件 > 新建项目
为了开始我们的扩展,我们将首先创建一个简单的 Visual Studio 扩展,然后将其移植到 SSMS。要创建新的 Visual Studio 扩展,请先转到文件 > 新建 > 项目…,然后选择已安装 > 模板 > Visual C# > 可扩展性 > VSIX 项目,并将其命名为 HelloWorldSsmsExtension
。
为了给我们的扩展添加一些功能,我们将右键单击解决方案资源管理器中的项目,选择添加 > 新项…,然后选择Visual C# 项 > 可扩展性 > VSPackage > 自定义命令,并将其命名为 HelloWorldCommand
。
一旦 Visual Studio 创建了命令,在 Visual Studio 扩展方面,我们基本上就准备好了。生成项目,在 HelloWorldCommand
类(HelloWorldCommand.cs)的 MenuItemCallback
方法中的某个位置设置一个断点,然后按“启动”。
应该会启动一个新的 Visual Studio 实例(实验实例),我们可以在“工具”菜单中找到我们的命令,名为“Invoke HelloWorldCommand”(如果这是你第一次执行此操作,你可能需要通过 Visual Studio 的实验实例初始设置向导)。如果我们现在尝试单击该命令,应该会看到原始实例中的断点被命中,并且会显示一条消息,作为我们命令代码的结果。
我们可以关闭消息框和实验实例,将消息更改为显示“Hello World from the SSMS extension!”,然后再次尝试查看更改是否生效。启动调试会话,然后再次单击命令。现在应该会显示新消息。
很简单,对吧?嗯,这就是简单部分结束的地方,问题就从这里开始了。
移植到 SSMS
好的,现在让我们尝试将扩展移植到 SSMS。当我们开始调试时,我们想做的第一件事就是启动 SSMS 而不是 Visual Studio。要做到这一点,我们首先需要关闭 Visual Studio,然后再次以管理员身份启动它(我们需要这样做,因为 Visual Studio 需要移动一些文件,要做到这一点,我们需要管理员权限)。一旦进入 Visual Studio,再次打开解决方案,右键单击解决方案资源管理器中的项目,然后选择“属性”。
在“调试”选项卡中,将外部程序更改为 SQL Server Management Studio 而不是 Visual Studio,方法是更改属性“启动外部程序”为
C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn\ManagementStudio\Ssms.exe
此外,一个好主意(但不是必需的)是在“命令行参数”字段中设置 /log
参数,使 SSMS 将其活动记录到文件中,这有助于我们以后进行故障排除。日志文件(ActivityLog.xml)可以在以下位置找到:
C:\Users\Username\AppData\Roaming\Microsoft\AppEnv\14.0
好了,现在我们已经设置好了一切,我们可以尝试再次启动扩展。如果我们在 Visual Studio 中开始调试,现在应该会看到当我们点击“启动”按钮时,SQL Server Management Studio 会启动。等待它完全启动,然后关闭“连接到服务器”窗口。好的,SQL Server Management Studio 确实启动了,但我们在工具菜单中找不到我们的命令。
此外,如果我们回到 Visual Studio,可以看到我们的断点并未处于活动状态。
这里有问题。嗯,SSMS 在启动时会在以下位置查找扩展:
C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn\ManagementStudio\Extensions\
如果我们去那个位置,可以看到我们的扩展不在那里。如果我们要让 SSMS 加载我们的扩展,它就必须在这里。所以,我们来修复这个问题。再次打开项目属性窗口,这次打开 VSIX 选项卡。在其中,勾选“将 VSIX 内容复制到以下位置”,并在文本框中输入 SSMS 扩展文件夹。在位置的末尾,我们将添加一个以我们的扩展命名的文件夹,所以完整的位置应该是这样的:
C:\Program Files
(x86)\Microsoft SQL Server\140\Tools\Binn\ManagementStudio\Extensions\HelloWorldSsmsExtension
再次开始调试,然后等待 SSMS 打开。看起来我们的扩展仍然不起作用,但如果我们查看扩展文件夹,可以看到为它创建的新文件夹。那么为什么我们的扩展没有加载呢?如果你查看活动日志,可以看到 SSMS 正在导入 .pkgdef 文件,但仅此而已。一切都应该没问题,但实际上不是。
解决方法 #1
你看,微软的 SSMS 团队维护着一个所有允许 SSMS 加载的扩展列表。它只是一个包 GUID 的列表,带有这些 GUID 标识的包(扩展)可以毫无问题地加载到 SSMS 中。其他任何东西都不会被 SSMS 加载。我在某个地方读到,这样做的原因是因为 SSMS 团队不希望 SSMS 被不相关的扩展淹没。另一个地方说,微软最终应该会支持 SSMS 扩展,以后就不会有问题了。但在此之前,如果我们希望我们的扩展能够加载,我们就必须使用一个解决方法来解决这个问题(或者你可以联系微软的 SSMS 团队,请求他们将你的扩展列入白名单)。那么,这个包 GUID 是什么,我们如何解决这个问题呢?
包 GUID 只是你扩展的一个唯一标识符,它可以在我们项目的两个地方找到。第一个是在 HelloWorldCommandPackage
类中:
public const string PackageGuidString = "c626db04-880e-41bf-b780-ab5804d1ea2f";
另一个是在 HelloWorldCommandPackage.vsct 文件中:
<GuidSymbol name="guidHelloWorldCommandPackage" value="{c626db04-880e-41bf-b780-ab5804d1ea2f}" />
为了解决验证问题,我们需要明确告诉 SSMS 在尝试启动时跳过此包的验证。要做到这一点,不幸的是,我们需要稍微编辑一下注册表。要打开注册表编辑器,我们首先需要通过按键盘上的Windows + R键打开“运行”窗口。在其中,键入regedit然后按 Enter。
当注册表编辑器打开时,转到:
HKEY_CURRENT_USER\Software\Microsoft\SQL Server Management Studio\14.0
在其中,检查是否存在“Packages”键,如果不存在,则通过右键单击14.0然后选择新建 > 键来创建它。
在(新创建的)Packages 键中,创建一个新的键,其名称为用花括号括起来的扩展的包 GUID。键名应如下所示:
{c626db04-880e-41bf-b780-ab5804d1ea2f}
在新键(以包 GUID 为名称的那个)中,通过右键单击该键并选择新建 > DWORD (32 位) 值来添加一个新的DWORD (32 位) 值。
创建该值后,右键单击它并将其重命名为 SkipLoading
。在此之后,我们的最后一步是通过右键单击新值(SkipLoading
)并从上下文菜单中选择修改…来将其值设置为1。
只需在“数值数据”字段中输入1,然后按 Enter 即可。
现在你可以关闭注册表编辑器了。如果我们正确完成了这一步,我们的扩展应该最终会加载到 SSMS 中,当我们启动调试时。回到 Visual Studio 尝试一下。太好了,我们的命令在这里并且工作正常。此外,当我们尝试运行它时,我们可以看到我们的 Visual Studio 断点被命中,最终一切都好了。
嗯,其实不是!
解决方法 #2
问题是……如果 SSMS 正确加载了扩展并且我们通过运行它来初始化它,那么 SSMS 将会从注册表中删除 SkipLoading
值,而我们的扩展在下次启动 SSMS 时将无法正常工作。这很容易检查。再次打开注册表编辑器,亲自看看你的包键中是否缺少 SkipLoading
值。
这仅在 SSMS 尝试初始化扩展时发生,默认情况下,这在你首次运行它时发生。如果你再次添加 SkipLoading
,一切都会再次正常工作,你的扩展将在你启动时加载。如果你在不运行命令的情况下关闭 SSMS,那么 SSMS 将不会初始化你的扩展,并且该值不会从注册表中删除。但对于我们尝试开发命令来说,不运行不是一个选项,并且每次运行之间在注册表编辑器中重新设置值太耗时了。为此,我们需要另一个解决方案和另一个解决方法。我们将打开 HelloWorldCommandPackage
类并在其中添加以下方法:
// Call this method from the Initialize method
// to add the SkipLoading value back to the registry
// 2 seconds after it’s removed by SSMS
private void AddSkipLoading()
{
var timer = new Timer(2000);
timer.Elapsed += (sender, args) =>
{
timer.Stop();
var myPackage = UserRegistryRoot.CreateSubKey(@"Packages\{" + PackageGuidString + "}");
myPackage?.SetValue("SkipLoading", 1);
};
timer.Start();
}
注意:要使用它,我们还需要导入 System.Timers
命名空间:
using System.Timers;
我们在 Initialize()
方法结束之前调用该方法。
/// <summary>
/// Initialization of the package; this method is called right after the package is sited,
/// so this is the place
/// where you can put all the initialization code that rely on services provided by VisualStudio.
/// </summary>
protected override void Initialize()
{
HelloWorldCommand.Initialize(this);
base.Initialize();
AddSkipLoading();
}
最终结果应该是这样的:
我们添加的方法会在 Initialize
方法完成 2 秒后恢复我们包的 SkipLoading
键,并且 SSMS 会从注册表中删除该值。我们添加 2 秒的延迟是为了确保我们在 SSMS 有时间删除它之前不会恢复该值。这将使我们能够最终开始开发我们的扩展。如果我们启动 SSMS 但不运行命令,一切都还好,因为扩展没有初始化,SSMS 也没有删除 SkipLoading
值。如果我们运行命令并且扩展初始化了,那么代码将在 SSMS 删除该值后重新设置它。所以下次我们启动 SSMS 时,我们的扩展应该可以正常加载。我们可以通过在 Visual Studio 中点击“启动”来尝试这一点,并最终开始开发我们全新的扩展。
分享你的扩展
假设你已经完成了扩展的开发,并且想把它发送给朋友或同事。这很简单。只需将位于 SSMS 扩展文件夹中的 HelloWorldSsmsExtension 文件夹压缩起来,发送给他们,然后告诉他们将其放在相同的位置。但是,如果你不想通过告诉他们如何进行注册表编辑器黑客才能让他们第一次获得你的扩展(请记住,扩展本身将在加载后设置 SkipLoading
值),从而让自己尴尬呢?
解决方法 #3
嗯,事实证明,我们可以让 SSMS 本身来设置这个值。简单回顾一下……如果你的扩展未能通过 SSMS 验证,那么在 SSMS 启动时它将不会被加载。但是,为了让 SSMS 知道你的扩展是否在白名单中,它至少必须读取 .pkgdef 文件(其中包含包 GUID 和其他配置信息),该文件在你生成扩展时生成。在其中,我们可以放置一个指令,让 SSMS 在启动时自动加载扩展。有了这个指令,SSMS 在加载扩展时就不会默默地失败,而是会抛出一个错误,我们可以用它来设置 SkipLoading
值。在错误消息中,SSMS 会抱怨扩展有问题,并询问你是否应该继续显示错误。如果你选择否,那么 SSMS 将自己添加 SkipLoading
值,但不幸的是,在启动时不会加载扩展。但是,下次你启动 SSMS 时,该值就会在那里,从那时起,扩展本身将负责其在注册表中的存在。换句话说,你只需要在选择“否”后重新启动 SSMS,然后一切都会正常。
要将指令添加到 .pkgdef 文件,你只需要将带有 NoSolution
上下文 GUID 的 ProvideAutoLoad
属性添加到你的 HelloWorldCommandPackage
类。
[ProvideAutoLoad(UIContextGuids80.NoSolution)]
最终代码应该是这样的:
这将强制 SSMS 在启动时加载你的扩展,它会失败,并询问是否应该继续显示错误。你选择“否”,重新启动 SSMS,你应该会看到你的扩展已加载并正常工作。只有 SSMS 的第一次启动是有问题的。但嘿,这是我们目前最好的选择。所以,当你把你的扩展发送给朋友时,只需告诉他们第一次选择“否”并重新启动 SSMS,从那时起一切都会正常。
结束语
这就是你需要了解的关于开始开发 SQL Server Management Studio 17 扩展的所有内容。对于旧版本的 SSMS,过程应该也类似。如果有人有其他有用的技巧,请随时与我联系,我将尝试将其纳入本教程。HelloWorldSsmsExtension
的完整代码也可以从 github 下载,地址如下:
注意:如果你想尝试附带的项目(或 github 上的项目),你首先需要在项目属性中设置“启动外部程序”属性。
就是这样……
参考文献和进一步阅读
以下是我为帮助我入门而使用的资源,它们值得深入研究以获取更多信息:
- https://www.sqlservercentral.com/Forums/1802009/Developing-Extensions-for-SSMS-2016(本教程主要基于此帖子)
- https://msdn.microsoft.com/en-us/library/cc138589.aspx
- http://dotneteers.net/blogs/divedeeper/archive/2008/03/23/LVNSideBar1.aspx
- https://github.com/ErikEJ/SqlCeToolbox/issues/73
- https://github.com/ErikEJ/SqlCeToolbox/issues/513
还有一些 github 项目,你可以用它们来获取更多想法: