Some of the many ways to Export Outlook Contacts

Some of the many ways to Export Outlook Contacts

  •  
  •  
  •  
  •  
  •  
  •  

Just had this come up, so I thought I’d post it for whoever else needs to hear this.  I can quickly think of three ways to do this: One using Outlook, one using PowerShell, and one using both.

Since you came to my blog, you’re probably most interested in figuring out how to get PowerShell to do the dirty work for you without interacting with the user.  You can manage it in two ways–one, using the Outlook COM object model (which we’ll do first–but it has the same caveat as having an Outlook profile logged in as the user).  Then, we’ll look at an EWS way to accomplish the same task.  Read on!

Export Contacts from Outlook

It’s been this way since as long as I can remember, but in case you’re curious what you need to do, here it is.  Make sure you’ve got an Outlook profile configured as the identity you want to export.

  1. With Outlook open, select File | Open & Export | Import/Export.
  2. Select Export to a file.
  3. Select Comma Separated Values
  4. Select Contacts from the Outlook tree browser.
  5. Enter a path and filename.
  6. Remap and fields as necessary (changing column names).  99% of the world just clicks Next here.
  7. Press firmly, as you’re making 3 copies.

PowerShell – Outlook COM Object.

This isn’t very glamorous, but it is functional, and a great way to get familiar with automating Outlook as a COM object. With it, you can expose just about anything in the Outlook user session to PowerShell, including the OAB/GAL, mailbox items, and Contacts.

Here’s the code you’ll need to connect to an Outlook com object as the user and export the contacts.

You’ll potentially want to build a list of objects to exclude (such as cached GAL contacts, etc).  You can look in the Outlook folder export (above) or view your contacts as folders in Outlook to see what’s stored in each of these to determine what you want:

For purposes of the script, I am not doing any filtering, though it certainly can be done (you, for example, may want to just get “Suggested Contacts,” which would be an equivalent to the users in your nickname cache that you’ve sent messages to that aren’t stored in your contacts).  You can do some filtering to exclude folders, but we’re going to keep this one simple:

# Create Outlook COMObject
$Outlook = New-Object –comobject Outlook.Application

# 10 is the Contacts folder well-known ID
$Contacts = $Outlook.session.GetDefaultFolder(10).items

# Get all contact folders
$Contacts | Select Account, Anniversary, AssistantName, AssistantTelephoneNumber, AutoResolvedWinner, BillingInformation, Birthday, Body, Business2TelephoneNumber, BusinessAddress, BusinessAddressCity, BusinessAddressCountry, BusinessAddressPostalCode, BusinessAddressPostOfficeBox, BusinessAddressState, BusinessAddressStreet, BusinessCardType, BusinessFaxNumber, BusinessHomePage, BusinessTelephoneNumber, CallbackTelephoneNumber, CarTelephoneNumber, Categories, Children, Class, Companies, CompanyAndFullName, CompanyLastFirstNoSpace, CompanyLastFirstSpaceOnly, CompanyMainTelephoneNumber, CompanyName, ComputerNetworkName, ConversationTopic, CustomerID, Department, Email1Address, Email1AddressType, Email1DisplayName, Email2Address, Email2AddressType, Email2DisplayName, Email3Address, Email3AddressType, Email3DisplayName, FileAs, FirstName, FTPSite, FullName, FullNameAndCompany, Gender, GovernmentIDNumber, HasPicture, Hobby, Home2TelephoneNumber, HomeAddress, HomeAddressCity, HomeAddressCountry, HomeAddressPostalCode, HomeAddressPostOfficeBox, HomeAddressState, HomeAddressStreet, HomeFaxNumber, HomeTelephoneNumber, IMAddress, Importance, Initials, InternetFreeBusyAddress, IsConflict, ISDNNumber, IsMarkedAsTask, JobTitle, Journal, Language, LastFirstAndSuffix, LastFirstNoSpace, LastFirstNoSpaceAndSuffix, LastFirstNoSpaceCompany, LastFirstSpaceOnly, LastFirstSpaceOnlyCompany, LastModificationTime, LastName, LastNameAndFirstName, Links, MailingAddress, MailingAddressCity, MailingAddressCountry, MailingAddressPostalCode, MailingAddressPostOfficeBox, MailingAddressState, MailingAddressStreet, ManagerName, MiddleName, Mileage, MobileTelephoneNumber, NetMeetingAlias, NetMeetingServer, NickName, NoAging, OfficeLocation, OrganizationalIDNumber, OtherAddress, OtherAddressCity, OtherAddressCountry, OtherAddressPostalCode, OtherAddressPostOfficeBox, OtherAddressState, OtherAddressStreet, OtherFaxNumber, OtherTelephoneNumber, PagerNumber, PersonalHomePage, PrimaryTelephoneNumber, Profession, RadioTelephoneNumber, ReferredBy, ReminderOverrideDefault, ReminderPlaySound, ReminderSet, ReminderSoundFile, ReminderTime, Saved, SelectedMailingAddress, Sensitivity, Size, Spouse, Subject, Suffix, TaskCompletedDate, TaskDueDate, TaskStartDate, TaskSubject, TelexNumber, Title, ToDoTaskOrdinal, TTYTDDTelephoneNumber, UnRead, User1, User2, User3, User4, UserCertificate, WebPage, YomiCompanyName, YomiFirstName, YomiLastName | Export-Csv -NoTypeInformation .\Contacts.csv
Those are all the non-object fields that should display text-only.  It’s lots of stuff.

