A GALSync powershell script

Here is a script I wrote to do a simple GAL synchronization between two Exchange organizations. The script finds the mail-enabled  users in one domain, and creates contacts for them in the other domain. Existing contacts will also be updated and deleted as needed.


There was a bug in the original script where I had forgotten to populate mailNickname. I have now done so, adding a “c-” to the front of it as a completely optional convention to avoid conflicts.

Update 2

Several people have commented below about needing to enable the contacts in Exchange after creation. I have used the modifications posted by Mark in the comments to make a new version that I hope will work better with 2007 and 2010, though I have only tested it with 2010. Both versions are linked below, and please keep adding your comments and modifications.

The Script

Now I have two versions the scripts have been moved off to separate pages. Follow the links below.

Version 2 Added the Update-Recipient command for Exchange 2007 (through local PowerShell) and Exchange 2010 (through remote PowerShell).
Version 1 Original version that uses the ActiveDirectory PowerShell module to create the contact objects.

Other people’s versions

Modified for Distribution Lists: https://www.wapshere.com/missmiis/galsync-v2/galsync-ps1-for-distribution-lists

78 Replies to “A GALSync powershell script”

  1. Ah, it turned out it was using a 2008 GC for DNS that wasn’t updated with the relevant patches.

    Now that it is, I’m getting a different error. I’m assuming the “No contact found for” is normal.

    ….. (many more above)
    No contact found for SRobinson2@qvideosystems.com.au
    No contact found for matthew@qvideosystems.com.au

    Updating apisec.com using DC APISERVER2.apisec.com
    ADDING contact for glenno@qvideosystems.com.au
    New-ADObject : Directory object not found
    At C:\ittools\GALSYNC.PS1:155 char:15
    + New-ADObject <<<< -name $user.displayName -type contact -Path $targetOU -Description $user.description -server $targetDC -credential $targetCred -OtherAttributes $hashAttribs
    + CategoryInfo : ObjectNotFound: (cn=Glenn Opray,…C=apisec,DC=com:String) [New-ADObject], ADIdentityNotFoundException
    + FullyQualifiedErrorId : Directory object not found,Microsoft.ActiveDirectory.Management.Commands.NewADObject

    New-PSSession : Cannot validate argument on parameter 'ConnectionUri'. The argument is null or empty. Supply an argument that is not null or empty and then try the command again.
    At C:\ittools\GALSYNC.PS1:168 char:111
    + if ($PSSession -eq $null) {$PSSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri <<<< $targetURI -Credential $targetCred -SessionOption $SO}
    + CategoryInfo : InvalidData: (:) [New-PSSession], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.NewPSSessionCommand

    Invoke-Command : Cannot validate argument on parameter 'Session'. The argument is null or empty. Supply an argument that is not null or empty and then try the command again.
    At C:\ittools\GALSYNC.PS1:169 char:28
    + Invoke-Command -Session <<<< $PSSession -ScriptBlock{param ($alias,$targetDC) Update-Recipient -Identity $alias -DomainController $targetDC} -ArgumentList $alias,$targetDC
    + CategoryInfo : InvalidData: (:) [Invoke-Command], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.InvokeCommandCommand

    qvideosystems.com.au is Domain_1
    apisec.com is Domain_2

  2. It’s probably just that something doesn’t have a value. There isn’t a whole lot of error checking in this script. Try opening it in PowerGUI so you can run one line at a time and check variable values.

  3. Hi Carol, great script. I got it working between a W2k8DC/E2k7 and a W2k8R2DC/E2010 forest. But I have one little problem.

    Say I already have a contact in E2010, Ext smtp:user1@abc.com in OU1. A mailbox in E2k7 has smtp address user1@abc.com. When I run the script and specify the destination OU as OU2, it will create a contact with the same external smtp address.
    If I were to manually create 2 contacts with the same ext email address using EMS, it will fail.
    How can I have the contact creation fail using your script if the same Ext smtp address was detected?

  4. The script assumes all contacts are in the OU you specified at the top, and is therefore both searching and provisioning to the same OU. Your options are to put all your contacts from the other forest in that OU (including pre-existing ones), or to rewrite the “Enumerate Contacts” part of the script so it uses a different OU as the search root – eg., the root of the domain. Be careful though – you may also need to modify the “Find Contacts to Delete” section so it doesn’t delete contacts you still wanted.

  5. Thanks for the reply, I will try that out.
    BTW, why are you appending c- to the alias of the imported contacts?


  6. Hi Carol,

    I found very interesting this script.I am new to this. In the video bat is running intead of ps1.Can you please explain me?..

    Also which is the write script to enble the contacts in source domain ie- version1 or version2

  7. Also which is the right script to enble the contacts in source domain ie- version1 or version2

  8. Thanks Carol.Is it possible to get Galsync-curuIT-v3_bat.txt? I am unable to see the files in the mentioned link.

  9. Sorry for the confusion.. I have got the links from the following site (Gal sync video)

  10. Thanks so much for this script, it’s been working like a charm ever since I found it back in April.
    One question, how difficult would it be to adapt it to transfer across mail enabled distribution groups?

  11. It shouldn’t be hard. You could try changing the definition for $strSelectUsers so it selects the DLs as well. If that doesn’t work then just define $strSelectDLs with the appropriate lookup string, and loop through again. And if you do post your modified script somewhere be sure to send me the link 🙂

  12. Just an FYI: in Google Chrome you can only see the comments, not the actual blog article. Thought you’d want to know this.

  13. I am working on a project that is 3 way

    Before I go too far I want to make sure this script definitely supports Exchange 2003 as both a source and a target?


  14. I’m having problems with the DNS part,

    I can seem to be able to GET-ADDOMAINCONTROLLER from DOMAIN A to DOMAIN B, i tried adding a new zone namely DOMAIN B. but to no avail.

    i can surely ping DOMAIN A to DOMAIN B and vice versa.

    Get-ADDomainController : The specified domain either does not exist or could not be contacted
    At C:\scripts\galsync.ps1:70 char:39
    + $objSourceDC = Get-ADDomainController <<<< -Discover -DomainName $sourceDomain
    + CategoryInfo : ObjectNotFound: (:) [Get-ADDomainController], ADException
    + FullyQualifiedErrorId : GetADDomainController:BeginProcessingOverride:DiscoverDC:1355,Microsoft.ActiveDirectory.

  15. You could hard code your domain names. Note I don’t give technical support on these scripts – they’re presented as-is (and FREE) and you will probably need to modify for your environment.

  16. Hi,

    I want to try to create mailbox instead of contacts. I try to change some of the code to find for existing user instead of contact and changing the -type to “mailbox”. But I cannot get it to work. I am fairly new to powershell scripting and am just experimenting.

    Any suggestions?

  17. Will this script (creating the mail enabled contacts in both forests) enable me (in forest A) to see free busy info for those users that are in the remote forest (Forest B). Or does this simply put an entry in the GAL for the users in the remote forest by creating a “dot forward”? thanks

  18. Hi, I got the following error, help to cope with it

    Both controllers domera running Windows Server 2008 r2

    PS C:\Scripts> C:\Scripts\GalSync.ps1
    You must provide a value expression on the right-hand side of the ‘-‘ operator.
    At C:\Scripts\GalSync.ps1:89 char:14
    + $targetCred – <<<< Properties targetAddress
    + CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : ExpectedValueExpression

  19. Any thoughts on enabling this to do the same for Linked Accounts in an Account forest? My environment has a mixture of user accounts in an Exchange Resource Forest and linked accounts from a trusted forest.


Comments are closed.