# Configuring Anti-Affinity in Failover Clusters

Many 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

Usage
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
Feedback is welcomed through the comments. If you got scripting suggestions or questions, do not hesitate using the contact form.

Revision History
-

# Removing Duplicate Items from a Mailbox

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.

Requirements
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.

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

Remove-DuplicateItems.ps1 [-Mailbox] <String> [[-Type] <String>] [-Server <String>] [-Impersonation] [-DeleteMode <String>] [-Mode <String>][-WhatIf] [-Confirm] [<CommonParameters>]


A quick walk-through on the parameters and switches:

• Mailbox is the name of the mailbox to process;
• Type determines what folders are checked for duplicates. Valid options are Mail, Calendar, Contacts, Tasks, Notes or All (Default);
• 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).
• 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 Distribution List FileAs, Number of Members Calendar Subject, Location, Start & End Date Task Subject, Start Date, Due Date, Status Note Contents, Color Mail Subject, Internet Message ID, DateTimeSent, DateTimeReceived, Sender Other Subject, DateTimeReceived

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 http://msdn.microsoft.com/en-us/library/cc815908.aspx. Note that PidTagSearchKey will have duplicate values for copied objects.

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 <MailboxID> -Type Calendar -Impersonation -DeleteMode MoveToDeletedItems -Mode Full -Verbose

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:

Mailbox
francis
philip

The cmdlet could then be something like:

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

Feedback
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)

I’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.

# Removing Messages by Message Class (Updated)

Recently, I was asked if it is possible to remove stub items. The reason was they were going to transition to a newer version of Exchange and they wouldn’t be using the archiving solution in the new environment. When required, vendor tooling would be used to search through the existing archives.

In such cases it makes sense to remove the stubs from the mailbox, which are shortcut messages that points to a copy of the original message in the archive solution. The new environment won’t contain the required Outlook plugins or extensions to retrieve the original message from the archive using the stub, making the stub lead to a partial or empty message.

To identify stubs, one can filter on an attribute of each item, MessageClass. This attribute defines which kind of item it is (in fact, determines what form Outlook should use in order to present or process the information). Examples of MessageClass definitions are IPM.Note (regular e-mail messages), IPM.Note.EnterpriseVault.Shortcut (message archived by Enterprise Vault) or IPM.ixos-archive (message archived by Opentext/IXOS LiveLink E-Mail Archive).

To identify stubs from Outlook, add the Message Class field to your Outlook view, e.g.:

When you want to remove the stubs using Outlook, you can utilize the Advanced Find function of Outlook, but that is a very labor intensive, tedious and non-centralized per-mailbox procedure:

Now I wouldn’t have started this article if the same thing wasn’t possible with a little bit of scripting against Exchange Web Services and so the script Remove-MessagesClassItems.ps1 was born. Using this script requires Exchange 2007 or later and Exchange Web Services Managed API 1.2 (or later) which you can download here or you can copy the Microsoft.Exchange.WebServices.DLL locally and adjust the DLL path mentioned in the script when necessary. 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-MessagesClassItems.ps1 uses the following syntax:

Remove-MessageClassItems.ps1 [-Mailbox] <String> [-MessageClass] <String> [-Server <String>] [-Impersonation] [-DeleteMode <String>] [-WhatIf] [-Confirm] [<CommonParameters>]

A quick walk-through on the parameters and switches:

• Mailbox is the name of the mailbox of which to fix the folder structure;
• MessageClass specifies the Message Class to remove, for example IPM.Note.EnterpriseVault.Shortcut (EnterpriseVault);
• 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).

So for example, suppose you want to remove  IPM.Note.EnterpriseVault.Shortcut items from the mailbox of user1, moving the items to the DeletedItems by Impersonation. In such case, you could use the following cmdlet:

Remove-MessageClassItems.ps1 -Mailbox user1 -MessageClass IPM.Note.EnterpriseVault.Shortcut -DeleteMode MoveToDeletedItems -Impersonation –Verbose

