Steve Goodman's Tech Blog – The weblog of an IT pro specialising in Exchange, Exchange, VMware, Servers and Storage
25Jul/100

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:

  1. Setup of EWS  impersonation, unless you only want to work with accounts you know the username/password for.
  2. 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 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):

image

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

image

.\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. At the bottom of the post you’ll find a ZIP file containing the Import-MailboxContacts.ps1 file and a sample CSV file, and below you’ll see the code itself. Happy importing!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
param([string]$CSVFileName,[string]$EmailAddress,[string]$Username,[string]$Password,[string]$Domain,[bool]$Impersonate,[string]$EwsUrl,[string]$EWSManagedApiDLLFilePath,[bool]$Exchange2007);

#
# Import-MailboxContacts.ps1
#
# By Steve Goodman, Use at your own risk.
#
# Parameters
#  Mandatory:
# -CSVFileName : Filename of the CSV file to import contacts for this user from. Same format as Outlook Export.
# -EmailAddress : Account SMTP email address. Required, but only used when impersonating or with Autodiscover - otherwise uses the user you login as
#  Optional:
# -Impersonate : Set to $true to use impersonation.
# -Username : The username to use. If this isn't specified (along with Password), attempts to use the logged on user.
# -Password : Used with above
# -Domain : Used with above - optional.
# -EwsUrl : The URL for EWS if you don't want to use Autodiscover. Typically https://casserver/EWS/Exchange.asmx
# -EWSManagedApiDLLFilePath : (Optional) Overwrite the filename and path to the DLL for EWS Managed API. By default, uses the default install location.
# -Exchange2007 : Set to $true to use the Exchange 2007 SP1+ version of the Managed API.
#

# Contact Mapping - this maps the attributes in the CSV file (left) to the attributes EWS uses.
# NB: If you change these, please note "First Name" is specified at line 102 as a required attribute and
# "First Name" and "Last Name" are hard coded at lines 187-197 when constructing NickName and FileAs.
$ContactMapping=@{
    "First Name" = "GivenName";
    "Middle Name" = "MiddleName";
    "Last Name" = "Surname";
    "Company" = "CompanyName";
    "Department" = "Department";
    "Job Title" = "JobTitle";
    "Business Street" = "Address:Business:Street";
    "Business City" = "Address:Business:City";
    "Business State" = "Address:Business:State";
    "Business Postal Code" = "Address:Business:PostalCode";
    "Business Country/Region" = "Address:Business:CountryOrRegion";
    "Home Street" = "Address:Home:Street";
    "Home City" = "Address:Home:City";
    "Home State" = "Address:Home:State";
    "Home Postal Code" = "Other:Home:PostalCode";
    "Home Country/Region" = "Address:Home:CountryOrRegion";
    "Other Street" = "Address:Other:Street";
    "Other City" = "Address:Other:City";
    "Other State" = "Address:Other:State";
    "Other Postal Code" = "Address:Other:PostalCode";
    "Other Country/Region" = "Address:Other:CountryOrRegion";
    "Assistant's Phone" = "Phone:AssistantPhone";
    "Business Fax" = "Phone:BusinessFax";
    "Business Phone" = "Phone:BusinessPhone";
    "Business Phone 2" = "Phone:BusinessPhone2";
    "Callback" = "Phone:CallBack";
    "Car Phone" = "Phone:CarPhone";
    "Company Main Phone" = "Phone:CompanyMainPhone";
    "Home Fax" = "Phone:HomeFax";
    "Home Phone" = "Phone:HomePhone";
    "Home Phone 2" = "Phone:HomePhone2";
    "ISDN" = "Phone:ISDN";
    "Mobile Phone" = "Phone:MobilePhone";
    "Other Fax" = "Phone:OtherFax";
    "Other Phone" = "Phone:OtherTelephone";
    "Pager" = "Phone:Pager";
    "Primary Phone" = "Phone:PrimaryPhone";
    "Radio Phone" = "Phone:RadioPhone";
    "TTY/TDD Phone" = "Phone:TtyTddPhone";
    "Telex" = "Phone:Telex";
    "Anniversary" = "WeddingAnniversary";
    "Birthday" = "Birthday";
    "E-mail Address" = "Email:EmailAddress1";
    "E-mail 2 Address" = "Email:EmailAddress2";
    "E-mail 3 Address" = "Email:EmailAddress3";
    "Initials" = "Initials";
    "Office Location" = "OfficeLocation";
    "Manager's Name" = "Manager";
    "Mileage" = "Mileage";
    "Notes" = "Body";
    "Profession" = "Profession";
    "Spouse" = "SpouseName";
    "Web Page" = "BusinessHomePage";
    "Contact Picture File" = "Method:SetContactPicture"
}

