A Script To Copy Your Exchange Global Address List To A Public Folder

[ 14 ] Comments
Share

Here is a little script that will copy your Exchange (2007, 2010, and possibly 2013) GAL entries into a Public Folder (as Contact items). I’m not sure why you would want to do this, but a question came up in the Experts Exchange forum recently regarding doing this, and this is what I came up with. I think they wanted to do it because the folder views in Outlook are better than the GAL view, and they wanted to see it grouped by department.

Anyway, there are a couple of things to be aware of.

1. You should have the folder (configured to contain Contact items) already created. You can use Outlook for this. The folder path is specified on line 13. In the example below, it looks like this

$folderPath = “\Folder1\SubFolder2″

you will need to change this to point to your own folder.

2. This script deletes anything already in the folder before copying anything into it.

3. It can be run in either Exchange Management Shell, or the plain PowerShell (since it loads the Exchange admin add-ins by itself).

4. Distribution Groups are created as Contacts. Although I did find a way to create an empty Group within the Public Folder, I couldn’t work out how to add all the entries. Should still work, though, but you won’t get to see who’s in the list.

If you want to try it, copy the following into a .ps1 file. You can schedule the execution if you wish, but please note that for a large GAL, this will take a long time, and a lot of CPU power.

# Add the snapin in case we're in the plain (i.e. non-Exchange Management) Shell

# Try the E2007 snapin first
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.Admin -ErrorAction SilentlyContinue
# Then try the E2010 snapin
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010 -ErrorAction SilentlyContinue
# This assumes you have v2.0 of the EWS Managed API
Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll"

# Supply the folder path, then try to locate it

$folderPath = "\Folder1\SubFolder2"
$ews = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService("Exchange2007_SP1")
$ews.Url = "http://localhost/EWS/Exchange.asmx"
$rootFolderId = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::PublicFoldersRoot)
$folder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($ews, $rootFolderId)
$arrPath = $folderPath.Split("\")
for ($i = 1; $i -lt $arrPath.length; $i++)
{
$folderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(1)
$searchFilter = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName, $arrPath[$i])
$findFolderResults = $ews.FindFolders($folder.Id, $searchFilter, $folderView)
if ($findFolderResults.TotalCount -gt 0)
{
$folder = $findFolderResults.Folders[0]
}
else
{
"$folderPath Not Found"
exit
}
}

# Delete the items in the folder
# Note that E2007 doesn't have folder.Empty()

$itemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
do
{
$findItemResults = $ews.FindItems($folder.Id, $itemView)
foreach ($item in $findItemResults.Items)
{
$item.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete)
}
}
while ($findItemResults.MoreAvailable)

# Could have used just the next single line in >= E2010

# $folder.Empty([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete, $false)

# Now create the new Contact items within the folder

$recipients = Get-Recipient | Where {$_.HiddenFromAddressListsEnabled -eq $false}
$recipients | foreach {
$_.DisplayName
$item = New-Object Microsoft.Exchange.WebServices.Data.Contact($ews)
$item.Subject = $_.DisplayName
$item.GivenName = $_.FirstName
$item.Surname = $_.LastName
$item.DisplayName = $_.DisplayName
$item.FileAs = $_.DisplayName
$item.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress1] = $_.PrimarySMTPAddress.ToString()
$item.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessPhone] = $_.Phone.ToString()
$item.Department = $_.Department
$item.Manager = $_.Manager.Name
$itemAddress = New-Object Microsoft.Exchange.WebServices.Data.PhysicalAddressEntry
if ($_.RecipientType -eq "UserMailbox")
{
$user = Get-User $_.Alias
$itemAddress.Street = $user.StreetAddress
$item.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::MobilePhone] = $user.MobilePhone.ToString()
}
elseif ($_.RecipientType -eq "MailContact")
{
$mailContact = Get-Contact $_.Alias
$itemAddress.Street = $mailContact.StreetAddress
}
$itemAddress.City = $_.City
$itemAddress.State = $_.StateOrProvince
$itemAddress.CountryOrRegion = $_.CountryOrRegion
$itemAddress.PostalCode = $_.PostalCode
$item.PhysicalAddresses[[Microsoft.Exchange.WebServices.Data.PhysicalAddressKey]::Business] = $itemAddress
$item.PostalAddressIndex = [Microsoft.Exchange.WebServices.Data.PhysicalAddressIndex]::Business
$item.Save($folder.Id)
$GUID = New-Object Guid("00062004-0000-0000-C000-000000000046")
$ePD = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition($GUID, 32896, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String)
$item.SetExtendedProperty($ePD, $_.DisplayName)
$item.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite)
}

