Skip to main content

Class library to help setting Out of Office (Oof) via Exchange Web Services with Powershell and .NET

One of the added features that Exchange 2007 gives you when teamed with Outlook 2007 is an Enhanced Out of Office functionality. In previous version of Exchange the OOF was a on/off setting with a Text based message to represent your OOF reply. In 2007 you now have the 3 states on/off and scheduled (meaning that oof message will only be sent during the scheduled time). The user also has the ability to set Internal and External OOF response messages and the ability to set who the External oof messages will be sent to (the three states for this is All, None and Known contacts).

From a coders point of view you have gone from something that was two properties to something a little more complex (kind of like the difference between a Volkswagen beetle and a decent car)(*note to self must not blog while watching Top Gear) . Although it is still possible to set the OOF message via the old API’s Exchange Web Services is the only method that actually provides a fully supported and functional method of setting the OOF programmatically.

To make scripting a bit easy I decided to put a class library together this facilitates a few things that while not impossible in script make for a lot of complexity, bloat and possible errors (kind of like a Leyland P76). To cater for a number of different way one might want to set Oof setting I create a lot of overloads to allow executing the methods in a number of different ways.

I’ve included a basic autodiscover function in the class library it does have overloads that will allow you to specify the Cas server you want to use. Otherwise it will first do a search of Active Directory to find a Service Connection Point and then it will do an Autodisover request based on the email to find the URL of a CAS server. What this SCP and discovery function doesn’t do is try to find the closest CAS to the calling client just the first one it can find.

With Authentication I was planning to offer both impersonation and Delegation functionality in the library but this one of the peculiarlarites of Exchange Web Services. While the impersonation header is honored for nearly all other EWS operations it’s not honored by the availably service. So if you want to use this library (or EWS) to change OOF setting you need to be running the code as the Mailbox Owner or a user that has been delegated access rights to the mailbox. I’ve included overloads to allow you to specify the account you want the EWS code to run under. If you also specify the URL to the CAS server you can use this to run the script/class library from a machine that is not a member of an Internal AD domain .Otherwise by default if you don’t specify a username and password the library will run using the calling process security context (or more easily the currently logged on on user’s credentials).

The class library contains two Methods the GetOof Method that returns the current Oofsetting (the setting themselves are returned as class library properties). The SetOof method provides the ability to set the Oof setting on Exchange 2007. The SetOof method first makes a call to get the current setting and then applies the changes based on the overload you use to call the method it applies the changes to the current setting and then posts back the results to Exchange as a SetUserOofSettings request.This gets around any issues where you may loss fidelity of Oof Data where you just want to flip or change one property. Of course if you do this at the same time as the user is making changes well then you have a competition that the last one to write will win. (The user may not understand this though).

A few Notes –
The Message Text is always returned as HMTL Message body even if you just set text

Because Exchange Stores DateTimes as UTC when your setting or retrieving a duration you need to make sure you do UTC conversion.

I’ve put together a samples page the show example of using the different overloads in Powershell and C#. I’ve included a compiled version of the code as well as the source code if you want to compile it yourself or improve on or just laugh at the source code. This is only version1 so like most of things on this blog is potentially full of errors and the error handling needs to be improved but if you do find any please let me know I love a good bug

The sample page is here the download for with the DLL and source is here . The more interesting parts of the code looks like.

public String GetOof(string EmailAddress, string UserName, string Password,string Domain, string OofURL) {
String rsResult = null;
try
{
ExchangeServiceBinding ebExchangeServiceBinding = createesb(EmailAddress, UserName, Password,Domain,OofURL);
UserOofSettings uoUserOofSettings = ewsGetOOF(ebExchangeServiceBinding, EmailAddress);
this.intOofStatus = uoUserOofSettings.OofState.ToString();
if (uoUserOofSettings.InternalReply.Message != null) { this.intInternalMessage = uoUserOofSettings.InternalReply.Message.ToString(); }
if (uoUserOofSettings.InternalReply.lang != null) { this.intInternalMessageLanguage = uoUserOofSettings.InternalReply.lang.ToString(); }
if (uoUserOofSettings.ExternalReply.Message != null) { this.intExternalMessage = uoUserOofSettings.ExternalReply.Message.ToString(); }
if (uoUserOofSettings.ExternalReply.lang != null) { this.intExternalMessageLanguage = uoUserOofSettings.ExternalReply.lang.ToString(); }
this.intExternalAudienceSetting = uoUserOofSettings.ExternalAudience.ToString();
if (uoUserOofSettings.Duration != null) { this.intDuration = uoUserOofSettings.Duration; }
rsResult = "OOF Settings retrieved";
ServicePointManager.ServerCertificateValidationCallback = null;
ebExchangeServiceBinding = null;
return rsResult;
}
catch (Exception exException)
{
Console.WriteLine(exException.ToString());
return exException.ToString();
}
}