Note: By default, Remove-MessageClassItems.ps1 will only process IPF.Note class folders (i.e. containing mail items), so you’ll only see those being processed. If you want all folders scanned (also class-less), use the ScanAllFolders switch.

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:

Mailbox
francis
philip

The cmdlet could then be something like:

Import-CSV users.csv1 | Remove-MessageClassItems.ps1 -MessageClass IPM.Note.EnterpriseVault.Shortcut -DeleteMode HardDelete -Impersonation

You’re feedback is welcomed through the comments; if you got scripting suggestions, please use the contact form.

Revision History
See TechNet Gallery page.

# Exchange 2013 Unattended Installation Script v1.1

Coming back from a nice vacation to the beautiful Brittany in France, I thought it was time to collect and process feedback and suggestions on several scripts, starting with the Exchange 2013 unattended installation script for Windows Server 2012.

Changes in version 1.1 of the script:

• When the script was used to also prepare Active Directory, RSAT-ADDS-Tools was uninstalled as part of the cleanup. Per request, I’ve removed the uninstallation of that feature;
• The script now detects pending reboots after installing the required features. When ran in AutoPilot mode, the script will reboot and restart the phase (preparing Active Directory, which can’t be run with pending reboots because Exchange’s Setup won’t like it). When not running in AutoPilot mode, you need to start the script manually. You can omit providing installation parameters as they are saved, even a pending is detected;
• The Windows feature Server-Media-Foundation will be installed explicitly as it is an UCMA 4.0 requirement;
• The credentials provided for AutoPilot mode will be validated;
• The OS version check is changed to a string which should enable installation on non-US Operating Systems.

You can download the updated version of the script via the original Exchange 2013 Unattended Installation Script page or directly from the Technet Gallery. Enjoy!

# Regular Expressions and Named Groups

When processing strings, you may sometimes need to split those strings into several parts based on criteria. For example, you may need to split the ActiveSync DeviceUserAgent string, which can be used to identify the model and version of a device, for example:

• Apple-iPhone4C1/1002.142
• Apple-iPhone4C1/1001.523

Now let’s assume you want to take this string apart in the device model and the version, using the slash as a marker. When asking 10 people, a majority will come up with something along the following kind of structure:

$pos= ($Device.DeviceUserAgent).IndexOf("/")+1
$Device= ($Device.DeviceUserAgent).Substring(0, $pos)$Version= ($Device.DeviceUserAgent).Substring($pos+1)

Of course, this works but you need to understand what’s going on and additional code is required the handle situations when there’s no “/” present. Perhaps a more elegant and readable way, especially if you want to split the string in more than 2 parts, is using regular expressions to perform pattern matching. When used in combination with named groups, you will get easily referable parts using a name.

For the purpose of demonstrating it’s power, we’ll start by assigning some user agent strings to a variable. Note that you could use the DeviceUserAgent property of a collection of ActiveSync devices as well.

$UserAgents=@("Apple-iPhone4C1/1001.523", "Apple-iPhone3C1/801.293") The pattern to look for in each user agent string is it starts with some characters, followed by a slash, followed by a number, followed by a dot and ending in a number. Translated to regular expression, the pattern would be: ^(.*)/(\d+)\.(\d+)$

where:

• ^ marks the start of the subject;
• (.*) marks a sub pattern of any character, the * indicates it’s present zero, one or more times;
• The slash is marked by (surprise!) a /;
• (\d+) marks a sub pattern of decimal digit, the + indicates it’s used one or more times;
• \. will match the dot (escaping it with “\” makes sure next character is used literally as ‘dot’ normally means any character);
• $marks the end of the subject. When matching a pattern against a string you can use –match, e.g. “string” –match “pattern”. When this results in True, a match is found; when the result is False, the format of the string is invalid (of course requires properly defined pattern). After performing a match, the predefined variable$matches will contain an array containing the results, where element [0] contains the complete matching string and [1] .. [n] each matching (sub) pattern.