# CSV File Checks
# Check filename is specified
if (!$CSVFileName)
{
    throw "Parameter CSVFileName must be specified";
}

# Check file exists
if (!(Get-Item -Path $CSVFileName -ErrorAction SilentlyContinue))
{
    throw "Please provide a valid filename for parameter CSVFileName";
}

# Check file has required fields and check if is a single row, or multiple rows
$SingleItem = $false;
$CSVFile = Import-Csv -Path $CSVFileName;
if ($CSVFile."First Name")
{
    $SingleItem = $true;
} else {
    if (!$CSVFile[0]."First Name")
    {
        throw "File $($CSVFileName) must specify at least the field 'First Name'";
    }
}

# Check email address
if (!$EmailAddress)
{
    throw "Parameter EmailAddress must be specified";
}
if (!$EmailAddress.Contains("@"))
{
    throw "Parameter EmailAddress does not appear valid";
}

# Check EWS Managed API available
if (!$EWSManagedApiDLLFilePath)
{
    $EWSManagedApiDLLFilePath = "C:\Program Files\Microsoft\Exchange\Web Services\1.0\Microsoft.Exchange.WebServices.dll"
}
if (!(Get-Item -Path $EWSManagedApiDLLFilePath -ErrorAction SilentlyContinue))
{
    throw "EWS Managed API not found at $($EWSManagedApiDLLFilePath). Download from http://bit.ly/9O7gLo";
}

# Load EWS Managed API
[void][Reflection.Assembly]::LoadFile("C:\Program Files\Microsoft\Exchange\Web Services\1.0\Microsoft.Exchange.WebServices.dll");

# Create Service Object
if ($Exchange2007)
{
    $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1)
} else {
    $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010)
}
# Set credentials if specified, or use logged on user.
if ($Username -and $Password)
{
    if ($Domain)
    {
        $service.Credentials = New-Object  Microsoft.Exchange.WebServices.Data.WebCredentials($Username,$Password,$Domain);
    } else {
        $service.Credentials = New-Object  Microsoft.Exchange.WebServices.Data.WebCredentials($Username,$Password);
    }
   
} else {
    $service.UseDefaultCredentials = $true;
}


# Set EWS URL if specified, or use autodiscover if no URL specified.
if ($EwsUrl)
{
    $service.URL = New-Object Uri($EwsUrl);
} else {
    try {
        $service.AutodiscoverUrl($EmailAddress);
    } catch {
        throw;
    }
}

# Perform a test - try and get the default, well known contacts folder.