private ExchangeServiceBinding createesb(String EmailAddress, string UserName, string Password, string Domain,string OofURL) {
ServicePointManager.ServerCertificateValidationCallback =
delegate(Object obj, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
{
// Ignore Self Signed Certs
return true;
};
ExchangeServiceBinding ebExchangeServiceBinding = new ExchangeServiceBinding();
ebExchangeServiceBinding.RequestServerVersionValue = new RequestServerVersion();
ebExchangeServiceBinding.RequestServerVersionValue.Version = ExchangeVersionType.Exchange2007_SP1;
if (UserName == "")
{
ebExchangeServiceBinding.UseDefaultCredentials = true;
}
else
{
NetworkCredential ncNetCredential = new NetworkCredential(UserName, Password, Domain);
ebExchangeServiceBinding.Credentials = ncNetCredential;
}
if (OofURL == "")
{
String caCasURL = DiscoverCAS();
OofURL = DiscoverOofURL(caCasURL, EmailAddress, UserName, Password,Domain);
}
ebExchangeServiceBinding.Url = OofURL;
return ebExchangeServiceBinding;

}
private String DiscoverCAS()
{
String ScpUrlGuidString = "77378F46-2C66-4aa9-A6A6-3E7A48B19596";
String ScpPtrGuidString = "67661d7F-8FC4-4fa7-BFAC-E1D7794C1F68";
DirectoryEntry rdRootDSE = new DirectoryEntry("LDAP://RootDSE");
DirectoryEntry cfConfigPartition = new DirectoryEntry("LDAP://" + rdRootDSE.Properties["configurationnamingcontext"].Value);
DirectorySearcher cfConfigPartitionSearch = new DirectorySearcher(cfConfigPartition);
cfConfigPartitionSearch.Filter = "(&(objectClass=serviceConnectionPoint)((keywords=" + ScpPtrGuidString + ")(keywords=" + ScpUrlGuidString + ")))";
cfConfigPartitionSearch.SearchScope = SearchScope.Subtree;
string CASURL = null;
SearchResult srSearchResult = cfConfigPartitionSearch.FindOne();
if (srSearchResult != null)
{
DirectoryEntry scpServiceConnectionPoint = srSearchResult.GetDirectoryEntry();
CASURL = scpServiceConnectionPoint.Properties["serviceBindingInformation"].Value.ToString();
}
else
{
throw new ADSearchException("No SCP found");
}
return CASURL;
}
private String DiscoverOofURL(string caCASURL, string emEmailAddress,string UserName,string Password,string Domain)
{

String OofURL = null;
String auDisXML = "" +
"" + emEmailAddress + "" +
"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a" +
"
" +
"
";
System.Net.HttpWebRequest adAutoDiscoRequest = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(caCASURL);
adAutoDiscoRequest.ContentType = "text/xml";
adAutoDiscoRequest.Headers.Add("Translate", "F");
adAutoDiscoRequest.Method = "Post";
if (UserName == "")
{
adAutoDiscoRequest.UseDefaultCredentials = true;
}
else
{
NetworkCredential ncNetCredential = new NetworkCredential(UserName, Password, Domain);
adAutoDiscoRequest.Credentials = ncNetCredential;
}

byte[] bytes = Encoding.UTF8.GetBytes(auDisXML);
adAutoDiscoRequest.ContentLength = bytes.Length;
Stream rsRequestStream = adAutoDiscoRequest.GetRequestStream();
rsRequestStream.Write(bytes, 0, bytes.Length);
rsRequestStream.Close();
WebResponse adResponse = adAutoDiscoRequest.GetResponse();
Stream rsResponseStream = adResponse.GetResponseStream();
XmlDocument reResponseDoc = new XmlDocument();
reResponseDoc.Load(rsResponseStream);
XmlNodeList OofNodes = reResponseDoc.GetElementsByTagName("OOFUrl");
if (OofNodes.Count != 0)
{
OofURL = OofNodes[0].InnerText;
}
else {
throw new AutoDiscoveryException("Error during AutoDiscovery");
}
return OofURL;

}
private UserOofSettings ewsGetOOF(ExchangeServiceBinding ebExchangeServiceBinding, String emEmailAddress)
{
GetUserOofSettingsRequest goGetUserOofSettings = new GetUserOofSettingsRequest();
UserOofSettings ouOffSetting = null;
EmailAddress mbMailbox = new EmailAddress();
mbMailbox.Address = emEmailAddress;
goGetUserOofSettings.Mailbox = mbMailbox;
GetUserOofSettingsResponse goGetOoFResponse = ebExchangeServiceBinding.GetUserOofSettings(goGetUserOofSettings);
if (goGetOoFResponse.ResponseMessage.ResponseClass == ResponseClassType.Success)
{
ouOffSetting = goGetOoFResponse.OofSettings;
}
else
{
throw new EWSException(goGetOoFResponse.ResponseMessage.MessageText.ToString());
}

return ouOffSetting;
}
private String ewsSetOOF(ExchangeServiceBinding ebExchangeServiceBinding, UserOofSettings uoNewOoFSettings, String emEmailAddress)
{
SetUserOofSettingsRequest soSetUserOofSettings = new SetUserOofSettingsRequest();
soSetUserOofSettings.UserOofSettings = uoNewOoFSettings;
EmailAddress mbMailbox = new EmailAddress();
mbMailbox.Address = emEmailAddress;
soSetUserOofSettings.Mailbox = mbMailbox;
SetUserOofSettingsResponse soSetOoFResponse = ebExchangeServiceBinding.SetUserOofSettings(soSetUserOofSettings);
String rsResponse = "";

if (soSetOoFResponse.ResponseMessage.ResponseClass == ResponseClassType.Success)
{
this.intOofStatus = uoNewOoFSettings.OofState.ToString();
if (uoNewOoFSettings.InternalReply.Message != null) { this.intInternalMessage = uoNewOoFSettings.InternalReply.Message.ToString(); }
if (uoNewOoFSettings.InternalReply.lang != null) { this.intInternalMessageLanguage = uoNewOoFSettings.InternalReply.lang.ToString(); }
if (uoNewOoFSettings.ExternalReply.Message != null) { this.intExternalMessage = uoNewOoFSettings.ExternalReply.Message.ToString(); }
if (uoNewOoFSettings.ExternalReply.lang != null) { this.intExternalMessageLanguage = uoNewOoFSettings.ExternalReply.lang.ToString(); }
this.intExternalAudienceSetting = uoNewOoFSettings.ExternalAudience.ToString();
if (uoNewOoFSettings.Duration != null) { this.intDuration = uoNewOoFSettings.Duration; }
rsResponse = "Oof Setting Update Succesfully";

}
else
{
throw new EWSException(soSetOoFResponse.ResponseMessage.MessageText.ToString());
}

return rsResponse;
}
}
class EWSException : Exception
{
public EWSException(string ewsError)
{
Console.WriteLine(ewsError);
}
}
class ADSearchException : Exception
{
public ADSearchException(string AdSearchError)
{
Console.WriteLine(AdSearchError);
}
}
class AutoDiscoveryException : Exception
{
public AutoDiscoveryException(string AutoDiscoveryError)
{
Console.WriteLine(AutoDiscoveryError);
}
}
class OofSettingException : Exception
{
public OofSettingException(string OofSettingError)
{
Console.WriteLine(OofSettingError);
}
}

