Client Message Size Limits

powershellExchange 2013 enforces certain message size limits when it comes to client messages. These limits are in-place so clients can’t generate excessive load on your Exchange environment. These limits are determined for various access methods in multiple web.config files on Exchange Client Access Servers as well as Mailbox Servers.

Sometimes you may have good reasons to increase those limits. For example, when migrating to Office 365 using a product like MigrationWiz, you may want to increase the limit for Exchange Web Service (EWS) requests to allow for migration of larger items. Another example is when you want to allow for bigger attachments in Outlook WebApp (OWA). On TechNet, there’s an article on how to reconfigure these limits. However, the process consists of editing multiple web.config files, replacing multiple values in the same file, and following this process on each Exchange 2013 server in your environment. This is not only labor intensive and prone to error, but becomes tedious when you consider that each Cumulative Update will overwrite your web.config files.

But do not despair. To execute these changes for OWA and EWS, I have created a PowerShell script which will perform these tasks for you.

Using the script requires Exchange 2013. You need to provide the server name (default is local server) or AllServers to apply to all Exchange 2013 servers in your environment. The script will modify the web.config remotely using the system share (e.g. C$), using the location of the Exchange installation, and uses IISRESET tool to restart IIS. It will create a backup of the web.config before modifying it.


  1. The script checks for running in elevated mode when running against the local machine.
  2. Current version of the script requires Exchange Management Shell, to run Exchange cmdlets for checking installed roles a.o., as the web.config files which require editing depend on the installed roles.
  3. For OWA, add ~33% to the value you want to specify to compensate for encoding overhead.
  4. When connected to an Exchange server, the script processes the server hosting the EMS session last to prevent abortion caused by IIS reset.
  5. Script currently runs against Exchange 2013.

The script Configure-ClientSizeLimits.ps1 uses the following syntax:

.\Configure-ClientSizeLimits.ps1 [-Server |-AllServers] [-OWA ] [-EWS ] [-Reset] 

A quick walk-through on the parameters and switches:

  • Server specifies the server to configure. When omitted, it will configure the local server. This parameter is mutually exclusive with AllServers.
  • AllServers switch specifies to configure all Exchange 2013 servers. This switch is mutually exclusive with Server.
  • OWA configures the message size limit for OWA. Value is in 1KB units.
  • EWS configures the message size limit for EWS. Value is in 1KB units.
  • Reset switch specifies to perform an IISRESET against servers after reconfiguration of client-specific message size limits.

So, suppose you want to configure an OWA message size limit for you can use:

.\Configure-ClientSizeLimits.ps1 -Server EX01 -OWA 100 -EWS 10240 -Reset

Configure Client Size Limits If you want to configure EWS limits for all servers without resetting IIS, you could use:

.\Configure-ClientSizeLimits.ps1 -AllServers -EWS 10240

You can download the script from the TechNet Gallery here.

Feedback is welcomed through the comments. If you got scripting suggestions or questions, do not hesitate using the contact form.

See TechNet Gallery page.

To Do
Compatibility with Exchange 2010 and removal of dependency on Exchange Management Shell.

Ignite 2015 Session Download Script

ignite ButtonYesterday was the first day of Ignite, and Exchange fellow Tony Redmond put up a nice summary of the first day, keynoted included, here.

For those not attending Microsoft Ignite, attending different sessions or not able to enter a session because the room was full, Microsoft publishes Ignite sessions on Channel 9. Because you may want to watch sessions offline, some people created scripts to retrieve all session videos and slidedecks.

Here is a slightly modified script, originally from Claus Nielsen, to download all Microsoft Ignite 2015 videos and slidedecks as the become available on Channel9. You can select sessions based on category or speaker, which helps narrowing down the contents offered at Ignite to sessions you are interested in. The script also allows you to download other session videos and decks, for example from Build 2015 or last year’s TechEd NA.

You can download the script from the TechNet Gallery here.

(Re)configuring IM Integration

powershellAnyone who has configured Exchange 2013 IM integration with Lync Server at some point has to modify the web.config file on the Mailbox servers to configure OWA with the proper certificate for enabling IM. Another thing (read: nuisance) is that when you have configured IM integration and you apply a Cumulative Update to Exchange 2013, the web.config will be overwritten, in which case you need to reapply those changes to the web.config file.

This is where the script Configure-IMIntegration.ps1 might come in handy.

Using the script requires Exchange 2013 and Lync Server. You need to provide the Lync pool and the Mailbox server you want to configure needs to have a valid certificate assigned to UM services. The script will modify the web.config remotely using the system share (e.g. C$), using the location of the Exchange installation, and uses WMI to recycle the OWA Application Pool in IIS. It will create a backup of the web.config before modifying it.

Note that the script does not perform the following steps:

  • It does not perform the Lync Server parts to configure IM integration, e.g. configure Exchange as a trusted application.
  • It does not configure Lync Server as an partner application for Exchange (Configure-EnterprisePartnerApplication.ps1).

