I’m working with a client who needs a few hundred contacts added to the GAL in Microsoft 365. Easy! Just upload a CSV and you’re good to go!
Shortest. Blog Post. Ever.
Hold on, not so fast – once you get into it, it’s really not as easy as this.
Problem 1. You have to split your contact list up into batches of 40. Not a huge problem, but still it’s another step that needs to be taken manually.
Problem 2. Microsoft gives you a CSV template to use, but what you can put in this CSV and what you actually see in Microsoft 365 are two different things. Plus, you simply cannot have a comma anywhere in the contact details. So this means no addresses like Unit 1, 234 Any Street. No job titles like Manager – Warranty, Service & Spares etc.
Problem 3. There are fields in the CSV that aren’t exposed for the contact, plus there are a lot of fields that contacts can use that are not in this CSV.
Problem 4. Importing in batches via the Microsoft 365 admin centre is slow.
So, PowerShell to the rescue.
As it turns out, there are few different commands for ExchangeOnline PowerShell to work with contacts.
First of all, there is New-MailContact
– this makes a contact in the GAL, but you can only give it very limited information like email, first name, last name and display name.
Then there’s Set-Contact
but this still doesn’t have everything – in particular it doesn’t expose any of the custom attributes, so we also need to use Set-MailContact
Still with me?
So, what I needed to do was go through the CSV and run the following 3 commands for each contact record:
New-MailContact `
-Name $_.ContactName `
-ExternalEmailAddress $_.Email `
-FirstName $_.FirstName `
-LastName $_.LastName
Set-Contact `
-Identity $_.Email `
-Company $_.Company `
-Phone $_.OfficePhone `
-MobilePhone $_.MobilePhone `
-Title $_.Title `
-WebPage $_.Website `
-StreetAddress $_.StreetAddress `
-City $_.City `
-State $_.State `
-PostalCode $_.Postcode `
-CountryOrRegion $_.Country
Set-MailContact `
-Identity $_.Email `
-CustomAttribute1 $_.Category
Wrapping it all up in a script, and putting in some basic error handling, I ended up with this:
# Define the path to your CSV file
$csvPath = "/path/to/contacts.csv"
# Import the CSV
$contacts = Import-Csv -Path $csvPath
foreach ($contact in $contacts) {
try {
# Check if the contact already exists
$existingContact = Get-MailContact -Identity $contact.Email -ErrorAction SilentlyContinue
if ($existingContact) {
Write-Host "Contact already exists: $($contact.ContactName) <$($contact.Email)>" -ForegroundColor Yellow
continue
}
# Create a new mail contact
New-MailContact `
-Name $contact.ContactName `
-ExternalEmailAddress $contact.Email `
-FirstName $contact.FirstName `
-LastName $contact.LastName `
-ErrorAction Stop
# Build parameter hashtable for Set-Contact
$contactParams = @{
Identity = $contact.Email
}
if ($contact.Company) { $contactParams["Company"] = $contact.Company }
if ($contact.OfficePhone) { $contactParams["Phone"] = $contact.OfficePhone }
if ($contact.MobilePhone) { $contactParams["MobilePhone"] = $contact.MobilePhone }
if ($contact.Title) { $contactParams["Title"] = $contact.Title }
if ($contact.Website) { $contactParams["WebPage"] = $contact.Website }
if ($contact.StreetAddress) { $contactParams["StreetAddress"] = $contact.StreetAddress }
if ($contact.City) { $contactParams["City"] = $contact.City }
if ($contact.State) { $contactParams["State"] = $contact.State }
if ($contact.Postcode) { $contactParams["PostalCode"] = $contact.Postcode }
if ($contact.Country) { $contactParams["CountryOrRegion"] = $contact.Country }
Set-Contact @contactParams -ErrorAction Stop
# Set custom attribute if present
if ($contact.Category) {
Set-MailContact `
-Identity $contact.Email `
-CustomAttribute1 $contact.Category `
-ErrorAction Stop
}
Write-Host "Successfully created and configured contact: $($contact.ContactName)" -ForegroundColor Green
}
catch {
Write-Host "Failed to process contact: $($contact.ContactName) <$($contact.Email)>. Error: $_" -ForegroundColor Red
}
}