Welcome to MSDN Blogs Sign in | Join | Help

Those of you who have RSS feeds attached to this blog may have noticed there has not been any new content for quite some time.  After considerable thoughts, I have decided to end this blog.  I will still keep all of my previous posts here as long as they are not removed, but in the end I decided that this blog should not be continued.

To be honest, one of the primary reasons is the platform we use for our blogs here.  As an owner of several other blogs, this one takes the most effort to update and maintain.  I may continue blogging on Office Communications Server in the near future, but the new blog will not exist on blogs.msdn.com.  If I do decide to create a new blog, I will post the URL on this blog.

In the meantime I have disabled all comments on this blog.  The spam filter setup is awful and has been flooding my inbox.  As this blog is no longer active, comments are not necessary.

We are almost at the point where our Powershell commandlet will be useful, but first we need to learn about establishing the session protocol.  You see, SIP stands for “Session Initiation Protocol”.  The key here is the word “Initiation”.  Yup, SIP just manages telling so and so that so and so wants to talk to them.  It doesn’t actually do the communication.

Imagine this scenario.  You want to talk to someone, let’s call him Iggy, who lives on the distant planet of Flurgleblarf.  You have no idea where exactly Iggy lives though, so you call your friend Sip over.  Sip knows how to figure out who is where on Flurgleblarf and responds that “yes. I can find Iggy”.  That’s fine, so you can now figure out where Iggy is.

However, Flurgleblarf is far from a homogenous planet.  Well over one hundred mutually unintelligible languages are spoken there and you know only two of them – Flurglussian and Blarfag.  If Iggy speaks a different language than these two, you’ll need to find a translator.  Luckily, you and everyone on Flurgleblarf knows someone who can speak Sdp.  You go to your local Sdp expert and ask him to ask Iggy’s Sdp expert what languages he knows.  Even better, you tell your Sdp expert to tell Iggy’s that you can speak Flurglussian and Blarfag.  If Iggy can’t speak one of those, he can suggest an alternate language.

So that is basically what SDP is.  It stands for “Session Description Protocol” and allows you to setup text, voice, and media communications.  For now, we are only going to talk about text communications, but we will discuss the others in future posts.

SDP Parts

SDP consists of different lines, beginning with a letter and an equal sign that describe the media types supported.  For a good short description of the information one can express with SDP, see the Wikipedia entry.

Lucky for us, UCMA provides support for SDP so we don’t need to worry about its syntax.  The interface that we will need to provide SDP support is IOfferAnswer.  In order for our commandlet to partake in media negotiation, it must implement this interface.

public class SendInstantMessageCommand : PSCmdlet, IOfferAnswer

This interface contains five methods.  Two of them, HandleReInviteOfferReceived and HandleThirdPartyControlOfferReceived apply only to reinvites and we will not discuss them today.  A ReINVITE occurs when one of the parties wishes to change media.  An example of this is two parties switching from an IM session to a video session.

 

The other three methods are of interest to us today.

 

·         GetOffer – This method is called when you are initiating the INVITE and allows you to specify what type of media to use for the dialog.

 

·         GetAnswer – This method is called when you are receiving the INVITE and the other party did not specify an offer.

 

·         SetAnswer – This method is called when you are receiving an INVITE and the other party did specify an offer.  In this case your job is to accept the offer or specify that certain media types are not acceptable.

 

In order to get UCMA to call your IOfferAnswer methods, you must set the OfferAnswerNegotiation property of the SignalingSession before you call BeginParticipate. 

 

_session.OfferAnswerNegotiation = this;

 

As mentioned before, you do not need to implement HandleReInviteOfferReceived or HandleThirdPartyControlOfferReceived because they only apply to reinvites.  We will have the same response to both GetAnswer and GetOffer so let’s create a method that returns a ContentDescription we can use for either one.  Below is the code for it, which I will proceed to explain.  You will need to add a using statement for System.Net.Mime to get this to compile.

 

/// <summary>

/// Retrieves the content description for offers and answers

/// </summary>

/// <param name="session">The session</param>

/// <returns>The content description</returns>

private ContentDescription GetContentDescription(SignalingSession session)

{

    IPAddress ipAddress;

 

    // This method is called back every time an outbound INVITE is sent.

    WriteVerbose("Getting the content description.");

    if (_session.Connection != null)

    {

        ipAddress = _session.Connection.LocalEndpoint.Address;

    }

    else

    {

        ipAddress = IPAddress.Any;

    }

    Sdp<SdpGlobalDescription, SdpMediaDescription> sessionDescription = new Sdp<SdpGlobalDescription, SdpMediaDescription>();

 

    //Set the origin line of the SDP

    //s, t, and v lines are automatically constructed

    sessionDescription.GlobalDescription.Origin.Version = 0;

    sessionDescription.GlobalDescription.Origin.SessionId = "0";

    sessionDescription.GlobalDescription.Origin.UserName = "-";

    sessionDescription.GlobalDescription.Origin.Connection.Set(ipAddress.ToString());

 

    //Set the connection line

    sessionDescription.GlobalDescription.Connection.TrySet(ipAddress.ToString());

    SdpMediaDescription mditem = new SdpMediaDescription("message");

    mditem.Port = 5061;

    mditem.TransportProtocol = "sip";

    mditem.Formats = "null";

    SdpAttribute aitem = new SdpAttribute("accept-types", "text/plain");

    mditem.Attributes.Add(aitem);

 

    //Append the Media description to the Global description

    sessionDescription.MediaDescriptions.Add(mditem);

    ContentType ct = new ContentType("application/sdp");

    WriteVerbose("Created content description");

 

    return new ContentDescription(ct, sessionDescription.GetBytes());

}

