Thursday, April 05, 2007

WebService to Find Room and Equipment Mailboxes in Exchange 2007

In Exchange 2007 one of the new features is resource mailboxes out of the box you have two types of these mailboxes Room mailbox and Equipment mailbox. I’m rewriting some Intranet Meeting availability pages at the moment to work with Exchange Web Services and one thing you can’t do in EWS is run a query to find all these type of objects in your Exchange Organization. There are also a bunch of new features such as being able to set the resource capacity (eg how many people a room can hold) and custom resource properties (eg what type of things are in the room such as a whiteboard, projector etc). As this information is all stored in Active Directory you need to use LDAP to query this information. Because this is the type of thing I might want to use in multiple applications I thought I’d put together a little WebService that I could consume that would query this information on behalf of the requesting application and return information about the resource mailboxes firstly their email address's so i could then use this in a GetUserAvailability EWS operation and also where they are located and the extra resource capacity and resource custom properties.

The code to do this is pretty simple it’s just your standard System.DirectoryServices searching code the Ldap filter I used was to filter on the msExchRecipientDisplayType property which seems to get set to 7 for a Room Mailbox and 8 for a Equipment Mailbox. The WebService needs the rights to make these queries into the directory which you will need to solve with Impersonation and Delegation on your server. Alternatively you can hard code the alternate credential in your code (or web.config file) this is what I’m actually doing so If left that code in and just commend it out.

You can have a bit more fun with this if you expand your code eg you can use one of the Exchange custom attributes to store the Ip ranges of the local Ip subnets that are in close proximity to the Meeting Room's physical location. And then grab the IP of the client at the Intranet and use this information to return the meeting rooms close to where the client is making the request from (this depends a lot on your local setup).

I’ve put a downloadable copy of the Webserivce code here the code itself looks like.

using System;
using System.Web;
using System.Web.Services;
using System.Xml;
using System.Web.Services.Protocols;
using System.DirectoryServices;
using System.IO;

