Using the Agent WebService of the Response Group Service

Your Office Communications Server 2007 R2, Response Group Service deployment comes with a tiny but nice little addition: the Agent WebService. It basically offers exactly the same data and functionality as the Agent OC tab but does this through a SOAP interface. If you have RGS deployed on a pool with the FQDN ‘ocs-pool-01.contoso.com’, you’ll find the Agent OC tab at https://ocs-pool-01.contoso.com/Rgs/Clients/Tab.aspx. The Agent WebService is then located at https://ocs-pool-01.contoso.com/Rgs/Clients/ProxyService.asmx. If you are running the OCS WebComponents on other machines than the front ends, then the host name is the FQDN of the WebComponents machine/farm. So here’s how you can write your own client to sign in/out with RGS Agent Groups. The TechNet article Deploying Response Group Clients gives more information about deploying RGS Clients, with focus on the Agent OC Tab.

Generating the Client Proxy

The first thing you typically want to do is to actually generate the proxy code which you will compile into your own client. You do so by calling

wsdl.exe /namespace:RgsAgentService /language:cs /out:RgsAgentService.cs https://ocs-pool-01.contoso.com/Rgs/Clients/ProxyService.asmx?wsdl

This will generate the RgsAgentService.cs file which you can include into your project and use right away. Web Services Description Language Tool (Wsdl.exe) on MSDN has more info on wsdl.exe if needed.

Using the Web Service

Now you’re already good to go and use the web service. The code below shows a sample console application which does the following.

  • Create a new instance of ProxyService (the generated class from the step above) which points to the service on the pool you’re interested in.
  • Query the web service if the current user is an agent or not. This requires you to authenticate with the user’s credentials.
  • If the current user is an agent
    • Determine some basic information (e.g. the name and SIP address of the agent).
    • Retrieve the list of agent groups the agent is a member of in the connected pool.
    • If there are formal agent groups to which the agent is not signed in, the user is asked if he wants to sign in.
      • If he choses to sign in, tries to sign the agent in to those formal groups.
using System;
using System.Collections.Generic;

using RgsAgentService;

namespace RgsClient
{
    class Program
    {
        static void Main(string[] args)
        {
            string poolFqdn = "ocs-pool-01.contoso.com";

            if (args.Length > 0)
            {
                poolFqdn = args[0];
            }

            ProxyService service = ConnectToPool(poolFqdn);

            // First, figure out if the current user is an Agent.
            if (!service.IsAgent())
            {
                Console.WriteLine("You are not an agent in Pool '{0}'.", poolFqdn);
                return;
            }

            // Now get some information about the Agent (i.e. the current User).
            AcdAgent self = service.GetAgent();

            Console.WriteLine("You were authenticated as agent '{0}' ('{1}') in pool '{2}'.",
                self.DisplayName, self.SipAddress, poolFqdn);
            Console.WriteLine();

            // Finally, determine which Agent Groups this Agent belongs to.
            AcdGroup[] agentGroups = service.GetGroups();

            Console.WriteLine("Agent Group Name                  Formal?   Signed In?  # Agents");
            Console.WriteLine("----------------------------------------------------------------");

            Dictionary<string, Guid> agentGroupIdsForSignIn = new Dictionary<string, Guid>();

            for (int i = 0; i < agentGroups.Length; i++)
            {
                Console.WriteLine("{0,-32}  {1,-8}  {2,-10}  {3,8}",
                    agentGroups[i].Name, agentGroups[i].CanSignIn,
                    agentGroups[i].IsSignedIn, agentGroups[i].NumberOfAgents);

                if (agentGroups[i].CanSignIn &&
                    !agentGroups[i].IsSignedIn)
                {
                    agentGroupIdsForSignIn.Add(agentGroups[i].Name, agentGroups[i].Id);
                }
            }

            // If the Agent is not signed in to all his formal groups, then offer
            // him to do so now.
            if (agentGroupIdsForSignIn.Count > 0)
            {
                Console.WriteLine();
                Console.WriteLine("You are not currently signed in to {0} agent group(s):",
                    agentGroupIdsForSignIn.Count);

                foreach (string agentGroupName in agentGroupIdsForSignIn.Keys)
                {
                    Console.WriteLine("    {0}", agentGroupName);
                }

                Console.WriteLine();
                Console.Write("Do you want to sign in to these groups now? [y/n] ");

                ConsoleKeyInfo key = Console.ReadKey();
                while (key.KeyChar != 'y' && key.KeyChar != 'n')
                {
                    key = Console.ReadKey();
                }

                if (key.KeyChar == 'n')
                {
                    return;
                }

                Console.WriteLine();

                if (service.SignInMultiple(agentGroupIdsForSignIn.Values.ToArray()))
                {
                    Console.WriteLine("You have successfully signed in.");
                }
                else
                {
                    Console.WriteLine("Sign-in to at leat one agent group has failed.");
                }
            }
        }

