Blocking Mixed Exchange 2013/2016 DAG

Ex2013 LogoIn the RTM version of Exchange 2016, there’s an issue in that it is allows you to add Exchange 2016 Mailbox servers to Exchange 2013 Database Availability Groups, and vice-versa. As stated in the Release Notes (you do read those?), creating such a mixed version DAG is not supported. In theory, you could even jeopardize your Exchange data, as database structures from both versions are different. This action is also not prevented from the Exchange Admin Center, requiring organizations to have very strict procedures and knowledgeable Exchange administrators.

If you are worried about this situation and you want to prevent accidently adding Mailbox servers to an existing DAG consisting of members of a different Exchange version, there is a way (until this is blocked by the product itself, of course). Cmdlet Extension Agents to the rescue!

The Scripting Agent not only allows you to add additional instructions to existing Exchange cmdlets, but also to provide additional validation before cmdlets are executed. I did two short articles on Cmdlet Extension Agents’ Scripting Agent here and here, so I will skip introductions.

First you need to download a file named ScriptingAgentConfig.xml from the location below. If you already have Scripting Agents, you need to integrate the code in your existing ScriptingAgentConfig.xml files. The code checks if the server you want to add using the Add-DatabaseAvailabilityGroup cmdlet is of a different major version than one of the current DAG members.

Next, you need to copy this ScriptingAgentConfig.xml file to $ENV:ExInstallPath on every Exchange 2013 and Exchange 2016 server in your organization, e.g. C:\Program Files\Microsoft\Exchange Server\V15\Bin\CmdletExtensionAgents\ScriptingAgentConfig.xml.  To help your with this process, Exchange fellow Paul Cunningham made a small script to push this XML from the current folder to every Exchange server in your organization, PushScriptingAgentConfig.ps1.

Last step is to enable the Scripting Agent using:

Enable-CmdletExtensionAgent ‘Scripting Agent’

After distributing the scripting agent file and enabling the scripting agent, when you try to add an Exchange 2016 (version 15.1) server to an Database Availability Group consisting of Exchange 2013 Mailbox servers, using Add-DatabaseAvailabilityGroupServer, you will receive an error message:


This also works vice-versa, thus when you inadvertently try to add Exchange 2013 servers to an Exchange 2016 Database Availability Group, provided you distributed the XML on the Exchange 2013 servers as well. The error is also thrown when you try to perform this action using the Exchange Admin Console.

You can download the ScriptingAgentConfig.XML for blocking Mixed Exchange 2013/2016 DAGs from the TechNet here.

Connecting to Office 365/Exchange


Last update: Version 1.7, May 2nd, 2017

Be advised: As of v1.7, Connect-AzureAD had been renamed to Connect-AzureActiveDirectory for disambiguation with the AzureAD v2 cmdlet.

Almost 3 years ago, I wrote an article on how to enhance the PowerShell Integrated Scripting Environment, or ISE. That seemed adequate for the Exchange admin back then, who mostly connected their PowerShell session to their his on-premises environment, and perhaps occasionally a bit of Exchange Online.

Fast forward to 2015, most modern Exchange administrators not only require a connection – if any – to their Exchange on-premises environment, but likely to one or more of the Office 365 services as well. This includes Exchange On-Premises, Azure Active Directory, Exchange Online Protection and perhaps even Skype for Business Online, SharePoint Online, Azure Rights Management Services or Compliance Center.

All these services use a different PowerShell session, use a different endpoint FQDN, and in some cases require a locally installed PowerShell module. Likely common denominator is the credential used to access each of these services. So, tired of re-entering my credentials every time when switching from Exchange Online to Exchange Online Protection, I created a script with a set of functions to allow me connect to each individual Office 365 service or Exchange Online:

  • Connect-AzureActiveDirectory: Connects to Azure Active Directory
  • Connect-AzureRMS: Connects to Azure Rights Management
  • Connect-ExchangeOnline: Connects to Exchange Online
  • Connect-SkypeOnline: Connects to Skype for Business Online
  • Connect-EOP: Connects to Exchange Online Protection
  • Connect-ComplianceCenter: Connects to Compliance Center
  • Connect-SharePointOnline: Connects to SharePoint Online
  • Get-Office365Credentials: Gets Office 365 credentials
  • Connect-ExchangeOnPremises: Connects to Exchange On-Premises
  • Get-OnPremisesCredentials: Gets On-Premises credentials
  • Get-ExchangeOnPremisesFQDN: Gets FQDN for Exchange On-Premises
  • Get-Office365Tenant: Gets Office 365 tenant name (SharePoint)