14 Responses to A Script To Copy Your Exchange Global Address List To A Public Folder

  1. Jeff says:

    Will this copy to a NON-public; i.e. “local” folder. Here’s the deal: There’s a HUGE need to be able to copy a select subset of contacts from GAL to each user’s local Outlook/Exchange contacts; and I have a script to do that, BUT the script ‘duplicates’ ALL contacts, for example, if you run it again, with same CSV input list; it does not “update” or “delete;” so, a script really is needed to “Find contact with some key (unique) fields that match against a list of contacts,” and then “delete” (or update) the matching contact; if you delete the matching contact, then you just copy (save) the contact again from the script, and you will get the updated contact. As of yet, I’ve not seen a clean “find/delete/update” script – yours comes closest. BTW, our “mobile user” contacts (that’s just what we call the dist list in GAL) all need to be copied either to a “sub-folder” in each users’ Outlook or to their main Contacts (well-known) folder. We currently let them get saved into ‘Contacts’ folder. So, if we save them to … Contacts\mobile and then run your script and do a “wipe-out and rebuild” each time, with all current updates, then that would do the trick.

    • admin says:

      It ought to work with a mailbox, provided you change the $rootFolderId to a users mailbox root (unfortunately, I don’t have the time to play with this today), but I’ll have a look in the next few days.

      On the other hand, it may be easier to get your exiting script to delete the existing items before it begins copying? I did think about using find/update/etc., but it’s a lot harder, and since the end result would be the same, it just turned out to be easier to delete everything.

  2. Paul says:

    I have been able to get this to work but I noticed that all phone numbers do not come into the new contact created. How can I get MobilePhone to update also?

    • admin says:

      I don’t have time to test it, but try adding these lines near where the other properties get added:

      $item.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessPhone] = $_.BusinessTelephoneNumber.ToString()

      $item.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::MobilePhone] = $_.MobileTelephoneNumber.ToString()

  3. Mark says:

    Did anyone every get the Mobile Phone code working? I haven’t been able to get it to work.

  4. Mark says:

    I get
    You cannot call a method on a null-valued expression.
    At J:\scripts\Copy_Gal.ps1:90 char:122
    + $item.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::MobilePhone] = $_.MobileTelephoneNumber.ToSt
    ring <<<< ()
    + CategoryInfo : InvalidOperation: (ToString:String) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

    • admin says:

      It’s probably upset because some records don’t have a value for the mobile number, and we’re trying to do something with it. Try inserting a line before that problem one, and brackets around it:

      if ($_.MobileTelephoneNumber -ne $null)
      {
      $item.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::MobilePhone] = $_.MobileTelephoneNumber.ToString()
      }

  5. Mark says:

    That got rid of the error message, but the numbers are not copying in.

    • admin says:

      Okay, I’ve got it. The problem with the Business Phone Number is that I used the wrong attribute name. It should be just $_.Pnone . The problem with the Mobile Phone Number is that it’s not returned by Get-Recipient (the output of which I use to get the other properties). I need to use the output from Get-User a bit further down. I’ll go and change the article script now.

  6. Marin says:

    When I run the script, I have the following error:

    Exception calling “Bind” with “2″ argument(s): “The request failed. The remote server returned an error: (403) Forbidden.”
    At C:\install\CopyGALtoPF.ps1:16 char:1
    + $folder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($ews, $rootFolderId …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ServiceRequestException

    Exception calling “FindFolders” with “3″ argument(s): “Value cannot be null.
    Parameter name: parentFolderId”
    At C:\install\CopyGALtoPF.ps1:22 char:1
    + $findFolderResults = $ews.FindFolders($folder.Id, $searchFilter, $folderView)
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ArgumentNullException

    • admin says:

      That means it’s getting a 403 response from IIS. For some reason, it’s not allowing you to do something. Have a look in the IIS log file for any lines with EWS near the beginning, and 403 near the end, and copy/paste them here for me.

  7. Marin says:

    I have looked in LOG files, but there is nothing with “403″ or nothin similar which can be related with the script…




Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>