Understanding the ContentDescription Code

In the following sections, let’s break this down into individual pieces that we can understand.

 

Connection Property

We need to specify the IP address for the connection piece in SDP.  If we have a connection associated with our session, we use its IP address.  If we do not have one, we play it safe and use IPAddress.Any.

 

if (_session.Connection != null)

{

    ipAddress = _session.Connection.LocalEndpoint.Address;

}

else

{

    ipAddress = IPAddress.Any;

}

Session Description Helper

This creates the session description helper that we will use to create our SDP response.

 

Sdp<SdpGlobalDescription, SdpMediaDescription> sessionDescription = new Sdp<SdpGlobalDescription, SdpMediaDescription>();

GlobalDescription Property

The following four statements construct the o= section of the SDP offer, or in other words the origin.  The version is the version of SDP and should always be 0.  We do not care about the session Id so we set it to 0.  The – for the user name indicates we do not care about it.  Finally, we set the connection address to the IP address we previously obtained.

sessionDescription.GlobalDescription.Origin.Version = 0;

sessionDescription.GlobalDescription.Origin.SessionId = "0";

sessionDescription.GlobalDescription.Origin.UserName = "-";

sessionDescription.GlobalDescription.Origin.Connection.Set(ipAddress.ToString());

Set the IP Address

This sets the c= line of the SDP offer.  The TrySet method makes sure the IP address is recognized but does not throw an exception if it is not.

 

sessionDescription.GlobalDescription.Connection.TrySet(ipAddress.ToString());

 

Define Acceptable Media

The SDPMediaDescription class creates perhaps the most important piece of the SDP – stating what types of media we accept.  There are a number of valid values for it and what is most important is the two peers agree on the names.  “Audio” and “video” are common values but here we will use “message”.

 

SdpMediaDescription mditem = new SdpMediaDescription("message");

 

Configure the Port

We will set the port to 5061 because this commandlet will always use Tls.  For Tcp, you would want to use port 5060.   The transport protocol should almost always be “sip” in the case of messages, though it could be some other protocol.  For video and audio, it will most certainly not be sip.  The message media type does not have any format information so we can leave that null – though note that we cannot just simply set it to null.

We will set the Formats property to the string null, but these are tokens found at the end of the “m” line.  For Rtp, this may be a list of payload types.

 

mditem.Port = 5061;

mditem.TransportProtocol = "sip";

mditem.Formats = "null";

Set the MIME Type

This is a very important line that states that the MIME type we will use is text/plain.

SdpAttribute aitem = new SdpAttribute("accept-types", "text/plain");

 

Add Final Session Attributes

These last lines basically add the MIME type description to our media description, then add the media description to our SDP (it is possible to have multiple media descriptions) and then return a new ContentDescription instance that accepts the MIME type of the content description (which should always be “application/sdp”) and the bytes of the session description we just created.

 

mditem.Attributes.Add(aitem);

 

//Append the Media description to the Global description

sessionDescription.MediaDescriptions.Add(mditem);

ContentType ct = new ContentType("application/sdp");

GetOffer and GetAnswer Methods

You can now set the method bodies of GetOffer and GetAnswer to the following.

 

return GetContentDescription((SignalingSession)sender);

 

SetAnswerMethod

We are now left only with SetAnswer.   In this case the client has sent us the offer and we need to respond to it.  For the short term, this code will not be called because our commandlet is not set up to be called itself.  It only sends messages and cannot receive them.  In the future though, this code will be helpful.

 

For SetAnswer we need to parse the response and see if it supports our “message” content type with the SIP protocol.  If for some reason we are unable to parse, we must terminate the session.  The code for SetAnswer is the following.

 

public void SetAnswer(object sender, ContentDescription answer)

{

    SignalingSession session = sender as SignalingSession;

 

    // Verify that the answer received is consistent

    WriteVerbose("Setting the media answer.");

    byte[] Answer = answer.GetBody();

 

    if (Answer != null)

    {

        Sdp<SdpGlobalDescription, SdpMediaDescription> sessionDescription = new Sdp<SdpGlobalDescription, SdpMediaDescription>();

 

        if (!sessionDescription.TryParse(Answer))

        {

            Cleanup();

            Error("CannotParseAnswer", "Unable to parse the answer.");

            return;

        }

        else

        {

            IList<SdpMediaDescription> ActiveMediaTypes = sessionDescription.MediaDescriptions;

            if ((ActiveMediaTypes.Count == 1) &&

                (ActiveMediaTypes[0].MediaName.Equals("message", StringComparison.Ordinal)) &&

                (ActiveMediaTypes[0].Port > 0) &&

                (ActiveMediaTypes[0].TransportProtocol.Equals("sip", StringComparison.OrdinalIgnoreCase)))

            {

                WriteVerbose("Supported media type");

            }

            else

            {

                Cleanup();

                Error("UnsupportedMediaType", "Unsupported media type");

            }

        }

    }

}

 

Now that I have explained it, this code probably does not look very scary.  We first try to parse the SDP and if that fails we terminate the session.  We don’t need to worry about the callback here because we don’t care about this session any more.  We also do not need to worry about removing it from our dictionary because EndParticipate will fail.

 

After we have successfully parsed the SDP, we make sure that we define a port, use the “message” media type, and use “sip” as the transport protocol.  By using SIP as the transport protocol, we are saying that we will use the SIP MESSAGE message to communicate.

 