The script Configure-IMIntegration.ps1 uses the following syntax:

.\Configure-IMIntegration.ps1 [-Server <String>] -PoolFQDN <String> [-AllCAS] [-AllMailbox]

A quick walk-through on the parameters and switches:

  • Server specifies the server to configure. When omitted, it will configure the local server. This parameter is mutually exclusive with AllMailbox.
  • AllMailbox switch specifies to configure all Mailbox servers. This switch is mutually exclusive with Server.
  • AllCAS switch specifies to enable IM integration on all Client Access servers.
  • PoolFQDN specifies the FQDN of the Lync Pool to use. This parameter is required.

So, suppose you want to quickly reconfigure IM integration on a Mailbox server after applying a Cumulative Update, you can use:

.\Configure-IMIntegration.ps1 -PoolFQDN –Server


Or, you can quickly configure Mailbox servers and CAS servers for IM integration after performing the required steps to configure the trusted application settings and installing and assigning the certificate for UM:

.\ Configure-IMIntegration.ps1 -PoolFQDN -AllMailbox –AllCAS


Note that the script will skip Mailbox servers for which it cannot find a valid UM certificate assignment. Also, in the example above, the CAS servers had already been enabled for IM.

You can download the script from the TechNet Gallery here.

Feedback is welcomed through the comments. If you got scripting suggestions or questions, do not hesitate using the contact form.

Revision History
See TechNet Gallery page.

Exchange-Processor Query Tool: PowerShell Edition

powershellAnyone sizing for Exchange Server 2013 or even still Exchange Server 2010, using the Server Role Requirements Calculator, has to determine processor requirements at some point. This is accomplished by looking up the SPECint_rate2006 score of the planned processor configuration and matching that against the calculated number of required megacycles by the calculator. To account for fail-over situations, additional overhead needs to be added to the number of megacycles. The process as part of the overall sizing has been explained in detail by Jeff Mealiffe here.

The Exchange consultants’ Swiss army knife when determining SPECint rates is the Exchange Processor Query Tool, an Excel sheet designed by Scott Alexander from Microsoft, which allows you to easily look up and determine the SPECint_rate2006 value by inputting a processor model. While still useful, the tool has been out there since 2011. Also, it would be nice sometimes to see which systems are eligible for a certain sizing specification, rather than validating if the planned processor configuration meets the sizing requirements.

So, I wrote a PowerShell script which can query the SPECint rates for you. Because the rating scores are returned as objects, you can perform additional tasks using PowerShell functionality, such as:

  • Use additional criteria, such as vendor, min/max number of cores, etc.
  • Calculate the average SPECint2006 Rate Value for a certain CPU/cores configuration.
  • You can use the SPECint value calculated by the Server Role Requirements Calculator  to find hardware configurations which meet the required total megacycles requirements, optionally including a required overhead percentage.
  • You can select if you are sizing for Exchange Server 2010 or Exchange Server 2013.

The script requires PowerShell and internet access to query the SPECint database.

The script is called Exchange-PQT.ps1, in honor of the Processor Query Tool (PQT).  The syntax is as follows:

Exchange-PQT.ps1 [-CPU <String>] [-Vendor <String>] [-System <String>] [-Overhead <Int32>] [-MinMegaCycles <Int32>] [-Type <String>] [-MinCores <Int32>] [-MaxCores <Int32>] [-MinChips <Int32>] [-MaxChips <Int32>] [<CommonParameters>]

The information returned and which you can use for post-processing is: Vendor, System, CPU (processor description), Cores, Chips (number of CPU’s), CoresPerChip (number of Cores per CPU), Speed, Result, Baseline, MCyclesPerCore (megacycles per core), MCyclesTotal (total megacycles), OS and Published. Note that megacycles calculations are based on the selected Exchange version, by default this is Exchange Server 2013.

A quick walk-through on the parameters:

  • CPU, Vendor or System can be used for partial matching on the respective attribute.
  • Type specifies what calculation to perform. Possible values are 2010 for Exchange Server 2010 and 2013 for Exchange Server 2013. Default value is 2013.
  • MinCores/MaxCores can be used to only return information for systems with this minimum or maximum number of cores. Note that you can not use MinCores and MaxCores at the same time.
  • MinChips/MaxChips can be used to only return information for systems with this minimum or maximum number of CPU’s. Note that you can not use MinChips and MaxChpis at the same time.
  • MinMegaCycles can be used to specify a treshold for the total megacycles value for returned items, using the specified Type for calculations.
  • Overhead can optionally be used to take into account a certain percentage for megacycles overhead. Default is 0 (0%).

Few notes:

  • MinCores/MaxCores and MinChips/MaxChips are mutually exclusive, because we can not specify both in the query against the SPECint database. However, you can use additional filtering on objects returned in the pipeline to distill information, e.g.
    Exchange-PQT.ps1 –MaxCores 32 –MaxChips 8 | Where { $_.Cores –ge 4 –and $_.Chips –ge 2}. 

    Do note that usage of these parameters is recommended when possibe, as it will minimize the result set from SPECint.

  • Make sure you set Type to 2010 when sizing for Exchange 2010.


