Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web

Using PowerShell and EWS to write/update public folder contacts

0.00/5 (No votes)
29 Sep 2014CPOL3 min read 20.7K   3  
Using PowerShell and EWS to write/update public folder contacts.

Introduction

Existing examples of PowerShell scripts didn't seem to include all the pieces to successfully manage contacts in an exchange public folder. Much of the setup and preperation in my script below is similar to existing examples, but I put together the rest of the pieces and added the logic to work with contacts in a public folder structure.

Background

We were replacing our VBScripts interfacing with exchange 2003/2007, and preparing to migrate into exchange 2010/2013.  Below are the basics that allowed me to not only import new contacts into Exchange using PowerShell, but also to add the ability to maintain existing contacts.

Using the code

This example sets up the EWS interface service, defines a process contacts function, then imports and loops through a CSV of contacts to be maintained. It also demonstrates several other PowerShell syntax items, including using # to start a comment line ` as continuation line character, as well as some other basic functions.

This script was written using PowerShell v3.0, running on a windows 7 desktop and interfacing with exchange 2007 on a Windows 2003 server

First we need to establish a link to the EWS assembly, and define the exchange version.

## Load Managed API dll
Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"  

## Set Exchange Version 
$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1  

Next we define a service variable that is used to connect to the exchange service. The connection can be made with default or specified credentials.

## Create Exchange Service Object
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)  

## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials  

#Credentials Option 1 using UPN for the windows Account 
#$creds = New-Object System.Net.NetworkCredential("adminuser","adminpass", "admindomain")
#$service.Credentials = $creds   
#Credentials Option 2 
$service.UseDefaultCredentials = $true  

 

If the server you are connecting to has a certificate error, or if you are connecting to the internal name of server with an external name certificate, you need to add code telling your script to ignore certificate errors.

 

## Choose to ignore any SSL Warning issues caused by Self Signed Certificates  

## Code From http://poshcode.org/624
## Create a compilation environment
$Provider=New-Object Microsoft.CSharp.CSharpCodeProvider
$Compiler=$Provider.CreateCompiler()
$Params=New-Object System.CodeDom.Compiler.CompilerParameters
$Params.GenerateExecutable=$False
$Params.GenerateInMemory=$True
$Params.IncludeDebugInformation=$False
$Params.ReferencedAssemblies.Add("System.DLL") | Out-Null

$TASource='
    namespace Local.ToolkitExtensions.Net.CertificatePolicy{
    public class TrustAll : System.Net.ICertificatePolicy {
        public TrustAll() {
        }
        public bool CheckValidationResult(System.Net.ServicePoint sp,
        System.Security.Cryptography.X509Certificates.X509Certificate cert,
        System.Net.WebRequest req, int problem) {
        return true;
        }
    }
    }
'
$TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)
$TAAssembly=$TAResults.CompiledAssembly

## We now create an instance of the TrustAll and attach it to the ServicePointManager
$TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")
[System.Net.ServicePointManager]::CertificatePolicy=$TrustAll

## end code from http://poshcode.org/624

 

PowerShell offers two ways to handle the exchange servers URL, you can specify the actual URL or auto-discover it based on an email address.

## 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 
#CAS URL Option 1 Autodiscover 
#$service.AutodiscoverUrl("email@domain.com",{$true}) 
#"Using CAS Server : " + $Service.url   

#CAS URL Option 2 Hardcoded 
$service.url = "https://mail.mycompanyurl.com/EWS/Exchange.asmx"

 

Next we will define the ENUM values, these are used to access Exchange properties that are stored in an array object. For example Exchange stores multiple types of phone numbers, addresses and email addresses. Here I only want to access EmailAddress1, BusinessPhone, and Business Address.