Of course, our commandlet still can’t do much that is interesting.  In the next post though, we’ll finally send our message.

In our next iteration, we will add to our commandlet to create a signaling session.  In essence we will send an INVITE to the other party so that we can send a message later.  There are a number of steps involved in setting up a session but for now we will just send the INVITE.  When running this sample, it is best if you are not signed in as the user account that is receiving the INVITE as we do not have the media negotiation piece in place so the INVITE will fail.

If you have no idea what an INVITE is, I suggest you spend some time perusing the many SIP resources on the Internet.  For example, see the SIP RFC at http://tools.ietf.org/html/rfc3261. This series is meant to help others learn the UCMA API, which assumes a decent knowledge of SIP.

SignalingSession is basically an INVITE dialog for our purposes.  The constructor for SignalingSession accepts an endpoint (which we already created) and a RealTimeAddress.  RealTimeAddress takes a string constructor with the SIP address of the other party in the dialog.  To obtain the SIP address of the other party, we will need to add an extra parameter to the commandlet called “SendTo”.

SignalingSession has a number of constructors.  The one we are interested in here accepts a RealTimeEndpoint and a RealTimeAddress.  The endpoint is the one we are registered as, which was covered in yesterday’s blog.  The address is the SIP address of the party we want to INVITE.  This is the simplest constructor and will meet our needs.

_session = new SignalingSession(_endPoint, new RealTimeAddress(_sendTo));

Other constructors have a string callId parameter.  In SIP parlance, each “conversation” (I call you – we talk – then hang up) is called a “dialog”.  The dialog is identified by a callId.  Therefore by using a callId that is identical to one in a different INVITE, you are stating that this INVITE is part of the same conversation.  A typical use for this is in forking scenarios – where you send the same INVITE to multiple locations where the user may be found.

Finally, there is a constructor that accepts a “localIdentityUri” and “requestUri” parameters.  The “localIdentityUri” parameter allows you to set the identity in the local header of the SIP URI.  RequestUri can be null if it is not different from the target address specified with the RealTimeAddress.

To action create the session, you must call BeginParticipate.  As this commandlet is synchronous, I will again perform the taboo operation of calling EndParticipate(BeginParticipate()).  As I have previously stated, never do this in a server or a client.  I placed the logic to create the session in a separate method.

private void CreateSession()

{

     // Send an INVITE

     try

     {

         _session.EndParticipate(_session.BeginParticipate(null, null));

     }

     catch (OperationTimeoutException ote)

     {

         Error(

             ote,

             "The Notification could not be received by {0} because the session establishment timed out.",

               _sendTo);

     }

 

     catch (FailureResponseException e)

     {

         if (e.ResponseData != null)

         {

             Error(e,

                 "The Notification could not be received by {0}; Error Code={1} Error Text={2}.",

                   _sendTo,

                   e.ResponseData.ResponseCode,

                   e.ResponseData.ResponseText);

         }

         else

         {

             Error(e, "The Notification could not be received by {0}.", _sendTo);

         }

     }

     catch (ConnectionFailureException cfe)

     {

         Error(

             cfe,

             "The Notification could not be received by {0}; the connection could not be established.",

             _sendTo);

     }

     catch (RealTimeException e)

     {

         Error(

             e,

             "The Notification could not be received by {0}; the following problem occurred {1}.",

             _sendTo,

             e.Message);

     }

 }

The code for the Error routine, which just lets us output a Powershell error, is the following.

 private void Error(Exception ex, string message, params object [] arguments)

 {

     string formattedMessage = String.Format(

         CultureInfo.CurrentUICulture,

         message,

         arguments);

     ErrorRecord record = new ErrorRecord(

         ex,

         ex.GetType().Name,

         ErrorCategory.OpenError,

         null);

     record.ErrorDetails = new ErrorDetails(formattedMessage);

     WriteError(record);

 }

So what do all of these exceptions mean? 

·         RealTimeException is a catch all for any other type of RTC exception.

·         A ConnectionFailureException will occur if the URI and/or port you specified do not exist. 

·         A FailureResponseException could mean several things.  Generally it means that the other party is unavailable or cannot respond.

·         The ServerPolicyException (not caught explicitly here) derives from it and indicates this message is not acceptable per the server policy.  There are several other descendents of FailureResponseException (such as PublishSubscribeException which is not relevant here) but in general they all indicate that the INVITE failed for a particular reason explained in the exception. 

·         OperationTimeoutException can occur if it took too long for the other party to accept the invite.

 

When you test this, I recommend that you do not sign in as the other party, which will generate a FailureResponseException.  We will fix this in the future when we talk about SDP media negotiation.

To terminate the session once we are done with it, we will start with a cleanup method.

private void Cleanup()

{

    // Terminate the session if we have one

    if (null != _session)

    {

        _session.Terminate();

        WriteVerbose("Closed session");

    }

}

 

As you can see it is as easy as calling _session.Terminate.  A BeginTerminate/EndTerminate version also exist.  Also note that Terminate should not throw.

 

Finally, this is our simple ProcessRecord method now.

 

protected override void ProcessRecord()

{

    // Create the session

    try

    {

        CreateSession();

    }

    finally

    {

        Cleanup();

    }

}

That’s it for now.  In the next blog, we’ll start negotiating a media session so we can send the message.

