Script: Export-Groups.vbs – For Exchange Cross Forest migrations

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: (This is where the latest version will always be posted)

Related Import scripts, to be covered in another blog post very soon:

' 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
' 	Output file can be used to addresses by
' Created 3/1/2012 by Jason Sherry (
'	Source:
' 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
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

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
' 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 =
			memberSamAccountName = objMember.samAccountName
			If memberSamAccountName <> "" Then
				userAccountControl = objMember.userAccountControl
				If userAccountControl  and ADS_UF_ACCOUNTDISABLE Then
					WScript.Echo "    User: [" & memberSamAccountName&nbsp;& "] is disabled, this user will not be exported"
					objLogFile.WriteLine "WARNING: User: [" & memberSamAccountName&nbsp;& "] is disabled, this user will not be exported"
					membermail = ""
				End If
			End If
			If membermail <> "" Then
				If membermailNickname = "" Then
					WScript.Echo "**** Unexpected data found ****"
					WScript.Echo "Empty field for : [" & Replace(membername,"CN=","") & "] mailNickName: [" & membermailNickname&nbsp;& "] mail: [" & membermail & "]"
					objLogFile.WriteLine "ERROR: Empty field for : [" & Replace(membername,"CN=","") & "] mailNickName: [" & membermailNickname&nbsp;& "] mail: [" & membermail & "]"
					WScript.Echo "    Name: [" & Replace(membername,"CN=","") & "] mailNickName: [" & membermailNickname&nbsp;& "] mail: [" & membermail & "]"
					objMemberFile.WriteLine mailNickName & "," & samAccountName & "," & membermailNickname&nbsp;& "," & membermail
				End If
			End If
	End If


About jasonsherry

I am a 20 year Exchange consultant and expert. I currently work for Commvault as a Solutions Specialist for Microsoft Infrastructure For more info see my resume at:
This entry was posted in Exchange, Technical and tagged , . Bookmark the permalink.

10 Responses to Script: Export-Groups.vbs – For Exchange Cross Forest migrations

  1. Johnny Chan says:

    Great post and script. I do have a question. How about distribution groups with “Linked Mailboxes”? These accounts are technically disabled accounts and your VB script does not export those.


    • jasonsherry says:

      True, the query can be modified in the script. But before Prepare-MoveRequest.ps1 will work the mailbox account must be enabled also; don’t think there is a switch or something for disabled accounts.


      • Peter says:


        Can you remove the check that is concerned with if no value is found/populated in mailNickname attribute, to allow the script to keep running. In fact if any attribute is not populated, can you make it so it continues and does not halt the process. Also, if an account is disabled or not (enabled), can you just report the status in the exported CSV instead (userAccountControl attribute I think), so disabled accounts are not skipped, but rather recorded in the output? Would be great for cross forest migrations I am involved in currently. Much appreciated if this is possible.

        Many Thanks.

        Kind Regards,



      • jasonsherry says:

        Yes I could, but the migration cmdlets don’t support disabled accounts. So that’s why I didn’t include them in the results.

        Basic scripting skills are really required for Exchange admins today, so I suggesting doing the research and trying to figure it out for yourself in a lab.


  2. Just wanted to say thanks, I’m busy doing a 2003 to 2010 cross forest migration this weekend and realised my group X500 addressing was missing quite late in the game. Used my own PS1 to import into 2010 but used your .vbs to export from 2003 🙂

    I never knew public folders also used X500 addressing, do you have any export scripts for these and contacts from 2003?

    Thanks again,


  3. Marco MI says:

    Hi Jason,
    I tried to download the scripts but unfortunately the provided links don’t work again 😦
    Could you please check the link or provide me an alternative one?

    Thank you very much in advance!


  4. Dean says:

    Hi Jason. Love your script, has helped me in my Cross-Forest migration immensely.

    Links still seems to be broken though.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s