[WebService(Namespace = "http://msgdev.mvps.org/resourceFind")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
{
public Service () {
}

[WebMethod]
public XmlDocument FindRooms() {
return FindMailboxes(7);
}
[WebMethod]
public XmlDocument FindEquipment()
{
return FindMailboxes(8);
}

private XmlDocument FindMailboxes(int MailboxType) {
string sqSearchQuery = "";
string mtMailboxType = "";
switch(MailboxType){
case 7:
sqSearchQuery = "(&(&(&(mailNickname=*)(objectcategory=person)(objectclass=user)(msExchRecipientDisplayType=7))))";
mtMailboxType = "Room Mailbox";
break;
case 8:
sqSearchQuery = "(&(&(&(mailNickname=*)(objectcategory=person)(objectclass=user)(msExchRecipientDisplayType=8))))";
mtMailboxType = "Equipment";
break;
}
XmlDocument rdReturnResult = new XmlDocument();
StringWriter xsXmlString = new StringWriter();
XmlWriter xrXmlWritter = new XmlTextWriter(xsXmlString);
xrXmlWritter.WriteStartDocument();
xrXmlWritter.WriteStartElement("Resources");
xrXmlWritter.WriteAttributeString("type", mtMailboxType);
SearchResultCollection srSearchResults;
string roRootDSE = dsDirectorySearcher.SearchRoot.Path;
//string roRootDSE = "LDAP://dcName/DC=e2007dev,DC=domain,DC=com,DC=au";
//DirectoryEntry deDirectoryEntry = new DirectoryEntry(roRootDSE,
@"e2007dev\username", "password");
DirectoryEntry deDirectoryEntry = new DirectoryEntry(roRootDSE);
DirectorySearcher dsDirectorySearcher = new DirectorySearcher(deDirectoryEntry);
dsDirectorySearcher.SearchScope = SearchScope.Subtree;
dsDirectorySearcher.Filter = sqSearchQuery;
dsDirectorySearcher.PropertiesToLoad.Add("mail");
dsDirectorySearcher.PropertiesToLoad.Add("msExchResourceCapacity");
dsDirectorySearcher.PropertiesToLoad.Add("msExchResourceDisplay");
dsDirectorySearcher.PropertiesToLoad.Add("co");
dsDirectorySearcher.PropertiesToLoad.Add("displayName");
dsDirectorySearcher.PropertiesToLoad.Add("department");
dsDirectorySearcher.PropertiesToLoad.Add("description");
dsDirectorySearcher.PropertiesToLoad.Add("physicalDeliveryOfficeName");
dsDirectorySearcher.PropertiesToLoad.Add("postalCode");
dsDirectorySearcher.PropertiesToLoad.Add("postOfficeBox");
dsDirectorySearcher.PropertiesToLoad.Add("st");
dsDirectorySearcher.PropertiesToLoad.Add("streetAddress");
dsDirectorySearcher.PropertiesToLoad.Add("telephoneNumber");
srSearchResults = dsDirectorySearcher.FindAll();
foreach (SearchResult srSearchResult in srSearchResults)
{
xrXmlWritter.WriteStartElement("Mailbox");
xrXmlWritter.WriteAttributeString("emailaddress",srSearchResult.Properties["mail"][0].ToString());
WriteAttributeValue(xrXmlWritter, srSearchResult, "msExchResourceCapacity");
WriteAttributeValue(xrXmlWritter, srSearchResult, "msExchResourceDisplay");
WriteAttributeValue(xrXmlWritter, srSearchResult, "displayName");
WriteAttributeValue(xrXmlWritter, srSearchResult, "co");
WriteAttributeValue(xrXmlWritter, srSearchResult, "department");
WriteAttributeValue(xrXmlWritter, srSearchResult, "physicalDeliveryOfficeName");
WriteAttributeValue(xrXmlWritter, srSearchResult, "postalCode");
WriteAttributeValue(xrXmlWritter, srSearchResult, "postOfficeBox");
WriteAttributeValue(xrXmlWritter, srSearchResult, "st");
WriteAttributeValue(xrXmlWritter, srSearchResult, "streetAddress");
WriteAttributeValue(xrXmlWritter, srSearchResult, "telephoneNumber");
xrXmlWritter.WriteEndElement();
}
xrXmlWritter.WriteEndElement();
xrXmlWritter.WriteEndDocument();
rdReturnResult.LoadXml(xsXmlString.ToString());
return rdReturnResult;
}
private void WriteAttributeValue(XmlWriter xrXmlWritter, SearchResult
srSearchResult, String atAttribute)
{
if (srSearchResult.Properties.Contains(atAttribute))
{
xrXmlWritter.WriteStartElement(atAttribute);
xrXmlWritter.WriteValue(srSearchResult.Properties[atAttribute][0].ToString());
xrXmlWritter.WriteEndElement();
}
else
{
xrXmlWritter.WriteStartElement(atAttribute);
xrXmlWritter.WriteEndElement();
}
}
}

14 comments:

Chris Hota said...

Instead of:

"(&(&(&(mailNickname=*)(objectcategory=person)(objectclass=user)(msExchRecipientDisplayType=8))))"

why not simplify with:

"(&(mailNickname=*)(objectcategory=person)(objectclass=user)(msExchRecipientDisplayType=8))"

? Are they functionally different? Does one "cost" less to run?

Glen said...

They should return the same result at the same cost. You can use a tool like adfind http://www.joeware.net/freetools/tools/adfind/ to work out the cost and effectiveness of any AD query. This tool rocks.

Cheers
Glen

Chris said...

Can you also post the ResourceFind class ?

Thanks

Glen said...

Sorry I'm not quite sure i follow you here there is no Class. Its basically just a webservice that performs a LDAP lookup so all the code you need is included in the download link.

Cheers
Glen

Ullas said...

This example does not mention the custom properties to use to retrieve the equipments that are configured as part of a conference room. For example - Which attribute to use if i want to find out whether the conference room has a Projector?

Anonymous said...

You tell Exchange which Custom Attribute to use to store things like room size, equipment type, etc. Then query that custom attribute.