You see that each match returns True (match found) and $matches[1] for example contains the first sub pattern for each match, i.e. the device. This is nice and perhaps neater than splitting strings as mentioned in the start of this article, but wouldn’t it be even cooler if you can refer to those parts using names, e.g. device? Here’s where named groups come into play and you can compare it to PowerShell’s calculated properties (select @{Name=”KB”; Expression={$_.Size/1kb}}) or column aliases in SQL (SELECT ColA as Name). To use named groups, put “?<name>” at the start of the (sub) pattern. For example, to use the name “device” for the first sub pattern “(.*)”, the expression would become “(?<device>.*)”. The complete pattern using named groups would then become something like:

^(?<device>.*)/(?<major>\d+)\.(?<minor>\d+)$ The cmdlet for this example would then become: $UserAgents | ForEach { [void]($_ -match "^(?<device>.*)/(?<major>\d+)\.(?<minor>\d+)$"); $matches } Which will give the following results: Note that I’ve added casting (i.e. converting a variable to a different type) the output of “-match” to [void] so the match results (True or False) won’t be part of the output. Having named groups now allows us to use easily match individual parts, filter or group information, e.g.: $UserAgents | ForEach {
[void]($_ -match "^(?<device>.*)/(?<major>\d+)\.(?<minor>\d+)$")
If( $matches.major –gt 800) { echo$matches[0]
}
}

Of course it’s a matter of taste, but having this information available as $matches.device instead of$matches[1] makes introducing changes more easy (no need to renumber when inserting/removing a sub pattern) and results in more readable code.

# Exchange 2013 Unattended Installation Script (Updated)

I’m pleased to announce the availability of Install-Exchange2013.ps1, a PowerShell script to perform a fully unattended setup of Exchange Server 2013.

The script takes care of:

• Installing required Windows Server 2008 R2 SP1 / 2012 / 2012 R2 features and optionally prepare Active Directory (phase 1);
• Install Exchange Server 2013 prerequisites (phase 2);
• Optionally install Exchange Server 2013 (phase 3)
• Optionally – depending on phase 3 – perform post-configuration (phase 4, tailor to your own needs);
• When done, the script will perform some cleaning up, like removing the state file and setting the startup of Transport Service back to Automatic (phase 5).

Usage
This script version requires a domain-joined Windows Server, an account to perform the installation (and optionally prepare Active Directory) and the location where the Exchange Server 2013 installation files are stored (e.g. an UNC path).

The syntax is as follows:

Install-Exchange2013.ps1 -[InstallCAS|InstallMailbox|InstallBoth] -SourcePath <string> [-Organization <string>] [-MDBName <string>] [-MDBDBPath <string>] [-MDBLogPath <string>] [-InstallPath <string>] [-TargetPath <string>] [-AutoPilot] [-Credentials <pscredential>] [-NoSetup] [<CommonParameters>]

A short description of the parameters:

• Organization (optional): Specifies name of the Exchange organization to create. When omitted, the step to prepare Active Directory (PrepareAD) will be skipped.
• InstallMailbox: Specifies you want to install the Mailbox server role.
• InstallCAS: Specifies you want to install the CAS role.
• InstallMultiRole:Specifies you want to install both Mailbox server and CAS roles.
• MDBName (optional): Specifies name of the initially created database.
• MDBDBPath (optional): Specifies database path of the initially created database (requires MDBName).
• MDBLogPath (optional): Specifies log path of the initially created database (requires MDBName).
• InstallPath (optional): Specifies (temporary) location of where to store prerequisites, transcript and state file. Default location is C:\Install.
• NoSetup (optional): Specifies you don’t want to perform Exchange setup.
• SourcePath: Specifies location of the Exchange 2013 installation files (setup.exe).
• TargetPath: Specifies the location where to install the Exchange 2013.
• AutoPilot (switch): Specifies you want to automatically restart, logon using credentials specified and continue the installation. When not specified, you will need to restart, logon and start the script manually each time (without parameters).
• Credentials (optional): Specifies credentials to use for automatic logon. Use DOMAIN\User or user@domain. When not specified, you will be prompted to enter credentials.
• IncludeFixes (optional).  Depending on operating system and detected Exchange version to install, will download and install recommended hotfixes (e.g. KB2938053) and tune .NET for store (DisableRetStructPinning).
• InstallFilterPack (optional). Adds installing Office filters for OneNote & Publisher support.