It’s about time that we began writing some code and getting into the details of the API.  To start out, I will cover the signaling part of UCMA.  Those of you who read my previous blog series on UCMA will find the next several posts very familiar.

For some of you, learning the signaling APIs in UCMA will not be very useful because you will use the collaboration layer that abstracts this.  However, I still feel that the signaling layer is the best way to understand SIP and prepare oneself for what the collaboration API does for you.

Today, we will begin our Powershell project and create a simple commandlet that signs in and then signs out.  I will not go over the majority of the Powershell specific code here, but the following are the steps to install and run the task.

1)      Download and install Powershell.

2)      Find the location of InstallUtil.exe on your machine – Get-ChildItem –Recurse –path ${env:systemroot} –Include installutil.exe

3)      Using the location found from the previous step, run installutil UCMAPowershell.dll

4)      Add-pssnapin UCMAPSSnapIn

Each SnapIn is run in its own process, so you can attach to the process in order to debug. 

For our first iteration, we will create a simple commandlet that will just sign in and sign out.  In subsequent posts, we will modify this commandlet to send a message.  For now, the commandlet will have the following parameters.

User – the SIP Uri of the user to log in as

ServerName – the name of the server to use

Retrieving the certificate

I should note that this first commandlet will be much simpler than the subsequent ones we will create.  For instance, it is possible to determine a default server name to use without requiring it on the command line.  When we start discussing the collaboration API, we will do this.

In order to perform TLS communications, we will need a certificate.  For now, let’s just pick the first available certificate.  This should work on your machine, but there is a chance that it will not.  Later on we will change this logic to be smarter about choosing a certificate.

Here’s the code for retrieving the first available certificate.

public static CertificateEntry GetCertificate()

{

// Load the certificates

X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);

store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

 

      // Make sure we have certificates

      if (store.Certificates.Count < 1)

      {

          return null;

      }

 

      // Pick a certificate

      foreach (X509Certificate2 certificate in store.Certificates)

      {

          // TODO: improve this

          CertificateEntry entry = new CertificateEntry(certificate);

          return entry;

      }

 

      return null;

  }

Creating a connection manager

We will put the sign in and sign out logic in the BeginProcessing and EndProcessing methods.  The primary reason for this is it allows us to send multiple messages without having to sign in and out each time.   The ProcessRecord override will contain the code to send the actual message.

The first thing we will need to do is create a connection manager.   To start, we will need to create an instance of SipEndpoint.  Conveniently named, this class encapsulates a SIP endpoint.  A SIP endpoint is the basis for all SIP communications.  It is both the “from” and the “to” when it comes to communicating.

So, in order to sign in to OCS we must first create an instance of the SipEndpoint class.  The SipEndpoint class derives from the abstract RealTimeEndpoint class.  There are two classes of interest to us that derive from RealTimeEndpoint.  Both of these classes allow you to send out of dialog messages or establish and receive long sessions.

·         SipEndpoint – can be constructed off any connection manager that derives from RealTimeConnectionManager (server or client)

·         SipPeerToPeerEndpoint –can only be constructed off connection managers that derive from RealTimeServerConnectionManager (RealTimeServerTlsConnectionManager or a RealTimeServerTcpConnectionManager)

 

For our purposes right now, we will use the SipEndpoint class.  We will likely use SipPeerToPeerEndpoint in the future.  In order to create a SipEndpoint instance, though, we will need to create a RealTimeConnectionManager.

 

RealTimeConnectionManager provides the connection manager features we need for our end points.  It contains the logic that allows us to send messages.  RealTimeConnectionManager by itself can only send messages.  It does not contain logic for listening for them.  Since we will likely want to receive messages in the future, we will use the RealTimeServerConnectionManager class.  This class allows us to receive messages as well, though we will do that in a future post.

 

RealTimeServerConnectionManager, which derives itself from RealTimeConnectionManager, has two descendents – RealTimeServerTcpConnectionManager and RealTimeServerTlsConnectionManager.  Tls is the better way to go here because it is encrypted.  Many parts of OCS will not work with TCP communications.  Therefore TCP will not be covered in this blog series. 

 

So once we have a RealTimeConnectionManager instance, we can create our SipEndpoint instance.  Once we have this, we need to register our SIP endpoint.  Registering in UCMA is basically the same as signing in in most other environments. 

 

Before we get into the details of creating our SipEndpoint, let’s look at the constructor.  There are three available constructors for SipEndpoint but the one we will use here is defined as follows.

 

public SipEndpoint (

        string uri,

        SipAuthenticationProtocols allowedAuthenticationProtocols,

        SipTransportType transportType,

        string serverName,

        int serverPort,

        bool allowNoAuthentication,

        RealTimeConnectionManager connectionManager,

        string endpointId

)

 

To create our SipEndpoint we must supply the information in the following table.

 

Argument

Description

uri

This is the SIP URI of our endpoint.  It typically has the form of sip:name@domain.com

allowedAuthenticationProtocols

You can use none,  Kerberos, or Ntlm here.  You will probably want to play with this some, but I will use Ntlm here.

transportType

This is Tcp or Tls as previously described

serverName

This is the name of the OCS server we will register with

serverPort

This is the port on the server we will use.  The default port for Tls it is 5061.

allowNoAuthentication

Use this setting if the server does not authenticate communications from our client.  This must be true if you use None for allowedAuthenticationProtocols.  In almost all cases, you will pass false here.

connectionManager

This is the connection manager that we must create.

endPointId

If you want to use a specific endpoint Id (also called epid) you can specify it here.  In this case I will pass null.

 