Heavy Equipment Parts said...

Have you tried the Exchange Server in Microsoft HDC 2008 system? i think you will not encounter any problems there.

A.R.Winters said...

Hello Glen, I`m looking for a solution for setting the msExchRecipientDisplayType attribute on a universal security group, but I don`t know how to do this, can you give me some information. Your Blog is great!

A.R.Winters said...

I found the solution :-) ADSIEdit.

Anonymous said...

LDAP filter worked like a charm; thanks for making the sample available.

IvanT said...

Hi all,

Thank you Glen for all the code which is very helpful.
However I stuck with the ResourceFind Webservoce...
I always get this and have no idea what changes I need to do.
Please See Below:

The remote name could not be resolved: 'mgnds02'
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.Net.WebException: The remote name could not be resolved: 'mgnds02'

Source Error:


Line 49: [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://msgdev.mvps.org/resourceFind/FindRooms", RequestNamespace="http://msgdev.mvps.org/resourceFind", ResponseNamespace="http://msgdev.mvps.org/resourceFind", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
Line 50: public System.Xml.XmlNode FindRooms() {
Line 51: object[] results = this.Invoke("FindRooms", new object[0]);
Line 52: return ((System.Xml.XmlNode)(results[0]));
Line 53: }

Any help is much appreciated!

Glen said...

mgnds02 is the name of my old Domain controller you need to search for this line in the code and replace it with the Name of your domain controller.

Cheers
Glen

IvanT said...

Thank you for your fast reply Glen.
I am a step closer, but still getting confused with the:
"http://msgdev.mvps.org/resourceFind/FindRooms" and





Should I change this to something relevant to my environment?
I have absolutely NO experience with .NET, that's why I maybe ask qiute simple questions..
The error I get now is:
A socket operation was attempted to an unreachable network 194.129.100.37:80
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.Net.Sockets.SocketException: A socket operation was attempted to an unreachable network 194.129.100.37:80

where that's the IP of my DC

You can send e-mail if you prefer:
ivantodorinskigmailcom

Corbin said...

Error 1 The type or namespace name 'ExchangeServiceBinding' could not be found (are you missing a using directive or an assembly reference?) \\hgbnwbs0101.emea.healthcare.cnb\~GEAQQ\Personal Data\Visual Studio 2012\Projects\MtgRoom\MtgRoom\Default.aspx.cs 88 9 MtgRoom
Error 2 The type or namespace name 'ExchangeServiceBinding' could not be found (are you missing a using directive or an assembly reference?) \\hgbnwbs0101.emea.healthcare.cnb\~GEAQQ\Personal Data\Visual Studio 2012\Projects\MtgRoom\MtgRoom\Default.aspx.cs 88 56 MtgRoom
Error 3 The type or namespace name 'Service' does not exist in the namespace 'MtgRoom.ResourceFind' (are you missing an assembly reference?) \\hgbnwbs0101.emea.healthcare.cnb\~GEAQQ\Personal Data\Visual Studio 2012\Projects\MtgRoom\MtgRoom\Default.aspx.cs 102 30 MtgRoom
Error 4 The type or namespace name 'Service' does not exist in the namespace 'MtgRoom.ResourceFind' (are you missing an assembly reference?) \\hgbnwbs0101.emea.healthcare.cnb\~GEAQQ\Personal Data\Visual Studio 2012\Projects\MtgRoom\MtgRoom\Default.aspx.cs 102 82 MtgRoom
Error 5 The name 'dgDataGrid' does not exist in the current context \\hgbnwbs0101.emea.healthcare.cnb\~GEAQQ\Personal Data\Visual Studio 2012\Projects\MtgRoom\MtgRoom\Default.aspx.cs 265 9 MtgRoom
Error 6 The name 'dgDataGrid' does not exist in the current context \\hgbnwbs0101.emea.healthcare.cnb\~GEAQQ\Personal Data\Visual Studio 2012\Projects\MtgRoom\MtgRoom\Default.aspx.cs 266 9 MtgRoom


Trying to run this on exchange 2010 - get the above errors - any ideas?