        private static ProxyService ConnectToPool(string poolFqdn)
        {
            ProxyService service = new ProxyService();

            service.Url = String.Format("https://{0}/Rgs/Clients/ProxyService.asmx", poolFqdn);
            service.UseDefaultCredentials = true;

            return service;
        }
    }
}

Running this program will yield something similar to the following.

You were authenticated as agent 'Bob' ('bob@contoso.com') in pool 'rgs-pool-01.contoso.com'.

Agent Group Name                  Formal?   Signed In?  # Agents
----------------------------------------------------------------
Payroll Questions                 False     True               5
General HR Questions              True      False              8

You are not currently signed in to 1 agent group(s):
    General HR Questions

Do you want to sign in to these groups now? [y/n] y
You have successfully signed in.

More Methods

The WebService has a few more methods. These are in particular

  • SignIn(Guid groupId) – Tries to sign the current agent in to the agent group with the given ID
  • SignOut(Guid groupId) – Tries to sign the current agent out of the agent group with the given ID
  • SignOutMultiple(Guid[] groupIds) – Tries to sign the current agent out of all the groups identified with their respective IDs

These methods all return a boolean which indicates success (true) or failure (false).

Summary

You can integrate the RGS Agent Services into your own application by using the corresponding web service which is installed with the Response Group Service. This allows you to offer the same information and functionality as the RGS Agent OC Tab in a customizable manner.

User Search in AD

I stumbled upon the System.DirectoryServices.AccountManagement namespace this week. It was introduced with .Net 3.5 and offers functionality to perform queries on AD objects like users, groups and computers in a more comfortable way than through the DirectorySearcher class from the System.DirectoryServices namespace. To illustrate the ease of using these classes, I came up with a tiny example which lists all users whose account name (the samAccountName attribute in AD) starts with an 'a'. On top of this, using LINQ it is quite simple to convert the resulting PrincipalSearchResult<Principal> collection into an IEnumerable<UserPrincipal>.

using System;
using System.Collections.Generic;
using System.DirectoryServices.AccountManagement;
using System.Linq;

namespace UserSearch
{
    class Program
    {
        static void Main(string[] args)
        {
            PrincipalContext context = new PrincipalContext(ContextType.Domain, "contoso.com");

            UserPrincipal searchFilter = new UserPrincipal(context);
            searchFilter.SamAccountName = "a*";

            PrincipalSearcher ps = new PrincipalSearcher(searchFilter);

            IEnumerable<UserPrincipal> results = from principal in ps.FindAll()
                                                 where principal is UserPrincipal
                                                 select principal as UserPrincipal;

            foreach (UserPrincipal user in results)
            {
                Console.WriteLine("User '{0}' ({1}) Info:", user.SamAccountName, user.Name);
                Console.WriteLine("    Password Set On  {0}", user.LastPasswordSet);
                Console.WriteLine("    Last Log On      {0}", user.LastLogon);
                Console.WriteLine();
            }
        }
    }
}

OCS 2007 R2 Launch Teaser Videos

Right on time for the upcoming launch of Office Communications Server 2007 R2 next Tuesday, a couple of videos were posted to youtube. There are also two videos for the products developed in the Zürich Development Center.

Attendant Console: http://www.youtube.com/watch?v=caMPdEXDIDk&feature=channel_page
Response Group Service: http://www.youtube.com/watch?v=1ietEAruOUM&feature=channel_page

Btw, it’s nice to see that as of now the video about the Response Group Service has been viewed the most out of all the videos in the channel.

Microsoft Office Communications Server 2007 R2 Resource Kit Book

Yesterday, Microsoft Press announced the new Microsoft Office Communications Server 2007 R2 Resource Kit book, available by February 4. This book holds much of the information about the new OCS 2007 R2 release which comes with the Response Group Service. IIRC, there's also a huge amount of information if not an entire chapter about RGS to which many people from the product development teams have contributed.