Balancing the number of mailboxes across Exchange 2010 and 2007 databases
Introduction
In Exchange 2010, you now have the option to allow mailboxes to be automatically distributed across databases. However, the algorithm used simply randomly allocates the new mailbox to your chosen databases - rather than ensuring the mailbox count is balanced, and doesn't do anything about re-distributing mailboxes if you add new databases.
To help with this, and of course to help with any situation where you want to balance the number of mailboxes across a set of databases, I've written a simple script that help with moving mailboxes to balance out your databases.
Using Generate-DBBalanceScript.ps1
There's nothing too complicated about the script- it doesn't balance based on mailbox size (a future version), but simply creates a script with Move-Mailbox or New-MoveRequest commands that once complete, balances based on mailbox counts across the databases. You pass it the results of a Get-MailboxDatabase command, along with an output file that will contain the Mailbox move commands.
First of all, lets see it in action:
Of course, my example was the simplest - across all databases. Here's a few examples including the one above, and some others that show how to drill down to databases on specific servers:
Example One - Generate a move file based on all Exchange 2010 Databases:
.\Generate-BalanceMoveRequests.ps1 -DBs (Get-MailboxDatabase) -OutputPowershellFile .\moves.ps1
Example Two - Generate a move file based on Exchange 2010 Databases located on a single server "servername":
$o=Get-MailboxDatabase -Server servername
.\Generate-BalanceMoveRequests.ps1 -DBs $o -OutputPowershellFile moves07.ps1 -Exchange2010:$false
Example Three - Generate a move file based on Exchange 2007 Databases located on two servers, "serverone" and "servertwo":
$o=Get-MailboxDatabase | where {$_.Server -eq "serverone" -or $_.Server -eq "servertwo"}
.\Generate-BalanceMoveRequests.ps1 -DBs $o -OutputPowershellFile moves07.ps1 -Exchange2010:$false
Download Generate-DBBalanceScript.ps1
You can download Generate-DBBalanceScript.ps1 here or view the script below.
Hope this helps!
<#
.SYNOPSIS
Generates a Powershell file containing Mailbox Move Cmdlets to help balance mailbox databases, based on mailbox count, not size.
.DESCRIPTION
Using a list of Databases, works out the average number of mailboxes there should be per DB, then generates move commands to run seperately.
By Steve Goodman
.PARAMETER DBs
Databases to use
The result of a Get-MailboxDatabase cmdlet. Eg. (Get-MailboxDatabase -Server name)
.PARAMETER OutputPowershellFile
The filename to write, for example C:\output.ps1 or .\output.ps1
.PARAMETER Exchange2010
By default, true. Set to $false to generate Exchagne2007 move-mailbox commands
.EXAMPLE
Generate a move file based on all Exchange 2010 Databases
.\Generate-BalanceMoveRequests.ps1 -DBs (Get-MailboxDatabase) -OutputPowershellFile .\moves.ps1
.EXAMPLE
Generate a move file based on Exchange 2007 Databases located on a single server "servername"
$o=Get-MailboxDatabase -Server servername
.\Generate-BalanceMoveRequests.ps1 -DBs $o -OutputPowershellFile moves07.ps1 -Exchange2010:$false
.EXAMPLE
Generate a move file based on Exchange 2007 Databases located on two servers, "serverone" and "servertwo"
$o=Get-MailboxDatabase | where {$_.Server -eq "serverone" -or $_.Server -eq "servertwo"}
.\Generate-BalanceMoveRequests.ps1 -DBs $o -OutputPowershellFile moves07.ps1 -Exchange2010:$false
#>
param(
[parameter(Position=0,Mandatory=$true,ValueFromPipeline=$false,HelpMessage="Mailbox database object")][array]$DBs,
[parameter(Position=1,Mandatory=$true,ValueFromPipeline=$false,HelpMessage="Filename for output Powershell script")][string]$OutputPowershellFile,
[parameter(Position=2,Mandatory=$false,ValueFromPipeline=$false,HelpMessage="Is this for Exchange 2010")][bool]$Exchange2010=$true
)
# Check for Exchange cmdlets
if (!(Get-Command Get-MailboxDatabase -ErrorAction SilentlyContinue))
{
throw "Exchange Cmdlets not available";
}
# Check input object
if ($DBs[0].GetType().Name -ne "MailboxDatabase")
{
throw "Object is not an array of mailbox databases"
}
if ($DBs.Count -eq 1)
{
throw "You can't balance a single database";
}
# Check file does not exist
if ((Test-Path $OutputPowershellFile))
{
throw "File $($OutputPowershellFile) already exists";
}
# Initialise file
"# Mailbox Mail Powershell Script File, generated $(date)`r`n"|Out-File -FilePath $OutputPowershellFile -NoClobber
if (!(Test-Path $OutputPowershellFile))
{
throw "Could not write to $($OutputPowershellFile)";
}
# Initialise Variables
[int]$TotalMailboxes=0; # Total Mailboxes
[int]$BalancedCount=0; # Balanced Number of Mailboxes per DB
[array]$DBCounters=@(); # Array with DB Id, Mailbox Count and Mailbox listing
[array]$UA_DBCounters=@(); # Under allocated list from above, populated later
[array]$OA_DBCounters=@(); # Over allocated list from above, populated later
[array]$PA_DBCounters=@(); # Perfectly allocated list from above, populated later
[string]$Output=""; # Variable to write output text to
# Gather initial mailbox counts
Write-Host "Gathering Mailbox Counts"
for ($i = 0; $i -lt $DBs.Count; $i++)
{
Write-Progress -activity "Gathering Mailbox Counts" -status "Processing Database $($DBs[$i].Identity)" -PercentComplete (($i / $DBs.Count) * 100)
$Mailboxes = (Get-Mailbox -Database $DBs[$i].Identity -ResultSize Unlimited | select Identity)
$DBCounters=$DBCounters+1;
$DBCounters[$DBCounters.Count-1] = New-Object Object
$DBCounters[$DBCounters.Count-1] | Add-Member NoteProperty Database $DBs[$i].Identity
$DBCounters[$DBCounters.Count-1] | Add-Member NoteProperty Total $Mailboxes.Count
$DBCounters[$DBCounters.Count-1] | Add-Member NoteProperty Mailboxes $Mailboxes
$TotalMailboxes+=$Mailboxes.Count
}
Write-Progress -Activity "Gathering Mailbox Counts" -Completed -Status "Completed"
Write-Host "DB Counts are as follows:"
$DBCounters|Select Database,Total
$BalancedCount = $TotalMailboxes / $DBs.Count
Write-Host "Found $($TotalMailboxes) mailboxes across $($DBs.Count) databases. Aiming for $($BalancedCount) mailboxes per database."
Write-Host "Sorting Databases into over, under and perfectly allocated."
# Sort DBs into under-allocated, over-allocated and perfectly allocated
for ($i = 0; $i -lt $DBCounters.Count; $i++)
{
Write-Progress -activity "Sorting Databases into over, under and perfectly allocated" -status "Processing Database $($DBCounters[$i].Database)" -PercentComplete (($i / $DBCounters.Count) * 100)
if ($DBCounters[$i].Total -lt $BalancedCount)
{
$UA_DBCounters=$UA_DBCounters+1;
$UA_DBCounters[$UA_DBCounters.Count-1] = $DBCounters[$i];
} elseif ($DBCounters[$i].Total -gt $BalancedCount) {
$OA_DBCounters=$OA_DBCounters+1;
$OA_DBCounters[$OA_DBCounters.Count-1] = $DBCounters[$i];
} else {
$PA_DBCounters=$PA_DBCounters+1;
$PA_DBCounters[$PA_DBCounters.Count-1] = $DBCounters[$i];
}
}
Write-Progress -activity "Sorting Databases into over, under and perfectly allocated" -Completed -Status "Completed"
Write-Host "Found $($OA_DBCounters.Count) over, $($UA_DBCounters.Count) under and $($PA_DBCounters.Count) perfectly allocated";
# Make move list
Write-Host "Generating Powershell File '$($OutputPowershellFile)' to Balance DBs."
for ($i = 0; $i -lt $OA_DBCounters.Count; $i++)
{
Write-Progress -activity "Generating Powershell File '$($OutputPowershellFile)' to Balance DBs" -status "Processing Database $($OA_DBCounters[$i].Database)" -PercentComplete (($i / $OA_DBCounters.Count) * 100)
# Get the mailbox list
[array]$OA_Mailboxes = $OA_DBCounters[$i].Mailboxes
$UA_DBPointer=0;
for ($j=$BalancedCount; $j -lt $OA_Mailboxes.Count; $j++)
{
# Move to next underallocated DB if required
if ($UA_DBCounters[$UA_DBPointer].Total -ge $BalancedCount -and $UA_DBPointer -lt $UA_DBCounters.Count-1)
{
$UA_DBPointer++;
}
Write-Progress -activity "Generating move commands" -status "Processing $($OA_Mailboxes[$j].Identity)" -PercentComplete (($j / $OA_Mailboxes.Count) * 100)
# Generate a Powershell command for the move request
if ($Exchange2010)
{
$Output+="New-MoveRequest -Identity '$($OA_Mailboxes[$j].Identity)' -TargetDatabase '$($UA_DBCounters[$UA_DBPointer].Database)' -Confirm:" + '$false' + "`r`n";
} else {
$Output+="Move-Mailbox -Identity '$($OA_Mailboxes[$j].Identity)' -TargetDatabase '$($UA_DBCounters[$UA_DBPointer].Database)'`r`n";
}
$OA_DBCounters[$i].Total--; # Take one mailbox of the total on the overallocated DB
$UA_DBCounters[$UA_DBPointer].Total++; # Add one mailbox tot the total of the current underallocated DB
}
Write-Progress -activity "Generating move commands" -Completed -Status "Completed"
}
Write-Progress -activity "Generating Powershell File '$($OutputPowershellFile)' to Balance DBs" -Completed -Status "Completed"
Write-Host "DB Counts will be as follows after executing $($OutputPowershellFile):"
$OA_DBCounters|Select Database,Total
$UA_DBCounters|Select Database,Total
$PA_DBCounters|Select Database,Total
Write-Host "Writing Powershell file '$($OutputPowershellFile)'"
$Output | Out-File -FilePath $OutputPowershellFile -NoClobber -Append
Set up Federated Free/Busy and Calendar Sharing between Exchange 2010 SP1 and Outlook Live [Updated]
As organizations move to make use of cloud based services, like Exchange Online or Outlook Live, it’s pretty important to be able to integrate both the on-premises service and the cloud service so that end-users can continue to work as normal. That’s especially important with a service like Outlook Live, aimed at Education institutions, where typically staff, faculty and some students will continue to be hosted on-premise and the majority of students will be hosted in the cloud. A seamless experience makes life easier for users and thus easier for IT…
So, with Outlook Live and Exchange 2010 On-Premises there is a pretty good opportunity to get Exchange working seamlessly between both systems. I’ll be covering a unified login in a further article (once I’ve re-written the code we use in-house into a re-distributable form). But for now, I’m focusing on the user experience with free/busy and calendar sharing.
One of the areas users might expect to
“just work” is free/busy and calendar sharing between on-premise and cloud. The options are there, it looks like it tries to do it.. But the end user just gets unfriendly error messages and “permission denied” errors. To get this up and running in Exchange 2010 SP1 is actually now pretty simple. Although SP1 allows self-signed certificates for Federation, you cannot use these with Outlook Live federation, and you'll need to perform an extra step. However it's still fairly straightforward…
Pre-requisites
First things first, we need a few things in-place and working already before we can get going. The main pre-req is Autodiscover, but I won’t cover how to set this up, as it's is covered in detail elsewhere on the good old ‘net..
-
Autodiscover working for On-Premises for the domain you want to use (using DNS names, not a service records)
-
Autodiscover working for Outlook Live for the domain you want to use (again, using DNS names, not a service record)
-
Separate domains for on-premises and Outlook Live (I’m testing getting shared working – follow up article to come)
-
Exchange 2010 CAS Connectivity to *.outlook.com
-
Org admin rights on Exchange 2010 SP1 and Tenant Admin rights on Outlook Live, along with Powershell access to both.
-
Access to manage the DNS for both domains and add TXT records.
-
External URL setup and tested for Exchange Web Services / WebServicesVirtualDirectory
Once that’s all setup and available, you should be ready to go..
Setting it up
To keep things simple, we’re not going to do anything too complicated, we’re going to set things up so all users in both on-premises and Outlook Live can see each other’s free/busy and share calendars (and contacts). Also, for the purposes of this article it’s assumed no Sharing Policy is setup already.
On our test setup, we’ve got two domains:
On Premises: rootuk.net
Outlook Live: test.rootuk.net
Basically we need to create four things on-premise:
- A Federation Trust to authenticate us against the MS Federation Gateway
- A new sub-domain ExchangeDelegation for the account namespace (in this example, ExchangeDelegation.rootuk.net)
- An Organization Relationship to say Outlook Live can see in for Free busy
- Finally a Sharing Policy to allow on-premise users to share their calendars with Outlook Live users.
In Outlook Live the trust with MS’s Federation Gateway is there by default so we just need the Organization Relationship to allow On-premise to see in and the Sharing Policy to allow the Outlook Live users to share with On-premise users.
First, we’ll setup the on-premise config, then get the Outlook Live stuff done. Afterwards we’ll test everything works.
On Premises Config
Step One – Setup a New Federation Trust using a trusted certificate [Updated 10th Nov 2010]
To setup a Federation Trust for use with Outlook Live, you need to use a certificate from an approved certificate authority. As this list is quite short, you may find it doesn't include you current SAN/UCC certificate provider (for example, if you use the JANET Certificate Service), however this isn't a major issue. The certificate you use for Federation doesn't have to be the same one you currently use for your other Exchange services, so fear not - you don't need to buy a new, expensive SAN/UCC certificate. I've found the cheapest one from GoDaddy works just fine.
If you've already got a supported certificate, you can skip this part and just use your existing certificate thumbprint for the New-FederationTrust command. Otherwise, to generate a new request for a single domain certificate just to use for Federation, use New-ExchangeCertificate:
New-ExchangeCertificate -GenerateRequest -SubjectName 'o=rootuk.net, cn=rootuk.net' -DomainName rootuk.net -FriendlyName 'Third Party Federation Certificate' -PrivateKeyExportable $true
Next, follow the standard process for your CA, by requesting, then downloading the certificate from the third party certificate provider:
After downloading the certificate, you now need to complete the certificate request by importing the certificate into Exchange:
Import-ExchangeCertificate -FileData ([Byte[]]$(Get-Content -Path c:\cert\certificate.crt -Encoding byte -ReadCount 0))
Note down the Thumbprint in the output, and then create the Federation Trust using that thumbprint value. Note that we’re choosing the "Legacy Provisioning Service" as part of this process. This is why we can't use a self-signed request – these only work against a new “Business” gateway. Outlook Live currently works against a legacy “Consumer” gateway and the -UseLegacyProvisioningService parameter switches us to use that one.
New-FederationTrust -Name 'Microsoft Federation Gateway' -Thumbprint 'thumbprint' –UseLegacyProvisioningService
NB.. You'll see in that example above I got an error the first time. This does happen occasionally and I've seen it reported in forums too. The error is "The request failed with HTTP status 403: Forbidden". Just retrying the command should work.
Next, we need to setup a record in DNS for the application ID using the ExchangeDelegation sub-domain and the main On-Premises domain we wish to use for federation. As of SP1, it's possible to use the "Federated Domain Proof" DNS TXT record, but as we're using the legacy method, we will use the Application ID method.
To add the App ID, you need to add a DNS record that contains your Application ID (shown as "ApplicationIdentifier" in the output from "New-FederationTrust", or by entering "Get-FederationTrust") and enter it in the form "AppID=<yourappid>".
If you use Windows DNS, it goes a little something like this:
Once those are both in, reload/HUP your DNS and make sure you can resolve it, both from your Exchange servers and from the outside world. The following command should work:
nslookup -querytype=TXT ExchangeDelegation.rootuk.net
nslookup -querytype=TXT rootuk.net
Now we’ve got the Federated Trust and DNS record sorted, we can get ourselves properly setup with the Federated Gateway. To do this, we first need to add the accepted domain of the Federated namespace:
New-AcceptedDomain -Name 'Federated Namespace' -DomainName 'ExchangeDelegation.rootuk.net' -DomainType 'Authoritative'
Next, we first use the Set-FederatedOrganizationIdentifier command to configure the account namespace:
Set-FederatedOrganizationIdentifier -DelegationFederationTrust 'Microsoft Federation Gateway' -AccountNamespace ExchangeDelegation.rootuk.net -Enabled:$true
Finally, add the main domain name of the On Premises domain:
Add-FederatedDomain -DomainName 'rootuk.net'
Assuming that completes successfully, we can move onto the next step..
Step Two– Setup the On-Premise Organization Relationship
The next step is to allow our Outlook Live domain to see our On-premise Free/Busy info. You can do this via Powershell or the Exchange Management Console:
Via Powershell:
Test that you can get the federation info for your Outlook Live domain. If you get an error, run the command again with the -Verbose parameter for a detailed error and check your autodiscover setup for Outlook Live:
Get-FederationInformation test.rootuk.net
Next, configure the relationship. The LimitedDetails option below is the most open setting for the relationship, which means any user who has chosen to show that much detail will also make it available to the Outlook Live tenant.
Get-FederationInformation test.rootuk.net | New-OrganizationRelationship -Name "Outlook Live" -FreeBusyAccessEnabled:$true -FreeBusyAccessLevel:LimitedDetails
If you'd rather configure via Exchange Management Console:
Navigate to Organization Configuration, and select the Organization Relationships Tab. Right click in the whitespace and choose “New Organization Relationship”:
in the New Organization Relationship window, Give a the new relationship a friendly name (e.g. Outlook Live). Select the checkbok "Enable this organization relationship", then choose to enable free/busy access. Choose “Free/busy access with time, plus subject and location” to select the widest access. This means any user who has chosen to show that much detail (i.e. people can see that on-premise right now) will also share it to Outlook Live.
Next enter the Outlook Live domain in the Automatically discover configuration information text box, and press Next.
Check for any errors, and press Finish. If there are errors, and it isn't a typo, then it’s likely to be an Autodiscover issue - but you'll find out most detail on the possible cause if you use the Powershell instructions above.
The new relationship should now be listed underneath Organization Relationships:
Step 3 – Setup the Sharing Policy
To enable our on-premise users to share their information with Outlook Live users we need a sharing policy setup. While you can setup multiple sharing policies and assign them to different users, for this basic sharing we’re going to modify the default sharing policy
Via Powershell:
First, check your settings. Note down the existing Domains value.
Get-SharingPolicy 'Default Sharing Policy'
Replace the domains value on the Default Sharing Policy setting the original value (or remove it, if you want) and adding the Outlook Live domain. For the Outlook Live domain we're setting the maximum scope for sharing:
Set-SharingPolicy 'Default Sharing Policy' –Domains '*:CalendarSharingFreeBusySimple', 'rootuk.net:CalendarSharingFreeBusyReviewer, ContactsSharing'
If you'd prefer to configure the sharing policy via the Exchange Management Console:
Navigate to Organization Configuration > Mailbox > Sharing Policies, then right click the Default Sharing Policy, then choose Properties:
In the Default Sharing Policy Properties, choose “Add”, then specify the Outlook Live domain. To allow for the maxium level of detail to be shared by individual users, select the last option “Calendar sharing with free/busy information plus subject, location and body, Contacts sharing:
And that’s it for On-Premise. Next.. We need to get the hosted environment setup with with the corresponding parameters:
Outlook Live Setup
For the Outlook Live setup you have to use Powershell – Exchange Management Shell isn’t supported when managing your hosted tenant. If you’re not sure how to connect Powershell see the guide on the Outlook.com help site, but it basically looks a little like this:
Step 1: Setup the Outlook Live Organization Relationship
We get to miss out the first step that we had to do on-premise, setup of the federation trust, because as a tenant of the outlook.com Exchange environment this has already in place.. So we can skip to getting an organization relationship setup with our on-premise domain.
First, we check that we can indeed discover the correct settings by using the Get-FederationInformation command, specifying the on-premise domain:
Get-FederationInformation rootuk.net
The output from the command should show details matching the Organization Identifier and Application URI we setup earlier, along with the details of the on-premise Autodiscover environment.
If it returns a failure you may need to double check your Autodiscover settings for the on-premise environment, or in some cases you may need to give it a little while (if you just setup your Federation Trust on-premise) and try again. For example, in my production environment it took around an hour for my production Outlook Live tenant to be able to discover the Federation Information although my test domains could see the same information without any issues.
So.. After you’ve tested Outlook Live can discover the Federation Information, it’s onto actually creating the Organization Relationship. The command is exactly the same as on-premise,we are really just swapping the domain:
Get-FederationInformation rootuk.net | New-OrganizationRelationship -Name "On-Premise" -FreeBusyAccessEnabled:$true -FreeBusyAccessLevel:LimitedDetails
Step 2: Setup the sharing policy
Finally, it’s on to the final step – getting the sharing policy sorted. Check out what the Default Sharing Policy currently is before we go and add our on-premise domain:
Get-SharingPolicy
Then replace the Domains list for the Default Sharing Policy with a value that includes the on-premise domain. In the example below, I’m keeping the settings that were already there and adding the on-premise domain. For consistency, I'm also keeping the same sharing level as set on-premise:
Set-SharingPolicy 'Default Sharing Policy' -Domains '*:CalendarSharingFreeBusySimple', 'rootuk.net:CalendarSharingFreeBusyReviewer, ContactsSharing'
After that, it should all be done. Give it a little bit of time (say, 15 minutes) for everything to take effect and we should be ready to have a play..
Testing it out
For testing purposes on my two domains, on-premise “rootuk.net” and Outlook Live “test.rootuk.net”, I’ve created test accounts in each –“onpremise@rootuk.net” and “outlooklive@test.rootuk.net”. For the screenshots I’ve used OWA, but it works equally well in Outlook.
The first test is for Free/Busy. I’ve created a few test appointments in both calendars and you’ll see availability works just as if the user was on-premise:
In Outlook Live, scheduling a new meeting with the on-premise user as an attendee:
Next, via on-premise Exchange 2010 SP1, with our Outlook Live user as an attendee this time:
The second test is to check it is possible to share a calendar either way, and this is where it strays slightly away from the full range of options available to users within a single environment.
The limitations we’ve got mean that the user must share the calendar using OWA or Outlook 2010, and must use the “share this calendar” options rather than setting permissions directly. The recipient of the permission must add the calendar using the resulting email (they can’t just add it by name) and it’s read-only. this of course means the process can’t be easily automated, but it does allow users to use a common workflow to share folders, via the “share” options.
Sharing a calendar in Outlook Live:
On-premise user receives the sharing invitation and chooses “Add this calendar” to add it:
After adding the calendar, it’s shown in red (in OWA, anyway) while the server retrieves the calendar:
After a minute or so, the Outlook Live calendar shows up alongside the On-premise calendar:
Finally, let's test it from On-premise to Outlook Live:
Select the Calendar’s “Share” option to share the calendar (it's worth noting that the OWA UI has changed between RTM and SP1.. but I digress):
Over in Outlook Live, the Outlook Live user receives the invitation and again, chooses the “Add this calendar” link:
After choosing to add the calendar, the on-premise calendar shows (again after a minute or so to do the first sync) alongside the Outlook Live calendar:
Contacts sharing is again a similar process, although it must be shared using Outlook 2010. Simply right click a contacts folder and choose Share>Share Contacts:
And that’s it – hopefully this will work for you, but if you have any questions or any issues, just use the comments form below.
Update - 10th November 2010: The original article used the -MetaDataURL parameter to use a self-signed certificate with the old "consumer" gateway. This is no longer valid. The article has been updated to reflect the new process, which requires an acceptable third-party certificate and the -LegacyProvisioningService parameter. If you have used the -MetaDataURL parameter to setup Federated Sharing you should remove and re-create both the organizational relationship (both sides) and the federation trust (on premises) using the updated information in "Step 1" of the On Premises Config. You do not need to remove/re-create the sharing policy.
Update - 15th December 2010: The article has been updated to use ExchangeDelegation.domain.com for the Federated Namespace based on advise from the Exchange team. This is a best practise and pre-req for cross-premises Free/Busy.
Using Powershell to import contacts into Exchange and Outlook Live
When performing migrations between different systems, there’s always the case where the tools available don’t do the job out of the box – and although IMAP migration tools for Exchange and Outlook Live can be great for moving mail, there isn’t a decent free solution for importing contacts.
For a recent migration from a Unix system (Dovecot + SquirrelMail) to Outlook Live, I came across this very scenario. While Microsoft provide IMAP migration tools to move mail to Outlook Live (which I was lucky enough to beta test before it was widely available), no tools are provided to move other data such as contacts and calendars.
While this isn’t the actual code I used (I wrote my original code in C#), I’ve re-visited what I’ve done and listened to what others say they need. What I’ve heard is it would be useful to have some Powershell code that out-of-the box can import contacts into any Exchange mailbox. The reason that Powershell is the right language for code like this is that it’s fairly easy for the enterprising administrator to modify to their needs.
Getting started
Before you can run this script, there are a few things you need to set up first. Don’t worry though! It’s nothing too onerous – all is needed to get going is two things:
- Setup of EWS impersonation, unless you only want to work with accounts you know the username/password for.
- Installing the Managed API DLL onto your computer.
Setup of Exchange Web Services Impersonation
Exchange Web Services is the programmatic interface to each user’s Exchange mailbox. Most actions that can be performed in Outlook can be performed via EWS – so much so, in fact, that Apple’s Mail.app in Snow Leopard and the latest update for Entourage 2008 use EWS as the backend for the mail clients themselves.
If you’re planning on doing a mass-import of contacts, or don’t know the user’s password you’re importing, then setup of EWS impersonation is the step you need to take to allow a trusted account to switch to the user you want to import.
In Exchange 2010, setup of Exchange impersonation is managed via RBAC. Full details of how to setup impersonal are on the Microsoft site, but if you just want to get it setup for a single admin/service account, org-wide, use the following command substituing serviceaccount for your service account:
New-ManagementRoleAssignment -Name:impersonationAssignmentName -Role:ApplicationImpersonation -User:serviceaccount
On Exchange 2007, it’s very different, and is managed by AD permissions on individual Client Access Servers. Again – full details on the Microsoft site, but to add the permission to all CAS servers, the following code will suffice, again substituting serviceaccount for your Service account:
Get-ExchangeServer | where {$_.IsClientAccessServer -eq $TRUE} | ForEach-Object {Add-ADPermission -Identity $_.distinguishedname -User (Get-User -Identity serviceaccount| select-object).identity -extendedRight ms-Exch-EPI-Impersonation}
Installing the Exchange Web Services Managed API
Next, whatever environment you are in, you need a copy of the DLL file that provides the bits and pieces that make this all work. The easiest way to get up and running is to download the API (choosing the right version for your platform), and install it to it’s default location.
Download the EWS Managed API v1.2 from the Microsoft site
Once downloaded and installed, you should be good to go with script.
Using the script Import-MailboxContacts.ps1
So, you’ve got impersonation setup if needed, got the EWS DLL on your machine. You should be good to go!
For each mailbox you want to import contacts from you need a separate CSV file. The format of the file is intentionally the same as Outlook will export in, mainly because a lot of apps already export in the format (Gmail does, for example) so you should find it easy to get test data, but if you haven’t I’ve included a sample file to get you started.
Along with the data, you also need, at a minimum, the mailbox email address to import to. This is used when impersonating, and for Autodiscover. If you’re not logged on as the user you want to use for the import, you can specify a username and password. Additionally, there’s a variety of options to specify the EWS Url manually, switching to the Exchange 2007 version of the API etc.
Examples
The first example is a straightforward import into the current logged-on user’s mailbox. We’re specifying the Email Address and the URL to EWS (to bypass Autodiscover):
.\Import-MailboxContacts.ps1 -CSVFileName .\Contacts.csv -EmailAddress steve@goodman.net -EwsUrl https://server/EWS/Exchange.asmx
As you can see in the above example – it’s all fairly straightforward. You’ll also see the output shows the contacts that have been created – you can use standard Powershell pipeling to export this data, pipe it to another command or filter it.
The second example is slightly more complicated, and reflects the main use case. We’re connecting to a remote exchange organisation, using Autodiscover to find the EWS address, enabling impersonation and providing credentials at the command line:
.\Import-MailboxContacts.ps1 -CSVFileName .\Contacts.csv –EmailAddress steve@contoso.com -Impersonate $true -Username serviceaccount -Password password -Domain contoso
In addition to the options above, you can also specify the parameters -Exchange2007 $true and use -EWSManagedApiDLLFilePath to specify a different path the the EWS DLL.
Powershell Code
Finally, you’ll need a copy of the code to do all this. Below you’ll find a ZIP file containing the Import-MailboxContacts.ps1 file and a sample CSV file. Happy importing!
Using the Exchange 2010 SP1 and SP2 Mailbox Export features for Mass Exports to PST files
In Exchange 2007 SP1 thru to Exchange 2010 RTM, the Export-Mailbox command was the replacement for the once-familiar ExMerge utility when it came to exporting mailboxes to PST files.
The main problem with Export-Mailbox for most Exchange administrators is the requirement for Outlook – either on a 32-bit machine with Management Tools for Exchange 2007, or on a 64-bit machine for Exchange 2010. All in all, it wasn’t ideal and certainly didn’t facilitate scripted mailbox exports.
Thankfully, with Exchange 2010 SP1, SP2 and above, Export-Mailbox is going the way of the dodo and new cmdlets for Mailbox imports and exports are available. Just like the New-MoveRequest cmdlet, the new import/export command use the Mailbox Replication Service to perform the move via one of the Client Access Servers giving performance benefits, such as ensuring the PST transfer doesn’t have to go via the machine with the Exchange Management Tools/Outlook installed, as was the case previously.
The main aim of this post is to give you an overview of how to use the new mailbox export cmdlets, and then show you how to put them to practical use, both at the command line and with a scheduled task for brick-level backups.
Getting it set up
The basic requirements for using the new feature are pretty straightforward. You need to use an account that’s a member of the organisational management groups, and have the “Mailbox Import Export” role assignment assigned to you or a role group you’re a member of. As the export is done at a CAS server (and if you’ve multiple CAS servers you can’t specify which one in each site will be used) you can’t specify a local drive letter and path – you must specify a UNC path to a network share that the “Exchange Trusted Subsystem” group has read/write access to.
Step One
Create a share on a server, and grant Exchange Trusted Subsystem read/write permission. In this example I’m using a share called Exports on a test server called Azua in my lab environment:
Step Two
Next, you’ll need to grant a user, or group, the Mailbox Import Export role assignment. You can do this using the Exchange Management shell with a single command. In this example, I’m granting my lab domain’s Administrator user the role assignment:
New-ManagementRoleAssignment –Role “Mailbox Import Export” –User AD\Administrator
After you’ve done this, close and re-open the Exchange Management shell, and you’re ready to go!
Exporting a Mailbox
At it’s simplest, use the New-MailboxExportRequest command with the –Mailbox parameter, to specify the mailbox to export along with the –FilePath parameter, to specify the PST file to create and export data to, e.g:
New-MailboxExportRequest -Mailbox Administrator -FilePath "\\AZUA\Exports\Administrator.pst"
In addition, there are some other useful options – such as –BatchName, which allows grouping of requests together, and –ContentFilter, which allows only certain content to be exported to the PST – useful for discovery purposes. As usual, use the Get-Help New-MailboxExportRequest –detailed command to review the full plethora of options.
After submission of your requests, you can check progress, including the percentage complete, with the two Get-MailboxExportRequest and the Get-MailboxExportRequestStatistics commands. Pipe the former into the latter to get a listing:
Get-MailboxExportRequest | Get-MailboxExportRequestStatistics
After the requests complete, you can remove the requests in a similar fashion, using the Remove-MailboxExportRequest command:
![]()
Get-MailboxExportRequest | Remove-MailboxExportRequest
Performing mass exports
One benefit of Powershell is it’s very easy to put together commands enabling mass-exports of PST data with only a few commands. If you really wanted to, you could even use a Powershell script as a secondary brick-level backup!
The Basics
So to check out how to do this, let’s look at it’s simplest – backing up all the mailboxes (assuming it’s a full Exchange 2010 environment) to a single share:
![]()
foreach ($i in (Get-Mailbox)) { New-MailboxExportRequest -Mailbox $i -FilePath "\\AZUA\Exports\$($i.Alias).pst" }
In the above example, we’re simply performing a for-each loop through each mailbox and creating a new Mailbox Export Request, using the alias to build the name for the PST.
But – what if we’re in a mixed environment, and only want to target the Exchange 2010 mailboxes?
![]()
foreach ($i in (Get-Mailbox | Where {$_.ExchangeVersion.ExchangeBuild.Major -eq 14})) { New-MailboxExportRequest -Mailbox $i -FilePath "\\AZUA\Exports\${$i.Alias).pst" }
In this example above, now, we’ve added a clause to only select the mailboxes where the Exchange Major Build is 14 – Exchange 2010. Simple!
Moving on from such wide-targeting, you may want to target just a pre-defined list, using a CSV file. To do this, simply create a CSV file with the column “Alias”, and list the Mailbox alias fields you wish to export. Then, using the Import-CSV command we can use this CSV file to create the requests:
![]()
foreach ($i in (Import-Csv .\exports.csv)) { New-MailboxExportRequest -Mailbox $i.Alias -FilePath "\\AZUA\Exports\$($i.Alias).pst" }
Performing Mass Exports as a scheduled task
Now you’ve seen the basics of how easy it is to perform mass mailbox exports using the New-MailboxExportRequest command, I’ll finish off with a final example showing how to use this mass export feature as part of a scheduled task.
This script is aimed at backing up all the mailboxes on a single database, or a single server. After creating the requests, it waits for the requests to complete then, if you’ve specified a report directory, it will write reports showing completed and incomplete (i.e. failed!) requests. Finally it removes the requests it created.
To use the script, you need to alter the config section and specify either a server or a database, a share to export to, a share to write a report to after the process has completed and you can choose whether to remove each mailbox’s associated PST file or leave as-is at each export – merging the contents.
You’ll see the content of the script below and at the bottom of the post I’ve zipped it up along with a bootstrap CMD file you could use when setting up a schedule task. As always – use at your own risk and test out in your lab environment first. Happy Exporting!
# Exchange 2010 SP1 Mailbox Export Script
# Steve Goodman. Use at your own risk!
###############
# Settings #
###############
# Pick ONE of the two below. If you choose both, it will use $Server.
$Server = "server"
$Database = ""
# Share to export mailboxes to. Needs R/W by Exchange Trusted Subsystem
# Must be a UNC path as this is run by the CAS MRS service.
$ExportShare = "\\server\share"
# After each run a report of the exports can be dropped into the directory specified below. (The user that runs this script needs access to this share)
# Must be a UNC path or the full path of a local directory.
$ReportShare = "\\server\share"
# Shall we remove the PST file, if it exists beforehand? (The user that runs this script needs access to the $ExportShare share)
# Valid values: $true or $false
$RemovePSTBeforeExport = $false
###############
# Code #
###############
if ($Server)
{
if (!(Get-ExchangeServer $Server -ErrorAction SilentlyContinue))
{
throw "Exchange Server $Server not found";
}
if (!(Get-MailboxDatabase -Server $Server -ErrorAction SilentlyContinue))
{
throw "Exchange Server $Server does not have mailbox databases";
}
$Mailboxes = Get-Mailbox -Server $Server -ResultSize Unlimited
} elseif ($Database) {
if (!(Get-MailboxDatabase $Database -ErrorAction SilentlyContinue))
{
throw "Mailbox database $Database not found"
}
$Mailboxes = Get-Mailbox -Database $Database
}
if (!$Mailboxes)
{
throw "No mailboxes found on $Server"
}
if (!$Mailboxes.Count)
{
throw "This script does not support a single mailbox export."
}
# Pre-checks done
# Make batch name
$date=Get-Date
$BatchName = "Export_$($date.Year)-$($date.Month)-$($date.Day)_$($date.Hour)-$($date.Minute)-$($date.Second)"
Write-Output "Queuing $($Mailboxes.Count) mailboxes as batch '$($BatchName)'"
# Queue all mailbox export requests
foreach ($Mailbox in $Mailboxes)
{
if ($RemovePSTBeforeExport -eq $true -and (Get-Item "$($ExportShare)\$($Mailbox.Alias).PST" -ErrorAction SilentlyContinue))
{
Remove-Item "$($ExportShare)\$($Mailbox.Alias).PST" -Confirm:$false
}
New-MailboxExportRequest -BatchName $BatchName -Mailbox $Mailbox.Alias -FilePath "$($ExportShare)\$($Mailbox.Alias).PST"
}
Write-Output "Waiting for batch to complete"
# Wait for mailbox export requests to complete
while ((Get-MailboxExportRequest -BatchName $BatchName | Where {$_.Status -eq "Queued" -or $_.Status -eq "InProgress"}))
{
sleep 60
}
# Write reports if required
if ($ReportShare)
{
Write-Output "Writing reports to $($ReportShare)"
$Completed = Get-MailboxExportRequest -BatchName $BatchName | Where {$_.Status -eq "Completed"} | Get-MailboxExportRequestStatistics | Format-List
if ($Completed)
{
$Completed | Out-File -FilePath "$($ReportShare)\$($BatchName)_Completed.txt"
}
$Incomplete = Get-MailboxExportRequest -BatchName $BatchName | Where {$_.Status -ne "Completed"} | Get-MailboxExportRequestStatistics | Format-List
if ($Incomplete)
{
$Incomplete | Out-File -FilePath "$($ReportShare)\$($BatchName)_Incomplete_Report.txt"
}
}
# Remove Requests
Write-Output "Removing requests created as part of batch '$($BatchName)'"
Get-MailboxExportRequest -BatchName $BatchName | Remove-MailboxExportRequest -Confirm:$false
Command file contents:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -command ". 'c:\Program Files\Microsoft\Exchange Server\V14\bin\RemoteExchange.ps1'; Connect-ExchangeServer -auto; .\MassExport.ps1"
New OWA themes available in Exchange 2010 SP1
In my earlier post about the Outlook Web App improvements in SP1 you’d have seen than in Exchange 2010 SP1, themes are making a welcome return.
If you don’t have the resources and time to install the SP1 beta yourself, or just want a quick look at all the new themes that will be included in the upcoming service pack, this quick post is for you!
Default
Mixxer
Botanical
One World
Super Sparkle Happy
It Came From Space
Autumn Blade
Herding Cats
Finger Paints
Damask
Arctic
Cupcake
Blibbet
Gothitech
Winterland
Pink
Blue
Green
Voilet
Grey