Note that the script uses an XML file to store the (original) parameters used to start the script but also to keep track of the the process. Of course, if required, you can use predefined XML files to run the script without parameters.

Note that when not present, the script will try to download the prerequisites from the internet. When that isn’t possible or to save bandwidth, you can put them in the location defined by InstallPath and the script will detect and use them.

For example, assume we want to start a fully unattended install of an Exchange Server 2013 Client Access server, using a network location for the Exchange Server 2013 source files. After setting the Execution Policy to Unrestricted and storing the script locally, we start the script using:

 .\Install-Exchange2013.ps1 –InstallCAS –SourcePath ‘\\server\share\isos\Microsoft\Exchange2013\mu_exchange_server_2013_x64_dvd_1112105’ –AutoPilot –Verbose

The script will perform some checks and since AutoPilot was specified without using the Credentials parameter, the script will ask for credentials.

After entering the credentials, the required features will be installed. Since OrganizationName wasn’t specified, Active Directory preparation will be skipped.

After rebooting, the system will automatically log on using the credentials specified earlier and start the script (RunOnce registry key is utilized for this purpose). It will read the last known state from the XML file and will continue with the next phase, which is downloading (when not present) and installing the Exchange prerequisites.

Next, after rebooting and the automatic logon, Exchange will be installed from the source location.

When done, the system will perform post configuration and finalization steps including reboots and logons. Note that it may seem like a lot of reboots, but rebooting after installing Windows features and Exchange prerequisites is required anyway so I put reboots after the other milestones as well.

Revision History
The revision history is maintained at the Technet Gallery page.

# Exchange Environment Report

A quick post on Exchange fellow Steve Goodman who created a nice PowerShell script which generates a basic HTML report on your Exchange environment. When required, you can also e-mail the report, which is nice if you want to schedule the script to run on a daily basis for example.

The script is provided as-is so you can tailor it to your needs. It’s still work in progress, so if you got any requests just send Steve a message.

You can find the post and script here.

# Switching E-mail Domains

Suppose you work at an organization that is about to change it’s primary e-mail domain and that organization doesn’t use e-mail address policies. Changing e-mail domains isn’t unlikely because of mergers or rebranding and yes, there are organizations out there not utilizing e-mail address policies for various reasons.

This post will describe what steps are involved and how to accomplish this task using PowerShell. I’ll use a phased approach since organizations performing all these steps at once are rare. Note that examples below will use oldcorp.com for the old e-mail domain and newcorp.com for the new e-mail domain.

First, we’ll start off by adding the new domain to the list of accepted e-mail domains in the Exchange organization. We’re authoritative for the new e-mail domain, so use the the New-AcceptedDomain cmdlet in conjunction with the Authoritative as DomainType to add this e-mail domain using Exchange Management Shell:

New-AcceptedDomain -Name "New Corp" -DomainName newcorp.com -DomainType Authoritative

Next, configure the MX records for the newcorp.com domain on DNS. Let them point to your inbound SMTP server or appliance and, if required, configure your anti-spam/anti-virus solution accordingly.

