はじめに
SQL Serverを使うためにSQLServer Management Studioをよく利用します。
最近プラグインを作成できることを知ったため、作成方法を確認しました。
インストール
今回は以下の環境で試しています。
- Windows 11
- Visual Studio 2019
- SQL Server Management studio 22
Visual Studioをインストールしたら、VISXのプロジェクトテンプレートからプロジェクトを作成
プロジェクトを右クリック > 追加 > 新しい項目
ExtensibilityからCommandを選択
デバッグとビルドの設定
プロジェクトを右クリック > デバッグ から、開始操作の外部プログラムの開始を以下に変更
C:\Program Files\Microsoft SQL Server Management Studio 22\Release\Common7\IDE\SSMS.exe
2019でも試していたのですが、コマンドライン引数は削除する必要がありました。
次にビルドの成果物を配置するパスを指定します。
Visual Studioのプロジェクトを右クリック > プロパティ
C:\Program Files\Microsoft SQL Server Management Studio 22\Release\Common7\IDE\Extensions\SSMSExtentionSample
Visual Studioを管理者として実行
デバッグ実行するとSSMSが起動
ツールの項目に Invoke Command1という項目が追加されています。
作成した通りにウィンドウが表示されました!
ビルド設定も行っているため、ローカルのSSMSにもdllファイルが配置されています。
C:\Program Files\Microsoft SQL Server Management Studio 22\Release\Common7\IDE\Extensions\SSMSExtentionSample
コードサンプル
using Microsoft.VisualStudio.Shell;
using System;
using System.Runtime.InteropServices;
using System.Threading;
using Task = System.Threading.Tasks.Task;
namespace SSMSExtentionSample
{
/// <summary>
/// このアセンブリによって公開されるパッケージを実装するクラスです。
/// </summary>
/// <remarks>
/// Visual Studioの有効なパッケージと見なされるための最低限の要件は、
/// IVsPackageインターフェースを実装し、それ自体をシェルに登録することです。
/// </remarks>
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[Guid(SSMSExtentionSamplePackage.PackageGuidString)]
[ProvideMenuResource("Menus.ctmenu", 1)]
public sealed class SSMSExtentionSamplePackage : AsyncPackage
{
/// <summary>
/// SSMSExtentionSamplePackage GUID string.
/// </summary>
public const string PackageGuidString = "c16db76a-****-****-****-*******853b7";
#region Package Members
/// <summary>
/// パッケージの初期化。パッケージがサイトに配置された直後に呼び出されます。
/// VisualStudioが提供するサービスに依存するすべての初期化コードを配置します。
/// </summary>
/// <param name="cancellationToken">初期化キャンセルを監視するためのキャンセルトークン。</param>
/// <param name="progress">進捗アップデートのためのプロバイダー。</param>
/// <returns>パッケージの非同期作業を表すタスク。</returns>
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
{
// 非同期で初期化された場合、この時点ではカレントスレッドはバックグラウンドスレッドである可能性があります。
// UIスレッドを必要とする初期化は、UIスレッドに切り替えた後で行います。
// UIスレッドに切り替える
await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
// Command1の初期化
await Command1.InitializeAsync(this);
}
#endregion
}
}
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using System;
using System.ComponentModel.Design;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Task = System.Threading.Tasks.Task;
namespace SSMSExtentionSample
{
/// <summary>
/// Command handler
/// </summary>
internal sealed class Command1
{
/// <summary>
/// Command ID.
/// </summary>
public const int CommandId = 0x0100;
/// <summary>
/// Command menu group (command set GUID).
/// </summary>
public static readonly Guid CommandSet = new Guid("56d38cb0-****-****-****-****ffe6f6f");
/// <summary>
/// VS Package that provides this command, not null.
/// </summary>
private readonly AsyncPackage package;
/// <summary>
/// Initializes a new instance of the <see cref="Command1"/> class.
/// Adds our command handlers for menu (commands must exist in the command table file)
/// </summary>
/// <param name="package">Owner package, not null.</param>
/// <param name="commandService">Command service to add command to, not null.</param>
private Command1(AsyncPackage package, OleMenuCommandService commandService)
{
this.package = package ?? throw new ArgumentNullException(nameof(package));
commandService = commandService ?? throw new ArgumentNullException(nameof(commandService));
var menuCommandID = new CommandID(CommandSet, CommandId);
var menuItem = new MenuCommand(this.Execute, menuCommandID);
commandService.AddCommand(menuItem);
}
/// <summary>
/// Gets the instance of the command.
/// </summary>
public static Command1 Instance
{
get;
private set;
}
/// <summary>
/// Gets the service provider from the owner package.
/// </summary>
private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider
{
get
{
return this.package;
}
}
/// <summary>
/// Initializes the singleton instance of the command.
/// </summary>
/// <param name="package">Owner package, not null.</param>
public static async Task InitializeAsync(AsyncPackage package)
{
// Switch to the main thread - the call to AddCommand in Command1's constructor requires
// the UI thread.
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);
OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService;
Instance = new Command1(package, commandService);
}
/// <summary>
/// This function is the callback used to execute the command when the menu item is clicked.
/// </summary>
private void Execute(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Command1 Execute method started.");
try
{
ThreadHelper.ThrowIfNotOnUIThread();
string message = string.Format(CultureInfo.CurrentCulture, "Inside {0}.MenuItemCallback()", this.GetType().FullName);
string title = "アドベントカレンダーSSMS拡張機能の実装テスト";
// Show a message box to prove we were here
VsShellUtilities.ShowMessageBox(
this.package,
message,
title,
OLEMSGICON.OLEMSGICON_INFO,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error during Command1 Execute: {ex.ToString()}");
}
}
}
}
クエリウィンドウを開いた際に、始めに定型の設定を行うクエリを自動実行するようなプラグインの作成を検討していますが、ウィンドウ表示のイベントをうまくキャプチャできず、今回はここまでです。
環境構築はできたので、今後開発を進めてみようと思います。
参考
下記の記事を参考にしました。