The following is how we start the BeginProcessing method.

// Get a certificate

CertificateEntry certificate = GetCertificate();

if (null == certificate)

{

return;

}

 

// Create the connection manager

_connectionManager = (RealTimeServerConnectionManager)new RealTimeServerTlsConnectionManager(

certificate.IssuerName,

ertificate.SerialNumber);

WriteVerbose("Created connection manager");

 

// Create the endpoint

_endPoint = new SipEndpoint(

_user,

      SipAuthenticationProtocols.Ntlm,

      SipTransportType.Tls,

      _serverName,

      5061,

      false,

      _connectionManager,

      null);

 

_endPoint.CredentialCache.Add(

SipEndpoint.DefaultRtcRealm,

      CredentialCache.DefaultNetworkCredentials);

WriteVerbose("Created endpoint");

After retrieving our certificate, we use it to create our connection manager.  We then use the connection manager to create our endpoint.  Finally, we update the endpoint with the currently running credentials in order to enable to endpoint to sign in.

The final thing we need to do is actually sign in.  In UCMA-speak, this is called “registering”.

// Register the endpoint

SipResponseData response =  _endPoint.EndRegister(_endPoint.BeginRegister(null, null));

WriteVerbose("Registered endpoint");

I did a big “no-no” here by performing the registration in a synchronous operation.  Later on we will create a commandlet that only uses the synchronous methods when we get to the collaboration API, but for now we will use this method.  In some respects this lends itself better to the Powershell approach as Powershell runs with more of a synchronous model.   One should never do this when creating server (or client) products.  Even in Powershell, this breaks down a bit when sending a number of messages.

To sign out, or unregister, add the following code to EndProcessing.

if (null != _endPoint)

{

_endPoint.EndUnregister(_endPoint.BeginUnregister(null, null));

      WriteVerbose("Unregistered endpoint");

}

When you run this code, you’ll see a number of logging messages but otherwise not much will happen.  Make sure to run this with –Verbose or you’ll see even less.  In the actual project’s files I have added an Xml help file that will allow you to use the help commands within Powershell.

So far our Powershell commandlet doesn’t do very much.  That will change over the next few blogs, starting with creating a signaling session in the next blog.

Today, we will get started with coding UCMA applications.  First though, I would like to cover again the Signaling namespace exposed by UCMA.  While much of this is not necessary when using the new Collaboration namespace, I feel that it is still important in terms of understanding how the API and SIP work.

In a change from my previous series of blogs on UCMA signaling, I have decided to no longer run the code through client and server executables.  Instead, I will create Powershell cmdlets (pronounced ‘commandlets’) that will accomplish specific actions for us.  The following are the reasons for this.

1)      The client and server executables had a large amount of code that was not necessary for understanding UCMA.  Powershell cmdlets contain much less non-necessary code.

2)      The Powershell cmdlets may actually be useful for some people.  The client and server were only useful for educational purposes.

3)      Due to the size and breadth of UCMA, a UI to cover all of the areas we will cover would be quite complex.

To run the code, therefore, you will need to download Powershell 2.0 if it is not already contained on your machine.   To learn more about Powershell, there are a vast number of good books and resources on the Internet about it.  One of the better books at the time of this writing is this one (though it only covers Powershell 1.0).

In my last blog series on UCMA, I showed step by step how to build the project and included the downloadable project in each blog.  The main problem with that approach was maintaining it.  If I found a problem that required fixing, I needed to make the fix in all subsequent projects and upload a new version to each blog.  This was rather counterproductive so I will now include the full solution from the beginning.

On the down side, this means that there will be a good deal of code you may not understand.  However on the positive side those who wish to look ahead now have the opportunity to do so.  Therefore, I will include the code in ‘blocks’.  Each blog will include code from future blogs.

This also means the format of the blog will be different.  While in the past I would give step by step instructions on how to build up the code, now I will focus solely on the parts that are important to the API.  Therefore there will be some boiler plate and ‘glue’ code that I will not mention in the blog but will exist in the project.  This allows each blog to be more on target – I can discuss only the code that is relevant for learning UCMA.

Before you really start coding in UCMA 2.0, or even UCMA 1.0, it is extremely important that you understand the asynchronous pattern.  UCMA is designed for server applications, meaning it supports many operations occurring at the same time.  In order to best manage this, it uses an asynchronous model.

For instance, let’s begin by discussing one of the most important classes in UCMA 2.0, CollaborationPlatform.  After you have created an instance of this class, you will need to start it.  The method to start CollaborationPlatform is not called ‘Startup’, but is instead called BeginStartup.  The method signature is the following.

IAsyncResult BeginStartup(AsyncCallback userCallback, object state)

The key thing to remember with asynchronous APIs, is that BeginStartup will return almost immediately after being called.  The actual work will be accomplished in another thread.  So how can you tell when it is completed?  There are two ways to accomplish this.

1)      Using the IAsyncResult object returned from BeginStartup.  For the best description, look this object up in MSDN, but in summary it provides a wait handle that you can wait on and an IsCompleted property.  Of course, waiting on either of these will block your thread, which is probably not what you would like to do.

2)      The other way is to receive the callback in the AsyncCallback method you provide.  When the operation has finished, UCMA will call this method.

To enable the same AsyncCallback method to work for several different method calls, you can pass any object as the state parameter.  When UCMA calls you back, it will send you this object.  The AsyncCallback delegate requires a method that takes a single parameter – IAsyncResult.  Within the IAsyncResult object passed back, you can retrieve the state variable from the AsyncState property.