Note that functions and credentials used in the script are global, and in principle only need to be entered once per shell or ISE session. If you need different credentials, call Get-Office365Credentials again. User interaction is a very basic (Read-Host), but it does the job. The script will also detect if the Exchange Online MFA PowerShell module, or the Skype for Business Online or SharePoint Online module with MFA support are installed. If detected, you will be prompted if you want to use MFA when authenticating to Exchange Online, Skype for Business Online or SharePoint online.

During initialization, the script will detect the modules which are required for certain Office 365 services. When not installed, it will notify you, and provide a link where to obtain the PowerShell module. The related Connect function will not be made available. The Azure Active Directory module also requires the Microsoft Online Sign-In Assistant to be installed. Needless to say, PowerShell is required to run this script, which is tested against version 4 (but should work with 3)

The functions are contained in a script called Connect-Office365Services.ps1. You can call this script manually from your PowerShell session to make the functions available. However, more convenient may be to have them always available in every PowerShell or ISE session. To achieve this, you need to edit your $profile, which is a script which always starts when you start a PowerShell or ISE session. By default this file does not exist and you need to create it, including the path. Also note that the files for PowerShell and ISE are different, Microsoft.PowerShell_profile.ps1
and Microsoft.PowerShellISE_profile.ps1 respectively.

Now, of course you can copy and paste the functions from the script file to your own $profile. Better is to call the script from your $profile, as this allows you to overwrite the Connect-Office365Services.ps1 with updates. To achieve this, assume you copied the Connect-Office365Services.ps1 in the same location as your $profile, for example C:\Users\Michel\Documents\WindowsPowerShell. You can then make PowerShell and ISE call this script by adding the following line to the $profile scripts:

& “$PSScriptRoot\Connect-Office365Services.ps1”

Now when you start a PowerShell session, you might see the following:


This shows the Microsoft Online Sign-In Assistant and Azure Active Directory PowerShell module is available, and related connect functions should be available.

When you load the script from ISE, it will show something similar. However, it will also show ISE is detected and make all functions available through the Add-On menu:


Customize this script to your liking. For example, if you always want to connect to Azure Active Directory when connecting to Exchange Online, add Connect-AzureAD in the Connect-ExchangeOnline function, or when you always want to connect to a fixed FQDN for Exchange On-Premises, insert it in the script or – better – configure your $profile to predefine the FQDN, e.g. $global:ExchangeOnPremisesFQDN=’’.

Also, you may with to leverage prefixing the imported cmdlets so you can easily switch between Exchange On-Premises and Exchange Online. For example, you can then having something like Get-EXOMailbox and Get-EOPMailbox corresponding to Get-Mailbox in your Exchange Online or Exchange On-Premises within the same shell session. However, as with aliases, think of the ‘the next guy’ who may not have these prefixed cmdlets, and instructions or scripts may require adoption to work, etc. But if you insist, for more information on prefixing cmdlets when importing a PowerShell session, see here.

Download / Revisions
You can download the script from the TechNet Gallery here. The TechNet Gallery page as well as the script contains revision information.

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

Multi-Factor Authentication in Office 365 (Part 2)

wp_ss_20140521_0001Multifactor Authentication is a must-have for services based in the cloud, especially for accounts with administrative purposes. We have already covered what Office 365 Multifactor Authentication is and how to configure it in Office 365 tenants with the Office 365 admin center, and we briefly showed the end user experience. Now we will look at how we can use the Azure Active Directory Module for Windows PowerShell to configure Office 365 authentication with MFA.

Azure Active Directory Module for Windows PowerShell (AADMPS) enables organizations to not only configure MFA for existing end users who use PowerShell, but also enhance their current provisioning process with MFA options. By pre-configuring MFA, administrators can prevent end users from having to go through the initial MFA setup process and use their currently configured mobile phone or office number for verification.

Read the full article over on SearchExchange

Multi-Factor Authentication in Office 365 (Part 1)

