Skip to main content

Creating a RSS feed of an Exchange 2007 Mailbox Folder using Exchange Web Services C# and Powershell

I’m a real fan of RSS it tends to by my preferred method of reading and aggregating information so I though I’d see how easy it would be to generate a RSS feed using the new Exchange Web Services in Exchange 2007. Fortunately the XmlTextWriter class in .NET makes creating XML very easy when compared with the old XMLDOM Com object. To create a feed of items from a folder using EWS you need to first use a FindItem request which will return a list of items in a folder I used a restriction so it would only return items that where less then 7 days old. One of the restictions with the FindItem operation as stated in the SDK “FindItem returns only the first 512 bytes of any streamable property. For Unicode, it returns the first 255 characters by using a null-terminated Unicode string. It does not return any of the message body formats or the recipient lists. FindItem will return a recipient summary. You can use the GetItem Operation to get the details of an item.” ref This means you would only be able to return a small percentage of body of a message which if you’re creating a summary feed is okay because this will be enough characters to give you a decent summary. I wanted a full feed so I went with using a separate GetItem Operation on each of the EntryIDs that are returned from the finditem request.

One of the other new things I’ve used in this Code is one of the cooler new features of Exchange Web Services which is Impersonation. Previously if you wanted to write code that was going to run against a mailbox that was being run under the security context of an account that wasn’t the owner of that mailbox that the account in question would need to be given rights to this mailbox eg via delegation or AD Users and Computers etc. With Impersonation you still have to grant rights for another account to access a mailbox other then its own but these rights are just specific to EWS impersonation so granting rights in this way means that the account can use EWS to access a mailbox but it cant use OWA or Outlook (or any other API). So from a security standpoint this is pretty desirable and gives you much more leverage in your applications (and keeps those auditors happy). The Exchange SDK has details on how to give an account Impersonation rights it involves granting two rights the first is on the Server which allows a user to submit impersonation calls and the second is either on the user account you wish to access itself or the Mailbox database if you wanted to access all mailboxes within a particular mail store see. Using Impersonation is relatively easy you can use the UPN, SID or SMTP address of the account you want to access I went for the SMTP address. The impersonation information is added to the SOAP header eg in the powershell script the following represents adding the SOAP header for impersation

+ "<soap:Header>"`
+ "<t:ExchangeImpersonation>" `
+ "<t:ConnectingSID>" `
+ "<t:PrimarySmtpAddress>" + $mbMailboxToAccess + "</t:PrimarySmtpAddress>" `
+ "</t:ConnectingSID>" `
+ "</t:ExchangeImpersonation>" `
+ "</soap:Header>" `
+ "<soap:Body>" `

Another thing to note in this script is in the Link node of each RSS item I put a link into so if you double clicked on the item it would open the original email in OWA. Now with Exchange 2003/2000 you could just use the Href value of the email in question and OWA would render the item okay. With OWA on 2007 this doesn’t work instead you need to use the EntryID of the Item which you can get from the FindItem request. The one thing I did find is that this itemID cant be used as is from the FindItem request and needed to be transposed a bit to make it work. I’m not sure if this will vary between servers or not and there’s no documentation on this type of thing so its really a best guess. What I used did work for me on multiple mailboxes on the same server.

I stared out writing this as a WebService in C# using the EWS proxy objects but I decided I’d much rather have it as a powershell script as it would be easy to schedule and run so I ended up creating a C# version and a Powershell version. Both versions require 5 variables to be set

$snServername = "Servername"

Self Explanatory
$unUserName = "Username"
$psPassword = "password"
$dnDomainName = "domain"


This is the Username and Password of the account you will be using to do the Impersonation. (You don’t have to hardcode them if you can use NTLM)


$mbMailboxToAccess = user@smtpdomain.com

This is the smtp address of the user you want to aggregate

The code aggregates the last 7 days worth or items in the inbox this could be changed to another folder other then the inbox (if you wanted) or the number of days to aggregate can be changed by modifying the following line.

+ "<t:Constant Value=`"" +
$datetimetoquery.ToUniversalTime().AddDays(-7).ToString("yyyy-MM-ddThh:mm:ssZ")
+ "`"/>"`

I’ve put a download of the C# code and powershell script here the script itself looks like.