So what do you do when you are called back?  In general, you are going to want to know what happened.  Did CollaborationPlatform start up correctly?  To accomplish this, you will call the Endxxx form of the method.  So in this case you will call EndStartup.  EndStartup has the following signature.

void EndStartup(IAsyncResult result)

OK, so this one isn’t very exciting.  There is no start returned, but to ensure that resources are no longer consumed, you should always call the appropriate Endxxx method in the callback from the Beginxxx method.  Let’s look at a more interesting Endxxx method – EndEstablish in ApplicationEndpoint.

SipResponseData EndEstablish(IAsyncResult result)

The SipResponseData object returned will contain information on whether we managed to establish the endpoint.  Of course, for simplicity sake, you could make the following call.

SipResponseData data = endpoint.EndEstablish(endpoint.BeginEstablish());

Within these blogs, I will make use of this sometimes.  The main reason I will use it is I will be showing Powershell commandlets written with UCMA.  As Powershell commandlets are synchronous, it makes sense to use this at times.  However, you should not use this when writing server applications.  When I switch from discussing signaling to the collaboration API, I will switch to using the callback.

Now that you understand the asynchronous pattern better, let’s take a look at the Signaling API.

Let’s be honest, UCMA 1.0, while very useful, was a bit lame.  The frustrating thing about it was it opened up many new possibilities, but didn’t give you the chance to take advantage of them.  You could send messages to another party, but could not determine that user’s presence.  You also were very limited with conferencing – basically you could only do conferencing by creating all of the protocols yourself, which wasn’t very handy.  Finally, you were limited to text messages as we did not provide access to the media stack.

All of that has changed with UCMA 2.0.  UCMA 2.0 may roughly be divided into three parts.

Signaling – This is very similar to UCMA 1.0.

Media  - provides access to the media streams

Collaboration – provides presence and conferencing support

Additionally, the Speech APIs may be used in conjunction with UCMA 2.0, but constitute a separate API.  Support for speech comes through hooking the speech API up to the media streams.

In this blog series, I will cover all of the main parts of the UCMA 2.0.  If I have time, I will also cover the Speech API and its interaction with UCMA 2.0.

Due to time constraints, it is unlikely that I will be able to provide a new blog each day.  However, I do have a plan for the blogs I want to write and the order in which they will appear.  I have divided the blogs into seven ‘modules’ – each covering a different area of UCMA.

Part I Signaling – This is a shortened version of my old 16 part series on UCMA.  In my opinion, before embarking on the more complex collaboration API classes, it is important to have a good grasp of signaling.

Part II Collaboration – Here I will cover the basic classes in the collaboration API, such as CollaborationPlatform.

Part III Presence  - This module will cover the main presence classes in UCMA 2.0.

Part IV Conferencing – I will show how to create, join, and manage conferences.

Part V Media – Here I will cover the intricacies of the media API.

Part VI Speech – The speech API and how it interacts with UCMA 2.0

Part VII Surprise – I cannot comment on this one right now, but can only say that it is a surprise that you will not find out about elsewhere.

So without further ado, in the next post we’ll start discussing the signaling API.

In truth, this is now a new blog.  I'm not sure what we will be covering in the next several months, short of the already stated UCMA 2.0 APIs.  However, there are some sharp differences between this blog and my old one.

First, it already has a new look and feel.  I would love to personalize the site a bit more, such as what I have done on my photo blog, but this system is quite a bit more limiting.

As with the recent past, I will not discuss photography or real estate on this blog.  Photography is avidly discussed in my other blog - http://www.calevphoto.com.  For real estate, my wife has taken that blog over and syndicates it in several places - such as http://activerain.com/blogs/nelyac

The biggest change is that this is no longer a Speech Server blog.  I may discuss some aspects of the Speech APIs from time to time, but only in the context of how they contribute to the OCS APIs.  The simple fact is I no longer work in this group.  I work in the OCS Server Core group and I am therefore most knowledgeable about what is going on in that area.

My goal is for this blog to be a consistent source of information on Office Communications Server.  In particular, I hope to cover many of the parts of the product new in R2 on this blog.

At long last this blog is back up and running.  Very soon (current goal is Wednesday), I will start a new series of UCMA blogs covering the new 2.0 API.  My goal is to eventually cover all parts of this now huge set of APIs, but unlike my previous set of posts on UCMA there will be some changes.

  1. I will not have a new post everyday.  In the last series I finished all posts before the first one was published.  They then followed each other day by day.  I do have several posts finished right now, but the rest will have to come later.
  2. My goal is to show you what the APIs can do and how to do it rather than bring you through a lengthy project buildup.  I simply don't have the time to provide a new VS project with each blog as I did with my previous series.  I also had a number of issues previously with people getting my projects to work.  I simply don't have the time right now to answer all of these questions.  I will of course debug the code on my machine to make sure it works, but you'll need to add your own project and infrastructure.

I have grand plans for this series of blogs and hope that I will have the time to implement them.  The old series consisted of 16 blogs on the signaling API - which at the time was the only part of UCMA available.  Now with UCMA 2.0 I will start with the signaling APIs again, then move to the collaboration and media APIs as well as the speech APIs (which are separate).  I currently do not plan to blog about the Workflow based APIs, though I may add them if I can find the time.  Right now I have a list of 51 blogs that I hope to write.

