Importing Global Address List entries into a user’s Contacts folder

I had a fairly unusual request from one of my customers whilst performing the final stages of an Exchange migration. A while ago, the CEO wasn’t able to lookup people to contact from the Global Address List due to connectivity problems and wanted “offline” access to the GAL from his phone.

The customer asked me if it would be possible for me to write a little script to effectively grab the GAL (they aren’t a massive company) and copy it into the CEO’s mailbox, into a dedicated Contacts folder, for example “OrgContacts”. Whilst certainly not a great idea for a larger company, for a few hundred users it’s not a bad idea, and in a previous post I’ve written something very similar, so in my spare time put together the following script…

What it does:

  • Connects to the user mailbox using EWS as a the logged on Administrator, using impersonation.
  • Checks if the dedicated contacts folder exists, and if so empties it.
  • Gets the organization’s users who have an email address set and at least a work phone or mobile phone number.
  • Adds a contact for each of the above users, populating the contact’s name, company, department, job title, email address and work and mobile phone numbers into the dedicated contacts folder.

Pre-requisites

As with the original script this is based on, you need to set up the impersonal for Exchange Web Services and install the Exchange Web Services Managed API 1.2 before using the script. The script uses the default installation location of the EWS Managed API, so if you’ve got it installed somewhere else, update the script.

To setup the pre-reqs, follow Setup of Exchange Web Services Impersonation and Installing the Exchange Web Services Managed API from the original article Using Powershell to import contacts into Exchange.

Additionally, the script expects to be used in the following scenario:

  • To be executed from the Exchange Management Shell, so it can get the InternalURL for Exchange Web Services (EWS) from Exchange 2010 SP1 or SP2.
  • The logged-in user is the administrator whom has impersonation rights over the mailbox you wish to copy contacts into.

Using the Copy-OrgContactsToUserContacts.ps1 Script

Once the pre-reqs are satisfied, run the script using the following parameters, substituting <mailbox> with the name of the Mailbox User you wish to create the Contacts folder within:

1
.\Copy-OrgContactsToUserContacts.ps1 -Mailbox <mailbox> -FolderName OrgContacts

Upon initial and subsequent executions, the script should output similar to shown below:

image

Finally, the new contacts folder, complete with copies of GAL entries should show within the user’s mailbox, as shown below in OWA:

image

Download the Copy-OrgContactsToUserContacts.ps1 here as a zip file, and as always if you’ve got any questions or suggestions for improvement, let me know in the comments below..