#Define ENUM values for accessing enumerated contact properties
$enumEmailAddress1Value = [Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress1
$enumBusinessPhoneValue = [Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessPhone
$enumPhysicalAddressValue = [Microsoft.Exchange.WebServices.Data.PhysicalAddressKey]::Business

 

The basic setup and definition is now done, next we create a PowerShell function to process the CSV address entries that are found, and add them to a hierarchical public folder structure.

Our function is defined, accepting each of the parameters required for adding or maintaining an email address.

The trick to dealing with a public folder directory tree, is to traverse through each level of the directory structure using the .FindFolders method to search for the next layer in the structure. 

In this example, the contacts are stored in the following directory structure is:

      \Public Folders\Company Contacts\Territory\State\Company\

 

#ProcessContacts Function, accessed for each row coming from import CSV
Function ProcessContact([string]$territory,  [string]$state,     [string]$company,  [string]$prefix,    [string]$firstName,  
                        [string]$middleName, [string]$lastName,  [string]$busPhone, [string]$busAddress, [string]$busCity,   
                        [string]$busState,   [string]$busZip,    [string]$title,    [string]$email)
{
    $fullName = (($firstName + " " + $middleName).trim() + " " + $lastName).trim()
    echo "Processing: $territory, $state, $company,  $fullName"

    $ccFolder = ""
    $ttFolder = ""
    $stFolder = ""
    $coFolder = ""

    $createTT = ""
    $createST = ""
    $createCO = ""

    $updateContact = ""
    $writeContact = ""

    $c = ""

    #Need to traverse through each level of the public folders, setting flag to create folders if missing levels are encountered
    #Set Folder Id to Public Folder Root, so service will know we are searching public folders
    $folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::PublicFoldersRoot)   

    #Begin search looking for CompanyContacts folder, within PublicFoldersRoot folder
    $Contacts = "CompanyContacts"
    $ccFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)  #Search should only retreive one result
    $ccSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$contacts)
    $ccFindFolderResults = $service.FindFolders($folderid, $ccSearchFilter, $ccFolderView)
    if ($ccFindFolderResults.TotalCount -gt 0){
        $ccFolder = $ccFindFolderResults.Folders[0]

        #Next search for Regional Manager folder, within CompanyContacts folder
        $ttFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)  #Search should only retreive one result
        $ttSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$territory)
        $ttFindResults = $ccFolder.FindFolders($ttSearchFilter, $ttFolderView)
        if ($ttFindResults.TotalCount -gt 0){
            $ttFolder = $ttFindResults.Folders[0]

            #Next search for State folder, within Regional Manager folder
            $stFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)  #Search should only retreive one result
            $stSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$state)
            $stFindResults = $ttFolder.FindFolders($stSearchFilter, $stFolderView)
            if ($stFindResults.TotalCount -gt 0){
                $stFolder = $stFindResults.Folders[0]

                #Next search for Company folder, within State folder
                $coFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)  #Search should only retreive one result
                $coSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$company)
                $coFindResults = $stFolder.FindFolders($coSearchFilter, $coFolderView)
                if ($coFindResults.TotalCount -gt 0){
                    $coFolder = $coFindResults.Folders[0]

                    #List the contants for the found company
                    $cFolderView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1)
                    $cSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$fullName)
                    $cFindResults = $coFolder.FindItems($cSearchFilter, $cFolderView)
                    if ($cFindResults.TotalCount -gt 0){
                        $c = $cFindResults.Items[0]
                        $updateContact = "Y"
                    } else {
                        $writeContact = "Y"
                    }
                } else {
                    echo "Company Not Found - $company"
                    $CreateCO = "Y"
                    $writeContact = "Y"
                }
            } else {
                echo "State not found - $state"
                $createST = "Y"
                $CreateCO = "Y"
                $writeContact = "Y"
            }
        } else {
            echo "Territory not found - $territory"
            $createTT = "Y"
            $createST = "Y"
            $CreateCO = "Y"
            $writeContact = "Y"
        }
    }
    if ($createTT -eq "Y") {  #Create Territory Folder if it was not found
        $f = New-Object Microsoft.Exchange.WebServices.Data.Folder($service)
        $f.DisplayName = $territory
        $f.Save($ccFolder.Id)
        $ttFolder = $f
    }
    if ($createST -eq "Y") { #Create State Folder if it was not found
        $f = New-Object Microsoft.Exchange.WebServices.Data.Folder($service)
        $f.DisplayName = $state
        $f.Save($ttFolder.Id)
        $stFolder = $f
    }
    if ($createCO -eq "Y") { #Create Company Folder if it was not found  (This will contain contacts, so must be ContactsFolder)
        $f = New-Object Microsoft.Exchange.WebServices.Data.ContactsFolder($service)
        $f.DisplayName = $company
        $f.Save($stFolder.Id)
        $coFolder = $f
    }
    if ($updateContact -eq "Y") { #Update Contact if it was found
        $c.DisplayName = $fullName
        $c.Surname = $lastName
        $c.MiddleName = $middleName
        $c.GivenName = $firstName
        $c.EmailAddresses[$enumEmailAddress1Value] = $email
        $c.CompanyName = $company
        $enumBusinessPhoneValue = [Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessPhone
        $c.PhoneNumbers[$enumBusinessPhoneValue] = $busPhone
        $pa1 = $c.PhysicalAddresses[$enumPhysicalAddressValue]
        $pa1.Street = $busAddress
        $pa1.City = $busCity
        $pa1.State = $busState
        $pa1.PostalCode = $busZip
        $enumPhysicalAddressValue = [Microsoft.Exchange.WebServices.Data.PhysicalAddressKey]::Business
        $c.PhysicalAddresses[$enumPhysicalAddressValue] = $pa1
        $c.JobTitle = $title
        Echo "Updating: $($c.DisplayName)  Phone: $($c.PhoneNumbers[$enumBusinessPhoneValue])"
        $c.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite)
    }
    if ($writeContact -eq "Y") {  #Write Contact if it was not found
        $c = New-Object Microsoft.Exchange.WebServices.Data.Contact($service)
        $c.DisplayName = $fullName
        $c.Surname = $lastName
        $c.MiddleName = $middleName
        $c.GivenName = $firstName
        $c.FileAsMapping = [Microsoft.Exchange.WebServices.Data.FileAsMapping]::SurnameCommaGivenName
        $c.EmailAddresses[$enumEmailAddress1Value] = $email
        $c.CompanyName = $company
        $c.PhoneNumbers[$enumBusinessPhoneValue] = $busPhone
        $pa1 = new-Object Microsoft.Exchange.WebServices.Data.PhysicalAddressEntry
        $pa1.Street = $busAddress
        $pa1.City = $busCity
        $pa1.State = $busState
        $pa1.PostalCode = $busZip
        $c.PhysicalAddresses[$enumPhysicalAddressValue] = $pa1
        $c.JobTitle = $title
        Echo "Writing: $($c.DisplayName)  Phone: $($c.PhoneNumbers[$enumBusinessPhoneValue])"
        $c.Save($coFolder.Id)
    }
}

