Search code examples
c#extensibilityssms-2012ssms-addin

SSMS Extensibility Project - howto research/debug


In the vein of this answer regarding creation of an SSMS Extension:

namespace SSMSAddin
{
    using System;
    using System.IO;
    using Extensibility;
    using EnvDTE;
    using EnvDTE80;
    using Microsoft.VisualStudio.CommandBars;
    using Microsoft.SqlServer.Management.UI.VSIntegration;
    using System.Windows.Forms;

    public class Connect : IDTExtensibility2, IDTCommandTarget
    {
        private DTE2 applicationObject;
        private CommandEvents executeSqlEvents;
        private AddIn addInInstance;

        public Connect() { }

        public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
        {
            this.applicationObject = (DTE2)application;
            this.addInInstance = (AddIn)addInInst;

            this.applicationObject = (DTE2)application;
            this.executeSqlEvents = this.applicationObject.Events.CommandEvents["{52692960-56BC-4989-B5D3-94C47A513E8D}", 1];
            this.executeSqlEvents.BeforeExecute += this.ExecuteSqlEventsBeforeExecute;

            if (connectMode == ext_ConnectMode.ext_cm_UISetup)
            {
                var contextGUIDS = new object[] { };
                var commands = (Commands2)this.applicationObject.Commands;
                string toolsMenuName = "Tools";

                //Place the command on the tools menu.
                //Find the MenuBar command bar, which is the top-level command bar holding all the main menu items:
                CommandBar menuBarCommandBar = ((CommandBars)this.applicationObject.CommandBars)["MenuBar"];

                //Find the Tools command bar on the MenuBar command bar:
                CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName];
                CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;

                //This try/catch block can be duplicated if you wish to add multiple commands to be handled by your Add-in,
                //  just make sure you also update the QueryStatus/Exec method to include the new command names.
                try
                {
                    //Add a command to the Commands collection:
                    Command command = commands.AddNamedCommand2(this.addInInstance, "SSMSAddin", "SSMSAddin", "Executes the command for SSMSAddin", true, 59, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);

                    //Add a control for the command to the tools menu:
                    if ((command != null) && (toolsPopup != null))
                    {
                        command.AddControl(toolsPopup.CommandBar, 1);
                    }
                }
                catch (ArgumentException)
                {
                    //If we are here, then the exception is probably because a command with that name
                    //  already exists. If so there is no need to recreate the command and we can 
                    //  safely ignore the exception.
                }
            }
        }

        private void ExecuteSqlEventsBeforeExecute(string guid, int id, object customin, object customout, ref bool canceldefault)
        {
            try
            {
                Document document = ((DTE2)ServiceCache.ExtensibilityModel).ActiveDocument;
                var textDocument = (TextDocument)document.Object("TextDocument");

                string queryText = textDocument.Selection.Text;

                if (string.IsNullOrEmpty(queryText))
                {
                    EditPoint startPoint = textDocument.StartPoint.CreateEditPoint();
                    queryText = startPoint.GetText(textDocument.EndPoint);
                }

                DateTime now = DateTime.Now;
                // string server = 
                string folderPath = string.Format(@"B:\SSMS Queries\{0}", now.ToString("yyyyMMdd"));
                string fileName = now.ToString("yyyyMMdd-HHmmss") + ".sql";
                Directory.CreateDirectory(folderPath);
                string fullPath = Path.Combine(folderPath, fileName);
                File.WriteAllText(fullPath, queryText);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

        public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom) { }

        public void OnAddInsUpdate(ref Array custom) { }

        public void OnStartupComplete(ref Array custom) { }

        public void OnBeginShutdown(ref Array custom) { }

        public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText, ref vsCommandStatus status, ref object commandText)
        {
            if (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
            {
                if (commandName == "SSMSAddin.Connect.SSMSAddin")
                {
                    status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
                    return;
                }
            }
        }

        public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)
        {
            handled = false;
            if (executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
            {
                if (commandName == "SSMSAddin.Connect.SSMSAddin")
                {
                    var document = ((DTE2)ServiceCache.ExtensibilityModel).ActiveDocument;
                    if (document != null)
                    {
                        //replace currently selected text
                        var selection = (TextSelection)document.Selection;
                        selection.Insert(
@"Welcome to SSMS. This sample is brought to you by

SSMSBoost add-in team

Check www.ssmsboost.com for updates.",
(Int32)EnvDTE.vsInsertFlags.vsInsertFlagsContainNewText);
                    }

                    handled = true;
                    return;
                }
            }
        }
    }
}

The code adds an event that fires before each SQL Execute in SSMS 2012... I hit F5, the sql query runs, but before it runs it saves a copy of the query to B:\SSMS Queries\20130225\083000.sql.

What's missing from this? I want to add options for the Connection/Databse used, say for example B:\SSMS Queries\Localhost\Northwind\20130225\083000.sql (Just an example).

What I would normally do... Breakpoint, step through, inspect objects, etc... This is a addon though. Class library. You can't breakpoint/step through a library...

How do I put a breakpoint into a class library that gets loaded into SSMS/Visual Studio so that I can research? Or what would be a good resource for this kind of tinkering? Somewhere in object customin, object customout is the information I want to tinker with.


Solution

  • Actually documenting my answer (after forgetting multiple times). Found my answer in a combination of SSMSBoost and TSQLTidy.Blogspot (and Martin Smith comment)

    1) Set SSMS as the startup project inside Debug Profile. File location for SSMS2012:
        C:\Program Files (x86)\Microsoft SQL Server\110\Tools\Binn\ManagementStudio\SSMS.exe
    2) I've created 2 Addin files:
        MyAddin.Debug.Addin
        MyAddin.Release.Addin
        (Contents updated as listed below)
    3) Add postbuild event to create directory if not exists
    4) Add postbuild event to copy Addin from ProjectDir to MSeventShared
    5) Turn off P-Invoke warnings. Press CRLT + ALT + E - In Managed Debugging Assistants, find PInvokeStackImbalance, untick it.
    

    Addin Files (Release changes the DLL location from Project directory to MSEnvShared\Admin folder):

    <?xml version="1.0" encoding="UTF-16" standalone="no"?>
    <Extensibility xmlns="http://schemas.microsoft.com/AutomationExtensibility">
        <HostApplication>
            <Name>Microsoft SQL Server Management Studio</Name>
            <Version>*</Version>
        </HostApplication>
        <Addin>
            <FriendlyName>MyAddin.</FriendlyName>
            <Description>MyAddin Description.</Description>
            <Assembly>C:\Projects\MyAddin\bin\Debug\MyAddin.dll</Assembly>
            <FullClassName>SSMSAddin.Connect</FullClassName>
            <LoadBehavior>0</LoadBehavior>
        <CommandPreload>1</CommandPreload>
        <CommandLineSafe>0</CommandLineSafe>
         </Addin>
    </Extensibility>
    

    Post Build Event:

    cmd /x /c mkdir "C:\ProgramData\Microsoft\MSEnvShared\Addins\"
    cmd /C copy "$(ProjectDir)MyAddin.$(ConfigurationName).Addin" "C:\C:\ProgramData\Microsoft\MSEnvShared\Addins\MyAddin.Addin"