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.

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

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

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
1.1: Added switch -ScanAllFolders to scan all mail folders, not only IPF.Note folders, and parameter -Before to only remove items received before specified date.

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!

Regular Expressions and Named Groups

powershellWhen 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-iPad2C1/1002.141
  • 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")

image

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.

image

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:

image

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)

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 2008 R2 SP1 / 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 from the Technet Gallery. 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).
1.5: Added support for WS2008R2 (i.e. added prereqs NET45, WMF3), IEESC toggling, KB974405, KB2619234, KB2758857 (supersedes KB2533623). Inserted phase for WS2008R2 for installing hotfixes; this phase is skipped for WS2012. Added InstallPath to AutoPilot set (or default won’t be set).
1.51: Rewrote Validate-Credentials due to missing .NET 3.5 Out of the Box in WS2008R2. Testing for proper loading of servermanager module in WS2008R2. Fixed logic in WS2008R2 SP1 detection.
1.52: Fix .NET / PrepareAD order for WS2008R2, relocated RebootPending check
1.53: Fix phase of Forest/Domain Level check

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.

Remote PowerShell to Office 365

imageWhile trying Office 365 you might want to connect your to a remote Exchange Management Shell session instead of using the portal interface. Here’s how to proceed.

Start up a PowerShell session. The first thing we’re going to do next is store credentials in a variable for later usage:

$cred= Get-Credential

A popup will be displayed where you can enter your Office 365 admin credentials, e.g. myadminname@yourdomain.onmicrosoft.com.

Next, create a new remote PowerShell session using the following cmdlet:

$o365= New-PsSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell -Credential $cred -AllowRedirection -Authentication Basic

image

Next, we can import the session. However, this might be confusing since you have no context; are you creating a mailbox local or in the Office 365 environment?

The cool thing is that with Import-Session you can specify a prefix. This prefix can be specified before the cmdlet noun so that PowerShell knows which session you want the cmdlet to run against. As you probably know, cmdlets are normally constructed using <verb>-<noun> syntax, but this should be <verb>-<session prefix><noun>. When the session prefix is omitted, PowerShell assumes the current session.

For example, let’s import our Office 365 session with a prefix of “o365”:

Import-PsSession $o365 –Prefix o365

image

Now, we can use that “o365” prefix before the noun. For example, to get a list of our Office 365 mailboxes, you’d use something like:

Get-o365Mailbox

image

Cool and simple, eh?

Don’t forget to close your online session afterwards using:

Remove-PsSession $o365

Have fun exploring Office 365 using PowerShell.