Exchange 2010 EWS Managed API – Report Total Size of Attachments for Single Mailbox

Download MailboxAttachmentStatus Console Application

I have the complete code posted below.  You will need to have EWS 2.1 (minimum) installed and referenced in your application in order to compile the code. The program is essentially a conglomeration of tweaked code snippets posted on stack overflow.

The EWS Managed API makes it very easy to interface with your Exchange 2010 instances.  Roughly the way it works is on your Exchange server there’s an iis service running and under the Default Website is the ews application service.  By default the url to accesss this service is https://mail.domain.com/EWS/Exchange.asmx .  However, I am able to use autodiscover in my environment and that’s how I access the ews service in my code.  I will go through the basics of connecting to ews, impersonating users, and dealing with large item sets.

The program requires five parameters and one optional one.   Parameters are:

  1. target mailbox SAM account name i.e. RFederer
  2. admin SAM account name used to access exchange i.e. SMay
  3. admin password i.e. Password123
  4. Active Directory Domain name used to authenticate to exchange i.e. TURKEYSDOIT
  5. SMTP suffix which is probably the same as your domain plus .com i.e. @turkeysdoit.com
  6. (optional) offset parameter.  determines how many items to return per call when looping through a large set of mail items.  Default is 500 and maximum is 1000.

Command line execution syntax: MailboxAttachmentStatus.exe RFederer SMay Password123 TURKEYSDOIT @turkeysdoit.com 500

note: I separate the domain and smtp suffix because in my environment they’re different.  Also makes the program more flexible.

The first thing we need to do is connect to the local exchange client access server using autodiscover.


public static void ConnectExchange(string user, string password, string domain, string suffix)
        {
            try
            {
                service = new ExchangeService(ExchangeVersion.Exchange2010);

                service.Credentials = new NetworkCredential(user, password, domain);

                service.AutodiscoverUrl(user + suffix);
            }
            catch(Exception e)
            {
                Console.WriteLine(Environment.NewLine + e.Data + Environment.NewLine + e.Message + Environment.NewLine);
                Console.ReadLine();
                Environment.Exit(1);
            }
        }

Provided the admin account has proper permissions to manage other mailboxes we can impersonate the target mailbox without knowing the password for the user attached to it.  When you’re impersonating another mailbox you have access to all the mail items and their properties.



            try
            {
                //impersonate target mailbox
                service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, targetMailbox + suffix);
                Console.WriteLine("Scanning " + targetMailbox + suffix + " mailbox...");

We now have access to the target mailbox and can start gathering mail items.  One challenge I ran into was searching the mail items of all folders underneath the inbox as well as the inbox.  by default if you search the inbox using FindItems it will not return items from sub-folders.  To solve this you need to expose the allitems folder and load all mail items from all folders into it.


//Define variables to be used in the FindFolders method which populates the allitemsfolder
FolderId rootFolderId = new FolderId(WellKnownFolderName.Root);
FolderView folderView = new FolderView(500);
folderView.Traversal = FolderTraversal.Shallow;

//define filter collection used to retrieve all items from all folders into the allitemsdfolder
ExtendedPropertyDefinition allFoldersType = new ExtendedPropertyDefinition(13825, MapiPropertyType.Integer);
SearchFilter searchFilter1 = new SearchFilter.IsEqualTo(allFoldersType, "2");
SearchFilter searchFilter2 = new SearchFilter.IsEqualTo(FolderSchema.DisplayName, "allitems");
SearchFilter.SearchFilterCollection searchFilterCollection = new SearchFilter.SearchFilterCollection(LogicalOperator.And);
searchFilterCollection.Add(searchFilter1);
searchFilterCollection.Add(searchFilter2);

//combine mail items from all folders and load into allitemsfolder
 FindFoldersResults findFoldersResults = service.FindFolders(rootFolderId, searchFilterCollection, folderView);
 allItemsFolder = findFoldersResults.Folders[0];

Now we don’t want the ews service to send us every single mail item.  Actually we don’t it to send us any whole item just the properties.  Furthermore, we only want to receive properties for mail items which have attachments.  The reason we want these things is for performance and because ews will throttle you if you ask for too much.
Very helpful thread on fetching mail items on stack overflow


                //define filter used to search the allitemsfolder for only items with attachments
                 SearchFilter filter = new SearchFilter.IsEqualTo(ItemSchema.HasAttachments, true);

                //loop through all ItemView pages and keep track of attachment count and cumulative size

                //reveals the hasAttachment property on mail items
                PropertySet psSet = new PropertySet(BasePropertySet.FirstClassProperties);

                //microsoft recommended technique for paging through FindItems results
                bool more = true;
                //int offset = 500;

                ItemView view = new ItemView(offset, 0, OffsetBasePoint.Beginning);

                while (more)
                {
                    FindItemsResults fItems = allItemsFolder.FindItems(filter, view);

                    mailItemCount += fItems.Count();

                    service.LoadPropertiesForItems(fItems.Items, psSet);

                    foreach (Item ibItem in fItems.Items)
                    {
                        foreach (Attachment at in ibItem.Attachments)
                        {
                            size += at.Size;
                            attachmentCount += 1;
                        }
                    }

                    more = fItems.MoreAvailable;
                    if (more)
                    {
                        view.Offset += offset;
                        Console.WriteLine("Attachments scanned so far: " + attachmentCount);
                    }
                }

Complete code:

using System;
using System.Configuration;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Exchange.WebServices;
using Microsoft.Exchange.WebServices.Data;
using System.Net;
using System.Net.Security;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
using System.DirectoryServices.ActiveDirectory;
 
namespace MailboxAttachmentsStatus
{
    class Program
    {
        private static ExchangeService service { getset; }
        private static string targetMailbox { getset; }
        private static int offset { getset; }
        private static string user { getset; }
        private static string password { getset; }
		private static string domain { getset; }
        private static string suffix { getset; }
 
