Reader’s Problems: Can’t send a Calendar sharing message
Sometimes an error message doesn’t get you very far, especially when you think you’ve covered the basics. This is one of those cases where a reader was part way through implementing Federated Free/Busy and Calendar Sharing between Exchange On-Premises and Live@EDU using the instructions in my guide, and when trying to share a calendar from an on-premises mailbox to a Live@EDU mailbox:
Hi Steve
Sorry - I'm stuck with this. Do you mind me bothering you some more?
I've set it all up, and sent my colleague (who has a 2010 mailbox) a calendar sharing offer and request from my admin@live.contoso.edu mailbox.
I've set his external email address on Live to in1150@exch.contoso.edu.
The invitation is delivered fine. If he opens the calendar of admin@live.contoso.edu from the message, he can see it, and it gets updated dynamically. However, when he tries to share his calendar with admin@live.contoso.edu (using the request in the message), he gets an error message:
This is very odd, because sharing is definitely set up with our domain…
That error again, for the benefit of anyone else experiencing this – “Your organization is not set up for secure sharing with this recipient”.
The first thing we did was check the basics for sharing were setup correctly which they were, but on further inspection and investigation into the Exchange On-Premises environment, the solution was pretty simple. For sharing to work you need to set the ExternalURL for the Exchange Web Services (EWS) virtual directory.
To set the ExternalURL attribute on the EWS Virtual Directory, use the following command against each of the Client Access Servers providing the facility to Internet clients, replacing servername and mail.contoso.edu with appropriate values for your organization:
Steve
How to get info about your ActiveSync, EWS and WebDAV clients before migrating to Exchange 2010
When you're moving from Exchange 2007 to Exchange 2010 there are a few gotchas that it's worth watching out for in the planning stages before you change over Client Access Namespaces or start migrating Mailboxes. If you don't you might find you have broken Exchange 2007 clients, due to the CAS changes, and find some clients can't connect after their mailboxes are migrated.
When it comes to the Windows version of Outlook, you can find out about currently logged-in clients using the Get-LogonStatistics command; however you also need to be aware of other clients using protocols like ActiveSync, Exchange Web Services and of course WebDAV.
ActiveSync clients will mostly be fine with CAS Namespace changes, as depending on version they should be automatically proxied from Exchange 2010 back to Exchange 2007 or should autodiscover; however some clients like the iPhone don't work properly and you need to consider a workaround.
Exchange Web Services clients, for the most part, shouldn't have issues thanks to AutoDiscover, however it's good to understand what clients you have out there already so you can test and plan for any issues. There's a number of iPhone apps that use EWS out there that your users may have bought and I've seen some funny issues myself with the EWS version Mac Mail on Snow Leopard that may require a client visit.
Finally, WebDAV. There's little to explain about WebDAV apart from it's not supported in Exchange 2010! You need to find these clients (think Entourage 2004 and 2008) and upgrade them.
Unfortunately there isn't anything built-in to Exchange 2007 or 2010 to examine this data, but the good news is it should all be available to you via the IIS log files. Whilst logparser is pretty good, personally I wanted the data collected and grouped all in one go ready to use. And that's where this script came from…
What information does the script output provide?
The output from the script is in CSV format, so it's easy to use in Excel for further data processing. The CSV file itself has the following fields:
Username: Logon name of the user
ActiveSyncUser: If the user uses an ActiveSync mobile device
ActiveSyncProxyUser: If the user is currently being proxied through to this client access server
ActiveSyncClients: A semi-colon separated list of the clients in use, eg. iPad;htchero;
ActiveSyncLastAccess: Last date found for ActiveSync use
EWSUser: If the user uses some sort of Exchange Web Services client
EWSPCOutlook: Version information if the user has the Windows version of Outlook 2007 or 2010
EWSMacMail: Version information if user has the EWS version of Mac Mail
EWSMacOutlook: Version information if the user has Mac Outlook 2011
EWSEntourage: Version information if the user has Entourage 2008, Web Services Edition
EWSOther: A semi-colon separated list of any other EWS clients the user has
EWSLastAccess: Last date found for EWS use
WebDavUser: If the user uses some sort of WebDAV Exchange client
WebDavClient: A semi-colon separated list of WebDAV client software and versions in use
WebDavLastAccess: Last date found for WebDAV use
In an actual export, the above looks a little bit like this (apart from the blurry usernames!)
How to use the script
Example one - Parses log files from the default log directory "C:\WINDOWS\system32\LogFiles\W3SVC1" to "C:\output.csv"
Example two - Parses the last 30 days of log files from the current directory to cas_results.csv in the current directory, and saves the state to state.xml in the current directory (could take a LONG time!)
Limitations
So far, only tested with Exchange 2007 IIS log files.. Any probs, let me know.
Script Code
.SYNOPSIS
Parses IIS Log files for records relating to ActiveSync, Exchange Web Services and WebDAV
Steve Goodman
.DESCRIPTION
Looks at logs files and produces a CSV file with summary data listing:
* Activesync clients, whether it is proxied by another CAS and ActiveSync device type
* EWS clients with columns for PC Outlook, Mac Outlook and Entourage 2008 EWS Edition and other clients
* WebDAV clients including client versions.
.PARAMETER LogFilePath
Path to base directory of IIS Log files, e.g. "C:\WINDOWS\system32\LogFiles\W3SVC1"
.PARAMETER Days
How many days log files to look back by
.PARAMETER OutputCSVFile
File to write CSV output to
.PARAMETER SaveStateFile
File to save or load internal state to. Useful when looking at multiple CAS servers or to update output later on based on only more recent logfiles
.EXAMPLE
Parses log files from the default log directory "C:\WINDOWS\system32\LogFiles\W3SVC1" to "C:\output.csv"
.\ExIISLogParser.ps1
.EXAMPLE
Parses the last 30 days of log files from the current directory to cas_results.csv in the current directory, and saves the state to state.xml in the current directory
.\ExIISLogParser.ps1 -LogFilePath ".\" -Days 30 -OutputCSVFile ".\cas_results.csv" -SaveStateFile ".\state.xml"
#>
param(
[parameter(Position=0,Mandatory=$false,ValueFromPipeline=$false,HelpMessage="Full path to log file directory")][string]$LogFilePath = "C:\WINDOWS\system32\LogFiles\W3SVC1",
[parameter(Position=1,Mandatory=$false,ValueFromPipeline=$false,HelpMessage="Last write date of logs to search back by")][int]$Days=0,
[parameter(Position=2,Mandatory=$false,ValueFromPipeline=$false,HelpMessage="CSV file for output")][string]$OutputCSVFile="C:\output.csv",
[parameter(Position=3,Mandatory=$false,ValueFromPipeline=$false,HelpMessage="Script state XML file")][string]$SaveStateFile
)
if (!(Test-Path $LogFilePath))
{
throw "LogFilePath does not exist"
}
if ((Test-Path $OutputCSVFile))
{
throw "OutputCSVFile already exists"
}
[hashtable]$Users = @{}
if ($SaveStateFile)
{
if ((Test-Path $SaveStateFile))
{
$Users = Import-Clixml -Path $SaveStateFile
}
}
$EarliestLogDate = (Get-Date).Subtract([timespan]"$($Days).00:00").Date
[array]$Files = Get-Item -Path "$($LogFilePath)\*.log" | Where {$_.LastWriteTime -gt $EarliestLogDate}
for ($i = 0; $i -lt $Files.Count; $i++)
{
Write-Progress -id 1 -activity "Overall Progress" -status "File $($i) of $($Files.Count)" -percentComplete (($i/$Files.Count*100)*0.9);
Write-Progress -id 2 -activity "Log $($Files[$i].Name)" -status "Loading" -percentComplete 0
$Log = Get-Content $Files[$i] | Where {$_ -like "*/Microsoft-Server-ActiveSync/*" -or $_ -like "*/EWS/*" -or $_ -like "*/exchange/*"}
for ($j = 0; $j -lt $Log.Count; $j++)
{
Write-Progress -id 2 -activity "Current Log $($Files[$i].Name)" -status "Working" -percentComplete ($j/$Log.Count*100);
# Clear Username
$Username=$null
# Split up log line
$arrLog = $Log[$j].Split(" ");
# Extract username first
if ($arrLog[5] -eq "/Microsoft-Server-ActiveSync/Proxy")
{
foreach ($QSPart in $arrLog[6].Split("&"))
{
if ($QSPart -like "User=*")
{
$Username =($QSPart.Split("="))[1]
}
}
} else {
# Not proxied, just get the username provided direct
$Username = $arrLog[8]
}
# Only process if it's an authenticated user
if ($Username)
{
# Take off domain or UPN suffix
if ($Username -like "*@*")
{
$Username = ($Username.Split("@"))[0]
} elseif ($Username -like "*\*") {
$Username = ($Username.Split("\"))[1];
} elseif ($Username -like "*%40*") {
$Username = ($Username.Split("%40"))[0]
} elseif ($Username -like "*/*") {
$Username = ($Username.Split("/"))[1]
}
# Make username lower case
$Username = $Username.ToLower()
# Update data for user
switch -wildcard ($arrLog[5])
{
"/EWS/*"
{
# Get EWS Client
$EWSClient = $arrLog[10].Replace("+"," ")
# Check if already found or create new hashtable item
if (!$Users.Contains($Username))
{
[hashtable]$obj = @{ActiveSyncUser=0; ActiveSyncProxyUser=0; ActiveSyncClients=@{}; ActiveSyncLastAccess=""; EWSUser=0; EWSClients=@{}; EWSLastAccess=""; WebDavUser=0; WebDavClients=@{}; WebDavLastAccess=""}
$Users.Add($Username,$obj)
}
# Set variables
$Users[$Username]["EWSUser"]=1
if (!$Users[$Username]["EWSClients"].Contains($EWSClient))
{
$Users[$Username]["EWSClients"].Add($EWSClient,1)
}
$Users[$Username]["EWSLastAccess"]=$arrLog[0]
break
}
"/Microsoft-Server-ActiveSync/*"
{
# Check if already found or create new hashtable item
if (!$Users.Contains($Username))
{
[hashtable]$obj = @{ActiveSyncUser=0; ActiveSyncProxyUser=0; ActiveSyncClients=@{}; ActiveSyncLastAccess=""; EWSUser=0; EWSClients=@{}; EWSLastAccess=""; WebDavUser=0; WebDavClients=@{}; WebDavLastAccess=""}
$Users.Add($Username,$obj)
}
# Set variables
# Is a ActiveSync user?
$Users[$Username]["ActiveSyncUser"]=1
# Is a proxy user?
if ($arrLog[5] -eq "/Microsoft-Server-ActiveSync/Proxy")
{
$Users[$Username]["ActiveSyncProxyUser"]=1
}
# Client Info
foreach ($QSPart in $arrLog[6].Split("&"))
{
if ($QSPart -like "DeviceType=*")
{
$ASClient = ($QSPart.Split("="))[1]
if (!$Users[$Username]["ActiveSyncClients"].Contains($ASClient))
{
$Users[$Username]["ActiveSyncClients"].Add($ASClient,1)
}
}
}
# Last Access Date
$Users[$Username]["ActiveSyncLastAccess"]=$arrLog[0]
break
}
"/exchange/*"
{
# Check if already found or create new hashtable item
if (!$Users.Contains($Username))
{
[hashtable]$obj = @{ActiveSyncUser=0; ActiveSyncProxyUser=0; ActiveSyncClients=@{}; ActiveSyncLastAccess=""; EWSUser=0; EWSClients=@{}; EWSLastAccess=""; WebDavUser=0; WebDavClients=@{}; WebDavLastAccess=""}
$Users.Add($Username,$obj)
}
# Set variables
$Users[$Username]["WebDavUser"]=1
$WDClient = $arrLog[10].Replace("+"," ")
if (!$Users[$Username]["WebDavClients"].Contains($WDClient))
{
$Users[$Username]["WebDavClients"].Add($WDClient,1)
}
$Users[$Username]["WebDavLastAccess"]=$arrLog[0]
break
}
}
}
}
}
Write-Progress -id 1 -activity "Overall Progress" -status "Preparing Output" -percentComplete 95;
[array]$Output=$null
$Users.GetEnumerator() | Foreach {
$OutputItem = New-Object Object
$OutputItem | Add-Member NoteProperty Username $_.Key
$OutputItem | Add-Member NoteProperty ActiveSyncUser $_.Value["ActiveSyncUser"]
$OutputItem | Add-Member NoteProperty ActiveSyncProxyUser $_.Value["ActiveSyncProxyUser"]
$ActiveSyncClients = $null
$_.Value["ActiveSyncClients"].GetEnumerator() | % { $ActiveSyncClients += "$($_.Key); "}
$OutputItem | Add-Member NoteProperty ActiveSyncClients $ActiveSyncClients
$OutputItem | Add-Member NoteProperty ActiveSyncLastAccess $_.Value["ActiveSyncLastAccess"]
$OutputItem | Add-Member NoteProperty EWSUser $_.Value["EWSUser"]
$EWSPCOutlook = ""
$EWSMacMail = ""
$EWSMacOutlook = ""
$EWSEntourage = ""
$EWSOther = ""
$_.Value["EWSClients"].GetEnumerator() | foreach {
$EWSClient = $_.Key
switch -wildcard ($EWSClient)
{
"Microsoft Office*"
{
$EWSPCOutlook=$EWSClient
break
}
"Mac*ExchangeWebServices*"
{
$EWSMacMail=$EWSClient
break
}
"MacOutlook*"
{
$EWSMacOutlook=$EWSClient
break
}
"Entourage*"
{
$EWSEntourage=$EWSClient
break
}
default
{
$EWSOther+="$($EWSClient); "
}
}
}
$OutputItem | Add-Member NoteProperty EWSPCOutlook $EWSPCOutlook
$OutputItem | Add-Member NoteProperty EWSMacMail $EWSMacMail
$OutputItem | Add-Member NoteProperty EWSMacOutlook $EWSMacOutlook
$OutputItem | Add-Member NoteProperty EWSEntourage $EWSEntourage
$OutputItem | Add-Member NoteProperty EWSOther $EWSOther
$OutputItem | Add-Member NoteProperty EWSLastAccess $_.Value["EWSLastAccess"]
$OutputItem | Add-Member NoteProperty WebDavUser $_.Value["WebDavUser"]
$WebDavClients=$null
$_.Value["WebDavClients"].GetEnumerator() | % { $WebDavClients += "$($_.Key); "}
$OutputItem | Add-Member NoteProperty WebDavClients $WebDavClients
$OutputItem | Add-Member NoteProperty WebDavLastAccess $_.Value["WebDavLastAccess"]
$Output += $OutputItem
}
if ($SaveStateFile)
{
$Users = Export-Clixml -Path $SaveStateFile
}
$Output[1..($Output.Count)] | Select Username,ActiveSyncUser,ActiveSyncProxyUser,ActiveSyncClients,ActiveSyncLastAccess,EWSUser,EWSPCOutlook,EWSMacMail,EWSMacOutlook,EWSEntourage,EWSOther,EWSLastAccess,WebDavUser,WebDavClients,WebDavLastAccess | Export-Csv -Path $OutputCSVFile -NoClobber -NoTypeInformation
Script Download
Using Powershell to import contacts into Exchange and Outlook Live
When performing migrations between different systems, there’s always the case where the tools available don’t do the job out of the box – and although IMAP migration tools for Exchange and Outlook Live can be great for moving mail, there isn’t a decent free solution for importing contacts.
For a recent migration from a Unix system (Dovecot + SquirrelMail) to Outlook Live, I came across this very scenario. While Microsoft provide IMAP migration tools to move mail to Outlook Live (which I was lucky enough to beta test before it was widely available), no tools are provided to move other data such as contacts and calendars.
While this isn’t the actual code I used (I wrote my original code in C#), I’ve re-visited what I’ve done and listened to what others say they need. What I’ve heard is it would be useful to have some Powershell code that out-of-the box can import contacts into any Exchange mailbox. The reason that Powershell is the right language for code like this is that it’s fairly easy for the enterprising administrator to modify to their needs.
Getting started
Before you can run this script, there are a few things you need to set up first. Don’t worry though! It’s nothing too onerous – all is needed to get going is two things:
- Setup of EWS impersonation, unless you only want to work with accounts you know the username/password for.
- Installing the Managed API DLL onto your computer.
Setup of Exchange Web Services Impersonation
Exchange Web Services is the programmatic interface to each user’s Exchange mailbox. Most actions that can be performed in Outlook can be performed via EWS – so much so, in fact, that Apple’s Mail.app in Snow Leopard and the latest update for Entourage 2008 use EWS as the backend for the mail clients themselves.
If you’re planning on doing a mass-import of contacts, or don’t know the user’s password you’re importing, then setup of EWS impersonation is the step you need to take to allow a trusted account to switch to the user you want to import.
In Exchange 2010, setup of Exchange impersonation is managed via RBAC. Full details of how to setup impersonal are on the Microsoft site, but if you just want to get it setup for a single admin/service account, org-wide, use the following command substituing serviceaccount for your service account:
On Exchange 2007, it’s very different, and is managed by AD permissions on individual Client Access Servers. Again – full details on the Microsoft site, but to add the permission to all CAS servers, the following code will suffice, again substituting serviceaccount for your Service account:
Installing the Exchange Web Services Managed API
Next, whatever environment you are in, you need a copy of the DLL file that provides the bits and pieces that make this all work. The easiest way to get up and running is to download the API (choosing the right version for your platform), and install it to it’s default location.
Download the EWS Managed API from the Microsoft site
Once downloaded and installed, you should be good to go with script.
Using the script Import-MailboxContacts.ps1
So, you’ve got impersonation setup if needed, got the EWS DLL on your machine. You should be good to go!
For each mailbox you want to import contacts from you need a separate CSV file. The format of the file is intentionally the same as Outlook will export in, mainly because a lot of apps already export in the format (Gmail does, for example) so you should find it easy to get test data, but if you haven’t I’ve included a sample file to get you started.
Along with the data, you also need, at a minimum, the mailbox email address to import to. This is used when impersonating, and for Autodiscover. If you’re not logged on as the user you want to use for the import, you can specify a username and password. Additionally, there’s a variety of options to specify the EWS Url manually, switching to the Exchange 2007 version of the API etc.
Examples
The first example is a straightforward import into the current logged-on user’s mailbox. We’re specifying the Email Address and the URL to EWS (to bypass Autodiscover):
As you can see in the above example – it’s all fairly straightforward. You’ll also see the output shows the contacts that have been created – you can use standard Powershell pipeling to export this data, pipe it to another command or filter it.
The second example is slightly more complicated, and reflects the main use case. We’re connecting to a remote exchange organisation, using Autodiscover to find the EWS address, enabling impersonation and providing credentials at the command line:
In addition to the options above, you can also specify the parameters -Exchange2007 $true and use -EWSManagedApiDLLFilePath to specify a different path the the EWS DLL.
Powershell Code
Finally, you’ll need a copy of the code to do all this. At the bottom of the post you’ll find a ZIP file containing the Import-MailboxContacts.ps1 file and a sample CSV file, and below you’ll see the code itself. Happy importing!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | param([string]$CSVFileName,[string]$EmailAddress,[string]$Username,[string]$Password,[string]$Domain,[bool]$Impersonate,[string]$EwsUrl,[string]$EWSManagedApiDLLFilePath,[bool]$Exchange2007); # # Import-MailboxContacts.ps1 # # By Steve Goodman, Use at your own risk. # # Parameters # Mandatory: # -CSVFileName : Filename of the CSV file to import contacts for this user from. Same format as Outlook Export. # -EmailAddress : Account SMTP email address. Required, but only used when impersonating or with Autodiscover - otherwise uses the user you login as # Optional: # -Impersonate : Set to $true to use impersonation. # -Username : The username to use. If this isn't specified (along with Password), attempts to use the logged on user. # -Password : Used with above # -Domain : Used with above - optional. # -EwsUrl : The URL for EWS if you don't want to use Autodiscover. Typically https://casserver/EWS/Exchange.asmx # -EWSManagedApiDLLFilePath : (Optional) Overwrite the filename and path to the DLL for EWS Managed API. By default, uses the default install location. # -Exchange2007 : Set to $true to use the Exchange 2007 SP1+ version of the Managed API. # # Contact Mapping - this maps the attributes in the CSV file (left) to the attributes EWS uses. # NB: If you change these, please note "First Name" is specified at line 102 as a required attribute and # "First Name" and "Last Name" are hard coded at lines 187-197 when constructing NickName and FileAs. $ContactMapping=@{ "First Name" = "GivenName"; "Middle Name" = "MiddleName"; "Last Name" = "Surname"; "Company" = "CompanyName"; "Department" = "Department"; "Job Title" = "JobTitle"; "Business Street" = "Address:Business:Street"; "Business City" = "Address:Business:City"; "Business State" = "Address:Business:State"; "Business Postal Code" = "Address:Business:PostalCode"; "Business Country/Region" = "Address:Business:CountryOrRegion"; "Home Street" = "Address:Home:Street"; "Home City" = "Address:Home:City"; "Home State" = "Address:Home:State"; "Home Postal Code" = "Other:Home:PostalCode"; "Home Country/Region" = "Address:Home:CountryOrRegion"; "Other Street" = "Address:Other:Street"; "Other City" = "Address:Other:City"; "Other State" = "Address:Other:State"; "Other Postal Code" = "Address:Other:PostalCode"; "Other Country/Region" = "Address:Other:CountryOrRegion"; "Assistant's Phone" = "Phone:AssistantPhone"; "Business Fax" = "Phone:BusinessFax"; "Business Phone" = "Phone:BusinessPhone"; "Business Phone 2" = "Phone:BusinessPhone2"; "Callback" = "Phone:CallBack"; "Car Phone" = "Phone:CarPhone"; "Company Main Phone" = "Phone:CompanyMainPhone"; "Home Fax" = "Phone:HomeFax"; "Home Phone" = "Phone:HomePhone"; "Home Phone 2" = "Phone:HomePhone2"; "ISDN" = "Phone:ISDN"; "Mobile Phone" = "Phone:MobilePhone"; "Other Fax" = "Phone:OtherFax"; "Other Phone" = "Phone:OtherTelephone"; "Pager" = "Phone:Pager"; "Primary Phone" = "Phone:PrimaryPhone"; "Radio Phone" = "Phone:RadioPhone"; "TTY/TDD Phone" = "Phone:TtyTddPhone"; "Telex" = "Phone:Telex"; "Anniversary" = "WeddingAnniversary"; "Birthday" = "Birthday"; "E-mail Address" = "Email:EmailAddress1"; "E-mail 2 Address" = "Email:EmailAddress2"; "E-mail 3 Address" = "Email:EmailAddress3"; "Initials" = "Initials"; "Office Location" = "OfficeLocation"; "Manager's Name" = "Manager"; "Mileage" = "Mileage"; "Notes" = "Body"; "Profession" = "Profession"; "Spouse" = "SpouseName"; "Web Page" = "BusinessHomePage"; "Contact Picture File" = "Method:SetContactPicture" } # CSV File Checks # Check filename is specified if (!$CSVFileName) { throw "Parameter CSVFileName must be specified"; } # Check file exists if (!(Get-Item -Path $CSVFileName -ErrorAction SilentlyContinue)) { throw "Please provide a valid filename for parameter CSVFileName"; } # Check file has required fields and check if is a single row, or multiple rows $SingleItem = $false; $CSVFile = Import-Csv -Path $CSVFileName; if ($CSVFile."First Name") { $SingleItem = $true; } else { if (!$CSVFile[0]."First Name") { throw "File $($CSVFileName) must specify at least the field 'First Name'"; } } # Check email address if (!$EmailAddress) { throw "Parameter EmailAddress must be specified"; } if (!$EmailAddress.Contains("@")) { throw "Parameter EmailAddress does not appear valid"; } # Check EWS Managed API available if (!$EWSManagedApiDLLFilePath) { $EWSManagedApiDLLFilePath = "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll" } if (!(Get-Item -Path $EWSManagedApiDLLFilePath -ErrorAction SilentlyContinue)) { throw "EWS Managed API not found at $($EWSManagedApiDLLFilePath). Download from http://bit.ly/9O7gLo"; } # Load EWS Managed API [void][Reflection.Assembly]::LoadFile("C:\Program Files\Microsoft\Exchange\Web Services\1.0\Microsoft.Exchange.WebServices.dll"); # Create Service Object if ($Exchange2007) { $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1) } else { $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010) } # Set credentials if specified, or use logged on user. if ($Username -and $Password) { if ($Domain) { $service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials($Username,$Password,$Domain); } else { $service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials($Username,$Password); } } else { $service.UseDefaultCredentials = $true; } # Set EWS URL if specified, or use autodiscover if no URL specified. if ($EwsUrl) { $service.URL = New-Object Uri($EwsUrl); } else { try { $service.AutodiscoverUrl($EmailAddress); } catch { throw; } } # Perform a test - try and get the default, well known contacts folder. if ($Impersonate) { $service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress); } try { $ContactsFolder = [Microsoft.Exchange.WebServices.Data.ContactsFolder]::Bind($service, [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Contacts); } catch { throw; } # Add contacts foreach ($ContactItem in $CSVFile) { # If impersonate is specified, do so. if ($Impersonate) { $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."First Name" -and $ContactItem."Last Name") { $ExchangeContact.NickName = $ContactItem."First Name" + " " + $ContactItem."Last Name"; } elseif ($ContactItem."First Name" -and !$ContactItem."Last Name") { $ExchangeContact.NickName = $ContactItem."First Name"; } elseif (!$ContactItem."First Name" -and $ContactItem."Last Name") { $ExchangeContact.NickName = $ContactItem."Last Name"; } $ExchangeContact.DisplayName = $ExchangeContact.NickName; $ExchangeContact.FileAs = $ExchangeContact.NickName; $BusinessPhysicalAddressEntry = New-Object Microsoft.Exchange.WebServices.Data.PhysicalAddressEntry; $HomePhysicalAddressEntry = New-Object Microsoft.Exchange.WebServices.Data.PhysicalAddressEntry; $OtherPhysicalAddressEntry = New-Object Microsoft.Exchange.WebServices.Data.PhysicalAddressEntry; # 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; } "Phone" { $ExchangeContact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::($MappingArray[1])] = $ContactItem.$Key; } "Address" { switch ($MappingArray[1]) { "Business" { $BusinessPhysicalAddressEntry.($MappingArray[2]) = $ContactItem.$Key; $ExchangeContact.PhysicalAddresses[[Microsoft.Exchange.WebServices.Data.PhysicalAddressKey]::($MappingArray[1])] = $BusinessPhysicalAddressEntry; } "Home" { $HomePhysicalAddressEntry.($MappingArray[2]) = $ContactItem.$Key; $ExchangeContact.PhysicalAddresses[[Microsoft.Exchange.WebServices.Data.PhysicalAddressKey]::($MappingArray[1])] = $HomePhysicalAddressEntry; } "Other" { $OtherPhysicalAddressEntry.($MappingArray[2]) = $ContactItem.$Key; $ExchangeContact.PhysicalAddresses[[Microsoft.Exchange.WebServices.Data.PhysicalAddressKey]::($MappingArray[1])] = $OtherPhysicalAddressEntry; } } } "Method" { switch ($MappingArray[1]) { "SetContactPicture" { if (!$Exchange2007) { if (!(Get-Item -Path $ContactItem.$Key -ErrorAction SilentlyContinue)) { throw "Contact Picture File not found at $($ContactItem.$Key)"; } $ExchangeContact.SetContactPicture($ContactItem.$Key); } } } } } } else { # It's a direct mapping - simple! if ($ContactMapping[$Key] -eq "Birthday" -or $ContactMapping[$Key] -eq "WeddingAnniversary") { [System.DateTime]$ContactItem.$Key = Get-Date($ContactItem.$Key); } $ExchangeContact.($ContactMapping[$Key]) = $ContactItem.$Key; } } } # Save the contact $ExchangeContact.Save(); # 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; } |