Multi-Factor AuthenticationMulti-Factor Authentication identifies an end user with more than one factor. Authentication is based on something you know, such as your password; something you have, such as a security token or smart card; or something that’s a physical characteristic of who you are, such as biometrics. By creating an additional factor on top of the password, identity is better protected. Multi-Factor Authentication is seen as a must-have for cloud-based services, especially for administrative types of accounts.

In this first tip on SearchExchange, I explain how you can configure Multi-Factor Authentication in Office 365, discuss the so-called contact methods, explain app passwords for non-MFA applications as well as show the MFA end user experience.

Read the full article over on SearchExchange

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.

Why Exchange Admins should learn PowerShell

techtarget_logoAs some of you may or may not be aware of, recently I have joined the ranks of Exchange and The UC Architects fellows Michael van Horenbeeck and Steve Goodman and started writing for the Exchange section at TechTarget.

The first two articles are a call to action for Exchange admins to start working on their PowerShell skills if they have not already done so. Learning can take the more academic approach, starting from scratch and learning the basics topic by topic, or a more practical one by reading and getting to understand existing scripts which may suit the more time-constrained admin.

Read the full articles here and the follow-up article here.

If you got topic suggestions, use the contact form or send me an e-mail.

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.63, February 17th, 2017.

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 2.0 (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 On-Premises or Office 365, see this blog post.

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

Remove-DuplicateItems.ps1 [[-Identity] ] [[-Type] ] [-Retain ] [-Server ] [-Impersonation] [-DeleteMode ] [-Credentials ] [-Mode ] [-MailboxOnly] [-ArchiveOnly] [-IncludeFolders <String[]>] [-ExcludeFolders <String[]>] [-MailboxWide] [-Force] [-WhatIf] [-Confirm] []

A quick walk-through on the parameters and switches:

  • Identity 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.
  • IncludeFolders specifies one or more names of folder(s) to include, e.g. ‘Projects’. You can use wildcards around of at the end to include folders containing or starting with this string, e.g. ‘Projects*’ or ‘*Project*’. Matching is always case-insensitive. You can also well-known folders, by using this format: #WellKnownFolderName#, e.g. #Inbox#. Supported are Calendar, Contacts, Inbox, Notes, SentItems, and Tasks.
  • ExcludeFolders specifies one or more folder(s) to exclude. Usage of wildcards and well-known folders identical to IncludeFolder.
  • MailboxWide specifies to perform duplicate cleanup against whole mailbox, instead of per folder. A WORD OF CAUTION: The first unique item encountered will be retained. Since there is no way to order mailbox folders, when an item is found in Folder A and in Folder B, it is undetermined which item will be kept.

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 -Identity 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 -Identity -Type Mail -DeleteMode MoveToDeletedItems -Mode Full -Credentials (Get-Credential) -Retain Oldest

An example using IncludeFolder and ExcludeFolder:

$cred= Get-Credential
Remove-DuplicateItems.ps1 -Identity -Credentials $cred -IncludeFolders 'Archive','Project*','#Inbox#' -ExcludeFolders 'ProjectX'

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.

Exchange 2013 Unattended Installation Script v1.5 (Updated)

Ex2013 LogoI’m pleased to announce that the Exchange 2013 unattended installation script has been updated and supports fully automated installation of Exchange 2013 on Windows Server 2008 R2 SP1.

The new version contains the following changes:

  • Added support for Windows Server 2008 R2 SP1. To fulfill the requirements, code was added to install .NET Framework 4.5, Windows Management Framework 3, disable/enable Internet Explorer Enhanced Security Configuration (IE-ESC), install required hotfixes KB974405, KB2619234 and KB2758857 (which supersedes KB2533623).
  • Because of the mandatory reboot after installation of the hotfixes, a phase was inserted; this phase will be skipped when installing on Windows Server 2012.
  • Added InstallPath to AutoPilot parameter set (or default path won’t get set).

You can download the updated version of the script via the original Exchange 2013 Unattended Installation Script page (which also contains instructions) or directly from the Technet Gallery.

The script has been tested with Exchange 2013 CU1 but it should work with RTM as well (if you must ..). Your feedback is very much welcomed!

The last version is version 1.53, dated June 15th, 2013; For changes, consult the changelog on the original article or Technet Gallery page.