Lookup the specifications of the server used by Jeff in his sizing example (Hewlett-Packard DL380p Gen8 server with Intel Xeon E5-2630 processors @2.30GHz). You will notice 25,481 is slightly off from Jeff’s 25,479, which is due to Jeff rounding numbers:

.\Exchange-PQT.ps1 -System 'DL380p Gen8'  -CPU 2630 | select System,MCycle*

Search all specs for systems from Dell containing x5470 processors and return megacycle information for Exchange 2010 calculations:

.\Exchange-PQT.ps1 -CPU x5470 -Vendor 'Dell Inc.' -Type 2010 | Select System,*cycle*


Calculate average SPECint 2006 rate values for  hex-core x5450 systems:

.\Exchange-PQT.ps1 -CPU x5470 | Where { $_.Cores -eq 8 } | Measure -Average Result

Search all specs for Dell systems using x5670 CPUs, with a minimum total of 16,000 megacycles and 20% megacycle overhead:

.\Exchange-PQT.ps1 –Vendor Dell -CPU x5670  -MinMegaCycles 16000 -Overhead 20 


You can download the script from the TechNet Gallery page.

Feedback is welcomed through the comments. If you got scripting suggestions or questions, do not hesitate using the contact form.

Revision History
See TechNet Gallery page.

Impersonation: To be, or pretend to be

imageAs frequent readers of this blog may know, I made several Exchange-related scripts available to the community. Some of these scripts make use of what is called Exchange Web Services (EWS). I receive lots of questions via e-mail and through the comments about configuring impersonation or permission-related issues when running those scripts, which support delegated access as well as impersonation, against mailboxes. This blog shows how can configure delegation, why you should use impersonation, and how to configure impersonation on Exchange 2007 up to Exchange 2013 and Exchange Online in Office 365.


EWS provides functionality to allow client applications, such as Outlook or OWA apps, tools, or in my case scripts, to communicate with Exchange server. Even Exchange itself makes uses of EWS when performing Free/Busy lookups by the Availability services for example. EWS was introduced in Exchange Server 2007 back in December 2006, which now seems decades ago.

Some of these EWS scripts or tools access or even manipulate mailbox contents. In the MAPI era, in order for you to access a mailbox that’s not yours, you required delegated full access permissions. These permissions could be granted at the mailbox, mailbox database or mailbox server level. The latter would grant you access to all mailboxes hosted in that mailbox database. For example, to grant an account Archibald full access permission on the mailbox of Nestor, you would typically use something like:

Add-MailboxPermission –Identity Nestor –User Archibald –AccessRights FullAccess –InheritanceType All

Note: Specifying InheritanceType is sometimes overlooked. Not specifying it only configures an Access Control Entry (ACE) on the top level folder (InheritanceType None), resulting in symptoms like scripts not processing subfolders for example.

EWS enables you to use another access method besides delegation, which is impersonation. Impersonation, as the many online available dictionaries may tell to you, is ‘an act of pretending to be another person for the purpose of entertainment or fraud’ or something along those lines. In the Exchange world, this means you can have an account which has the permission to pretend to be the owner of the mailbox, including being subject to the same effective permissions. So, if for some reason the owner only has Read permission on a certain folder, so will the impersonator. Typical use cases for impersonation are for example applications for archiving, reporting or migration, but also scheduled scripts that need to process mailboxes could be one.

Before we dive into the configuration itself, first some of the reasons why you should should prefer Impersonation over delegated access:

  • No mailbox needed for the account requesting access.
  • Throttling benefits, since the operation is subject to the throttling policy settings configured on the mailbox accessed, not the throttling policy configured on the mailbox requesting access. To bypass these delegate limits, one had to configure and assign a separate throttling policy with no limits for the account. Of course, a bad behaving application could then run without boundaries from a resource perspective, something throttling policies try to limit.
  • In Exchange 2010 and up, impersonation leverages Role Based Access Control, which is better manageable than a collection of distributed  ACEs.
  • Actions performed by the impersonator are on behalf of the impersonated. This may complicate auditing, as logging will come up with actions performed by the impersonated user, not the impersonator.

Note that where ‘user’ is specified below with regards to granting permissions, one could also specify a security group as well unless mentioned otherwise.

Impersonation on Exchange 2007

On Exchange 2007, you configure impersonation by granting the following two permissions:

  • The ms-Exch-EPI-Impersonation permission grants the impersonator the right to submit impersonation calls. It is configured on Client Access Servers. This does not grant the impersonation right, just the right the make the call through a CAS server.
  • The ms-Exch-EPI-May-Impersonate when granted, allows the impersonator to impersonate selected accounts.

To configure these permissions in your Exchange 2007 environment, use:

Get-ClientAccessServer | Add-AdPermission –User svcExchangeScripts –ExtendedRights ms-Exch-EPI-Impersonation