        static void Main(string[] args)
        {
            //show program help information
            if (args[0] == "/?" || args[0] =="?")
            {
                Console.WriteLine("\nWritten by: Sean May \nMailboxAttachmentsStatus.exe \nEWS 2.1 for Exchange 2010");
                Console.WriteLine("\nThis program will return in MB the amount of space attachments use in a mailbox\n\n");
                Console.WriteLine("-- Parameters --\n");
                Console.WriteLine("TargetUserName - SAM account name of the target user");
                Console.WriteLine("AdminUserName - SAM account name of exchange admin");
                Console.WriteLine("AdminPassword - password of exchange admin");
                Console.WriteLine("Domain - Active Directory domain exchange server is in");
                Console.WriteLine("SMTP-Suffix - @TurkeysDoIT.com include the @ symbol\n");
                Console.WriteLine("MailboxAttachmentStatus.exe RFederer SMay Password123 turkeysdoit @turkeysdoit.com 100\n");
                Console.WriteLine("*** Offset: Must be between 1-1000.  Default is 500");
                Environment.Exit(69);
            }
 
            //Command line parameters
            targetMailbox = args[0];
            user = args[1];
            password = args[2];
			domain = args[3];
			suffix = args[4];
 
            int j;
            if (Int32.TryParse(args[5], out j))
            {
                if (j < 0 || j > 1000)
                {
                    offset = 500;
                }
                else { offset = j; }
            }
            else { offset = 500; }
 
            //connect to exchange
            ConnectExchange(user, password, domain, suffix);
 
            //Loop through items with attachments and calculate total size
            ReportAttachmentsStatus();
        }
 
        private static void ReportAttachmentsStatus()
        {
            long size = 0;
            int mailItemCount = 0;
            int attachmentCount = 0;
            Folder allItemsFolder = null;
 
            //Define variables to be used in the FindFolders method which populates the allitemsfolder          
            FolderId rootFolderId = new FolderId(WellKnownFolderName.Root);
            FolderView folderView = new FolderView(500);
            folderView.Traversal = FolderTraversal.Shallow;
 
            //define filter collection used to retrieve all items from all folders into the allitemsdfolder
            ExtendedPropertyDefinition allFoldersType = new ExtendedPropertyDefinition(13825, MapiPropertyType.Integer);
            SearchFilter searchFilter1 = new SearchFilter.IsEqualTo(allFoldersType, "2");
            SearchFilter searchFilter2 = new SearchFilter.IsEqualTo(FolderSchema.DisplayName, "allitems");
            SearchFilter.SearchFilterCollection searchFilterCollection = new SearchFilter.SearchFilterCollection(LogicalOperator.And);
            searchFilterCollection.Add(searchFilter1);
            searchFilterCollection.Add(searchFilter2);
 
            //define filter used to search the allitemsfolder for only items with attachments
            SearchFilter filter = new SearchFilter.IsEqualTo(ItemSchema.HasAttachments, true);
 
            try
            {
                //impersonate target mailbox
                service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, targetMailbox + suffix);
                Console.WriteLine("Scanning " + targetMailbox + suffix + " mailbox...");
 
                //combine mail items from all folders and load into allitemsfolder
                FindFoldersResults findFoldersResults = service.FindFolders(rootFolderId, searchFilterCollection, folderView);
                allItemsFolder = findFoldersResults.Folders[0];
 
                //loop through all ItemView pages and keep track of attachment count and cumulative size
                //reveals the hasAttachment property on mail items
                PropertySet psSet = new PropertySet(BasePropertySet.FirstClassProperties);
 
                //microsoft recommended technique for paging through FindItems results
                bool more = true;
                //int offset = 500;
 
                ItemView view = new ItemView(offset, 0, OffsetBasePoint.Beginning);
 
                while (more)
                {
                    FindItemsResults<Item> fItems = allItemsFolder.FindItems(filter, view);
 
                    mailItemCount += fItems.Count();
 
                    service.LoadPropertiesForItems(fItems.Items, psSet);
 
                    foreach (Item ibItem in fItems.Items)
                    {
                        foreach (Attachment at in ibItem.Attachments)
                        {
                            size += at.Size;
                            attachmentCount += 1;
                        }
                    }
 
                    more = fItems.MoreAvailable;
                    if (more)
                    {
                        view.Offset += offset;
                        Console.WriteLine("Attachments scanned so far: " + attachmentCount);
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Data + Environment.NewLine + Environment.NewLine + e.Message + Environment.NewLine);
            }
            finally
            {
                Console.WriteLine();
                Console.WriteLine("Total mail item count: " + allItemsFolder.TotalCount);
                Console.WriteLine("Total number of mail items with attachments: " + mailItemCount);
                Console.WriteLine("Total number of attachments scanned: " + attachmentCount);
                Console.WriteLine("total size of attachments in MB of " + targetMailbox + "'s mailbox is: " + ConvertBytesToMegabytes(size).ToString("F"));
                Environment.Exit(0);             
            }
        }
 
        public static void ConnectExchange(string user, string password, string domain, string suffix)
        {
            try
            {
                service = new ExchangeService(ExchangeVersion.Exchange2010);
 
                service.Credentials = new NetworkCredential(user, password, domain);
 
                service.AutodiscoverUrl(user + suffix);
            }
            catch(Exception e)
            {
                Console.WriteLine(Environment.NewLine + e.Data + Environment.NewLine + e.Message + Environment.NewLine);
                Console.ReadLine();
                Environment.Exit(1);
            }
        }
 
        static double ConvertBytesToMegabytes(long bytes)
        {
            return (bytes / 1024f) / 1024f;
        }
    }
}