The above function is pretty complicated, but breaks down into the following steps:

  1. Initialize variables controlling adding necessary folders and contacts to exchange
  2. Traverse through 4 levels of folders to look for the contact in the company folder
  3. Determineif the contact exists or not.
  4. Set creating flags based on which folders or levels were found the directory structure
  5. Create necessary folders (Company folder is created as a "Contact Folder")
  6. Maintain or Write the new contact.

 

Below is the logic that imports the CSV file, and sends the pending contacts into the ProcessContact function. We will only process rows that include a territory value.

 

$data = Import-Csv c:\temp1\ContactImport.csv -delimiter ","

foreach($record in $data) {
    if ($record.Territory -gt "") {
        ProcessContact -territory  $record.Territory       -state      $record.State `
                       -company    $record.Company         -prefix     $record.Prefix `
                       -firstName  $record.FirstName       -middleName $record.MiddleName `
                       -lastName   $record.LastName        -busPhone   $record.BusinessPhone `
                       -busAddress $record.BusinessAddress -busCity    $record.BusinessCity `
                       -busState   $record.BusinessState   -busZip     $record.BusinessZip `
                       -title      $record.Title           -email      $record.Email
    }
}

 

There may be a few things here that can be improved upon, as I am new to PowerShell and EWS.  But this script contains the basics that got me where I needed to be.

 

 

History

Being new to PowerShell and EWS, I spent way too much time searching for appropriate articles and trial and error to assemble a working script. As I relied heavily on internet resources for examples and ideas, I felt obligated to return my results to assist others.

2014-09-25 - Authored this post

2014-09-29 - Broke up code to offer better descriptions of the primary script parts.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)