I’ve been doing Exchange cross forest migrations now for about 15 years, since Exchange 5.0! With each major version of Exchange the procedures would changed a bit and the scripts would be updated or recreated as needed. Without 3rd party tools there are many steps that are not covered by the built-in cmdlet or scripts provided by Microsoft.
Microsoft include the Prepare-MoveRequest.ps1 script, starting with Exchange 2010 RTM, which is used to create Mail Enabled Users (MEU) in the target Exchange 2010/2013 environment. The script will create the MEU, copy the SMTP and x500 addresses (include the LegacyExchangeDN), GAL attributes, and a few others attributes. Then New-MoveRequest can be used to migrate the mailbox from Exchange 2003, 2007, 2010, or 2013 in another forest to Exchange 2010/2013. New-MoveRequest will create a mailbox for the user, replace the MEU with a Mailbox enabled user, and replace the Mailbox enabled user in the source environment with a Mail Enabled User. So the Prepare-MoveRequest script and the New-MoveRequest are all that’s normally needed to migrate mailboxes across forest.
But this does address Groups, Contacts, or Public Folders. All of which have properties, mainly SMTP & x500 addresses, that must be migrated also to ensure correct mail-flow and prevent NDRs when users reply to existing messages, meetings, or when using the Nickname cache.
So I’m going to start a series of blog post that will cover the key scripts I used, and created, to handle the other objects. I have several other scripts that I use to find and address many issues that come up when doing a cross forest migration and probably won’t go into all of these.
Export-Group.vbs – Yes this is a VBScript, but I wanted one script that will work when migrating from Exchange 2003, 2007, 2010, and 2013. So I created this script, actually I had a couple of other scripts I used to use before this one but this one has superseded them now.
This script will export SMTP, x500 (including LegacyExchangeDN), and group membership to two CSV files. It assumes the groups have already been created, which can be done via ADMT or via LDIFDE (which I’ve found quicker and easier than using ADMT in smaller environments). It does not export disabled members, since Prepare-MoveRequest doesn’t support disabled mailboxes either, but this can be easily changed.
It will generate two files: GroupAddressess.csv & GroupMembers.csv. GroupAddressess.csv will have one each line per SMTP or x500 address, in the format of: mailNickName, samAccountName, proxyAddress. GroupMembers.csv will have one line per member in the format of mailNickName, samAccountName, member mailnickname, and member primary e-mail address. I include samAccountName in the export file but don’t actually use it since some groups may not be security enabled or members by be other non-security groups or contacts.
Source: Export-Groups.vbs
Related Import scripts, to be covered in another blog post very soon:
Import-GroupMembers.ps1
Import-GroupAddresses.ps1
'
' This script will export the SMTP, x500, & LegancyExchangeDN address and members of all groups with e-mail addressess to GroupSMTP.csv & GroupMembers.csv
' Output file can be used to import members by http://izzy.org/scripts/Exchange/Migration/Import-GroupAddresses.ps1
' Output file can be used to addresses by http://izzy.org/scripts/Exchange/Migration/Import-GroupMembers.ps1
'
' Created 3/1/2012 by Jason Sherry (izzy@izzy.org) http://jasonsherry.org
' Source: http://izzy.org/scripts/Exchange/Migration/Export-Groups.vbs
' Last Updated: 8/29/2013
' 05/09/2012 Added legacyExchangeDN export support and fixed SMTP case issue
' 08/28/2013 Added support to export members (disabled users skipped), logging to file for WARNING & ERROR events
' 08/29/2013 Added x500 export support
Option Explicit
Dim strDomainDN, strBase, strFilter, strAttrs, strScope, rootDSE
Dim objCmd, objConn, objRS, objLogFile
Dim proxyaddresses, proxyaddress
Dim objAddressFile, objfs, GroupAddressFile, GroupMemberFile, objMemberFile
Dim name, mailNickName, samAccountName, legacyExchangeDN, adspath
Dim member, members, objMember, membermailNickname, membermail, membername, memberSamAccountName, userAccountControl
Const ADS_UF_ACCOUNTDISABLE = 2
GroupAddressFile= "GroupAddressess.csv"
GroupMemberFile= "GroupMembers.csv"
Set rootDSE = GetObject("LDAP://rootDSE")
'strDomainDN = "dc=company,dc=local" #Required if the script needs to get data from a domain other than the one it is being run in
strDomainDN = rootDSE.Get("defaultNamingContext")
strBase = "
strFilter = "(&(mailNickName=*)(proxyAddresses=*)(objectCategory=group));"
strAttrs = "name,adspath,mailNickName,proxyaddresses,legacyExchangeDN,samAccountName,member;"
strScope = "subtree"
Set objfs = CreateObject ("Scripting.FileSystemObject")
Set objAddressFile = objfs.CreateTextFile (GroupAddressFile, True)
Set objMemberFile = objfs.CreateTextFile (GroupMemberFile, True)
Set objLogFile = objfs.CreateTextFile ("Export-Groups.log", True)
Set objConn = CreateObject ("ADODB.Connection")
objConn.Provider = "ADsDSOObject"
objConn.Open "Active Directory Provider"
Set objCmd = CreateObject ("ADODB.Command")
objCmd.ActiveConnection = objConn
objCmd.CommandText = strBase & strFilter & strAttrs & strScope
objCmd.Properties ("Page Size") = 5000
Set objRS = objCmd.Execute
objRS.MoveFirst
objAddressFile.WriteLine "mailNickName,samAccountName,ProxyAddress"
objMemberFile.WriteLine "mailNickName,samAccountName,membermailNickname,membermail"
while Not objRS.EOF
'Wscript.Echo "name = " & objRS.Fields(0).Value
name = objRS.Fields(0).Value
adspath = objRS.Fields(1).Value
mailNickName = objRS.Fields(2).Value
proxyaddresses = objRS.Fields(3)
legacyExchangeDN = objRS.Fields(4).Value
samAccountName = objRS.Fields(5).Value
members = objRS.Fields(6)
WScript.Echo "Processing group: [" & samAccountName & "]"
WScript.Echo " Path: " & adspath
WScript.Echo " SMTP, x500, & legacyExchangeDN addresses:"
' Get SMTP & x500 addresseses
For Each proxyaddress in proxyaddresses
If UCase(Left(proxyaddress,4)) = "SMTP" Then
WScript.Echo " Address: " & Replace(proxyaddress,"smtp:","",1,-1,1)
objAddressFile.WriteLine mailNickName & "," & samAccountName & "," & Replace(proxyaddress,"smtp:","",1,-1,1)
End If
If UCase(Left(proxyaddress,4)) = "x500" Then
WScript.Echo " Address: " & Replace(proxyaddress,"x500:","",1,-1,1)
objAddressFile.WriteLine mailNickName & "," & samAccountName & "," & Replace(proxyaddress,"x500:","",1,-1,1)
End If
Next
' Get legacyExchangeDN
' WScript.Echo vbtab & vbtab & "Address: " & legacyExchangeDN
objAddressFile.WriteLine mailNickName & "," & samAccountName & "," & legacyExchangeDN
' Get Group Members
If TypeName(members) = "Variant()" Then
WScript.Echo " Members:"
For Each member in members
Set objMember = GetObject("LDAP://" & member)
membermailNickname = objMember.mailNickname
membermail = objMember.mail
membername = objMember.name
memberSamAccountName = objMember.samAccountName
If memberSamAccountName "" Then
userAccountControl = objMember.userAccountControl
If userAccountControl and ADS_UF_ACCOUNTDISABLE Then
WScript.Echo " User: [" & memberSamAccountName & "] is disabled, this user will not be exported"
objLogFile.WriteLine "WARNING: User: [" & memberSamAccountName & "] is disabled, this user will not be exported"
membermail = ""
End If
End If
If membermail "" Then
If membermailNickname = "" Then
WScript.Echo
WScript.Echo "**** Unexpected data found ****"
WScript.Echo "Empty field for : [" & Replace(membername,"CN=","") & "] mailNickName: [" & membermailNickname & "] mail: [" & membermail & "]"
objLogFile.WriteLine "ERROR: Empty field for : [" & Replace(membername,"CN=","") & "] mailNickName: [" & membermailNickname & "] mail: [" & membermail & "]"
Else
WScript.Echo " Name: [" & Replace(membername,"CN=","") & "] mailNickName: [" & membermailNickname & "] mail: [" & membermail & "]"
objMemberFile.WriteLine mailNickName & "," & samAccountName & "," & membermailNickname & "," & membermail
End If
End If
Next
End If
objRS.MoveNext
Wend
objAddressFile.Close