Sunday, September 17, 2006 5:03 PM
bart
Your first Windows Live Messenger add-in
Introduction
Did you know that the new Windows Live Messenger comes with a add-in support? It's not enabled by default and only intended to gather first feedback from developers but hey, you know what, aren't we all developers? Time to try out! In this post, you'll learn the basics of Windows Live Messenger add-in development.
Getting started
As I said in the introduction, "it's not enabled by default". So what'bout enabling it in the first place? To do this, exit Windows Live Messenger (i.e. msnmsgr.exe shouldn't appear in the process list anymore - [PS] gps -p msnmsgr | stop-process) and open up the Registry Editor. Go to HKCU\SOFTWARE\Microsoft\MSNMessenger and add a DWORD value named AddInFeatureEnabled set to 1. Or in PS-talk (in a "verbosity is your enimy" [J. Snover] mood):
PS C:\Users\Bart> cd HKCU:\Software\Microsoft\MSNMessenger
PS HKCU:\Software\Microsoft\MSNMessenger> new-itemproperty -n AddInFeatureEnabled -path . -va 1 -t Dword
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Sof
tware\Microsoft\MSNMessenger
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Sof
tware\Microsoft
PSChildName : MSNMessenger
PSDrive : HKCU
PSProvider : Microsoft.PowerShell.Core\Registry
AddInFeatureEnabled : 1
Lovely isn't it?

Now launch Windows Live Messenger, sign in and go to Tools, Options, Add-ins. You should see this:

We'll revisit this dialog later on to add our add-in.
The basics of Live Messenger add-ins
Windows Live Messenger add-ins are written using a managed library called MessengerClient.dll (can be found in %programfiles%\MSN Messenger) which holds the namespace Microsoft.Messenger. In order to create an add-in, open up Visual Studio 2005 (.NET 2.0 is required), create a new Class Library project (e.g. called "MyAddin") and add a reference to the MessengerClient.dll file:

Next, change the project properties so that the assembly name equals the (fully qualified) class name of the add-in. This is important to get the add-in to work. In my case the class is defined as follows:
namespace
MessengerAddin
{
public class MyAddin
{
So, we need the following assembly name:

Next, import the namespace Microsoft.Messenger and implement the IMessengerAddin interface:
using
Microsoft.Messenger;
namespace MessengerAddin
{
public class MyAddin : IMessengerAddIn
{
private MessengerClient messenger;
public void Initialize(MessengerClient messenger)
{
this.messenger = messenger;
}
}
}
So, you've just created your first non-functional add-in for Windows Live Messenger.
Something useful - Say Hello
Let's try to do something useful now. What about some simple "Hello World" alike add-in (did I say useful?)... In order to do this, we'll do some nice things inside the Initialize method:
public
void Initialize(MessengerClient messenger)
{
this.messenger = messenger;
messenger.AddInProperties.Creator = "Bart De Smet";
messenger.AddInProperties.Description = "Some sample add-in";
messenger.AddInProperties.FriendlyName = "MyAddin";
messenger.AddInProperties.PersonalStatusMessage = "Greetings from MyAddin";
//messenger.AddInProperties.Status = ...;
messenger.AddInProperties.Url = new Uri("http://blogs.bartdesmet.net/bart");
messenger.AddInProperties.UserTile = GetImage(i);
}
The GetImage method is a custom helper method which relies on System.Drawing, so add a reference to System.Drawing.dll and import the System.Drawing namespace:
private
Image GetImage(int i)
{
Bitmap bmp = new Bitmap(40, 40);
Graphics g = Graphics.FromImage(bmp);
g.FillPie(Brushes.Red, 5, 5, 30, 30, i * 90 , 90);
g.FillPie(Brushes.Blue, 5, 5, 30, 30, (i + 1) * 90, 90);
g.FillPie(Brushes.Yellow, 5, 5, 30, 30, (i + 2) * 90, 90);
g.FillPie(Brushes.Green, 5, 5, 30, 30, (i + 3) * 90, 90);
return bmp;
}
Now compile the project and go to Windows Live Messenger. Load the add-in via Tools, Options, Add-ins:

Now you should something like this:

Click OK to continue. In the Windows Live Messenger main window, click your nickname and choose "Turn on "MyAddin"":

Watch what happens to the personal message and the display picture:

Something even more useful - A simple conversation content filter
Cool stuff, we're in control now. Time for something more useful: a simple conversation content filter. Every time a message is sent by the Windows Live Messenger client, we can receive an event to control what's going on. Go back to the Initialize method, comment out everything from our first sample and add the following:
public void Initialize(MessengerClient messenger)
{
this.messenger = messenger;
messenger.IncomingTextMessage += new EventHandler<IncomingTextMessageEventArgs>(messenger_IncomingTextMessage);
}
Using tab-completion (after the += operator) you'll get a stub for the event handler. Fill it out as follows:
void
messenger_OutgoingTextMessage(object sender, OutgoingTextMessageEventArgs e)
{
string msg = e.TextMessage.ToLower();
if (msg.Contains("password"))
e.Cancel = MessageBox.Show("Your outgoing message seems to contain a password. Do you really want to send it?", "MyAddin", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.No;
}
(Note: you can get the destination user using e.UserTo)
Needless to say you need to add a reference to System.Windows.Forms.dll and import the System.Windows.Forms namespace to get this to work. Time to recompile. VS2005 will most likely complain:
Error 1 Unable to copy file "obj\Debug\MessengerAddin.MyAddin.dll" to "bin\Debug\MessengerAddin.MyAddin.dll". The process cannot access the file 'bin\Debug\MessengerAddin.MyAddin.dll' because it is being used by another process. MessengerAddin
The best way to get around this is to exit Live Messenger (and/or to kill msnmsgr.exe). Next, compile the project and then and re-launch Live Messenger. Now the following should happen in a conversation (if the add-in is enabled, see above on how to do that). Type something with password in it:

Click Send and watch the following message box coming up:

Click No and see how the message delivery is cancelled:

Other ideas:
- intercept outgoing messages and apply more configurable filtering (warning versus instant blocking)
- logging of outgoing messages (requires CAS stuff - strong-name your assembly)
- a spelling-checker (won't be easy and the API doesn't allow to alter outgoing messages straight away)
Something just for fun - A string reverser responder
Wow, what the **** is this? I couldn't (or wasn't in the mood to) think about something useful in case a message arrives on the client. And I wanted to show you how to send messages to some user. An AutoReply kind of thing was too trivial (will post that leter on), so take a look at this. Again, uncomment our previous piece of Initialize-code and add the following:
public void Initialize(MessengerClient messenger)
{
this.messenger = messenger;
messenger.OutgoingTextMessage += new EventHandler<OutgoingTextMessageEventArgs>(messenger_OutgoingTextMessage);
}
Again, the event handler can be stubbed automatically, but of course you need to supply some additional code:
void
messenger_IncomingTextMessage(object sender, IncomingTextMessageEventArgs e)
{
string s = e.TextMessage;
StringBuilder sb = new StringBuilder();
for (int i = s.Length - 1; i >= 0; i--)
sb.Append(s[i]);
string reverse = sb.ToString();
messenger.SendTextMessage(reverse, e.UserFrom);
}
(alternative implementation using Array.Reverse and String.ToCharArray possible). This piece of code illustrates how to get the sender user with e.UserFrom and how to send a text message using SendTextMessage (you could send other things as well, see IntelliSense).
Kill Live Messenger, compile, restart Live Messenger, make sure the add-in is enabled and have someone say something to you. He'll see something weird:

Other ideas:
- an AutoReply tool that queries the current user's status to determine when to auto-reply
- logging stuff
Timers, timers, timers
Of course, actions shouldn't be event-driven (or better: not user-initiated) all the time. We could also hook in a timer and use that one to change (for instance) the personal message and the display picture on a regular basis. Here's how:
public void Initialize(MessengerClient messenger)
{
this.messenger = messenger;
System.Timers.Timer tmr = new System.Timers.Timer(5000);
tmr.Elapsed += new System.Timers.ElapsedEventHandler(tmr_Elapsed);
tmr.Start();
}
private DateTime birthday = new DateTime(1983, 2, 11);
private int i = 0;
void tmr_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
i = (i + 1) % 4;
TimeSpan span = DateTime.Now - birthday;
messenger.AddInProperties.PersonalStatusMessage = String.Format("{0} days {1} hours {2} minutes {3} seconds on planet Earth", span.Days, span.Hours, span.Minutes, span.Seconds);
messenger.AddInProperties.UserTile = GetImage(i);
}
(using the GetImage method used in the first demo above). I think you don't need screenshots to figure out what will happen now...
End-user configuration
Our birthday example is already pretty cool but nobody wants to hardcode the birthday in code of course. Time to enter the scene of end-user configuration. Not difficult at all. Start by capturing the event ShowOptionsDialog from the MessengerClient. If Live Messenger sees that an event handler is available, the Settings button in the Add-ins pane of the Live Messenger Options dialog will become available as show a little further.
private System.Timers.Timer tmr;
private DateTime birthday;
private int i = 0;
public void Initialize(MessengerClient messenger)
{
this.messenger = messenger;
messenger.ShowOptionsDialog += new EventHandler(messenger_ShowOptionsDialog);
tmr = new System.Timers.Timer(5000);
tmr.Elapsed += new System.Timers.ElapsedEventHandler(tmr_Elapsed);
StartTimer();
}
private void StartTimer()
{
if (DateTime.TryParse(messenger.SavedState, out birthday))
tmr.Start();
}
private void messenger_ShowOptionsDialog(object sender, EventArgs e)
{
tmr.Stop();
SettingsDialog settings = new SettingsDialog(birthday);
if (settings.ShowDialog() == DialogResult.OK)
messenger.SavedState = settings.Birthday.ToShortDateString();
StartTimer();
}
The StartTimer method I've added over here takes the SavedState string property from the add-in and tries to parse it to a DateTime object. Next, we'll create the SettingsDialog form:

A quick overview of settings:
- Button - btnOK - Text="OK"
- Button - btnCancel - Text="Cancel"
- Label - label1 - Text="Birthday:"
- DateTimePicker - birthday
- Form - this - AcceptButton=btnOK
- Form - this - CancelButton=btnCancel
- Form - this - FormBorderStyle=FixedDialog
- Form - this - MaximizeButton=false
- Form - this - MinimizeButton=false
- Form - this - StartPosition=CenterParent
- Form - this - Text="MyAddin settings"
The code is straightforward:
public
partial class SettingsDialog : Form
{
public SettingsDialog(DateTime birthday)
{
InitializeComponent();
if (birthday != null)
this.birthday.Value = birthday;
else
this.birthday.Value = DateTime.Now;
}
private void btnOK_Click(object sender, EventArgs e)
{
this.bd = birthday.Value;
this.DialogResult = DialogResult.OK;
}
private DateTime bd;
public DateTime Birthday
{
get { return bd; }
set { bd = value; }
}
}
Time to try out. Usual steps again: exit Live Messenger, compile, start Live Messenger and enable the add-in. Go to Tools, Options, Add-ins and look at the enabled Settings button now:

When you click it, you'll see the dialog popping up (unfortunately somewhere in the back - have to check this little issue) with a security warning (caused by the CAS feature):

Ignore this for now (you can solve it by strong-naming the add-in and some good deal of CAS knowledge; add-ins in Live Messenger are sandboxed by putting them in the Internet security zone) and set your birthday:

Windows Live Messenger will now start counting the days, hours, minutes and seconds we've spent so far on our lovely planet:

Warning: This might cause a relatively big amount of internet traffic!
I've created a similar out-of-process counter for MSN 7.0 in using the technologies described in these two blog posts: one and two. Some time in the future I'll upload a counter based on Windows Live Messenger technology that allows to countdown and to count"up" to or from a given event (e.g. Windows Vista launch date ;-)).
But where are our settings? The answer: first look in the registry under HKCU\SOFTWARE\Microsoft\MSNMessenger\PerPassportSettings\<passport identifier>\InstalledAddInsList\MessengerAddin.MyAddin.dll:

The StateObjectStoreName value contains a piece of XML with a reference to (and additional hashes to ensure integrity) the file holding the settings:

<msnobj Creator="bartdesmet@hotmail.com" Size="20" Type="12" Location="TFR22A9.dat" Friendly="QwA6AFwAVQBzAGUAcgBzAFwAQgBhAHIAdABcAEQAbwBjAHUAbQBlAG4AdABzAFwAVgBpAHMAdQBhAGwAIABTAHQAdQBkAGkAbwAgADIAMAAAAA==" SHA1D="TxXeos2NbAv1uHBCxWYkbUmN2Y8=" SHA1C="Q3L8ZsFzRfP2Xu7aYuZaGEXty6I="/>
The specified file (TFR22A9.dat) in my case can be found in the Application Data folder of the currently logged in user, under Microsoft\MSN Messenger\<passport identifier>\PlugInState, as shown below (notice the slightly different file location in Vista due to the renewed profile system):

Conclusion
Although the add-in functionality of Windows Live Messenger is still hidden and should be considered as kind of a sneak preview towards developers, you can already use it to do some cool things. Nevertheless, the API still lacks some much-desired functionality to control even more aspects of Windows Live Messenger (that is, you'll hit the boundaries quite rapidly).
Useful resources
Special thanks to Robin Vermeirsch who was so kind to be my test victim on Windows Live Messenger.
Happy Live Messenger messing-around!
Del.icio.us |
Digg It |
Technorati |
Blinklist |
Furl |
reddit |
DotNetKicks
Filed under: Windows Live, Windows Live Messenger