PowerShell – EWS

This one is a lot more in-depth, and I got inspiration, tips, help, and snippets from a number of sources, including the very prolific Glen Scales (since his blog has taught me just about everything I know about EWS), and the master of all things PowerShell, Jeff Hicks, whose basics on datatables I’m constantly referring to because I can’t seem to remember it.

I’ll clean this up and eventually post it to the gallery, but it’s 3am now and I do have customers to go see.

param (
 # Identity
 [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True, Position = 1)]$Identity,
 
 # Credential
 [Parameter(Mandatory = $true, HelpMessage = "Enter Admin Credential with ApplicationImpersonation and Mailbox Import Export roles")]
 [System.Management.Automation.CredentialAttribute()]
 $Credential = (Get-Credential),
 $ExchangeServer,
 $Output = "$($identity)_Contacts.csv",
 [bool]$Recursive,
 [ValidateSet('Exchange2007','Exchange2010','Exchange2013')]$Version = "Exchange2013"
 )

begin
{
 ## Functions
 # Write-Log
 function Write-Log([string[]]$Message, [string]$LogFile = $Script:LogFile, [switch]$ConsoleOutput, [ValidateSet("SUCCESS", "INFO", "WARN", "ERROR", "DEBUG")][string]$LogLevel)
 {
  $Message = $Message + $Input
  If (!$LogLevel) { $LogLevel = "INFO" }
  switch ($LogLevel)
  {
   SUCCESS { $Color = "Green" }
   INFO { $Color = "White" }
   WARN { $Color = "Yellow" }
   ERROR { $Color = "Red" }
   DEBUG { $Color = "Gray" }
  }
  if ($Message -ne $null -and $Message.Length -gt 0)
  {
   $TimeStamp = [System.DateTime]::Now.ToString("yyyy-MM-dd HH:mm:ss")
   if ($LogFile -ne $null -and $LogFile -ne [System.String]::Empty)
   {
    Out-File -Append -FilePath $LogFile -InputObject "[$TimeStamp] [$LogLevel] :: $Message"
   }
   if ($ConsoleOutput -eq $true)
   {
    Write-Host "[$TimeStamp] [$LogLevel] :: $Message" -ForegroundColor $Color
   }
  }
 }
 
 # Load EWS libraries
 function LoadEWS
 {
  
  # Locating EWS Managed API and loading
  Write-Progress "Testing for Preqresuisites"
  If (Test-Path 'C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll')
  {
   Write-Progress "Testing for Prerequisites" -Status "Found Exchange Web Services DLL."
   $WebServicesDLL = "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"
   Import-Module $WebServicesDLL
  }
  ElseIf
  ($filename = Get-ChildItem 'C:\Program Files' -Recurse -ea silentlycontinue | where { $_.name -eq 'Microsoft.Exchange.WebServices.dll' })
  {
   Write-Progress "Testing for Prerequisites" -Status "Found Exchange Web Services DLL at $($filename).FullName."
   $WebServicesDLL = $filename.FullName
   Import-Module $WebServicesDLL
  }
  ElseIf
  (!(Test-Path 'C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll'))
  {
   Write-Progress -Activity "Testing for Prerequisites" -Status "This requires the Exchange Web Services Managed API. Attempting to download and install."
   wget http://download.microsoft.com/download/8/9/9/899EEF2C-55ED-4C66-9613-EE808FCF861C/EwsManagedApi.msi -OutFile ./EwsManagedApi.msi
   msiexec /i EwsManagedApi.msi /qb
   Sleep 60
   If (Test-Path 'C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll')
   {
    Write-Progress "Testing for Prerequisites" -Status "Found Exchange Web Services DLL."
    $WebServicesDLL = "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"
    Import-Module $WebServicesDLL
   }
   Else
   {
    Write-Log -ConsoleOutput -LogLevel ERROR -Message "Please download the Exchange Web Services API and try again."
    Break
   }
  }
 }
 
 # Verify rights/roles
 function RoleCheck
 {
  # Check Management Roles 
  Write-Progress -Activity "Configure roles." -Status "Checking for Mailbox Import/Export and Application Impersonation roles."
  $ManagementRoles = Get-ManagementRoleAssignment -AssignmentMethod Direct -RoleAssignee $Credential.UserName
  If ($ManagementRoles -match "ApplicationImpersonation" -and $ManagementRoles -match "Mailbox Import Export")
  {
   Write-Progress -Activity "Configure roles." -Status "Correct management roles are granted."
  }
  Else
  {
   If (!($ManagementRoles -match "Mailbox Import Export"))
   {
    Write-Progress -Activity "Configure roles." -Status "You do not currently have the Mailbox Import/Export Role."
    Try { New-ManagementRoleAssignment -User $Credential.UserName -Role "Mailbox Import Export" -ea stop}
    Catch
    {
     $Message = $_.Exception.Message
     Write-Log -ConsoleOutput -Message "Cannot grant the Mailbox Import/Export role." -LogLevel ERROR
     Write-Log -ConsoleOutput -Message $Message -LogLevel ERROR
    }
   }
   If (!($ManagementRoles -match "ApplicationImpersonation"))
   {
    Write-Progress -Activity "Configure roles." -Status "You do not currently have the ApplicationImpersonation Role."
    Try { New-ManagementRoleAssignment -User $Credential.UserName -Role "ApplicationImpersonation" -ea stop}
    Catch
    {
     $Message = $_.Exception.Message
     Write-Log -ConsoleOutput -LogLevel ERROR -Message "Cannot grant ApplicationImpersonation role."
     Write-Log -ConsoleOutput -LogLevel ERROR -Message $Message
    }
   }
   If ($Message -like "*customization*") { Write-Log -LogLevel WARN -Message "You must run Enable-OrganizationCustomization manually." -ConsoleOutput }
   Write-Log -ConsoleOutput -LogFile $LogFile -LogLevel WARN -Message "We have attempted to grant you the required roles. Please log out of your Office 365 session, log back in, and try again."
   Break
  }
 }
 
 # Connect to EWS/Autodiscover endpoint
 function ConnectToEWS
 {
  Write-Progress -Activity "Connecting to AutoDiscover endpoint for $($Credential.UserName)."
  switch ($Version)
  {
   Exchange2007 { $script:ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1 }
   Exchange2010 { $script:ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2 }
   Exchange2013 { $script:ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1 } 
  }
  $global:Service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)
  $global:creds = New-Object System.Net.NetworkCredential($Credential.UserName.ToString(), $Credential.GetNetworkCredential().password.ToString())
  $global:Service.Credentials = $creds
  $global:Service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $PrimarySmtp)
  $global:Service.AutodiscoverUrl($Credential.Username, { $true })
 }
 
 # Try to find an Exchange Server
 function LocateExchange
 {
  If (!$ExchangeServer)
  {
   #$global:ProgressPreference = "SilentlyContinue"
   Write-Progress -Activity "Connect to Exchange" -Id 1
   If (Get-Command Get-ADObject -ea silentlycontinue)
   {
    [array]$ExchangeServers = (Get-ADObject -Filter { objectCategory -eq "msExchExchangeServer" } -SearchBase (Get-ADRootDSE).configurationNamingContext).Name
    If ($ExchangeServers)
    {
     Write-Progress -Activity "Connect to Exchange" -Status "No Exchange server specified.  Attempting to locate Exchange servers in Configuration container."
     $SuccessfulTest = @()
     ForEach ($obj in $ExchangeServers)
     {
      Write-Progress -Activity "Connect to Exchange" -Status "No Exchange server specified.  Attempting to locate Exchange servers in Configuration container." -CurrentOperation "Testing: $($obj)"
      $Result = Try { Test-NetConnection $obj -ea stop -wa silentlycontinue -Port 443 }
      catch { $Result = "FAIL" }
      If ($Result.TcpTestSucceeded -eq $True)
      {
       Write-Progress -Activity "Searching for Exchange Servers..." -Status "Successfully connected to discovered Exchange Server: $($obj)"
       $SuccessfulTest += $obj
      }
      if ($Result.TcpTestSucceeded -eq $False)
      {
       # Write-Log -LogFile $Logfile -LogLevel ERROR -Message "Unable to connect to discovered Exchange Server: $($obj)."
      }
     }
     If ($SuccessfulTest)
     {
      $script:ExchangeServer = (Get-Random $SuccessfulTest)
      # Write-Log -Logfile $Logfile -LogLevel SUCCESS -Message "Selected Exchange Server $($ExchangeServer)."
      Write-Progress -Activity "Selected Exchange Server $($ExchangeServer)." -Id 2 -Completed
      $script:Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://$($ExchangeServer)/PowerShell -Credential $Credential -Authentication Kerberos -AllowRedirection
     }
     Else
     {
      If (!$ExchangeServer)
      {
       $script:Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $Credential -Authentication Basic -AllowRedirection
      }
     }
    }
   }
   Else
   {
    $global:ProgressPreference = "Continue"
    $script:Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $Credential -Authentication Basic -AllowRedirection
   }
  }
  Else
  {
   $global:ProgressPreference = "Continue"
   $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $Credential -Authentication Basic -AllowRedirection  
  }
  $global:ProgressPreference = "Continue"
  return $ExchangeServer 
 }
 
 # Constants
 $script:Fields = @{
  "Assistant's Phone"      = "Phone:AssistantPhone";
  "Anniversary"       = "WeddingAnniversary";
  "Birthday"        = "Birthday";
  "Business City"       = "Address:Business:City";
  "Business Country/Region"   = "Address:Business:CountryOrRegion";
  "Business Fax"       = "Phone:BusinessFax";
  "Business Phone"      = "Phone:BusinessPhone";
  "Business Phone 2"      = "Phone:BusinessPhone2";
  "Business Postal Code"     = "Address:Business:PostalCode";
  "Business State"      = "Address:Business:State";
  "Business Street"      = "Address:Business:Street";
  "Callback"        = "Phone:CallBack";
  "Car Phone"        = "Phone:CarPhone";
  "Company"        = "CompanyName";
  "Company Main Phone"     = "Phone:CompanyMainPhone";
  "Department"       = "Department";
  "E-mail Address"      = "Email:EmailAddress1";
  "E-mail 2 Address"      = "Email:EmailAddress2";
  "E-mail 3 Address"      = "Email:EmailAddress3";
  "First Name"       = "GivenName";
  "Home Street"       = "Address:Home:Street";
  "Home City"        = "Address:Home:City";
  "Home State"       = "Address:Home:State";
  "Home Fax"        = "Phone:HomeFax";
  "Home Phone"       = "Phone:HomePhone";
  "Home Phone 2"       = "Phone:HomePhone2";
  "Home Postal Code"      = "Other:Home:PostalCode";
  "Home Country/Region"     = "Address:Home:CountryOrRegion";
  "ISDN"         = "Phone:ISDN";
  "Initials"        = "Initials";
  "Job Title"        = "JobTitle";
  "Last Name"        = "Surname";
  "Manager's Name"      = "Manager";
  "Middle Name"       = "MiddleName";
  "Mileage"        = "Mileage";
  "Mobile Phone"       = "Phone:MobilePhone";
  "Notes"         = "Body";
  "Office Location"      = "OfficeLocation";
  "Other City"       = "Address:Other:City";
  "Other Country/Region"     = "Address:Other:CountryOrRegion";
  "Other Fax"        = "Phone:OtherFax";
  "Other Phone"       = "Phone:OtherTelephone";
  "Other Postal Code"      = "Address:Other:PostalCode";
  "Other State"       = "Address:Other:State";
  "Other Street"       = "Address:Other:Street";
  "Pager"         = "Phone:Pager";
  "Primary Phone"       = "Phone:PrimaryPhone";
  "Profession"       = "Profession";
  "Radio Phone"       = "Phone:RadioPhone";
  "Spouse"        = "SpouseName";
  "Telex"         = "Phone:Telex";
  "TTY/TDD Phone"       = "Phone:TtyTddPhone";
  "Web Page"        = "BusinessHomePage";
  "Contact Picture File"     = "Method:SetContactPicture"
 }
 #>
 
 LoadEWS
 ConnectToEWS
 
 If (!(Get-Command Get-Mailbox -ea silentlycontinue))
 {
  If (!$ExchangeServer)
  {
   LocateExchange
  }
  Import-PSSession $Session
 }
 
 RoleCheck
 # Check parameters
 If ($PSBoundParameters.ContainsKey('Identity') -and $MyInvocation.PipelineLength -eq 1)
 {
  [object[]]$Identity = Get-Mailbox $Identity
 }

}
process
{
 If ($PSBoundParameters.ContainsKey('Identity') -and $MyInvocation.PipelineLength -eq 1)
 {
  $DisplayName = $Identity.Name
  $global:PrimarySmtp = $Identity.PrimarySmtpAddress.ToString()
 }
 
 # Enumerate contacts function
 function LoadContacts([Microsoft.Exchange.WebServices.Data.Folder]$folder, [string]$path)
 {
  if ($folder.TotalCount -gt 0)
  {
   $TotalItems = $folder.TotalCount
   $i = 1
   
   $ItemOffset = 0;
   $View = new-object Microsoft.Exchange.WebServices.Data.ItemView(100, $ItemOffset)
   while (($results = $folder.FindItems($View)).Items.Count -gt 0)
   {
    $response = $service.LoadPropertiesForItems($results, [Microsoft.Exchange.WebServices.Data.PropertySet]::FirstClassProperties)
    
    foreach ($global:item in $results)
    {
     Write-Progress -Activity "Exporting contacts for $($Identity)" -Status "Exporting $($item.DisplayName)" -PercentComplete (($i/$TotalItems) * 100)
     if ($item.ItemClass -eq "IPM.Contact")
     {
      # Start a new data table row for the current contact
      $global:DataRow = $ContactsTable.NewRow()
            
      foreach ($FieldMap in $Fields.GetEnumerator())
      {
       if ($FieldMap.Value -like "*:*")
       {
        # Split array on : in $Fields hash
        [array]$FieldMapArray = $FieldMap.Value.Split(":")
        switch ($FieldMapArray[0])
        {
         "Email"
         {
          $global:DataRow.($FieldMap.Key) = $item.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::($FieldMapArray[1])].Address
         }
         "Phone"
         {
          $global:DataRow.($FieldMap.Key) = $item.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::($FieldMapArray[1])]
         }
         "Address"
         {
          $global:PhysicalAddressEntry = $item.PhysicalAddresses[[Microsoft.Exchange.WebServices.Data.PhysicalAddressKey]::($FieldMapArray[1])]
          $global:DataRow.($FieldMap.Key) = $PhysicalAddressEntry.($FieldMapArray[2])
         }
         "Method"
         {
          switch ($FieldMapArray[1])
          {
           "SetContactPicture"
           {
            # Do nothing, as this is a text output and we're not going to manipulate binary data 
           }
          }
         }
         
        }
       }
       else
       {
        $global:DataRow.($FieldMap.Key) = $item.($FieldMap.Value)
       }
      }
      # Add the row to the table
      $global:ContactsTable.Rows.Add($DataRow)
     }
     else
     {
      # Not a properly-formed contact, so do nothing
     }
     # Iterate the counter
     $i++
    }
    $ItemOffset += $results.Items.Count
    $View = new-object Microsoft.Exchange.WebServices.Data.ItemView(100, $ItemOffset)
   }
  }
  
  try
  {
   $ItemOffset = 0;
   
   if ($Recursive)
   {
    # Process subfolders
    $folderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(2147483647)
    $subfolders = $folder.FindFolders($folderView)
    foreach ($subfolder in $subfolders)
    {
     try
     {
      LoadContacts $subfolder ($path + "\" + $subfolder.DisplayName)
     }
     catch { "Error processing folder: " + $subfolder.DisplayName }
    }
   }
  }
  catch
  {
   throw
  }
 }
 
 # Create a data table
 $global:ContactsTable = New-Object System.Data.DataTable "Contacts"
 foreach ($FieldMap in $Fields.GetEnumerator())
 {
  $global:DataColumn = New-Object System.Data.DataColumn $FieldMap.Key, ([string])
  $global:ContactsTable.Columns.Add($DataColumn)
 }
 
 $global:ContactFolder = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Contacts, $PrimarySmtp)
 $global:Rootfolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service, $ContactFolder)
 
 # Process contact entries
 LoadContacts $Rootfolder ""
  
 $ContactsTable | Export-Csv -NoTypeInformation $Output -Force
}
End
{
 Write-Log -ConsoleOutput -Message "All done!" -LogLevel INFO
}

Copy/paste into a PS1, save a credential object ($Cred=Get-Credential), and then off you go!  Be sure to specify the identity of the mailbox whose contact you want to export:

Good luck!

 

Published by Aaron Guilmette

Helping companies conquer inferior technology since 1997. I spend my time developing and implementing technology solutions so people can spend less time with technology. Specialties: Active Directory and Exchange consulting and deployment, Virtualization, Disaster Recovery, Office 365, datacenter migration/consolidation, cheese.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.