Tuesday, February 25, 2014

Exporting Contacts to a CSV file using the EWS Managed API and Powershell

Somebody asked last week about exporting contacts from Exchange via EWS to a CSV file and I realised I didn't have a basic sample for doing this. Contacts are one of the more richer exchange datatypes and can hold a lot of different information which you may or may not want to capture in a CSV export. In this sample script I'll show you how you can export data from the normal contact strongly typed properties like GivenName and Surname and the Indexed properties which are used to store the EmailAddresses, PhoneNumbers and Address details and also any extended properties like the Gender property which there are no strongly typed property for.

As I mentioned Contacts have a lot of properties so this script doesn't export everything just a subsection to show how to export properties from each of the different subgroups I talked about. To add other properties to script eg  like the JobTitle you need to make the following modifications

Add the property to the Custom object

$expObj = "" | select DisplayName,GivenName,Surname,Gender,Email1DisplayName,Email1Type,Email1EmailAddress,BusinessPhone,MobilePhone,HomePhone,BusinessStreet,BusinessCity,BusinessState,HomeStreet,HomeCity,HomeState,JobTitle

Then set the property within the Item Iteration

$expObj.JobTitle  = $item.JobTitle

I've put a download of this script here the code looks like

  1. ## Get the Mailbox to Access from the 1st commandline argument  
  3. $MailboxName = $args[0]  
  5. ## Load Managed API dll    
  6. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll"    
  8. ## Set Exchange Version    
  9. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2  
  11. ## Create Exchange Service Object    
  12. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
  14. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
  16. #Credentials Option 1 using UPN for the windows Account    
  17. $psCred = Get-Credential    
  18. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
  19. $service.Credentials = $creds        
  21. #Credentials Option 2    
  22. #service.UseDefaultCredentials = $true    
  24. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
  26. ## Code From http://poshcode.org/624  
  27. ## Create a compilation environment  
  28. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
  29. $Compiler=$Provider.CreateCompiler()  
  30. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
  31. $Params.GenerateExecutable=$False  
  32. $Params.GenerateInMemory=$True  
  33. $Params.IncludeDebugInformation=$False  
  34. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
  36. $TASource=@' 
  37.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  38.     public class TrustAll : System.Net.ICertificatePolicy { 
  39.       public TrustAll() {  
  40.       } 
  41.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
  42.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
  43.         System.Net.WebRequest req, int problem) { 
  44.         return true; 
  45.       } 
  46.     } 
  47.   } 
  48. '@   
  49. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
  50. $TAAssembly=$TAResults.CompiledAssembly  
  52. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
  53. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
  54. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
  56. ## end code from http://poshcode.org/624  
  58. ## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use    
  60. #CAS URL Option 1 Autodiscover    
  61. $service.AutodiscoverUrl($MailboxName,{$true})    
  62. "Using CAS Server : " + $Service.url     
  64. #CAS URL Option 2 Hardcoded    
  66. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
  67. #$service.Url = $uri      
  69. ## Optional section for Exchange Impersonation    
  71. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
  72. $ExportCollection = @()  
  73. Write-Host "Process Contacts"  
  74. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Contacts,$MailboxName)     
  75. $Contacts = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  77. $psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)   
  78. $PR_Gender = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(14925,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Short)  
  79. $psPropset.Add($PR_Gender)  
  81. #Define ItemView to retrive just 1000 Items      
  82. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)      
  83. $fiItems = $null      
  84. do{      
  85.     $fiItems = $service.FindItems($Contacts.Id,$ivItemView)   
  86.     [Void]$service.LoadPropertiesForItems($fiItems,$psPropset)    
  87.     foreach($Item in $fiItems.Items){       
  88.         if($Item -is [Microsoft.Exchange.WebServices.Data.Contact]){  
  89.             $expObj = "" | select DisplayName,GivenName,Surname,Gender,Email1DisplayName,Email1Type,Email1EmailAddress,BusinessPhone,MobilePhone,HomePhone,BusinessStreet,BusinessCity,BusinessState,HomeStreet,HomeCity,HomeState  
  90.             $expObj.DisplayName = $Item.DisplayName  
  91.             $expObj.GivenName = $Item.GivenName  
  92.             $expObj.Surname = $Item.Surname  
  93.             $expObj.Gender = ""  
  94.             $Gender = $null  
  95.             if($item.TryGetProperty($PR_Gender,[ref]$Gender)){  
  96.                 if($Gender -eq 2){  
  97.                     $expObj.Gender = "Male"   
  98.                 }  
  99.                 if($Gender -eq 1){  
  100.                     $expObj.Gender = "Female"   
  101.                 }  
  102.             }  
  103.             $BusinessPhone = $null  
  104.             $MobilePhone = $null  
  105.             $HomePhone = $null  
  106.             if($Item.PhoneNumbers -ne $null){  
  107.                 if($Item.PhoneNumbers.TryGetValue([Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessPhone,[ref]$BusinessPhone)){  
  108.                     $expObj.BusinessPhone = $BusinessPhone  
  109.                 }  
  110.                 if($Item.PhoneNumbers.TryGetValue([Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::MobilePhone,[ref]$MobilePhone)){  
  111.                     $expObj.MobilePhone = $MobilePhone  
  112.                 }     
  113.                 if($Item.PhoneNumbers.TryGetValue([Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::HomePhone,[ref]$HomePhone)){  
  114.                     $expObj.HomePhone = $HomePhone  
  115.                 }     
  116.             }             
  117.             if($Item.EmailAddresses.Contains([Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress1)){                  
  118.                 $expObj.Email1DisplayName = $Item.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress1].Name  
  119.                 $expObj.Email1Type = $Item.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress1].RoutingType  
  120.                 $expObj.Email1EmailAddress = $Item.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress1].Address  
  121.             }  
  122.             $HomeAddress = $null  
  123.             $BusinessAddress = $null  
  124.             if($item.PhysicalAddresses -ne $null){  
  125.                 if($item.PhysicalAddresses.TryGetValue([Microsoft.Exchange.WebServices.Data.PhysicalAddressKey]::Home,[ref]$HomeAddress)){  
  126.                     $expObj.HomeStreet = $HomeAddress.Street  
  127.                     $expObj.HomeCity = $HomeAddress.City  
  128.                     $expObj.HomeState = $HomeAddress.State  
  129.                 }  
  130.                 if($item.PhysicalAddresses.TryGetValue([Microsoft.Exchange.WebServices.Data.PhysicalAddressKey]::Business,[ref]$BusinessAddress)){  
  131.                     $expObj.BusinessStreet = $BusinessAddress.Street  
  132.                     $expObj.BusinessCity = $BusinessAddress.City  
  133.                     $expObj.BusinessState = $BusinessAddress.State  
  134.                 }  
  135.             }  
  137.             $ExportCollection += $expObj  
  138.         }  
  139.     }      
  140.     $ivItemView.Offset += $fiItems.Items.Count      
  141. }while($fiItems.MoreAvailable -eq $true)   
  143. $fnFileName = "c:\temp\" + $MailboxName + "-ContactsExport.csv" 
  144. $ExportCollection | Export-Csv -NoTypeInformation -Path $fnFileName 
  145. "Exported to " + $fnFileName  


Anonymous said...

Sometimes it's best to know the best tool to do the job. Want all of the contact info?

csvde -f filename -r "(objectClass-contacts)"

That gets you everything there is about a contact.

Glen Scales said...

That's fine for exporting an Active Directory Mail enabled contact but won't work for Exchange Objects,this script is for exporting Exchange Mailbox items eg Contact's located in a User's Contacts Folder.


Anonymous said...


great post, but I'm trying to export the user defined fields as well.

Some of our mailboxes use the user defined fields (for the contacts) to hold important data. Now they'd like this exported. Do you happen to know how I can get to these fields using Powershell and EWS?
Thanks in advance.

Anonymous said...

I am having a different problem - I am having a EWS object folder - with uniqueid etc
and can't seem to be able to convert it into a path - I'd like to convert it back into hexid or straight into a path for a specific folder.
I can't seem to get that to work at all. My whole script is running perfectly but I can't get the paths in any way. I am stuck with EWS objects with their uniqueids and parentids etc, but I need paths in the output.

Colin Matchett said...

Great script. How do i take input from a text file containing all the users?

MakuBa said...

How can i connect to a PublicFolder to export Contacts?

Anonymous said...

Great script. How can I pull contacts from a specific folder within the contents directory?


Ariel M said...

Its over a year since you posted this script and I hope you will respond.
I run the script and I receive errors.
here is the output after running the script:

PS C:\Users\user.DOMAIN> C:\Users\user.DOMAIN\Downloads\exportcnts\exportcnts.ps1

cmdlet Get-Credential at command pipeline position 1
Supply values for the following parameters:
Exception calling "AutodiscoverUrl" with "2" argument(s): "A valid SMTP address must be specified."
At C:\Users\user.DOMAIN\Downloads\exportcnts\exportcnts.ps1:61 char:1
+ $service.AutodiscoverUrl($MailboxName,{$true})
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ServiceValidationException

Using CAS Server :
Process Contacts
Exception calling "Bind" with "2" argument(s): "The Url property on the ExchangeService object must be set."
At C:\Users\user.DOMAIN\Downloads\exportcnts\exportcnts.ps1:75 char:1
+ $Contacts = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderi ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ServiceLocalException

Exception calling "FindItems" with "2" argument(s): "The element at position 0 is invalid
Parameter name: parentFolderIds"
At C:\Users\user.DOMAIN\Downloads\exportcnts\exportcnts.ps1:85 char:5
+ $fiItems = $service.FindItems($Contacts.Id,$ivItemView)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ArgumentException

Exception calling "LoadPropertiesForItems" with "2" argument(s): "Value cannot be null.
Parameter name: items"
At C:\Users\user.DOMAIN\Downloads\exportcnts\exportcnts.ps1:86 char:5
+ [Void]$service.LoadPropertiesForItems($fiItems,$psPropset)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ArgumentNullException

Exported to c:\-ContactsExport.csv

The first error is about Autodiscover, and I guess all the consequenting errors are related to that one.
Any suggestions?

Glen Scales said...

When you run the script you need to pass in the PrimarySMTP address of the Mailbox you want to run it against are you doing that ? what is cmdline your running ?

Ariel M said...

basically I want to do it for all my users, but as a start I will test it against my account.
the first step is to type "credentials", and there I type my user/email and password.
does it make sense?

Glen Scales said...

The way this script is written is to work against one mailbox if you want to run it against multiples you need to rewrite the script to cater for that.

currently the script gets the mailbox its going to run against from the parameter that passed into the script which is what this line does eg when you run the script use

./scriptname.ps1 yourmailbox@domain.com

3.$MailboxName = $args[0]

It will prompt for the credentials its going to use via

16.#Credentials Option 1 using UPN for the windows Account
17.$psCred = Get-Credential
18.$creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())
19.$service.Credentials = $creds

Ariel M said...

OK, got it. thanks.
It worked for a single mailbox.
I will try to modify it to work for multiple users a well.
Thanks anyway.

Ariel M said...

I modified the script ( I will publish it here or email you, as you choose) to export Contacts for all mailboxes.
However, I wonder if you can help me with a small little thing:
The script exports only items in the "Contacts" folder. What should I use to retrieve "Suggested Contacts" as well?

Ariel M said...

I found this:
I made the same modifications I did on your script and it exports Suggested Contacts and Cached Contacts for all mailboxes.
Thanks for everything.

Tyler Hoover said...

Sorry if I'm missing it, did someone post the modification for exporting from all mailboxes and creating a separate file for each?

Anonymous said...

Glen, thanks so much for sharing. I needed to export contacts from O365 across a couple hundred mailboxes. Your code gave me a good base to start from.

Anonymous said...

When I try and run this on an exchange 2010 server I'm getting the following error -
Exception calling "FindItems" with "2" argument(s): "Exchange Server doesn't support the requested version."

Is anyone else having this issue?