code it

Martins Tech Blog

Erstellung eines Addins für SQL Server Management Studio

So wie man Addins für Visual Studio erzeugen kann, ist es auch möglich das SQL Server Management Studio (SSMS) mit eigenen Addins zu erweitern. Allerdings sind die Schritte und verwendbaren Objekte weitgehend undokumentiert. Aber es ist möglich und es funktioniert!

Erstellung eines neuen Addin-Projekts in Visual Studio

Die Erstellung eines SSMS-Addins ähnelt sehr dem Vorgehen der Erstellung eines Addins für Visual Studio. Man erzeugt zunächst ein neues Addin-Projekt (Other Project Types -> Extensibility -> Visual Studio Add-in).

Daraufhin öffnet sich ein Assistent. Nachdem man diesen erfolgreich durchlaufen hat, sollte das Projekt ungefähr wie im Bild dargestellt aussehen.

Registrierung des Addins

Die im Projekt enthaltenen ".AddIn"-Dateien können nun gelöscht werden. In Visual Studio 2005 und auch 2008 gibt es einen vergleichsweise neuen Weg, Addins ohne Eingriffe in die Windows Registry zu registrieren. SSMS scheint diesen allerdings nicht zu verwenden. Um das Addin zu registrieren muss also ein neuer Schlüssel in der Registry angelegt werden. Der richtige Platz dafür ist HKLM\SOFTWARE\Microsoft\Microsoft SQL Server\90\Tools\Shell\Addins bzw. HKCU\SOFTWARE\Microsoft\Microsoft SQL Server\90\Tools\Shell\Addins. Hier ist ein Schlüssel mit dem kompletten Namen der Klasse (inklusive Namespace) anzulegen, die das Interface IDTExtensibility2 implementiert. Wenn das Addin-Projekt mit dem Assistenten angelegt wurde, ist das die Klasse Connect. Unterhalb des Schlüssels ist nun ein DWORD-Wert mit dem Namen LoadBehavior und dem Wert 1 anzulegen. Diversen Ressourcen im englischsprachigen Netz zufolge kann man hier noch Werte bzgl. des Speicherortes (SatelliteDllName, SatelliteDllPath), der Addin-Beschreibung (ProductDescription, ProductName) oder des Verhaltens (CommandLineSafe, LoadBehavior) anlegen, allerdings hab ich im SSMS noch keinen Menüpunkt gefunden unter dem diese Informationen wieder sichtbar wären. Damit erübrigt sich meines Erachtens diese Mühe und der erzeugte Registry-Zweig sollte ungefähr wie folgt aussehen:

 

Achtung: Wird das SSMS jetzt gestartet, erscheint eine Fehlermeldung, dass ein Addin nicht korrekt geladen werden konnte und die Einstellung wird wieder zurückgesetzt und das Addin wird so lange nicht mehr geladen, bis die oben genannten Schritte wiederholt wurden.

COM-Sichtbarkeit

Das SSMS nutzt unter der Haube anscheinend noch sehr viel COM. Aus diesem Grund muss das Assembly "COM-visible" gemacht werden. Den entsprechenden Haken findet man im Assembly Information-Dialog.

 

Der Debugger sorgt von nun an beim Debuggen selbst für die Registrierung des Addins. Soll das Addin auf einem anderen Rechner zum Einsatz kommen, muss dafür gesorgt werden, dass die Assembly dort mit regasm.exe bzw. von einem Setup-Projekt registriert wird.

Coding 

Nun geht es an's Programmieren. Zusätzlich zu den Microsoft.SqlServer-Assemblies ist die  Assembly SqlWorkBench.Interfaces (C:\Program Files\Microsoft SQL Server\90\Tools\Binn\VSShell\Common7\IDE\SqlWorkbench.Interfaces.dll) für die Interaktion mit dem SSMS wichtig und es sollte auch auf diese eine Referenz hinzugefügt werden.

 

In der OnConnection-Methode des Addins kann man sich nun eine Referenz auf den Objekt-Explorer holen und damit einen Event wenn sich der ausgewählte Knoten ändert.

 

IObjectExplorerService objectExplorer = ServiceCache.GetObjectExplorer(); 
IObjectExplorerEventProvider provider = (IObjectExplorerEventProvider)objectExplorer
   .GetService(typeof(IObjectExplorerEventProvider));
provider.SelectionChanged +=new NodesChangedEventHandler(Provider_SelectionChanged);

Im so registierten Eventhandler kann man nun direkt in das Kontextmenü eingreifen und neue Einträge mit den gewünschten Funktionen hinzufügen.

 

Das hinzugefügte Objekt muss das Interface IWinformsMenuHandler implementieren und von ToolsMenuItemBase abgeleitet sein. Dabei stellt die Methode GetMenuItems die eigentlichen Kontextmenüeinträge zur Verfügung.

 

Debugging 

Zum Debuggen ändert man die Projekteigenschaften so, dass das SSMS gestartet wird:

 

MSN Live Messenger erweitern

Manchmal möchte man eben etwas mehr Funktionalität.... und manchmal geben einem die Programme auch die Möglichkeit, diese Funktionalität selbst hinzuzufügen. So auch beim Messenger. Das Ganze ist etwas tricky, aber wenn man weiß an welchen Stellen man aufpassen muss und was dann zu tun ist, dann ist es doch recht einfach.

Die erste Hürde besteht darin, Messenger dazu zu überreden, AddIns zu akzeptieren. Hierzu erstellt man in der Registry unter HKEY_CURRENT_USER\Software\Microsoft\MSNMessenger einen neuen Wert AddInFeatureEnabled, den man auf 1 setzt.

 