function
GetItem($smSoapMessage){
$bdBodytext = ""
$WDRequest1 = [System.Net.WebRequest]::Create($strRootURI)
$WDRequest1.ContentType = "text/xml"
$WDRequest1.Headers.Add("Translate", "F")
$WDRequest1.Method = "Post"
$WDRequest1.Credentials = $cdUsrCredentials
$bytes1 = [System.Text.Encoding]::UTF8.GetBytes($smSoapMessage)
$WDRequest1.ContentLength = $bytes1.Length
$RequestStream1 = $WDRequest1.GetRequestStream()
[void]$RequestStream1.Write($bytes1, 0, $bytes1.Length)
[void]$RequestStream1.Close()
$WDResponse1 = $WDRequest1.GetResponse()
$ResponseStream1 = $WDResponse1.GetResponseStream()
$ResponseXmlDoc1 = new-object System.Xml.XmlDocument
$ResponseXmlDoc1.Load($ResponseStream1)
$tbBodyNodes = @($ResponseXmlDoc1.getElementsByTagName("t:Body"))
for($itemNums=0;$itemNums -lt $tbBodyNodes.Count;$itemNums++){
$bdBodytext = $tbBodyNodes[$itemNums].'#text'.ToString()
}
return $bdBodytext
}


$snServername = "servername"
$unUserName = "user"
$psPassword = "password"
$dnDomainName = "domain"
$mbMailboxToAccess = "user@smtpdomain.com"
$cdUsrCredentials = new-object System.Net.NetworkCredential($unUserName , $psPassword
, $dnDomainName)
$xsXmlFileName = "c:\feedname.xml"
[System.Reflection.Assembly]::LoadWithPartialName("System.Web") > $null
$xrXmlWritter = new-object
System.Xml.XmlTextWriter($xsXmlFileName,[System.Text.Encoding]::UTF8)
$xrXmlWritter.WriteStartDocument()
$xrXmlWritter.WriteStartElement("rss")
$xrXmlWritter.WriteAttributeString("version", "2.0")
$xrXmlWritter.WriteStartElement("channel")
$xrXmlWritter.WriteElementString("title", "Inbox Feed For " + $mbMailboxToAccess)
$xrXmlWritter.WriteElementString("link", "https://" + $snServerName + "/owa/")
$xrXmlWritter.WriteElementString("description", "Exchange Inbox Feed For" +
$mbMailboxToAccess)
$datetimetoquery = get-date
$smSoapMessage = "<?xml version='1.0' encoding='utf-8'?>" `
+ "<soap:Envelope xmlns:soap=`"http://schemas.xmlsoap.org/soap/envelope/`" " `
+ " xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`"
xmlns:xsd=`"http://www.w3.org/2001/XMLSchema`"" `
+ " xmlns:t=`"http://schemas.microsoft.com/exchange/services/2006/types`" >" `
+ "<soap:Header>" `
+ "<t:ExchangeImpersonation>" `
+ "<t:ConnectingSID>" `
+ "<t:PrimarySmtpAddress>" + $mbMailboxToAccess + "</t:PrimarySmtpAddress>" `
+ "</t:ConnectingSID>" `
+ "</t:ExchangeImpersonation>" `
+ "</soap:Header>" `
+ "<soap:Body>" `
+ "<FindItem
xmlns=`"http://schemas.microsoft.com/exchange/services/2006/messages`" " `
+ "xmlns:t=`"http://schemas.microsoft.com/exchange/services/2006/types`"
Traversal=`"Shallow`"> " `
+ "<ItemShape>" `
+ "<t:BaseShape>AllProperties</t:BaseShape>" `
+ "<AdditionalProperties
xmlns=""http://schemas.microsoft.com/exchange/services/2006/types"">" `
+ "<ExtendedFieldURI PropertyTag=""0x3FD9"" PropertyType=""String"" />" `
+ "<ExtendedFieldURI PropertyTag=""0x10F3"" PropertyType=""String"" />" `
+ "<ExtendedFieldURI PropertyTag=""0x0C1A"" PropertyType=""String"" />" `
+ "</AdditionalProperties>" `
+ "</ItemShape>" `
+ "<Restriction>" `
+ "<t:IsGreaterThanOrEqualTo>" `
+ "<t:FieldURI FieldURI=`"item:DateTimeSent`"/>"`
+ "<t:FieldURIOrConstant>" `
+ "<t:Constant Value=`"" +
$datetimetoquery.ToUniversalTime().AddDays(-7).ToString("yyyy-MM-ddThh:mm:ssZ")
+ "`"/>"`
+ "</t:FieldURIOrConstant>"`
+ "</t:IsGreaterThanOrEqualTo>"`
+ "</Restriction>"`
+ "<ParentFolderIds>" `
+ "<t:DistinguishedFolderId Id=`"inbox`"/>" `
+ "</ParentFolderIds>" `
+ "</FindItem>" `
+ "</soap:Body></soap:Envelope>"