Then, we can configure impersonation permission on the mailbox level:

Get-Mailbox Tintin| Add-ADPermission –User svcExchangeScripts –ExtendedRights ms-Exch-EPI-May-Impersonate

on the database level:

Get-MailboxDatabase MailboxDB1 | Add-ADPermission –User svcExchangeScripts –ExtendedRights ms-Exch-EPI-May-Impersonate

or mailbox server level:

Get-MailboxServer MailboxServer1 | Add-ADPermission –User svcExchangeScripts –ExtendedRights ms-Exch-EPI-May-Impersonate

Be advised that members of the various built-in Admin groups are by default explicitly denied impersonation permissions on the server and database level, and deny overrules allow. You will notice this when querying impersonation configuration settings, for example on the database level (in the screenshot example, olrik was granted impersonation permissions):

Get-MailboxDatabase | Get-AdPermission | Where { $_.ExtendedRights –like ‘ms-Exch-EPI-Impersonation’} | Format-Table Identity, User, Deny, IsInherited, ExtendedRights –AutoSize


Note that permissions assigned on the mailbox may not immediately be reflected as you are administering them in Active Directory. Changes in Active Directory are subject to AD replication, and the Exchange Information Store caches information for up to 2 hours, so worst case it may take up to 2 hours and 15 minutes for new permission settings to be re-read from Active Directory.

Impersonation on Exchange 2010 and 2013

Exchange 2010 introduced Role Based Access Control, better known by its acronym RBAC. For a quick introduction to RBAC, see one of my earlier blogs here. There is a management role associated with impersonation, which is ApplicationImpersonation.

To enable a user impersonation rights, create a new assignment for ApplicationImpersonation and assign it to the user:

New-ManagementRoleAssignment –Name 'AIsvcExchangeScripts' –Role ApplicationImpersonation –User svcExchangeScripts

Note that if we want to assign these permissions to a security group, we need to use the SecurityGroup parameter instead of User, specifying the group name.

Now be careful, when used like this you will have granted that user or group permission to impersonate all users in your Exchange organization. Here is where RBAC comes into play, or more specific the RBAC feature named management role scopes. With write scopes for example, you can limit the scope of where you can make changes in Active Directory. For more information on management role scopes, see here.

Let  us assume we want to limit the scope to a distribution group named ‘All Employees’, using New-ManagementScope in combination with RecipientRestrictionFilter. Note that when specifying MemberOfGroup in the filter, you need to use the distinguishedName of the group:

New-ManagementScope –Name 'Employee Mailboxes' –RecipientRestrictionFilter { MemberOfGroup –eq 'CN=All Employees,OU=Distribution Groups,OU=NL,DC=contoso,DC=com'} 

We can then apply this scope to the assignment created earlier:

Set-ManagementRoleAssignment –Identity 'AIsvcExchangeScripts' –CustomWriteScope 'Employee Mailboxes'

Be advised that in a multi-forest environment, impersonation doesn’t work when you assign permissions to cross-forest accounts. You either need to assign impersonation permissions to an account residing in the same forest as Exchange, or create a linked role group.

Impersonation on Exchange Online

Impersonation is available in most Office 365 plans, but currently not in the small business plans.  To configure Impersonation in Exchange Online we need to connect anyway, so we’ll first open a remote PowerShell session to Exchange Online:

$EXO= New-PsSession -ConfigurationName Microsoft.Exchange -ConnectionUri -AllowRedirection -Authentication Basic
Import-PsSession $EXO

Provide tenant administrator credentials when prompted. You can then see if you have the ApplicationImpersonation role at your disposal using:

Get-ManagementRole –Identity ApplicationImpersonation

If nothing is returned, you may need to resort to delegate access permissions.

Configuring impersonation is identical to configuring it in Exchange 2013. Nonetheless, some people may be more comfortable using the Exchange Admin Center. If so:

  1. Open up Exchange Admin Center.
  2. Navigate to Permissions > Admin Roles
  3. Now we can’t directly assign a management role through EAC, so assume we’ll create a role group for our application account by clicking New (+).
  4. Enter a name for your role group, e.g. ExchangeMaintenanceScripts.
  5. Add the role ApplicationImpersonation.
  6. Add the accounts which need Impersonation permissions, e.g. svcExchangeScript.
  7. Optionally, you can also select a Write Scope, which you need to create upfront through Exchange Management Shell.
  8. In Exchange on-premises, instead of a Write Scope you will have the option to select a a specific OU instead (scope filter RecipientRoot parameter) .
  9. When done, Save.


One word of caution: scopes are not automatically updated when objects referenced are relocated or change names. Now, for your own environment you may have this under control through some form of change management process. For Exchange Online however, your tenant might get relocated without notice. Therefor, should impersonation fail, verify any management scopes you may have defined for distinguishedName references, and check if they require updating, e.g.

