使用PowerShell和EWS写入/更新公用文件夹联系人
使用PowerShell和EWS写入/更新公用文件夹联系人。
引言
现有的PowerShell脚本示例似乎并没有包含成功管理Exchange公用文件夹中联系人的所有部分。下面我的脚本中的大部分设置和准备工作与现有示例类似,但我整理了其余部分,并添加了用于处理公用文件夹结构中联系人的逻辑。
背景
我们正在替换与Exchange 2003/2007交互的VBScript,并准备迁移到Exchange 2010/2013。以下是使我能够不仅使用PowerShell将新联系人导入Exchange,而且能够维护现有联系人的基础知识。
使用代码
此示例设置EWS接口服务,定义一个处理联系人的函数,然后导入并循环处理要维护的联系人的CSV文件。它还演示了其他一些PowerShell语法项目,包括使用#作为注释行开头,`作为续行字符,以及其他一些基本函数。
此脚本使用PowerShell v3.0编写,运行在Windows 7桌面上,并与Windows 2003服务器上的Exchange 2007交互。
首先,我们需要建立与EWS程序集的链接,并定义Exchange版本。
## 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
接下来,我们定义一个用于连接到Exchange服务的服务变量。可以使用默认凭据或指定凭据进行连接。
## 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
如果您连接到的服务器存在证书错误,或者您使用外部名称证书连接到服务器的内部名称,则需要添加代码来告诉脚本忽略证书错误。
## 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提供两种处理Exchange服务器URL的方法:您可以指定实际URL或基于电子邮件地址自动发现它。
## 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"
接下来,我们将定义ENUM值,这些值用于访问存储在数组对象中的Exchange属性。例如,Exchange存储多种类型的电话号码、地址和电子邮件地址。在这里,我只想访问EmailAddress1、BusinessPhone和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
基本设置和定义现在已经完成,接下来我们创建一个PowerShell函数来处理找到的CSV地址条目,并将它们添加到分层公用文件夹结构中。
我们的函数已定义,接受添加或维护电子邮件地址所需的每个参数。
处理公用文件夹目录树的诀窍是使用.FindFolders方法遍历目录结构的每一层,以搜索结构中的下一层。
在此示例中,联系人的存储目录结构如下:
\公用文件夹\公司联系人\地区\州\公司\
#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) } }
上述函数非常复杂,但可分解为以下步骤:
- 初始化控制向Exchange添加必要的文件夹和联系人的变量。
- 遍历四层文件夹以查找公司文件夹中的联系人。
- 确定联系人是否存在。
- 根据在目录结构中找到的文件夹或级别设置创建标志。
- 创建必要的文件夹(公司文件夹创建为“联系人文件夹”)。
- 维护或写入新的联系人。
以下是导入CSV文件并将待处理的联系人发送到ProcessContact函数的逻辑。我们只处理包含地区值的行。
$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 } }
这里可能有一些可以改进的地方,因为我刚开始使用PowerShell和EWS。但是这个脚本包含了我需要的基本内容。
历史
由于我刚开始使用PowerShell和EWS,我花了太多时间搜索合适的文章并反复试验以组装一个可工作的脚本。由于我大量依赖互联网资源来获取示例和想法,我觉得有义务分享我的成果以帮助其他人。
2014-09-25 - 创作此文章
2014-09-29 - 将代码分解以更好地描述主要脚本部分。