Popular posts from this blog

Export calendar Items to a CSV file using EWS and Powershell

Somebody asked about this last week and while I have a lot of EWS scripts that do access the Calendar I didn't have a simple example that just exported a list of the Calendar events with relevant information to a CSV file so here it is. I've talked on this one before in this howto  but when you query the calendar folder using EWS you need to use a CalendarView which will expand any recurring appointments in a calendar. There are some limits when you use a calendarview in that you can only return a maximum of 2 years of appointments at a time and paging will limit the max number of items to 1000 per call. So if you have a calendar with a very large number of appointments you need to break your query into small date time blocks. In this example script I'm just grabbing the next 7 days of appointments if you want to query a longer period you need to adjust the following lines (keeping in mind what I just mentioned) #Define Date to Query $StartDate = (Get-Date) $EndDate

Downloading a shared file from Onedrive for business using Powershell

I thought I'd quickly share this script I came up with to download a file that was shared using One Drive for Business (which is SharePoint under the covers) with Powershell. The following script takes a OneDrive for business URL which would look like https://mydom-my.sharepoint.com/personal/gscales_domain_com/Documents/Email%20attachments/filename.txt This script is pretty simple it uses the SharePoint CSOM (Client side object Model) which it loads in the first line. It uses the URI object to separate the host and relative URL which the CSOM requires and also the SharePointOnlineCredentials object to handle the Office365 SharePoint online authentication. The following script is a function that take the OneDrive URL, Credentials for Office365 and path you want to download the file to and downloads the file. eg to run the script you would use something like ./spdownload.ps1 ' https://mydom-my.sharepoint.com/personal/gscales_domain_com/Documents/Email%20attachments/filena

The MailboxConcurrency limit and using Batching in the Microsoft Graph API

If your getting an error such as Application is over its MailboxConcurrency limit while using the Microsoft Graph API this post may help you understand why. Background   The Mailbox  concurrency limit when your using the Graph API is 4 as per https://docs.microsoft.com/en-us/graph/throttling#outlook-service-limits . This is evaluated for each app ID and mailbox combination so this means you can have different apps running under the same credentials and the poor behavior of one won't cause the other to be throttled. If you compared that to EWS you could have up to 27 concurrent connections but they are shared across all apps on a first come first served basis. Batching Batching in the Graph API is a way of combining multiple requests into a single HTTP request. Batching in the Exchange Mail API's EWS and MAPI has been around for a long time and its common, for email Apps to process large numbers of smaller items for a variety of reasons.  Batching in the Graph is limited to a m
All sample scripts and source code is provided by for illustrative purposes only. All examples are untested in different environments and therefore, I cannot guarantee or imply reliability, serviceability, or function of these programs.

All code contained herein is provided to you "AS IS" without any warranties of any kind. The implied warranties of non-infringement, merchantability and fitness for a particular purpose are expressly disclaimed.