Set-ManagementScope -Name 'All Employees' -RecipientRestrictionFilter { MemberOfGroup -eq 'CN=All Employees,,OU=Microsoft Exchange Hosted Organizations,DC=EURPR05A001,DC=prod,DC=outlook,DC=com'}

Final words

Note that many EWS-based scripts or tools do not natively support EWS but make use of the Exchange Web Services Managed API. This installable package consists of support files (e.g. DLL’s) which provide EWS functions to your PowerShell environment. You can download the current version of EWS Managed API here (2.2). You can read more on developing with EWS Managed API here, or you can have a peek at the source of code of one of my EWS scripts or the ones published by Exchange MVP-fellow Glen Scales’ here.

Script Updates

powershellA small heads-up for those not following me on Twitter of one of the other social media channels. Last week I made updates to the following three scripts:

Install-Exchange2013.ps1, version 1.72

  • Added CU5 support
  • Added KB2971467 (CU5 Disable Shared Cache Service Managed Availability probes)

Remove-DuplicateItems.ps1, version 1.3

  • Changed parameter Mailbox, you can now use an e-mail address as well.
  • Added parameter Credentials.
  • Added item class and size for certain duplication checks.
  • Changed item removal process
  • Remove items after, not while processing folder. Avoids asynchronous deletion issues.
  • Works against Office 365.

Remove-MessageClassItems.ps1, version 1.3

  • Changed parameter Mailbox, you can now use an e-mail address as well
  • Added parameter Credentials
  • Added parameter PartialMatching for partial class name matching.
  • Changed item removal process. Remove items after, not while processing folder. Avoids asynchronous deletion issues.
  • Works against Office 365.
  • Deleted Items folder will be processed, unless MoveToDeletedItems is used.
  • Changed EWS DLL loading, can now be in current folder as well.

Be advised I keep am overview of the scripts and their current versions with publish dates here.



powershellLast update: Version 1.02, November 21st, 2014.

Those leveraging quota settings to manage their Exchange environments, you are probably periodically running some sort of script or set of cmdlets to retrieve information on mailbox sizes, quota settings and if any mailbox is above any of the quota thresholds. For a quick indication of the current size in relation to the quota settings, StorageLimitStatus may contain one of the following indicators depending on the quota settings on the mailbox or mailbox database hosting the mailbox:

  • BelowLimit – Speaks for itself
  • IssueWarning – Mailbox size above Issue Warning limit
  • ProhibitSend – Mailbox size above Prohibit Send limit
  • NoChecking – No quota checking
  • MailboxDisabled – Mailbox size above Prohibit Send and Receive quota limit

So, to get a list of all mailboxes with any over-quota status, you can use:

Get-MailboxDatabase | Get-MailboxStatistics | Where {$_.StorageLimitStatus -match 'IssueWarning|ProhibitSend|MailboxDisabled'} | Select DisplayName, ItemCount, TotalItemSize, StorageLimitStatus, LastLogonTime

Unfortunately, in Exchange 2013 the StorageLimitStatus gets no longer populated:


As KB2819389 explains, this is by design. In Exchange 2013, mailbox quotas are no longer cached. By not being cached, retrieving quota information may result in poor performance as it queries Active Directory for quota related attributes. The argument is a bit puzzling, considering there is a NoADLookup switch which directs the cmdlet to retrieve information from the mailbox database (cache) instead of Active Directory. Perhaps a better workaround would have been to make NoADLookup a parameter, make it $true by default and leave StorageLimitStatus unpopulated when NoADLookup is $true.

Of course, that does not help customers who want a quick quota report. For this purpose I have created two things in 1 script:

  1. A helper function Get-StorageLimitStatus() which will take a mailbox statistics object and return a StorageLimitStatus object.
  2. A script Get-MyMailboxStatistics.ps1, a proxy function for Get-MailboxStatistics which will use the Get-StorageLimitStatus helper function to populate the StorageLimitStatus.

When you want to use the helper function, extract it and include it in your quota reporting script or PowerShell profile (making it available when firing up a shell). To use the helper function in the cmdlet shown earlier, use:

Get-MailboxDatabase | Get-MailboxStatistics | Select -ExcludeProperty StorageLimitStatus DisplayName, ItemCount, TotalItemSize, @{n="StorageLimitStatus"; e={ Get-StorageLimitStatus $_}}, LastLogonTime | Where {$_.StorageLimitStatus -match 'IssueWarning|ProhibitSend|MailboxDisabled'}

This will remove StorageLimitStatus from the output and add a calculated field bearing the same the name, calling the Get-StorageLimitStatus helper function with the current mailbox statistics object to set its value.

This is a proxy function for the Exchange Management Shell cmdlet Get-MailboxStatistics. This means that the current, original cmdlet was used to create a wrapper which will call the original cmdlet. Having a wrapper allows you to restrict or enhance the original cmdlet and tailor it to your needs.

A quick tip on how to create a proxy script in the clipboard (more information on creating proxy commands here):