if ($Impersonate)
{
    $service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
}
try {
    $ContactsFolder = [Microsoft.Exchange.WebServices.Data.ContactsFolder]::Bind($service, [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Contacts);
} catch {
    throw;
}

# Add contacts
foreach ($ContactItem in $CSVFile)
{
    # If impersonate is specified, do so.
    if ($Impersonate)
    {
        $service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
    }

    $ExchangeContact = New-Object Microsoft.Exchange.WebServices.Data.Contact($service);
    if ($ContactItem."First Name" -and $ContactItem."Last Name")
    {
        $ExchangeContact.NickName = $ContactItem."First Name" + " " + $ContactItem."Last Name";
    }
    elseif ($ContactItem."First Name" -and !$ContactItem."Last Name")
    {
        $ExchangeContact.NickName = $ContactItem."First Name";
    }
    elseif (!$ContactItem."First Name" -and $ContactItem."Last Name")
    {
        $ExchangeContact.NickName = $ContactItem."Last Name";
    }
    $ExchangeContact.DisplayName = $ExchangeContact.NickName;
    $ExchangeContact.FileAs = $ExchangeContact.NickName;
   
    $BusinessPhysicalAddressEntry = New-Object Microsoft.Exchange.WebServices.Data.PhysicalAddressEntry;
    $HomePhysicalAddressEntry = New-Object Microsoft.Exchange.WebServices.Data.PhysicalAddressEntry;
    $OtherPhysicalAddressEntry = New-Object Microsoft.Exchange.WebServices.Data.PhysicalAddressEntry;
   
    # This uses the Contact Mapping above to save coding each and every field, one by one. Instead we look for a mapping and perform an action on
    # what maps across. As some methods need more "code" a fake multi-dimensional array (seperated by :'s) is used where needed.
    foreach ($Key in $ContactMapping.Keys)
    {
        # Only do something if the key exists
        if ($ContactItem.$Key)
        {
            # Will this call a more complicated mapping?
            if ($ContactMapping[$Key] -like "*:*")
            {
                # Make an array using the : to split items.
                $MappingArray = $ContactMapping[$Key].Split(":")
                # Do action
                switch ($MappingArray[0])
                {
                    "Email"
                    {
                        $ExchangeContact.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::($MappingArray[1])] = $ContactItem.$Key;
                    }
                    "Phone"
                    {
                        $ExchangeContact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::($MappingArray[1])] = $ContactItem.$Key;
                    }
                    "Address"
                    {
                        switch ($MappingArray[1])
                        {
                            "Business"
                            {
                                $BusinessPhysicalAddressEntry.($MappingArray[2]) = $ContactItem.$Key;
                                $ExchangeContact.PhysicalAddresses[[Microsoft.Exchange.WebServices.Data.PhysicalAddressKey]::($MappingArray[1])] = $BusinessPhysicalAddressEntry;
                            }
                            "Home"
                            {
                                $HomePhysicalAddressEntry.($MappingArray[2]) = $ContactItem.$Key;
                                $ExchangeContact.PhysicalAddresses[[Microsoft.Exchange.WebServices.Data.PhysicalAddressKey]::($MappingArray[1])] = $HomePhysicalAddressEntry;
                            }
                            "Other"
                            {
                                $OtherPhysicalAddressEntry.($MappingArray[2]) = $ContactItem.$Key;
                                $ExchangeContact.PhysicalAddresses[[Microsoft.Exchange.WebServices.Data.PhysicalAddressKey]::($MappingArray[1])] = $OtherPhysicalAddressEntry;
                            }
                        }
                    }
                    "Method"
                    {
                        switch ($MappingArray[1])
                        {
                            "SetContactPicture"
                            {
                                if (!$Exchange2007)
                                {
                                    if (!(Get-Item -Path $ContactItem.$Key -ErrorAction SilentlyContinue))
                                    {
                                        throw "Contact Picture File not found at $($ContactItem.$Key)";
                                    }
                                    $ExchangeContact.SetContactPicture($ContactItem.$Key);
                                }
                            }
                        }
                    }
               
                }                
            } else {
                # It's a direct mapping - simple!
                if ($ContactMapping[$Key] -eq "Birthday" -or $ContactMapping[$Key] -eq "WeddingAnniversary")
                {
                    [System.DateTime]$ContactItem.$Key = Get-Date($ContactItem.$Key);
                }
                $ExchangeContact.($ContactMapping[$Key]) = $ContactItem.$Key;            
            }
           
        }    
    }
    # Save the contact    
    $ExchangeContact.Save();
   
    # Provide output that can be used on the pipeline
    $Output_Object = New-Object Object;
    $Output_Object | Add-Member NoteProperty FileAs $ExchangeContact.FileAs;
    $Output_Object | Add-Member NoteProperty GivenName $ExchangeContact.GivenName;
    $Output_Object | Add-Member NoteProperty Surname $ExchangeContact.Surname;
    $Output_Object | Add-Member NoteProperty EmailAddress1 $ExchangeContact.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress1]
    $Output_Object;
}

Download as ZIP

11Mar/100

Checking for unmanaged Live IDs in Outlook Live

This isn’t a post for everyone – it’s aimed at Administrators of a Live@EDU / Outlook Live environment who are looking to find existing unmanaged EASI IDs / Windows Live IDs on the domain they’ve brought onto Outlook Live.

If you’re using automated provisioning you’ll find this can be a problem as when you use the New-Mailbox cmdlet it will fail, and you’ll have to re-run it with either the –ImportLiveID or –EvictLiveID options. This script allows you to get a CSV input file, with the column Username and generate a list of the status of the Live IDs so you can embark on your batch creation knowing where you stand.