$strRootURI = "https://" + $snServername + "/ews/Exchange.asmx"
$WDRequest = [System.Net.WebRequest]::Create($strRootURI)
$WDRequest.ContentType = "text/xml"
$WDRequest.Headers.Add("Translate", "F")
$WDRequest.Method = "Post"
$WDRequest.Credentials = $cdUsrCredentials
$bytes = [System.Text.Encoding]::UTF8.GetBytes($smSoapMessage)
$WDRequest.ContentLength = $bytes.Length
$RequestStream = $WDRequest.GetRequestStream()
$RequestStream.Write($bytes, 0, $bytes.Length)
$RequestStream.Close()
$WDResponse = $WDRequest.GetResponse()
$ResponseStream = $WDResponse.GetResponseStream()
$ResponseXmlDoc = new-object System.Xml.XmlDocument
$ResponseXmlDoc.Load($ResponseStream)
$subjectnodes = @($ResponseXmlDoc.getElementsByTagName("t:Subject"))
$FromNodes = @($ResponseXmlDoc.getElementsByTagName("t:Name"))
$SentNodes = @($ResponseXmlDoc.getElementsByTagName("t:DateTimeSent"))
$SizeNodes = @($ResponseXmlDoc.getElementsByTagName("t:Size"))
$IDNodes = @($ResponseXmlDoc.getElementsByTagName("t:ItemId"))
$dsDescription = @($ResponseXmlDoc.getElementsByTagName("t:Value"))
for($i=0;$i -lt $subjectnodes.Count;$i++){
$Senttime = [System.Convert]::ToDateTime($SentNodes[$i].'#text'.ToString())
$Senttime.ToString() + " " + $FromNodes[$i].'#text' + " " +
$subjectnodes[$i].'#text' + " " + $SizeNodes[$i].'#text'
$IdNodeID = $IDNodes[$i].GetAttributeNode("Id")
$ckChangeKey = $IDNodes[$i].GetAttributeNode("ChangeKey")
$smSoapMessage = "<?xml version='1.0' encoding='utf-8'?>" `
+ "<soap:Envelope xmlns:soap=`"http://schemas.xmlsoap.org/soap/envelope/`" "
`
+ " xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`"
xmlns:xsd=`"http://www.w3.org/2001/XMLSchema`"" `
+ " xmlns:t=`"http://schemas.microsoft.com/exchange/services/2006/types`" >"
`
+ "<soap:Header>" `
+ "<t:ExchangeImpersonation>" `
+ "<t:ConnectingSID>" `
+ "<t:PrimarySmtpAddress>" + $mbMailboxToAccess + "</t:PrimarySmtpAddress>"
`
+ "</t:ConnectingSID>" `
+ "</t:ExchangeImpersonation>" `
+ "</soap:Header>" `
+ "<soap:Body><GetItem
xmlns=`"http://schemas.microsoft.com/exchange/services/2006/messages`"><ItemShape>"
`
+ "<BaseShape
xmlns=`"http://schemas.microsoft.com/exchange/services/2006/types`">Default</BaseShape></ItemShape>"
`
+ "<ItemIds><ItemId Id=`"" + $IdNodeID.'#text' + "`"" `
+ " xmlns=`"http://schemas.microsoft.com/exchange/services/2006/types`"
/></ItemIds></GetItem></soap:Body>" `
+ "</soap:Envelope>"
$xrXmlWritter.WriteStartElement("item")
$xrXmlWritter.WriteElementString("title", $subjectnodes[$i].'#text')
$xrXmlWritter.WriteElementString("link", "https://" + $snServername +
"/owa/?ae=Item&t=IPM.Note&id=Rg" +
[System.Web.HttpUtility]::UrlEncode($IdNodeID.'#text').Substring(58).Replace("%3d","J"))
$xrXmlWritter.WriteElementString("author", $FromNodes[$i].'#text')
$xrXmlWritter.WriteStartElement("description")
$xrXmlWritter.WriteRaw("<![CDATA[")
$bdBodytext = GetItem($smSoapMessage)
$xrXmlWritter.WriteRaw($bdBodytext)
$xrXmlWritter.WriteRaw("]]>")
$xrXmlWritter.WriteEndElement()
$xrXmlWritter.WriteElementString("pubDate", $Senttime.ToString("r"))
$xrXmlWritter.WriteElementString("guid", $IdNodeID.'#text')
$xrXmlWritter.WriteEndElement()

}
$xrXmlWritter.WriteEndElement()
$xrXmlWritter.WriteEndElement()
$xrXmlWritter.WriteEndDocument()
$xrXmlWritter.Close()
"Done"




Popular posts from this blog

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

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

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.