As you can imagine, things are quite busy here.  While many of you are eagerly anticipating Office Communications Server 2007 R2, we are busy planning and working on future releases.  To be honest, I have worked on R2 projects before at Microsoft and OCS 2007 RS really doesn't feel like an R2 - there are just too many new features and parts.  However, as much work as we accomplished for R2, a lot more will take place in the future so I guess when you put it into perspective it is an R2.

So, Wednesday will be the day that the new UCMA 2.0 blogs show up.  Be warned though that I intend to start off with the Signaling APIs, which are very similar to the UCMA 1.0 API.  I have given myself until Wednesday to try to write a few more blogs in the series so you don't see too many days where there are no blogs.

Over the past several months I have come to realize that I needed to change the way I blog.  The primary issue is that everything that comes from this blog is often interpreted as coming from Microsoft itself.  This has created a number of challenges and problems recently.

Back in January, I created a new blog called "Photographing the Earth. One millimeter at a time..." that covers my interests in photography.  I often felt with this blog that I had two audiences - those interested in photography and those interested in Office Communications Server and Speech Server.  The two audiences cared little for the others' posts.  This new blog has been very active and I have posted there every day.

This leaves me now with the issue of what to do with this blog.  For the most part, I would like to keep it on topic with OCS material, but most of that material I cannot blog about right now.  When we do release something public, you can expect to find more information here, but in the meantime I don't expect to have the time to write much more that is OCS specific.

Currently in my spare time I have been learning more of the technical aspects of photography.  This is beyond reading photography books and more towards understanding "what is light?" and learning the algorithms behind image processing.  I am still at the beginning stage here, but do intend to blog about this more as my knowledge increases.  As this is quite technical material, I may place it here or at my other blog - I haven't decided yet.

Therefore I just wanted to let everyone know that this blog has not falled off the face of the Earth, but it will be awhile before anything much shows up here again.

It is pretty much impossible right now to not see the news and get a little depressed.  While Microsoft is a healthy company and work life has been good here, all is not rosy in the economy in general.  I now feel like a pauper when I travel to Europe as the dollar picture isn't looking good, the real estate market is hurting (which hurts us directly because my wife is a real estate agent), and the latest news is the economy is heading for a recession.  However, things are still not that bad.

Yesterday my wife came home and announced that the nephew of one of the agents in her office died.  He was four years old and was killed in a sledding accident of all things.  You can read more details about it here.  As a father of a four year old and a three year old who desperately want to go sledding my heart went out to the parents.  I simply cannot imagine such a horrible thing happening and I don't know how I could manage to live day to day if something happened to one of our boys.

So, although some people already have or will lose their homes, jobs, and money in the next several months, things really aren't all that bad.  I'm sure these poor parents would glady have traded their home, jobs, and entire life savings right now.

I spent some time researching sled safety, as my kids still want to go sledding.  The following are general safety tips you should stick to when sledding.

1) Do not sled where there are any obstacles.  The hill should not have a road  or body of water below and there should be no buildings or trees that you have a remote possibility of hitting.

2) At least if the child is young, have him or her wear a helmet.

3) Give the child instruction on how to turn and stop the sled.  Make sure the child understands how to do this.

4) Avoid inner tube, saucer, and disk sleds which have no control.  Ideally, use a sled with a steering mechanism.

5) Supervise your children at all times while they are sledding.

6) Do not sled in areas where there are protruding rocks or where the area is rocky in general.

7) Never sled in the dark.

8) Never pull a sled with a motor vehicle.

9) Never sled head first.  Always sit face forward.

10) Do not use plastic sheets to sled as they can be punctured from objects under the snow.

OK, this may be old news to many of you out there, but the Office Communications Server 2007 Resource Kit just shipped.  I haven't had a chance to look at it yet but you can be sure I will.  I hope to receive a free copy of it at an internal event next month, at which time I also hope to sign up to contribute to the next resource kit.  As there still aren't a lot of resources out there on OCS 2007 yet, I expect this to be on a lot of bookshelves.  Actually, after looking at Amazon.com there is only one other book out there - though I suspect several more are on the way.

Personally, I think it's about time that a number of books be released on different aspects of OCS 2007.  For instance, there could be some books on setup and deployment and other books on developing applications with UCMA and UCCA.  Time will only tell.

There are several golden rules that I have learned over the last week while spending some time PInvoking Win32 functions from C#.

1) The content at PInvoke.net can often not be trusted.

2) The overwhelming precentage of articles on the net explaining how to PInvoke certain methods can also not be trusted.

The following are some helpful tricks and tips that I have learned.

Pay attention to whether the Win32 method returns BOOL or BOOLEAN.

The Win32 BOOL is 4 bytes, while a BOOLEAN is only one byte.  Why do we care?  We care because the .Net Boolean only maps cleanly to BOOL, not to BOOLEAN.  The following is an example of how to marshal the TranslateName function.

[return:MarshalAs(UnmanagedType.U1)]

[DllImport("Secur32.dll", CharSet = CharSet.Unicode, SetLastError = true)]

private static extern Boolean TranslateName(

String lpAccountName,

EXTENDED_NAME_FORMAT AccountNameFormat,

EXTENDED_NAME_FORMAT DesiredNameFormat,

StringBuilder lpTranslatedName,

ref uint nSize);

 

Notice in particular the return: attribute specified above.  This allows us to marshal back the returned BOOLEAN to the .Net Boolean.  If you do not do this, I have noticed that the function will actually fail but will still to appear to return true.   This can be very frustrating to debug.

 

When using EntryPoint, make sure the specify the ‘W’ version.

 

