Removing Messages by Message Class (Updated)

powershellRecently, 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 won’t be using the archiving solution in the new environment. When required, vendor tooling would be used to search through the (old) 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, so the stub will mostly 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 (and 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.:

StubsOutlook

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:

SearchFromOutlook

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 

SampleOutput

Note: Remove-MessageClassItems.ps1 will only process IPF.Note class folders (i.e. containing mail items), so you’ll only see those being processed.

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.

You can download the script from the Technet Gallery here.

Revision History
1.01: Fixed example (IPM.ixos-archive instead of IBM.ixos-archive) and removed EMS requirement

Exchange 2013 Unattended Installation Script v1.1

Ex2013 LogoComing 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!

Exchange 2013 Unattended Installation Script (Updated)

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

The script takes care of:

  • Installing required Windows Server 2012 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 to Automatic (phase 5).

Usage
This script version requires a domain-joined Windows Server 2012 system, 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] -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.
  • 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.

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.

The post-configuration is currently adding IFilters for OneNote and Publisher (Mailbox) only. There are comments in the script where to add your own additional post-configuration steps.

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.

Capture1

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

Capture2

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.

Capture3

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

Capture4

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.

You can download Install-Exchange2013.ps1 here. Please leave your feedback or bug reports in the comments.

Revision History
1.02: Fixes AD preparation logic and adds checks to see if domain is in native mode. It also fixes a small typo in the post-prepare AD function.
1.03: Tested against Exchange Server 2013 CU1, replaced installing OS features manually by using /InstallWindowsComponents and removed installation of Office Filtering Pack (routines still there because they’re required for the Exchange 2010 SP3 version I’m working on).
1.1: When AD was prepared, RSAT-ADDS-Tools won’t be uninstalled, pending reboot detection (in AutoPilot, script will reboot and restart phase), installs Server-Media-Foundation feature (UCMA 4.0 requirement), validates provided credentials for AutoPilot and checks OS version as string (should accommodate non-US OS).

Fixing Well-Known Folders Troubles (Update)

powershellDepending on your migration scenario, you could be exporting and importing PST files when migrating mailbox contents from one Exchange environment to another. Now folders in PST files are just folders, i.e. Inbox is a folder just like any other folder and well-known folders lose their identification. Because of this, you may end up with unexpected additional folders in your mailbox after importing PST files.

For example, when importing a non-US mailbox PST in a mailbox set to en-US you may end up with well-known folders and their regional counterparts, e.g. “Inbox” and “Postvak In” or “Calendar” and “Agenda”.

image

It can also happen that you import the PST file before setting the regional settings of the mailbox. In that case the folders with the local names are already present and Exchange will generate sequence number folders, e.g. Inbox1 or Calendar1.

image

Note that the above behavior goes for all well-known folders, such as Inbox, Calendar, Contacts and Sent Items. Users logging in early, i.e. logging on to their mailbox while they shouldn’t, can also create these situations.

One way to fix this situation is to manually move contents to the proper location using an e-mail client, which is tedious and something you don’t want to trouble end users or admins with. Another way is to set the mailbox’ regional configuration before importing the PST file.

A better way is of course to have an Exchange Management Shell script which talks to Exchange Web Services, essentially doing the same but in an automated kind of fashion. Here’s where the Fix-MailboxFolder.ps1 script comes into play. Using Fix-MailboxFolders.ps1 requires Exchange 2010 SP1 and Exchange Web Services 1.2 (or later), which you can download here. The script hasn’t been tested yet against Exchange 2013.

Since the script will process user mailboxes, it needs to be run by a user with full mailbox permissions or impersonation permissions. Granting full mailbox access can be done on several levels, e.g. mailbox or database. For example, to grant ExAdmin Full Access on a mailbox:

Add-MailboxPermission –Identity User –User ExAdmin –AccessRight FullAccess

However, a more elegant and preferred method is to utilize impersonation through Role-Based Access Control (RBAC). Information on configuring impersonation in your Exchange environment using RBAC is found here. As an example, to grant ExAdmin permissions to impersonate all(!) users in an organization, use the following cmdlets:

New-ManagementRoleAssignment –Name:impersonationAssignmentName –Role:ApplicationImpersonation –User:ExAdmin

After executing this cmdlet, the account ExAdmin can succesfully impersonate mail-enabled users. To limit impersonation to a certain subset of the user population, you could use a write scope; for more information on RBAC and write scopes, check out my past blog on this topic here.

Fix-MailboxFolders.ps1 uses the following syntax:

Fix-MailboxFolders.ps1 [-Mailbox] <String> [[-Language] <String>] [[-FromLanguage] <String>] [[-Server] <String>] [-ScanNumericals] [-Impersonation] [<CommonParameters>]

A quick walk-through on the parameters and switches:

  • Mailbox is the name of the mailbox of which to fix the folder structure;
  • Language is the language to configure. If this isn’t specified, the script will use a default value of en-US;
  • FromLanguage is the language in which to scan for folders. When omitted, the script will use the currently configured mailbox language;
  • Server is the name of the Client Access Server to access for Exchange Web Services. When omitted, the script will attempt to use Autodiscover;
  • The switch ScanNumericals will tell the script to look for folders in the language specified by FromLanguage with sequence numbers, e.g. Inbox1 or Calendar1;
  • When the Impersonation switch is specified, impersonation will be used for mailbox access. When this switch isn’t used, the script will run in the context of the current user.
  • Of the common parameters, only the Verbose switch is currently supported and will tell you exactly what the script is doing