Das sorgt dafür, dass ab dem nächsten Start von Messenger unter Optionen auf wundersame Weise plötzlich die Seite "Add-ins" verfügbar ist

 

Nachdem nun geklärt ist, wo das Add-in geladen werden kann, muss nur noch eines entwickelt werden. Dazu erzeugt man sich eine neue Klassenbibliothek und in dieser Klassenbibliothek eine Klasse. Im Anschluss daran fügt man eine Referenz auf die MessengerClient.dll hinzu. Diese befindet sich im Verzeichnis der Messenger-Installation und beinhaltet unter anderem eine Schnittstellendefinition IMessengerAddIn. Die neu erstellte Klasse muss nun eben dieses Interface implementieren. Dadurch erhält die Klasse eine Funktion Initialize, die ein Messenger-Objekt als Parameter erhält.

namespace UniqueSoftware.LiveMessenger
{
 public class HeadlineTicker : IMessengerAddIn
 {
     private MessengerClient _client = null;

     public void Initialize(MessengerClient messenger)
     {
         Client = messenger;
         initAddin();
     }

     private void initAddin()
     {
     }

     private MessengerClient Client
     {
         get { return this._client; }
         set { this._client = value; }
     }
 }
}

Mit dem hier erhaltenen Messenger-Objekt kann auf einige Funktionen des Messengers zugegriffen werden. Die Eigenschaft LocalUser bietet die Möglichkeit des schreibgeschützten Zugriffes auf die Informationen des aktuellen Nutzers; AddInProperties gibt dem AddIn lesenden und schreibenden Zugriff auf einige Eigenschaften. Dazu stehen noch einige Ereignisse und Methoden zur Verfügung, die das Empfangen und Schreiben von Nachrichten ermöglichen.

 

Das wohl am häufigsten genannte Beispiel - vermutlich auch weil im Optionen-Dialog des Messengers der Text "...Sie können ein Add-in erhalten, welches mit Ihren Freunden chattet, wenn Sie nicht verfügbar sind..." steht, ist es, dass das Add-in als "Anrufbeantworter" fungiert. Ich fand es interessanter, ein Add-in zu entwickeln, das automatisch in einem bestimmten Intervall die Headline-Message ändert. Das meiste dafür ist mit dem oben gezeigten Codesnippet auch schon getan. Es fehlt noch ein Timer-Objekt, das beim Start initialisiert wird und ein Array mit Headline-Texten aus dem im Elapsed-Event ein Eintrag gewählt und in die Eigenschaft AddInProperties.PersonalStatusMessage damit zu befüllen (auch wenn LocalUser.PersonalStatusMessage meines Erachtens hier logischer gewesen wäre)

private Timer _timer = null;
private List<string> _statusmessages = null;
private bool _randomize = true;
private int _currentindex = -1;

private void initAddin()
{
 Client.AddInProperties.Creator = "Martin Hey";
 Client.AddInProperties.FriendlyName = "HeadlineTicker AddIn";
 Client.AddInProperties.Description = "This addin rotates the headline in Messenger.";
 this._statusmessages = new List<string>();
 this._statusmessages.Add("Viele Köche verderben den Brei.");
 this._statusmessages.Add("Man soll den Tag nicht vor dem Abend loben.");
 this._statusmessages.Add("Lebe deinen Traum.");

 this._timer = new Timer();
 this._timer.AutoReset = true;
 this._timer.Interval = 10000;
 this._timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
 this._timer.Start();
}

private List<string> StatusMessages
{
 get
 {
     return this._statusmessages;
 }
}

void timer_Elapsed(object sender, ElapsedEventArgs e)
{
 string newmessage;
 if (StatusMessages.Count > 0)
 {
     if (!_randomize)
     {
         _currentindex++;
         if (_currentindex > (StatusMessages.Count - 1))
         {
             _currentindex = 0;
         }
     }
     else
     {
         Random random = new Random();
         _currentindex = random.Next(0, (StatusMessages.Count - 1));
     }
     newmessage = StatusMessages[_currentindex];
  }
  else
  {
      newmessage = string.Empty;
  }
  try
  {
      Client.AddInProperties.PersonalStatusMessage = newmessage;
  }
  catch (Exception ex)
  {
      //throw;
  }
}
Wie auch immer... der Effekt ist der gleiche. Die Headline rotiert! Schön wäre es jetzt noch, die Informationen von außen konfigurierbar zu machen, aber das ist etwas umfangreicher, da das Add-in im Security-Kontext InternetZone läuft und damit minimale Berechtigungen im lokalen System hat.

Nun da das Add-in fertig ist, muss es noch geladen werden. Dabei ist zunächst unbedingt darauf zu achten, dass die Assembly genau so benannt wird, wie eben im Quellcode der Namespace und die Klasse benannt wurden.Wer darauf nicht achtet, wird beim Versuch das Add-in in den Messenger zu laden eine entsprechende Meldung erhalten. Die Auswahl selbst erfolgt über "Zu Messenger hinzufügen".

 

Danach kann das Add-in automatisch bei Statuswechsel aktiviert werden (sinnvoll bei automatischer Messagebeantwortung) oder manuell über das "Online"-Menü. Etwas schade finde ich, dass immer nur ein Add-in zu einer Zeit aktiviert sein kann und man so sehr viel Konfigurationsaufwand hat, wenn man mehrere Funktionen implementieren möchte.