[DllImport("advapi32.dll", SetLastError = true, EntryPoint = "ChangeServiceConfig2W")]

private static extern bool ChangeServiceConfigDescription(

      IntPtr hService,

      uint dwInfoLevel,

      ref SERVICE_DESCRIPTION lpInfo);

 

This makes sense, but it is easy to forget.  If I do not use the W version above, I will have only one character in the description.

 

If a string must be passed in a passed structure, it must be passed using an IntPtr.

 

See the definition of SERVICE_DESCRIPTION used above.

 

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]

private struct SERVICE_DESCRIPTION

{

    public IntPtr lpDescription;

}

 

To copy your managed string to the structure, use Marshal.StringToHGlobalUni.  Just make sure to call Marshal.FreeHGlobal to free the memory once you no longer need it.  The same applies when retrieving a string from a structure, except there you must use Marshal.PtrToStringUni.

I have noticed through working both at Microsoft and in other places a tendency for the two following practices to occur.  At first, it would seem they contradict each other but in truth I very often see them together.

1) The product is made to fit one kind of ideal user, most often the user who is the least tech savy.  While the team may use multiple personas, almost every feature must still be accessible and understandable to this user.  While this may help in some everyday programs, I find it particularly dangerous in APIs - where a class is designed for only simple uses and when confronted with a more advanced use case becomes completely useless.

2) The underlying mechanisms that make up the product are overly complicated.  In some ways, it's almost like the developers were bummed that the product itself isn't that complicated, so they intentionally make the code that runs it complicated.

Perhaps these are two different issues, but I suspect they are heavily intertwined.  For instance, it is unfortunately a common practice here at Microsoft to not use our own APIs to accomplish things.  While this has improved recently, the main reason developers give is the API doesn't do exactly what they want or doesn't perform as they need.  Here are some nightmare cases that I have actually seen (note: none of these occurred in my current group and to my knowledge this product no longer ships).

a) A development team did not trust the implementation of ATL, so they created their own version from the ground up.  Predictably, it suffered from numerous hard to debug issues.

b) A developer decided to learn more about multiple inheritance in C++, so he implemented the entire feature with it.  When the bugs mounted up, the developer left the team because he was sick of fixing them.  The feature was eventually removed from the product shortly before shipping due to instability.

c) To save a few MB of download, several developers decided to create their own image format to replace the PNG format.  The effort took several months, crippled the product until late in the design cycle on a legacy OS, caused the product to take 10% more memory, and resulted in a 5% performance degredation of the product.

These are plain examples of the code behind a product being unnecessarily complex.  Had the product team used ATL, avoided multiple inheritance, and stuck with the PNG format the code would have been shorter, customers would have seen fewer crashes and other bugs, and the product team would have finished sooner or have been able to add more useful features.  While these examples are extremes, I have seen the same types of issues occur many other times.

Very often, however, more advanced users of our products become very frustrated when they try to do something more difficult.  For example, recently I became very frustrated with the ServiceBase and ServiceInstaller classes that ship in .Net.  First, I needed to be able to install a service myself without the user getting involved.  This is possible, though rather tricky, to do with the ServiceInstaller class.  Eventually I wound up having to PInvoke all of the code because a number of things I needed to set on the service weren't available in ServiceInstaller.  ServiceBase soon suffered a similar fate, as I was playing around with shared services which ServiceBase had poor support for.

I won't delve much more into APIs though because it is truly very hard to get things right there.  From a UI point, I cannot count how many times I have needed to do something in Word and Excel that was hidden in some strange place.  Of course, that's when I was lucky enough to find it.

Note that this should not be construed as Microsoft bashing.  Every company I have ever worked for had an element like this.  I once worked for a now defunct dot com that was creating a web site for the customer.  The web site had to be as simplistic to use as possible - so users would need minimal training.  However the implementation consisted of the team trying to get several different products to work together.  I asked why they had such a strange colloboration of products and the answer was - "we wanted to learn more about them and also see if we can do it".  Unfortunately for them, the products didn't work and their solution never shipped.

Today I will venture outside the safe confines of Office Communications Server and discuss some quirks I have noticed with Windows services.  (Disclaimer: I am not in the Windows org so these are just my observations after some experimentation) Some of you may be aware that in the services control panel, you can right click on a service and on the "Recovery" tab you can manage what occurs when a service fails.

The following are the options.

First failure: what should occur the first time the service fails.  Valid options are "Take No Action", "Restart the Service", "Run a Program", and "Restart the Computer".

Second failure: same options the second time a service fails

Subsequent failures: same options for any subsequent failure

Reset fail count after: the number of days the service must be running before the failure count is reset

Restart service after: the amount of time in minutes to wait to restart the service

This is very nice, but it is very easy to misunderstand what these values actually do.  I have seen a number of services (and I tried this myself) set these values to 0 days and 0 minutes.  The problem is your service will continually restart if you set the failure count to reset after 0 days, if the service at least started correctly.  The result is only the first option ("first failure") will ever be run. 

To fix this, set the failure count to reset after one day.  The drawback to this approach is your service may stay stopped after failing several times but this likely means something is toast anyways.

One thing also to take into account is not all services will work with the reset logic - or in other words just setting the recovery options on any service does not guarantee that it will restart.  In order for the service to restart, it must exit abnormally.  This generally means the service must exist with a non-zero exit code and the service status must not be stopped (note: this has changed for Vista - it is possible to set the service status to stopped and provide an exit code to trigger the restart logic).

More Posts Next page »
 
Page view tracker