Now for the more interesting part: how to add the new e-mail domain to objects and make the switch from oldcorp.com to newcorp.com in bulk. We’ll start by adding newcorp.com proxy addresses (the EmailAddresses property) for all existing olddomain.com e-mail addresses using Set-Mailbox, using the current user part (the part before the “@”). Take caution when adding an e-mail address to EmailAddresses, since EmailAddresses is an array and you could end up overwriting all current proxy addresses with a single element when used like this:

Set-Mailbox mderooij -EmailAddresses michel.de.rooij@newcorp.com

Instead, we need to utilize the Add method. All roads lead to Rome, so I’ll pick the most efficient manner as shown in the script below. Be advised that the script as shown processes all mail-enabled objects which primary address is @oldcorp.com (as a form of filtering). Therefore, take caution and use it in your test environment first when appropriate.

Get-Mailbox -ResultSize Unlimited | Where { $_.WindowsEmailAddress -like "*@oldcorp.com" } | ForEach {$This= $_$_.EmailAddresses | where { $_.SmtpAddress -like "*@oldcorp.com" } | ForEach {$NewMailAddress= $_.SmtpAddress.ToString().Split( "@")[0] + "@newcorp.com" "Adding$NewMailAddress for "+ $This.WindowsEmailAddress Set-Mailbox$_.Identity -EmailAddresses @{Add=$NewMailAddress} } } Now, inbound e-mail for newcorp.com is accepted and accounts are configured to receive e-mail on the new newcorp.com e-mail domain. Add a certain point in time, we’ll make the switch from oldcorp.com to newcorp.com, so all outbound e-mail will use the new e-mail address. To do this, we need to use the Set-Mailbox cmdlet in conjuction with the PrimarySmtpAddress parameter. We’ll only look at the WindowsEmailAddress, which holds the current Primary SMTP address: Get-Mailbox -ResultSize Unlimited | Where {$_.WindowsEmailAddress -like "*@oldcorp" } | ForEach {
$ID=$_
$NewPrimary=$_.WindowsEmailAddress.ToString().Split( "@")[0] + "@newcorp.com"
"Promoting "+ $NewPrimary Set-Mailbox$ID -PrimarySmtpAddress $NewPrimary } The last thing to do after determining everything is working fine and everyone is used to the new e-mail domain is removing those @olddomain.com secondaries. We’ll take the current set of EmailAddresses, filter on not having the olddomain.com e-mail domain, and put the filtered result back in the EmailAddress property. Get-Mailbox -ResultSize Unlimited | Where {$_.WindowsEmailAddress -like "*@newcorp.com" } | ForEach {
$NewSecondaries= ($_.EmailAddresses | where { $_.SmtpAddress -notlike "*@oldcorp.com" }) Set-Mailbox$_ -EmailAddresses \$NewSecondaries
}

I hope you find this information useful. If you have questions, do not hesitate asking me in the comments.

# Exchange & PowerShell Presentation (Dutch)

After last years NGN Exchange event, where I presented a session on Exchange Autodiscover,  it was time for a followup. This event took place on May 19th and had a turn up of about 80 attendees, which was better than expected given the fact this was a non-free NGN event unlike last year. Early questions indicated large part of the attendees are still on Exchange 2003, though the figure has dropped a bit from last year’s 80%.

I held a duo-presentation with Maarten Piederiet on the topic PowerShell and the Exchange 2003 Administrator. A fingers up poll also showed lots of people still working with batchfiles, VBScript or KiXtart. This was expected, so our topic was spot on since we included PowerShell primer topics. Of course, this 45 minute session doesn’t replace a proper PowerShell course, let alone a course for decent development skills but our goal was to get people enthusiastic by demonstrating how simple yet powerful PowerShell is.

Besides presenting, which went a lot better than last year on a side note, it was also a time to catch up with fellow Exchange MVPs and enjoy discussions with attendees during the Ask The Expert breaks.

Below you’ll find the presenters’ slidedecks in PDF format. Note that the presentations are in Dutch.

Unfortunately, NGN decided not to publish the recorded sessions so I can’t share those with you.