Usage: .\Check-Unmanaged.ps1 -AdminUsername admin@uni.edu -AdminPassword Pa$$w0rd -InputFile .\input.csv -OutputFile .\output.csv

The -AdminUsername is typically you’re Outlook Live admin. It needs to be able to connect to the Windows Live Admin web service .-OutputFile is optional - results are printed to the screen also. For each user in your domains you will see either InUse for managed IDs, InUseUnmanaged for IDs you need to evict/import to use and Available for IDs not in use.

The included DLL,  ManageDomain2.dll is required. This is auto-generated from the WSDL. If you wish to generate your own you need Visual Studio 2008 or similar. See the source of the ps1 file for the two-lines you need to re-generate the DLL - it's just a proxy web service library.

(NB. No warranty is provided with this script, it is provided as-is and any damage it may do is your responsibility. Please use on your own test environment first. That said - apart from connecting to the service, the main method used is GetMemberNameState)

Download CheckUnmanaged.zip

19Feb/101

Writing Powershell scripts that target Exchange 2007 and 2010

If you’re planning a migration to Exchange 2010 that won’t be completed overnight, you may want to be able to run Exchange Powershell scripts that execute both Exchange 2007 and 2010 cmdlets from the same script. There’s a few reasons for doing this, including:

  • If you use logic to determine where to place new user mailboxes, and some mailboxes will be on 2007 and some on 2010.
  • If you create new mailboxes on Exchange 2007 and then add the new mailbox to a Exchange 2010 distribution group (for example, if you use moderated distribution groups).
  • If you’re using Outlook Live (Live@EDU hosted Exchange 2010) and create MailUsers on-premise and Mailboxes in the hosted environment.
  • You are using the Transporter suite to migrate mail and need to create mailboxes on a temporary Exchange 2007 server, migrate mail, and then move mailboxes to Exchange 2010.

OK – so my examples are mostly about provisioning and these are real-life examples of where I’ve used this method, but I’m sure you can think of other examples where it might be useful to you. So – what do you need?

  • Exchange 2007 SP2 management tools on any OS that supports them.
  • Powershell 2.0
  • Remote Powershell enabled on the account you run the script as (using Set-User username –RemotePowerShellEnabled:$true)

You can use this on Windows Server 2003 R2, x86; no Exchange 2010 management tools are required. Enough of what you need – here’s what you need to put in your Powershell scripts to do Exchange 2007/2010:

# Add Exchange 2007. If you'll always run from the Exchange Management Shell, you don't need the next line.
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.Admin
# Exchange 2007 Commands Go Here - i.e.
Get-MailboxDatabase
# Unload Exchange 2007
Remove-PSSnapin Microsoft.Exchange.Management.PowerShell.Admin
# Create Remote Powershell session with Exchange 2010 - edit the server name in ConnetionUri
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://exchange.contoso.com/powershell/ -Authentication Kerberos
Import-PSSession $Session
# Exchange 2010 Commands Go Here - i.e.
Get-MailboxDatabase
# Unload Exchange 2010
Remove-PSSession $Session

If you’re looking to connect to Outlook Live instead of Exchange 2010, simply replace the line beginning “$Session =” with a couple of lines similar to this:

$LiveCred = New-Object System.Management.Automation.PSCredential "yourliveadmin@contoso.edu", (ConvertTo-SecureString "password" -AsPlainText -Force)
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell/ -Credential $LiveCred -Authentication Basic -AllowRedirection -WarningAction SilentlyContinue

Hope this helps!

Steve

16Feb/104

Scripted Shared Mailbox Creation on Exchange 2007/2010

I thought I’d share one of the scripts we use for automating creation of Shared Mailboxes within our organisation. One of the more laborious parts of creating shared mailboxes after creation of the actual Mailbox, is setting the manager, department, and granting a (possibly long) list of users permissions and send-as rights.

This script is a simple Powershell script that takes some of the pain out of the process. The assumption is that you want to achieve the following:

  • Create a shared mailbox with a common alias, email address and username, checking input values before attempting to create the mailbox.
  • You would like to specify the Department at creation and use the Department AD attribute to generate Address Lists
  • You would like to attach a user as Manager for the Shared Mailbox so it is clear who manages it

You can use the script with Parameters – e.g:

.\New-SharedMailbox.ps1 -Department "Human Resources" -Alias hr_test -DisplayName "HR Test" -Manager managerusername -Usernames "username1,username2"

–Department is the text to use for the department attribute
–Alias is the Email Alias & Username
–DisplayName is the friendly textual name shown to users and recipients
–Manager is the username, UPN or email address of the Manager (who will have full access + send as rights along with being set as Manager)
–Usernames is a comma separated list of usernames of people who should also have full access and send-as rights.

Or - if that isn’t your thing, then you can just run the script without any options, and it will prompt you as you go along for the parameters. If you are using the Department attribute for Address List generation, you can hit enter when prompted for the department to get a list of in-use departments. As you go along values will be checked, to ensure the Alias and Display Name aren’t in use by Mailboxes or Mail Users and that the Manager and Usernames are attached to valid Mailboxes.

Before you get started with script it does need a little setup.

The setup is fairly straightforward, though. Open the script in a text editor, and change the values for $DomainController, $OU, $UPNDomain and $MailboxDatabase to values appropriate for your organisation. It goes without saying you should test this out on something not connected to your production environment. I won’t take any responsibility for any damage you do with this!

I’ve obviously taken some stuff out that is specific to our organisation (for example, we have a switch statement to determine the mailbox database and exchange server; and also generate Unix information) and if this almost does what you want let me know and I’ll be happy to help you customise it.

Download New-SharedMailbox.ps1

7Jan/101

Exchange – Bulk-add forwarding addresses

There's not many circumstances where you'd want to set forwarding on lots of mailboxes at once - but if you're in that situation (perhaps you're moving mailboxes to a Cloud mail provider)  it's useful to know that it's fairly straightforward and can be accomplished with a little PowerShell.

In the example below, we're assuming that your on-premise Exchange 2007/2010 domain is contoso.com and all your mailboxes are in the Staff OU.

The Cloud provider you're moving your mail to will eventually take over the MX record for your domain, but will also accept mail to each user's Username@cloud.contoso.com. We'll keep mail contacts used for the forwarding in the OU CloudContacts.

To accomplish this, we quite simply need to get the mailboxes in the specific OU, create a mail contact for each one (with the external email address they will use on the cloud provider's domain) and then set the forwarding address on the mailbox:

# Loop though the object returned by Get-Mailbox with each element represented by $mailbox
foreach ($mailbox in (Get-MailBox -ResultSize Unlimited  -OrganizationalUnit contoso.com/Staff)
{
# Create the forwarding address string
$ForwardingAddress= $mailbox.SamAccountName + "@cloud.contoso.com"
# Check there isn't a contact, then add one
If (!(Get-MailContact $ForwardingAddress -ErrorAction SilentlyContinue))
{
New-MailContact $ForwardingAddress-ExternalEmailAddress $ForwardingAddress- OrganizationalUnit contoso.com/CloudContacts
}
# Set the forwarding address
Set-Mailbox $mailbox -ForwardingAddress $ForwardingAddress
}

Of course, that's a fairly simple example.  What if the email addresses at the cloud provider don't match the Windows Logon ID or anything else that is an attribute of the Mailbox? Well the simple solution is to use something like a CSV file containing a mapping between an attribute on each Mailbox and the external email address for that user:

SamAccountName,ForwardingAddress
jamesw,jimbo@cloud.contoso.com
philipg,phil@cloud.contoso.com

In this example, we'll save that file as input.csv and use that as are input to the foreach loop. Again CloudContacts OU will contain our new mail contact objects:

# Loop through the object returned by Import-Csv with each element represented by $person
foreach ($person in (Import-Csv .\input.csv))
{
# Check the Mailbox for the person exists
If ((Get-Mailbox $person.SamAccountName))
{
# Check the mail contact doesn't exist and if not add it
If (!(Get-MailContact $person.ForwardingAddress))
{
New-MailContact $person.ForwardingAddress -OrganizationalUnit contoso.com/CloudContacts
}
# Set the Forwarding Address on the Mailbox
Set-Mailbox $person.SamAccountName -ForwardingAddress $person.ForwardingAddress
}
}

Word of caution - it's worth (as always) running these commands in your test environment first - and of course consider appending -WhatIf to the New-MailContact and Set-Mailbox commands to check they'll do what you want in your production environment.

Hope this helps,

Steve