74 thoughts on “Importing Global Address List entries into a user’s Contacts folder

  1. So this is very nice script and I got it up and running. But I noticed that when a user for example leaves the company (my company 500+ people) The enrty of the deleted person resides on the phone! It is deleted from the outlook OrgContacts folder but not on the Phone… Did someone fount a wat to avoid this? (android phones here).

  2. Thanks for your script. In my organization, we having many contacts under GAL which sync from other trusted domain by FIM. Is is possible to copy all those GAL contacts also? Thanks again.

  3. I like the script just as it is. How can I have it run against ALL of my mailboxes without manually creating a list. I tried simply -mailbox * but it doesn’t like.

  4. Hi, Is there anyway to recover contacts that have been deleted using this script.? I copied a users contacts into a Sub Folder of the main contacts folder and then ran this script. The folder was deleted as were the contacts. The problem is the folder I created was not synced to exchange. Can I recover these contacts without having to resore from backup?

  5. First: Thanks for the Amazing Script! It works perfect!

    But, isn’t it possible to Add the StreetAdress,, City and PostalCode (ZIP)?
    i haven’t found anything 🙁

  6. Hi,
    very nice script.
    I want to import the addresses as well (Street, Postal Code, City, Country or Region). Has anyone done this so far or can tell me how to change the script?
    Best regards
    Reiner

  7. Can anyone share the code changes required to have the script sync business address information as well?

    Have tried a few changes with no luck!

  8. I have used your script and change it to import other fields such as fax, address and so on.
    So now the caller ID is working fine on company smartphones, without using owa for ios/android which claims to support this important feature.

    Thanks Steve for sharing this script

    Regards
    Hal

  9. Hello

    I try use script for Office 365, but i got following error:

    Cannot process argument transformation on parameter ‘Identity’. Cannot convert
    value “Adam M” to type “Microsoft.Exchange.Configuration.Tasks.Mailbo
    xIdParameter”. Error: “Cannot convert hashtable to an object of the following t
    ype: Microsoft.Exchange.Configuration.Tasks.MailboxIdParameter. Hashtable-to-Ob
    ject conversion is not supported in restricted language mode or a Data section.

    + CategoryInfo : InvalidData: (:) [Get-Mailbox], ParameterBindin.
    ..mationException
    + FullyQualifiedErrorId : ParameterArgumentTransformationError,Get-Mailbox
    + PSComputerName : podxxxx.outlook.com

    Mailbox Adam M not found
    At C:\Users\adrian\Desktop\skrypt.ps1:18 char:1
    + throw “Mailbox $($Mailbox) not found”;
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : OperationStopped: (Mailbox Adam M not
    found:String) [], RuntimeException
    + FullyQualifiedErrorId : Mailbox Adam M not found

    Any one have idea how to solve this error?

    • Hi
      I’m trying to use this script for Office365, I’ve received the following error message, Kindly Assist anyone how to resolve this error message.

      “Cannot process argument transformation on parameter ‘Identity’. Cannot convert
      value “Ben Andrews” to type
      “Microsoft.Exchange.Configuration.Tasks.MailboxIdParameter”. Error: “Cannot
      convert hashtable to an object of the following type:
      Microsoft.Exchange.Configuration.Tasks.MailboxIdParameter. Hashtable-to-Object
      conversion is not supported in restricted language mode or a Data section.”
      + CategoryInfo : InvalidData: (:) [Get-Mailbox], ParameterBindin.
      ..mationException
      + FullyQualifiedErrorId : ParameterArgumentTransformationError,Get-Mailbox
      + PSComputerName : pod51057psh.outlook.com

      Mailbox Ben Andrews not found
      At C:\Users\raj\Desktop\GAL.ps1:32 char:2
      + throw “Mailbox $($Mailbox) not found”;
      + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      + CategoryInfo : OperationStopped: (Mailbox Ben Andrews not found
      :String) [], RuntimeException
      + FullyQualifiedErrorId : Mailbox Ben Andrews not found”

    • When I try this on O365 I get errors on Get-WebServicesVirtualDirectory and when I research that it says it is only valid for On Prem.

      So, what do I need to do to get this to work with O365?

  10. Okay, I still need ability to ‘discreetly’ delete/update individual contacts. I don’t really like the idea of wholesale wipe-out of the Contacts folder.

    BTW, to the person who said the folder that’s created is not an address book – try modifying the “create folder” portion to create like “Contacts\sub-folder” – and see if the sub-folder inherits the ‘address-book’ property. I’m guessing any ‘sub-folder’ will inherit that property from the parent folder. And, yes, it would be nice to “create “Contacts2” folder at peer (root folder) level and set it to be an address-book type.

    For my case, it needs to be like:
    1) Open user’s Contacts folder remotely (impersonation)
    2) read through contacts; if they match what is in my source-CSV file, then Delete the contact
    3) place new contact in place of old contact

    I saw there is a “hard-delete” option but, in your example, you only are using it at the ‘folder’ level.

    Thanks for any info or examples you can give – I am trying to code it myself already, so we will see how that goes. I also agree that we need some way to invoke a command that will flip a folder to an “address-book” folder.

  11. Heya Tim Gipson, I hope you may still follow this page. Would it be possible for you to share the script you used for your office 365 deployment? I am beating my head against the wall trying to get this script to run

    • Jim,

      I understand what you are going through.

      The way I got this working was by using a “For Each” loop. You can put your creds to Office 365 right in the script and as long as you’ve created a secure string to log in with then you just have to run the script and it will log in and start doing the work for you. I also edited the contact mapping somewhat. I didn’t need all the info originally in the script.

      Here it is!

      $pass = cat C:\Users\”location of your securestring”\365securestring.txt | convertto-securestring

      $mycred = new-object -typename System.Management.Automation.PSCredential -argumentlist “office 365 admin”,$pass

      Import-Module MSOnline

      $O365Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell -Authentication Basic -AllowRedirection -Credential $mycred

      Import-PSSession $O365Session

      Connect-MsolService -Credential $mycred

      $ContactMapping=@{
      “FirstName” = “GivenName”;
      “LastName” = “Surname”;
      “Title” = “JobTitle”;
      “WindowsEmailAddress” = “Email:EmailAddress1”;
      “Phone” = “Phone:BusinessPhone”;
      “MobilePhone” = “Phone:MobilePhone”;
      }

      $Mailboxes = Get-Mailbox -ResultSize Unlimited
      foreach ($Mailbox in $Mailboxes)
      {

      $UserMailbox = Get-Mailbox $Mailbox

      if (!$UserMailbox)
      {
      throw “Mailbox $($Mailbox) not found”;
      exit;
      }

      $EmailAddress = $UserMailbox.PrimarySMTPAddress

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

      $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2)
      # $service.UseDefaultCredentials = $false;
      $service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials(“username”,”password”)
      $service.URL = New-Object Uri(“https://pod51035.outlook.com/EWS/Exchange.asmx”);

      # Search for an existing copy of the Folder to store Org contacts
      $service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
      $RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
      $RootFolder.Load()

      $service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
      $FolderView = new-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
      $ContactsFolderSearch = $RootFolder.FindFolders($FolderView) | Where {$_.DisplayName -eq $FolderName}
      if ($ContactsFolderSearch)
      {
      # Empty if found
      $service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
      $ContactsFolder = [Microsoft.Exchange.WebServices.Data.ContactsFolder]::Bind($service,$ContactsFolderSearch.Id);
      $ContactsFolder.Empty([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete, $true)
      } else {

      # Create new contacts folder
      $service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
      $ContactsFolder = New-Object Microsoft.Exchange.WebServices.Data.ContactsFolder($service);
      $ContactsFolder.DisplayName = $FolderName
      $ContactsFolder.Save([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
      # Search for the new folder instance
      $RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
      $RootFolder.Load()
      $FolderView = new-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
      $ContactsFolderSearch = $RootFolder.FindFolders($FolderView) | Where {$_.DisplayName -eq $FolderName}
      $ContactsFolder = [Microsoft.Exchange.WebServices.Data.ContactsFolder]::Bind($service,$ContactsFolderSearch.Id);
      }

      # Add contacts
      $Users = get-user -Filter {WindowsEmailAddress -ne $null -and (MobilePhone -ne $null -or Phone -ne $null) -and WindowsEmailAddress -ne $EmailAddress}
      $Users = $Users | select DisplayName,FirstName,LastName,Title,Company,Department,WindowsEmailAddress,Phone,MobilePhone

      foreach ($ContactItem in $Users)
      {
      $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.FirstName -and $ContactItem.LastName)
      {
      $ExchangeContact.NickName = $ContactItem.FirstName + ” ” + $ContactItem.LastName;
      }
      elseif ($ContactItem.FirstName -and !$ContactItem.LastName)
      {
      $ExchangeContact.NickName = $ContactItem.FirstName;
      }
      elseif (!$ContactItem.FirstName -and $ContactItem.LastName)
      {
      $ExchangeContact.NickName = $ContactItem.LastName;
      }
      elseif (!$ContactItem.FirstName -and !$ContactItem.LastName)
      {
      $ExchangeContact.NickName = $ContactItem.DisplayName;
      $ContactItem.FirstName = $ContactItem.DisplayName;
      }

      $ExchangeContact.DisplayName = $ExchangeContact.NickName;
      $ExchangeContact.FileAs = $ExchangeContact.NickName;

      # 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.ToString();
      }
      “Phone”
      {
      $ExchangeContact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::($MappingArray[1])] = $ContactItem.$Key;
      }
      }
      } else {
      $ExchangeContact.($ContactMapping[$Key]) = $ContactItem.$Key;
      }

      }
      }
      # Save the contact
      $ExchangeContact.Save($ContactsFolder.Id);

      # 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;
      }
      }

      • Hi Tim,

        it was Great Job,

        I try use this script file for Office365, I got the following error message. Kindly assist your cooperation highly appreciated.

        Error Message

        ““Cannot process argument transformation on parameter ‘Identity’. Cannot convert
        value “Ben Andrews” to type
        “Microsoft.Exchange.Configuration.Tasks.MailboxIdParameter”. Error: “Cannot
        convert hashtable to an object of the following type:
        Microsoft.Exchange.Configuration.Tasks.MailboxIdParameter. Hashtable-to-Object
        conversion is not supported in restricted language mode or a Data section.”
        + CategoryInfo : InvalidData: (:) [Get-Mailbox], ParameterBindin.
        ..mationException
        + FullyQualifiedErrorId : ParameterArgumentTransformationError,Get-Mailbox
        + PSComputerName : pod51057psh.outlook.com

        Mailbox Ben Andrews not found
        At C:\Users\raj\Desktop\GAL.ps1:32 char:2
        + throw “Mailbox $($Mailbox) not found”;
        + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo : OperationStopped: (Mailbox Ben Andrews not found
        :String) [], RuntimeException
        + FullyQualifiedErrorId : Mailbox Ben Andrews not found”

  12. Pingback: 把 Exchange 上的全域通訊錄同步到手機上 | ocappi

  13. This looks like a really good start with what I would like to accomplish. I am wondering though, is there a way to export it to a Public Folder instead of an individual’s mailbox? We have it shared here so that any user can connect to these contacts, which are updated nightly from the GAL. At least that is what we would like to do. Any advice?

  14. hi there!

    Is this exchange 2013 compatible? I have ran it on my exchange however stumbled upon a few errors.

    Thanks!

    • this is the error I get:

      Exception calling “Save” with “1” argument(s): “Value cannot be null.
      Parameter name: parentFolderId”
      At C:\Copy-OrgContactsToUserContacts.ps1:135 char:5
      + $ExchangeContact.Save($ContactsFolder.Id);
      + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
      + FullyQualifiedErrorId : ArgumentNullException

      • Is that the first error you get? There is typically another error before you get a null error.

        Also, I recommend you download EWS Editor to check and see that the account you are using for impersonation actually has access to the mailbox.

        That helped me identify that it was not, in fact, a permissions issue.

  15. Has anyone figured out how to do this in an Exchange Online/Office 365 environment yet? This script is what I need, however I don’t think it will work with Office 365. For instance, do you need the EWS API when you can connect powershell directly into your exchange online pod?

    I’m at a bit of a crossroads here because so far this is the only evidence of a script that I can run that will automatically import GAL contacts into a users contacts folder. I’m trying my doggiest to get this thing working in O365. I just don’t have the necessary knowledge or skills to do so!

    Also, anyway to modify this script so that it does it in a loop for every user?

    Thanks!

    Tim G.

    • Steve,

      Once again, thanks for the script. I’m learning a lot just by working with it. I am pretty close to getting it working in our Office 365/Exchange Online Environment, but I just need a push over the wall I’ve hit.

      I did have to supply the EWS URL manually, like has been suggested, and I did end up using the pod number to get EWS to work. Now I am stuck with the following error:

      ================
      Exception calling “Bind” with “2” argument(s): “The request failed. The remote server returned an error: (401)
      Unauthorized.”
      At C:\Powershell Scripts\Copy-OrgContacts.ps1:46 char:1
      + $RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,[Micro …
      + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
      + FullyQualifiedErrorId : ServiceRequestException

      ================

      I am able to go to the EWS site through a web browser and supply username and password and it does take my creds successfully.

      Any other trouble shooting steps that you can think of to help me identify the issue?

      Thanks,

      Tim G.

      • Another update:

        After looking all over the internet it seems that most issues around the EWS 401 error were permissions or impersonation related. I downloaded EWS Editor to help test and I was able to use impersonation as my administrator account to get logged into the mailbox through EWS. I am able to see the entire folder structure in EWS Editor so I don’t think it’s a permissions issue or ApplicationImpersonation issue.

        We don’t have a hybrid exchange environment and that may have something to do with my issues.

        So here is how I’m using the script:

        Start Powershell on my machine, remote powershell into our Pod on Office 365, authenticate in powershell as the admin. Then I run the script.

        Am I doing something wrong here?

        • SOOOOO after much fiddling and farting I got the script working.

          Things I had to do to get it working:

          I had to specify the Administrator credentials – $service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials(“username”,”password”)

          I had to specify the EWS URL with my Pod – $service.URL = New-Object Uri(“https://pod#####.outlook.com/EWS/Exchange.asmx”);

          My environment is Office 365 Medium Business with Exchange Plan 1.

          I used Powershell to remote into our Pod, then I ran the script from that Session and it worked.

          Steve, thanks again for all your work on this and thanks to all the good comments that helped me get it working!

          Thanks!

          Tim G.

          • Ahh Tim – can you help? I need this also and following your steps I’m getting errors. Are you able to provide me with a copy of the completed script working with Office 365? I would be grateful. Thanks

  16. Hi Steve,

    I’m a total novice. I’ve managed to get the original script working, but haven’t been able to adapt it for my mail contacts. If you have time for an update, it would be much appreciated.

    Thanks,
    Matt

  17. Thank you for the great script. However, I encountered the following error when I ran the script on the CAS server. I google the error message but no luck. Can anyone help? Thank you so much for your help!

    ========snip=========
    Exception calling “Bind” with “2” argument(s): “The request failed. The remote server returned an error: (504) Gateway
    Timeout.”
    At E:\Exchange Server\Scripts\ContactSync.ps1:47 char:65
    + $RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind <<<< ($service,[Microsoft.Exchange.WebServices.Data.
    WellKnownFolderName]::MsgFolderRoot)
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : DotNetMethodException
    =========snip=========

      • Thank you for your quick response. I ran the command you suggested and here is the outcome:

        [PS] C:\Windows\system32>netsh winhttp show proxy

        Current WinHTTP proxy settings:

        Direct access (no proxy server).

        • HI, i’m having this error too… checked the proxy and have the same results of Direct Access (no proxy server)… yet the copy contacts script gives this error:

          ========snip=========
          Exception calling “Bind” with “2” argument(s): “The request failed. The remote server returned an error: (504) Gateway
          Timeout.”
          At E:\Exchange Server\Scripts\ContactSync.ps1:47 char:65
          + $RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind <<<< ($service,[Microsoft.Exchange.WebServices.Data.
          WellKnownFolderName]::MsgFolderRoot)
          + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
          + FullyQualifiedErrorId : DotNetMethodException
          =========snip=========

      • The script is working for me now. Is there a way to import only partial GAL (or members of a DL) to a user’s Contacts folder? Our top management wants contact information for a list of critical people to be downloaded to their Contacts folder so if our data center is down, they still have the contacts information to call all critical people for the issue. Any help is highly appreciated.

  18. Can I get some help with wht it would take to only include the mailboxes that do NOT have “hide from address book” checked?

  19. Thank to Pouyan (SilenceW) for posting his script which works very well.
    If anybody wants to synchronize further contact details please have a look at another post of StevieG at http://www.stevieg.org/2010/07/using-powershell-to-import-contacts-into-exchange-and-outlook-live/.

    But I have one problem:
    The contacts folder which is created by this script (Copy-OrgContactsToUserContacts.ps1) is not an E-Mail Address Book. If you go to this contacts folders properties and then section Outlook Address Book the option “Show this folder as an e-mail address book” is not switched on. How can this be done during the creation via Powershell? Alternatively afterwards?
    I was not able to find a solution in the web.

  20. Thanks for the great script. Can you please show me how to import the addresses as well? Especially, the address has multiple lines besides city, state, country. e.g.:

    Customer Services
    Suite 500,
    123456 – 789 Street

    On the CSV file, this shows:

    BusinessAddress Customer Services
    BusinessAddress2 Suite 500,
    BusinessAddress3 123456 – 789 Street

    How can they make returns properly when import to exchange?

    I guess this also should go to the $MappingArray[0] so that it could identify Business or Home address.

    Thanks for your help!

  21. Very nice!

    What fields would I need to bring over if I wanted to add the Fax #?

    I’m sure it’s in this $ContactMapping array, but not sure what needs to be added/changed.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $ContactMapping=@{
        "FirstName" = "GivenName";
        "LastName" = "Surname";
        "Company" = "CompanyName";
        "Department" = "Department";
        "Title" = "JobTitle";
        "WindowsEmailAddress" = "Email:EmailAddress1";
        "Phone" = "Phone:BusinessPhone";
        "MobilePhone" = "Phone:MobilePhone";
    }
        • Not sure what I’m missing. I made my $ContactMapping array =

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          $ContactMapping=@{
              "FirstName" = "GivenName";
              "LastName" = "Surname";
              "Company" = "CompanyName";
              "Department" = "Department";
              "Title" = "JobTitle";
              "WindowsEmailAddress" = "Email:EmailAddress1";
              "Phone" = "Phone:BusinessPhone";
              "MobilePhone" = "Phone:MobilePhone";
              "Fax" = "Phone:BusinessFax";
          }

          And I changed the Get-User to:

          1
          $Users = $Users | select DisplayName,FirstName,LastName,Title,Company,Department,WindowsEmailAddress,Phone,MobilePhone,Fax

          When I do this for one user and view the contents of $Users it shows the Fax #, but it’s not writing it into the contact.

  22. Hello, great script, I have it working in Exchange 2010 however I’m looking for one change. The problem I have is the script wipes out the entire user contact folder before copying in the GAL, can the script be configured rather than wipe all contacts out to remote only contacts that have *@domain.com as their email? This way it will not wipe out the personal contacts the user has added on their own from their mobile device. This would wipe out all the previous contacts added from the GAL and make sure they have the most recent copy, we are looking to run the script every day or two.

    Thanks.

  23. Hello.. I’m new to powershell and would like to know if this script can be changed to run as “non-impersonated”. We have an org level admin service account that has full mailbox access already and doesn’t need to impersonate an account. Reading the MS article, it says admin groups are specifically denied impersonate access. Thanks.

    • I did it by adding the impersonation to one account:

      1
      New-ManagementRoleAssignment -Name:impersonationAssignmentName -Role:ApplicationImpersonation -User:serviceaccount
  24. This is a great script, thnx for sharing Steve. I am trying to copy the thumbnailPhoto also while copying/creating the contacts. The thumbnailPhoto is written in the AD trough powershell script, So I have two options:
    #I can first export the thumbnailPhoto from ad to jpg files and import them back while creating the contact
    # Or I can do a AD query and get the thumbnailPhoto Hexadecimal code from ad and then use it while creating contact

    The problem I am having is that I can’t find the right syntax to import JPG or hexadecimal back while creating the contact? Do you know what expression I can use? for example you are using “$ExchangeContact.NickName” to configure the contacts nickname, what can I use to configure the thumbnailPhoto?

    thanks, SilenceW

          • thanks so much for your amazing script, I seem to be stuck at one point that’s sort of driving me nuts a little bit – i am hard coding just a single mailbox to test with, after exporting all the thumbnails this is where the script bombs out, I’ve been trying to compare your revision to the original posted above (which works) so far I have not been able to figure out what may be wrong, thanks:

            Exception calling “Bind” with “2” argument(s): “The Id property must be set.”
            At C:\scripts\photo.ps1:58 char:65
            + $RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind <<<< ($service,[Microsoft.Exchange.WebServices.Data.
            WellKnownFolderName]::MsgFolderRoot)
            + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
            + FullyQualifiedErrorId : DotNetMethodException

            You cannot call a method on a null-valued expression.
            At C:\scripts\photo.ps1:59 char:17
            + $RootFolder.Load <<<< ()
            + CategoryInfo : InvalidOperation: (Load:String) [], RuntimeException
            + FullyQualifiedErrorId : InvokeMethodOnNull

            You cannot call a method on a null-valued expression.
            At C:\scripts\photo.ps1:63 char:48
            + $ContactsFolderSearch = $RootFolder.FindFolders <<<< ($FolderView) | Where {$_.DisplayName -eq $FolderName}
            + CategoryInfo : InvalidOperation: (FindFolders:String) [], RuntimeException
            + FullyQualifiedErrorId : InvokeMethodOnNull

            Exception calling "Save" with "1" argument(s): "The Id property must be set."
            At C:\scripts\photo.ps1:75 char:21
            + $ContactsFolder.Save <<<< ([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
            + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
            + FullyQualifiedErrorId : DotNetMethodException

            Exception calling "Bind" with "2" argument(s): "The Id property must be set."
            At C:\scripts\photo.ps1:77 char:65
            + $RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind <<<< ($service,[Microsoft.Exchange.WebServices.Data.
            WellKnownFolderName]::MsgFolderRoot)
            + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
            + FullyQualifiedErrorId : DotNetMethodException

            You cannot call a method on a null-valued expression.
            At C:\scripts\photo.ps1:78 char:17
            + $RootFolder.Load <<<< ()
            + CategoryInfo : InvalidOperation: (Load:String) [], RuntimeException
            + FullyQualifiedErrorId : InvokeMethodOnNull

            You cannot call a method on a null-valued expression.
            At C:\scripts\photo.ps1:80 char:48
            + $ContactsFolderSearch = $RootFolder.FindFolders <<<< ($FolderView) | Where {$_.DisplayName -eq $FolderName}
            + CategoryInfo : InvalidOperation: (FindFolders:String) [], RuntimeException
            + FullyQualifiedErrorId : InvokeMethodOnNull

            Exception calling "Bind" with "2" argument(s): "Value cannot be null.
            Parameter name: folderId"
            At C:\scripts\photo.ps1:81 char:77
            + $ContactsFolder = [Microsoft.Exchange.WebServices.Data.ContactsFolder]::Bind <<<< ($service,$ContactsFolderSearch.Id)
            ;
            + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
            + FullyQualifiedErrorId : DotNetMethodException

        • sorry for my late response, below a brief description of how I’ve solved the problem

          First I export all the photos from AD to a temporally folder:

          Get-ADUser -Filter * -Properties thumbnailphoto | where {$_.thumbnailphoto} | foreach {$_.thumbnailphoto | Set-Content -Path “$thumbnailPhotoPath\$($_.SamAccountName).jpg” -Encoding Byte} -ErrorAction SilentlyContinue

          And this is how I import the picture, because not all the users have an thumbnail photo I have
          created a simple if check:

          #Set profile picture from exported thumbnailphoto, if empty the skip
          $ContactPictureFile = $thumbnailPhotoPath + ‘\’ + $ContactItem.SamAccountName + ‘.jpg’
          if ((Test-Path -Path $ContactPictureFile)-eq $true){
          $ExchangeContact.SetContactPicture($ContactPictureFile);
          } else {}

          ~SilenceW (Pouyan)

          • This is the full script with photo import function for those who are interested:

            $StartDate = Get-Date -Format “dd-MM-yyyy HH:mm”
            #Import the required module and PSSnapin
            Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010
            Import-Module ActiveDirectory
            #Define the folder name
            $FolderName = “GALContact”
            #select all mailbox’s
            $Mailbox = Get-Mailbox *

            #export the thmbnailphotos from AD to local storage
            $thumbnailPhotoPath = “D:\Scripts\thumbnailPhoto”
            if ((Test-Path -Path $thumbnailPhotoPath)-eq $true){
            Remove-Item -Path $thumbnailPhotoPath -Force -Recurse
            New-Item -Path $thumbnailPhotoPath -ItemType directory -Force
            } else {
            New-Item -Path $thumbnailPhotoPath -ItemType directory -Force
            }
            clear
            #Get and export all thumbnail photo from ad of all the users..
            Get-ADUser -Filter * -Properties thumbnailphoto | where {$_.thumbnailphoto} | foreach {$_.thumbnailphoto | Set-Content -Path “$thumbnailPhotoPath\$($_.SamAccountName).jpg” -Encoding Byte} -ErrorAction SilentlyContinue
            Write-Host “Thumbnail photos are exported..”
            Write-Host
            Start-Sleep -Seconds 20

            #Define the EWS url
            $EwsUrl = ([array](Get-WebServicesVirtualDirectory))[0].InternalURL.AbsoluteURI
            #Define contact mapping variables
            $ContactMapping=@{
            “FirstName” = “GivenName”;
            “MiddleName” = “MiddleName”;
            “LastName” = “Surname”;
            “Company” = “CompanyName”;
            “Department” = “Department”;
            “Title” = “JobTitle”;
            “WindowsEmailAddress” = “Email:EmailAddress1”;
            “Phone” = “Phone:BusinessPhone”;
            “MobilePhone” = “Phone:MobilePhone”;
            “ContactPictureFile” = “Method:SetContactPicture”
            }
            Write-Host “Start of the first loop for each mailbox..”
            Write-Host

            #Start the Loop for each mail address
            foreach ($UserMailbox in $Mailbox){
            Write-Host $UserMailbox.Name
            Write-Host
            $EmailAddress = $UserMailbox.PrimarySMTPAddress
            #Load EWS Managed API
            [void][Reflection.Assembly]::LoadFile(“C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll”);

            $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2)
            $service.UseDefaultCredentials = $true;
            $service.URL = New-Object Uri($EwsUrl);

            #Search for an existing copy of the Folder to store Org contacts
            $service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
            $RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
            $RootFolder.Load()

            $service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
            $FolderView = new-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
            $ContactsFolderSearch = $RootFolder.FindFolders($FolderView) | Where {$_.DisplayName -eq $FolderName}
            if ($ContactsFolderSearch)
            {
            #Empty if found
            $service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
            $ContactsFolder = [Microsoft.Exchange.WebServices.Data.ContactsFolder]::Bind($service,$ContactsFolderSearch.Id);
            $ContactsFolder.Empty([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete, $true)
            } else {
            #Create new contacts folder
            $service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
            $ContactsFolder = New-Object Microsoft.Exchange.WebServices.Data.ContactsFolder($service);
            $ContactsFolder.DisplayName = $FolderName
            $ContactsFolder.Save([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
            #Search for the new folder instance
            $RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
            $RootFolder.Load()
            $FolderView = new-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
            $ContactsFolderSearch = $RootFolder.FindFolders($FolderView) | Where {$_.DisplayName -eq $FolderName}
            $ContactsFolder = [Microsoft.Exchange.WebServices.Data.ContactsFolder]::Bind($service,$ContactsFolderSearch.Id);
            }
            #Add contacts
            $Users = get-user -Filter {WindowsEmailAddress -ne $null -and (MobilePhone -ne $null -or Phone -ne $null) -and WindowsEmailAddress -ne $EmailAddress}
            $Users = $Users | select DisplayName,FirstName,LastName,Initials,Title,Company,Department,WindowsEmailAddress,Phone,MobilePhone,UserPrincipalName,SamAccountName -ErrorAction SilentlyContinue

            foreach ($ContactItem in $Users)
            {
            $GetMailBox = Get-Mailbox $ContactItem.SamAccountName| Select-Object CustomAttribute3,CustomAttribute4 -ErrorAction SilentlyContinue
            $DisabledUser = Get-ADUser $ContactItem.SamAccountName
            #Filter
            if (!($DisabledUser.Enabled -eq $false) -or ($GetMailBox.CustomAttribute4 -eq “test”)){
            $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);
            $FirstName = $ContactItem.FirstName
            $LastName = $ContactItem.LastName
            if ($ContactItem.FirstName -and $ContactItem.LastName)
            {
            $ExchangeContact.NickName = $ContactItem.DisplayName
            }
            elseif ($ContactItem.FirstName -and !$ContactItem.LastName)
            {
            $ExchangeContact.NickName = $ContactItem.FirstName;
            }
            elseif (!$ContactItem.FirstName -and $ContactItem.LastName)
            {
            $ExchangeContact.NickName = $ContactItem.LastName;
            }
            elseif (!$ContactItem.FirstName -and !$ContactItem.LastName)
            {
            $ExchangeContact.NickName = $ContactItem.DisplayName;
            $ContactItem.FirstName = $ContactItem.DisplayName;
            }
            #Set profile picture from exported thumbnailphoto, if empty the skip
            $ContactPictureFile = $thumbnailPhotoPath + ‘\’ + $ContactItem.SamAccountName + ‘.jpg’
            if ((Test-Path -Path $ContactPictureFile)-eq $true){
            $ExchangeContact.SetContactPicture($ContactPictureFile);
            }
            $ExchangeContact.MiddleName = $GetMailBox.CustomAttribute3
            $ExchangeContact.DisplayName = $ContactItem.DisplayName
            $ExchangeContact.FileAs = $ContactItem.DisplayName
            #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.ToString();
            }
            “Phone”
            {
            $ExchangeContact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::($MappingArray[1])] = $ContactItem.$Key;
            }
            }
            } else {
            $ExchangeContact.($ContactMapping[$Key]) = $ContactItem.$Key;
            }
            }
            }
            #Save the contact
            $ExchangeContact.Save($ContactsFolder.Id);
            } else {}
            }
            }
            $EndDate = Get-Date -Format “dd-MM-yyyy HH:mm”

            Send-MailMessage -To “GALSync@DOMAIN.nl” -From “noreply@DOMAIN.nl” -Subject “GAL Sync” -Body “GAL Sync is started at $StartDate and finished at $EndDate” -SmtpServer “smtp.DOMAIN.nl” -BodyAsHtml -ErrorAction SilentlyContinue

            if ((Test-Path -Path $thumbnailPhotoPath)-eq $true){
            Remove-Item -Path $thumbnailPhotoPath -Force -Recurse
            } else {}

            ###Loaded PSSnapin will be unloaded
            Remove-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010

            ~Pouyan (SilenceW)

          • Hi Silence-W

            I’ve put your code below into a .ps1 file and tried to run it.

            When I do, I get:

            Unexpected token ‘Phone” = “Phone:BusinessPhone”;
            “MobilePhone” = “Phone:MobilePhone”;
            “ContactPictureFile” = “Method:SetContactPicture”
            }
            Write-Host “Start’ in expression or statement.
            At C:\scripts\im.ps1:42 char:18
            + “Phone” = “Phone: <<<< BusinessPhone";
            + CategoryInfo : ParserError: (Phone" = "Phone…ite-Host "Start:String) [], ParseException
            + FullyQualifiedErrorId : UnexpectedToken

            I can run Steve's code and it puts all the contacts (without pictures into a contacts folder within the specified mailbox).

            I've got Web services 1.2 installed and I'm trying this on my Exchange 2010 server.

            Any pointers would really be appreciated.

            Kind Regards
            Harris

  25. Pingback: Import GAL to your contacts | Ammar Hasayen – I – Blog

  26. I found out that on Exchange 2010 a user also needs to be a member of the “View-Only Organziation Configuration”, otherwise the call to Get-User causes an error message in the Windows event log: Add-RoleGroupMember “View-Only Organization Management” -Member “username”

  27. I found a problem in the script: A regular user with just the ApplicationImpersonation right cannot run the Get-WebServicesVirtualDirectory commandlet. What permissions do you need for this?

    @Alberto de_la_Torre:
    To run the script for multiple mailboxes, create an array with the mailboxes in question and then use a foreach loop over the part where the contacts are stored in the mailbox. Something like:

    $Users = $Users | select DisplayName,FirstName,…
    $Mailboxes=@(“user1″;”user2″;”user3”;)
    foreach ($Mailbox in $Mailboxes)
    {
    $UserMailbox = Get-Mailbox $Mailbox

    You can use the task scheduler to run the script regulary: Add the line “Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010” at the beginning of the script, and then execute it as powershell.exe -NoLogo -NonInteractive -Command “& ‘C:\Folder\Copy-OrgContactsToUserContacts.ps1′”

  28. Hello,

    I’ve a problem, can you help me?

    La propiedad ‘FirstName’ no se encuentra en este objeto; asegúrese de que existe y de que se puede establecer
    En C:\Users\administrador.EUROFIRMS\Desktop\Contactes.ps1: 98 Carácter: 16
    + $ContactItem. <<<< FirstName = $ContactItem.DisplayName;
    + CategoryInfo : InvalidOperation: (FirstName:String) [], RuntimeException
    + FullyQualifiedErrorId : PropertyNotFound

  29. I’m very new to powershell, please excuse me if this is obvious or trivial. How would you modify the script so it executes for each person in the GAL?

    How would you schedule it to run once a day?

    Thanks.

    • I’d suggest something like

      $Mailboxes = Get-Mailbox -ResultSize Unlimited
      foreach ($Mailbox in $Mailboxes)
      {
      .\Copy-OrgContactsToUserContacts.ps1 -Mailbox $Mailbox -FolderName OrgContacts
      }

      However, this is probably a bad idea, this script was only intended for edge cases.

      Steve

  30. Hey,

    great script – but: can you extend the script to import the contacts of GAL too? and not only users ?

    regards

  31. Pingback: Global mailbox | Firstchoiceloc

  32. Figures. I spent a weekend hand-creating a CSV file from scratch for this same purpose a few months ago.

    This is really useful though, and it’s not just useful for reaching out, but even having the directory in Contacts helps identify callers too, which is really useful.

    I’m curious… does this grab the user picture too?

Comments are closed.