So, for example assume you have a mailbox with en-US folders but also Dutch folders and the proper regional setting is en-US, use the following cmdlet:

.\Fix-MailboxFolders.ps1 –Mailbox Francis -Language nl-NL -FromLanguage us-EN –Impersonation

image

If you want to fix the folders of multiple mailboxes you can use a CSV file. The CSV needs to contain at least the Mailbox, but can optionally settings for Language and FromLanguage since the script will accept both through the pipe.

A sample of how the csv could look:

Mailbox,Language,FromLanguage
francis,en-US,nl-NL
philip,en-US,nl-NL

The cmdlet should then be something like:

Import-Csv .\Users.csv | .\Fix-MailboxFolders.ps1 -Language en-US -ScanNumericals -Impersonation –Verbose

Be advised that the script currently only contains en-US and nl-NL settings; you need to add additional language settings to the $LanguageInfo structure. The $LanguageInfo structure contains localized names for each of the well-known folders and has the following format.

"en-US"= @{ 
     "Inbox"="Inbox"; 
     "SentItems"="Sent Items"; 
     "Notes"="Notes"; 
     "Drafts"="Drafts"; 
     "DeletedItems"="Deleted Items"; 
     "Outbox"="Outbox"; 
     "Contacts"="Contacts"; 
     "Calendar"="Calendar"; 
     "Tasks"="Tasks"; 
     "JunkEmail"="Junk E-mail"; 
     "Journal"="Journal"; 
     "DateFormat"="M/d/yyyy"; 
     "TimeFormat"="h:mm tt" 
};

To increase readability I’ve created one setting per line in the script; everything could be stored on a single line of course. The values DateFormat and TimeFormat should be provided in a valid format for the related regional setting. Depending on feedback and demand, I can add additional languages to future versions of the script.

I will gladly take feedback on the script in the comments or through the contact form. If you want to start programming against Exchange Web Services yourself, I would highly recommend Glen Scales blog to look for PowerShell sample code.

Special thanks to co-worker Maarten Piederiet (Exchange 2010 MCM) for reviewing the initial version of the script (hence why the initial public version is 1.1).

You can download the script from the Technet Gallery here.

March 29th, 2013: Version 1.2 fixes loop condition when “Top of Information Store” is localized resulting in call depth overflow.

Cluster Name Object Pre-staging

Ex2013 LogoWhen creating a Database Availability Group (DAG) in Exchange 2010 or Exchange 2013 you leverage Fail-over Clustering from the operating system, e.g. Windows Server 2008 R2.

Behind the scenes Kerberos authentication is used, for which a so called Cluster Name Object (CNO) has to be created in Active Directory. This CNO will be associated with the Cluster Name Resource.

Depending on the situation, like having the ability to create computer accounts in the domain, you may need to create – or pre-stage – the cluster name object as  computer account upfront. For Exchange 2013 on Windows Server 2012, pre-staging the CNO is a requirement. This manual task is described here.

However, there may be circumstances where having the ability to automate the process would be more appropriate, like when you want a fully automated setting up a DAG for example. For this purpose I have created a small script, Create-CNO.ps1. The syntax is as follows:

Create-CNO.ps1 [-Identity] <String> [[-Computers] <Array>] [[-OU] <String>

A small explanation of the available parameters:

  • The Identity is used to specify the name of the CNO;
  • The optional Computers parameter can be used to specify the computer account which should be granted permissions on the CNO. You can specify multiple accounts seperated by commas (when for example you’re not sure which your will be used to create the DAG). When the Computers parameter is omitted, the Exchange Trusted Subsystem will be granted permissions on the CNO;
  • OU is the name of the container to create the CNO in. When not specified, the default container for computer accounts will be used. This is done by querying for the Well-Known GUID for the computers container, aa312825768811d1aded00c04fd8d5cd (more on Well-Known GUIDs here). Note that when specifying the OU, you need to enclose it in quotes otherwise PowerShell will assume the parameter is an array;
  • The Verbose parameter is supported.

So, for example assume you want to create a DAG called DAG001 and the first Mailbox Server will be L14Ex1. The computer object for the cluster is to be stored in the OU ou=Temp,dc=litware,dc=com. In that case, you would call the script as follows:

Create-CNO.ps1 –Identity DAG001 –Computers L14EX1 –OU “ou=Temp,dc=litware,dc=com” –Verbose

If you want to grant Exchange Trusted Subsystem permissions as well and let the script look up the CNO name, you can use:

Create-CNO.ps1 –Identity DAG001 –Verbose

create-cno-1You can download the script from the TechNet Gallery here.

Copying Receive Connectors (update)

Once in a while you may have to execute a task so tedious and repetitive, you end up with an idea for a script to make your life easier. By tidying and publishing that script, I hope to make the life of others easier as well. This is one of those scripts.

When implementing Hub Transport servers on Exchange 2010, you may have to configure multiple receive connectors. Because receive connectors are defined on the Hub Transport server itself, contrary to send connectors, you may end up defining each receive connector on each Hub Transport server. This gets painful when you need to implement the ForeFront Online Protection for Exchange servers in the Remote IP ranges for example.

Yes, you can create a script which configures the connectors for you, but wouldn’t it be nice if you can create definitions on one server using the GUI and then just copy and paste those definitions to the other Hub Transport servers? This script also allows you to simply duplicate existing Receive Connector definitions after adding an additional Hub Transport server afterwards, not only after the initial configuration of the Exchange environment.

Here’s were my Copy-ReceiveConnector.ps1 script may come in handy.

The script is quite simple, and can help you with the following:

  • Copy Receive Connectors from one Exchange server to another (CopyFrom);
  • Export Receive Connector definitions to an XML file (ExportTo);
  • Import Receive Connector definitions from an XML file (ImportFrom).

In addition, you can specify whether you want to overwrite existing Receive Connector definitions (based on name) using the -Overwrite switch or clear all existing Receive Connectors before copying/importing using the -Clear switch.

So, let’s say you have two Hub Transport servers, L12EX1 and L12EX2. You have configured L12EX1 and you need to create the same set of receive connectors on L12EX2.

image

You can see in the example above, you can use the script to copy definitions from an existing server, e.g.

Copy-ReceiveConnector.ps1 <TargetServer> –CopyFrom <SourceServer>

You can also export and import settings, which may come in handy when you need to troubleshoot (you can have the customer export the receive connectors to a file) or when you want to prepare receive connector definitions off-site, e.g.

Copy-ReceiveConnector.ps1 <TargetServer> –ExportTo .\conn.xml

Copy-ReceiveConnector.ps1 <TargetServer> –ImportFrom .\conn.xml –Clear

image

Note that when ExchangeServer is specified as AuthMechanism on a receive connector, the FQDN needs to be set to the server’s FQDN, NetBIOS name or $null; in such cases I set it to the FQDN of the target server. Also, it uses the existing name, meaning you may need to rename the Default and Client connectors, which contain the server name, afterwards.

Update 24th August, 2012 (v1.1): Added find/replace in Receive Connector name so that “Default L12EX1″ on server L12EX1 will become or match with “Default L12EX2″ on server L12EX2.

Click here to download the script from the Technet Gallery.

The case of the missing Free/Busy public folder pt.2

In an earlier blog, I described the situation where a customer had improperly decommissioned Exchange 2003 Administrative Groups and ended up with invalid, orphaned legacyExchangeDN values causing all sorts of issues, most Public Folder / Free Busy related. Read more on this story here.

In the blog, I had two options on how to proceed:

  1. Edit the legacyExchangeDN attribute of the users affected;
  2. Recreate the Free/Busy public folder.

In the first blog, I described how to fix the situation using the 2nd option. Here’s how to solve this if you have no Exchange 2003 server left and want to go with the other option.

To fix this situation by changing the legacyExchangeDN values, you need to perform the following steps:

  1. Identify all mailboxes containing improper legacyExchangeDN values;
  2. For all those mailboxes, add the current legacyExchangeDN value as an x500 address;
  3. Fix the current legacyExchangeDN.

Note that by adding the invalid legacyExchangeDN value as an X500 address, we make sure (responding to) old e-mail messages or nickname entries can resolve properly.

You could use tools like ADModify to bulk edit those values. However, you also achieve the same result using a little PowerShell (surprise!), as shown in the following script:

Note: Use the script at your own risk. I cannot accept any responsibility for consequences when using this in your production environment. Before using it, prepare it in a lab environment first: test, test, test! Also, this script fixes invalid legacyExchangeDN values; it does not fix any related invalid settings, like delegates; that might be something for a next version when there’s demand for it.

$oldDN="/o=ADATUM/ou=First Administrative Group"
$newDN="/o=CONTOSO/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients"
$mbx= get-mailbox -Filter "LegacyExchangeDN -like '$($oldDN)/*'"
$mbx | ForEach {
    $x5= "x500:"+ $_.legacyExchangeDN
    Set-Mailbox $_.Identity -EmailAddresses @{Add=$x5}
    $User = [ADSI]("LDAP://"+$_.distinguishedName)
    $newDN= $newDN+ “/cn=”+ $_.Name
    $User.Put("legacyExchangeDN", $newDN)
    $User.SetInfo()
}

To use the script, replace the $oldDN value with your old, invalid legacyExchangeDN value (as reported in the Event Log entries with Event ID 14031). Set $newDN to your new legacyExchangeDN value; the default value of would be in the format “/o=<Organisation Name>/ou=<Administrative Group, i.e. Exchange Administrative Group (FYDIBOHF23SPDLT)>/cn=Recipients/cn=<Name>”.

If you have any questions, drop them in the comments below.

For all the PowerShell purists: Sometimes I prefer readability over trying to fit everything one 1 line. After all, this isn’t an Obfuscated Code Contest :)