$data= New-Object System.Management.Automation.CommandMetaData (Get-Command Get-MailboxStatistics) 
[System.Management.Automation.ProxyCommand]::create($data) | clip.exe

Downside is that future changes to the Get-MailboxStatistics cmdlet will not be automatically incorporated in the wrapper. Feeding it objects also doesn’t work, but you can work around that by temporary storing the objects in a variable and passing that to the script (see examples below).

To populate the StorageLimitStatus, we will post-process each object in the output of Get-MailboxStatistics, using Add-Member to overwrite (-Force) its current value and –PassThru to pass it along in the pipeline. Being a proxy command, the parameter options are identical to the original Get-MailboxStatistics. Some examples:

.\Get-MailboxStatistics.ps1 -Database MDB2
$m= Get-Mailbox –Database MDB2 
$m | .\Get-MailboxStatistics.ps1 | ft –AutoSize DisplayName,TotalItemSize,StorageLimitStatus


Do be aware that this will incur Active Directory queries and thus performance of the script may not seem fast. However, in previous versions of Exchange you got immediate results as all the quota information was readily available from the cache. On the plus side, the status you see will be non-cached, current information.

On a final note and maybe needless to say that in order to use this you need to run it from the Exchange Management Shell and since it’s an unsigned script you need to set ExecutionPolicy to Unrestricted.

Feedback is welcomed through the comments. If you got scripting suggestions or questions, do not hesitate using the contact form.

You can download the script from the TechNet Gallery here.

Revision History
See Technet Gallery page.

Cmdlet Extension Agents & XML Case Sensitivity

Ex2013 LogoOccasionally, I get requests to come to the aid of a fellow IT professional (I seldomly get requests to come to the aid of fair maidens. Oh, well). This weekend I responded to one of those distress calls by someone who couldn’t get his Cmdlet Extension Agent to work. This post is a quick heads-up for the collective memory of IT Professionals as it took me quiet a bit of screen staring to spot the issue.

For those unfamiliar with Cmdlet Extension Agents, they are modules which allow you to enhance or customize the behavior of cmdlets in Exchange. For example, the built-in Mailbox Resources Management agent is responsible for picking the database when creating new mailboxes when a database hasn’t been specified. By means of the Script Agent and an XML file named ScriptingAgentConfig.xml containing PowerShell code fragments, you can enhance and tailor Exchange cmdlets to your own needs. For those interested in Cmdlet Extension Agents, I did two earlier articles on Cmdlet Extension Agents, here and here.

The code provided was a simple agent to enhance New-Mailbox with enabling SingleItemRecovery after the mailbox was created:

<?xml version="1.0" encoding="utf-8" ?>
<Configuration version="1.0">    
        <Feature Name="Mailbox Provisioning" cmdlets="new-mailbox">
         <ApiCall Name="OnComplete">
             if($succeeded) {
                $newmailbox = $provisioningHandler.UserSpecifiedParameters["Name"]
                set-mailbox $newmailbox -SingleItemRecoveryEnabled $true

The Scripting Agent was enabled using Enable-CmdletExtensionAgent. Yet, for some reason after creating a new mailbox, SingleItemRecoveryEnabled wasn’t enabled. Running New-Mailbox in Verbose mode showed that the Scripting Agent did not come into play:

Capture1Now know that debugging and troubleshooting Cmdlet Extension Agents can be an unpleasant task since you put PowerShell code in XML files and there is no way to easily perform simple tests except running the Cmdlet you’re customizing or developing script fragments in an external PowerShell script and copy/paste it in the Scripting Agent XML file when you think it’s ready for and want to perform some final tests.

I didn’t immediately spot it, so to see if the problem was actually in the XML I picked the example of my 2nd article on Cmdlet Extension Agents and I compared it with the non-working XML using WinMerge (which by the way is an excellent tool to compare code or plain texts):

WinMergeIt took some time to discover why the Scripting Agent wouldn’t pick up the XML and it can be easily overlooked. The culprit in underlined in red: the C of the Cmdlets attribute in the Feature tag should be uppercase. Doh! This case sensitivity is perhaps not a primary suspect by Windows folks, as mostly we don’t have to worry about it and things “just work”, but in the case of XML it is essential. The XML standard prescribes that element names (<Feature> .. </Feature>) and attribute names (<Feature Cmdlets=..>) are case-sensitive entitling the Scripting Agent to be strict.

Configuring Anti-Affinity in Failover Clusters

powershellMany customers nowadays are running a virtualized Exchange environment, utilizing Database Availability Groups, load balanced Client Access Servers and the works. However, I also see environments where it is up to the Hypervisor of choice on the hosting of virtual machines after a (planned) fail-over. This goes for Exchange servers, but also for redundant infrastructure components like Domain Controllers or Lync Front-End servers for example.

So, leaving it to “default” is not a good idea when you want to achieve the maximum availability potential. Think about what will happen if redundant roles are located on the same host and that host goes down. What you want to do is prevent hosts from becoming the single point of failure, something which can be accomplished by using a feature called anti-affinity. This will distribute virtual machines over as much hosts as possible. Where affinity means to have an preference for, like in Processor Affinity for processes, Anti-Affinity can be regarded as repulsion in magnetism.


For VMWare, you can utilize DRS Anti-Affinity rules; I’ll describe how you can configure Anti Affinity in Hyper-V clusters using the AntiAffinityClassNames property (which by the way already exists since Windows Server 2003). And yes, property means it’s not accessible from the Failover Cluster Manager, but I’ve create a small PowerShell script which lets you configure the AntiAffinityClassNames property (in pre-Server 2012 you could also use cluster.exe to configure this property).

Note: For readability, when you see virtual machine(s), read cluster group(s); In Microsoft failover clustering, a clustered virtual machine role is a cluster group.

Now, before we’ll get to the script, first something on how AntiAffinityClassNames works. The AntiAffinityClassNames property may contain multiple unique strings which you can make up yourself. I’d recommend creating logical names based on the underlying services, like ExchangeDAG or ExchangeCAS. When a virtual machine is moved the process is as follows:

  1. When defined, the cluster tries to locate the next preferred node using the preferred owner list;
  2. Does the designated node host a virtual machine with a matching element in their AntiAffinityClassNames property; if not, the designated host is selected; if it is, move to the next available preferred owner and repeat step 2;
  3. If the list is exhausted (i.e. only anti-affined hosts), the anti-affinity attribute is ignored and the preferred owner list is checked again, ignoring anti-affinity (“last resort”).

Traces of Anti-Affinity influencing failover behavior can be found in the cluster event log:

00000648.00000d54::2013/07/22-10:40:33.162 INFO  [RCM] group ex2 should fail back from node 2 to node 3 now due anti-affinity

Now on to the script, Configure-AntiAffinity.ps1. The syntax is as follows:

Configure-AntiAffinity.ps1 [-Cluster] <String> [-Groups] <Array> [-Class] <String> [[-Overwrite]] [[-Clear]] [<CommonParameters>]

A small explanation of the available parameters:

  • Cluster is used to specify which cluster you cant to configure (mandatory);
  • Groups specifies which Cluster Groups (Virtual Machines) you want to configure Anti-Affinity for (mandatory);
  • Class specifies which name you want to use for configuring Anti-Affinity (optional, AntiAffinityClassName);
  • When Overwrite is specified, all existing Anti-Affinity class names will be overwritten by Class for the specified Groups, otherwise Class will be added (default);
  • When Clear is specified, all existing Anti-Affinity class names will be removed for the specified Groups;
  • The Verbose parameter is supported.

So, for example assume you have 3+ Hyper-V cluster named Cluster1 consisting of 3+ nodes running 3 virtualized Exchange servers hosting a 3-node DAG, ex1, ex2 and ex3 and you want to configure anti-affinity for these virtual machines using the label PRODEX, you could use the script as follows :

Configure-AntiAffinity.ps1 -Cluster Cluster1 -Groups ex1,ex2, ex3 –Class PRODEX –Verbose

To clear anti-affinity you could use:

Configure-AntiAffinity.ps1 -Cluster Cluster1 -Groups ex1,ex2,ex3 -Clear

Here’s a screenshot of the script for creating anti-affinity, add additional anti-affinity class names and clearing anti-affinity settings:


Feedback is welcomed through the comments. If you got scripting suggestions or questions, do not hesitate using the contact form.

You can download the script from the TechNet Gallery here.

Revision History

Removing Duplicate Items from a Mailbox

powershellLast version: 1.41, November 26th, 2014.

For those involved with Exchange migration projects or managing Exchange environments, at some point you probably have experienced the situation where people ended up with duplicate items in their mailbox. Duplicate items can be caused by many things, but most common are:

  • Synchronization tools or plug-in. Entries from the mailbox are treated as new entries and as a consequence are added to the mailbox when synchronizing information back to the mailbox, creating duplicates. In the past, I’ve seen this happening with Nokia PC Suite and Google Apps Sync for example;
  • Importing existing data. Accidental import from – for example – a PST file to a mailbox  can lead to duplicate entries.


When looking for a solution, you’ll probably encounter MSKB299349, “How to remove duplicate imported items in Outlook”. This article describes a manual procedure to remove duplicates entries from your calendar, contacts, inbox or other folders. Not a very helpful and labor intensive.

When continuing your search, you’ll find lots (I mean lots!) of tools and Outlook add-ins, like Vaita’s DIR or MAPILab’s Duplicate Remover. Not all this software is free (some even require payment per duplicate removal of appointments, contacts or e-mail) and some might not even work (MAPI-based tools may not work against Exchange 2013).

When you finally have selected a tool, in most cases they require installation of a piece of software and someone to perform the removal process using the tool or Outlook with add-in. When you’re an Apple shop you’ll require different tools, unless you’re running a Windows desktop somewhere (I’ll just pretend I didn’t hear you saying ‘Why don’t you install the tool on the Exchange server’).

Wouldn’t it be nice if you’d have a PowerShell script you can conveniently run from any workstation (or server) with PowerShell installed, removing those duplicate items from a user’s mailbox remotely? If the answer is yes, the Remove-DuplicateItems.ps1 script may be something for you.

Using the Remove-DuplicateItems.p1 script requires Exchange 2007 or later and Exchange Web Services (EWS) Managed API 1.2 (or later) which you can download here. Alternatively, when you already got the EWS Managed API Microsoft.Exchange.WebServices.DLL somewhere, you can copy it to the same folder where the script is located and it will be picked up. The script has been developed and tested against Exchange 2007, meaning it’s a PowerShell 1.0 script which should be compatible with later versions of PowerShell or Exchange.

Also take notice that since you’ll be processing user mailboxes, you’ll need to have full mailbox access or impersonation permissions; the latter is preferred. For details on how to configure impersonation for Exchange 2010 using RBAC, see this article or check here for details on how to configure impersonation for Exchange 2007.

The script Remove-DuplicateItems.ps1 uses the following syntax:

Remove-DuplicateItems.ps1 [-Mailbox] <String> [[-Type] <String>] [-Retain <String>] [-Server <String>] [-Impersonation] [-DeleteMode <String>] [-Credentials <PSCredential>] [-Mode <String>] [-MailboxOnly] [-ArchiveOnly] [-WhatIf] [-Confirm] [<CommonParameters>]

A quick walk-through on the parameters and switches:

  • Mailbox is the e-mail address or name of the mailbox to process. If name is used, it is matched against cn/SAMAccountname/email address of local AD.
  • Type determines what folders are checked for duplicates. Valid options are Mail, Calendar, Contacts, Tasks, Notes or All (Default).
  • Retain determines which item to retain by comparing last modification times. Valid options are Newest (default) or Oldest.
  • Server is the name of the Client Access Server to access for Exchange Web Services. When omitted, the script will attempt to use Autodiscover.
  • When the Impersonation switch is specified, impersonation will be used for mailbox access, otherwise the current user context will be used.
  • DeleteMode specifies how to remove messages. Possible values are HardDelete (permanently deleted), SoftDelete (use dumpster, default) or MoveToDeletedItems (move to Deleted Items folder).
  • Credentials specifies credentials to use (provide credentials using Get-Credential).
  • Mode determines how items are matched. Options are Quick, which uses PidTagSearchKey and is the default mode, or Full which uses a predefined set of attributes to match items, depending on the item class:
ItemClass Criteria
Contacts File As, First Name, Last Name, Company Name, Business Phone, Mobile Phone, Home Phone, Size
Distribution List FileAs, Number of Members, Size
Calendar Subject, Location, Start & End Date, Size
Task Subject, Start Date, Due Date, Status, Size
Note Contents, Color, Size
Mail Subject, Internet Message ID, DateTimeSent, DateTimeReceived, Sender, Size
Other Subject, DateTimeReceived
  • MailboxOnly specifies you only want to process the primary mailbox of specified users. You als need to use this parameter  when running against mailboxes on Exchange Server 2007.
  • ArchiveOnly specifies you only want to process personal archives of specified users.

Few notes:

  • When MoveToDeletedItems is specified, the Deleted Items folder will be skipped;
  • When Type is omitted or set to All, all folders are scanned, including folders like Conversation History, RSS Feeds, etc.;
  • When Quick mode is used and PidTagSearchKey is missing or inaccessible, search will fall back to Full mode;
  • For more info on PidTagSearchKey, see Note that PidTagSearchKey will have duplicate values for copied objects.
  • You need to specify MailboxOnly when running against mailboxes on Exchange Server 2007 as the Exchange 2010 personal archive options in EWSare not support in Exchange 2007 mode.

So, suppose you want to remove  duplicate Appointments from the calendar of mailbox migtester1 using attribute matching, moving duplicate items to the DeletedItems, using Impersonation and you want to generate extra output using Verbose. In such case, you could use the following cmdlet:

Remove-DuplicateItems.ps1 -Mailbox migtester1 -Type Calendar -Impersonation -DeleteMode MoveToDeletedItems -Mode Full -Verbose


Alternative, you can use an e-mail address and specify credentials.  This allows the script to run against mailboxes in Office 365, for example:

Remove-DuplicateItems.ps1 -Mailbox -Type Mail -DeleteMode MoveToDeletedItems -Mode Full -Credentials (Get-Credential) -Retain Oldest

In case you want to process multiple mailboxes, you can use a CSV file which needs to contain the Mailbox field. An example of how the CSV could look:


The cmdlet could then be something like:

Import-CSV users.csv1 | Remove-DuplicateItems.ps1 -DeleteMode HardDelete -Impersonation

You can download the script from the TechNet Gallery here.

Feedback is welcomed through the comments. If you got scripting suggestions or questions, do not hesitate using the contact form.

Revision History
See TechNet Gallery page.