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

Testing and Sending email via SMTP using Opportunistic TLS and oAuth in Office365 with PowerShell

As well as EWS and Remote PowerShell (RPS) other mail protocols POP3, IMAP and SMTP have had OAuth authentication enabled in Exchange Online (Official announcement here ). A while ago I created  this script that used Opportunistic TLS to perform a Telnet style test against a SMTP server using SMTP AUTH. Now that oAuth authentication has been enabled in office365 I've updated this script to be able to use oAuth instead of SMTP Auth to test against Office365. I've also included a function to actually send a Message. Token Acquisition  To Send a Mail using oAuth you first need to get an Access token from Azure AD there are plenty of ways of doing this in PowerShell. You could use a library like MSAL or ADAL (just google your favoured method) or use a library less approach which I've included with this script . Whatever way you do this you need to make sure that your application registration  https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-

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

How to test SMTP using Opportunistic TLS with Powershell and grab the public certificate a SMTP server is using

Most email services these day employ Opportunistic TLS when trying to send Messages which means that wherever possible the Messages will be encrypted rather then the plain text legacy of SMTP.  This method was defined in RFC 3207 "SMTP Service Extension for Secure SMTP over Transport Layer Security" and  there's a quite a good explanation of Opportunistic TLS on Wikipedia  https://en.wikipedia.org/wiki/Opportunistic_TLS .  This is used for both Server to Server (eg MTA to MTA) and Client to server (Eg a Message client like Outlook which acts as a MSA) the later being generally Authenticated. Basically it allows you to have a normal plain text SMTP conversation that is then upgraded to TLS using the STARTTLS verb. Not all servers will support this verb so if its not supported then a message is just sent as Plain text. TLS relies on PKI certificates and the administrative issue s that come around certificate management like expired certificates which is why I wrote th
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.