Exchange Contacts Mirroring & Global Distribution Automation
Exchange Contacts Mirroring (Global Distribution) Automation
Summary
This automation synchronizes contacts between licensed user mailboxes and a shared mailbox folder named All Contacts using Microsoft Graph app-only authentication.
It provides:
- One centrally maintained contact set.
- Automatic distribution back to users.
- Traceability in
personalNoteswith sync metadata.
Functional Overview
Step 1: User -> Shared mailbox
- Reads each user's default contacts (
/users/{id}/contacts). - Writes into shared mailbox folder All Contacts.
- Also stamps metadata back to source user contacts when needed.
Step 2: Shared mailbox -> User
Matching and deduplication
Contacts are matched in this order:
SyncIdinpersonalNotes- Primary email (
emailAddresses[0].address) displayName(fallback)
Metadata Stamping (personalNotes)
The script maintains sync tags in personalNotes:
SyncId=<guid>CreatedBy=<upn-or-id>LastUpdatedAt=<utc-iso8601>LastUpdatedBy=<upn-or-id>
Notes behavior:
- Existing non-sync note text is preserved.
- Old sync tags are replaced with fresh values.
SyncIdis generated if missing.CreatedByis preserved from existing synced contact when available.
Update Behavior
UpdateExisting is enabled by default.
An existing target contact is only updated if:
- Data has changed, and
- Target contact is older than source (
lastModifiedDateTimecomparison).
If no change or target is newer/equal, it is skipped.
User Scope / Test Mode
User list resolution:
- Per-tenant test users (if configured).
- Otherwise all licensed users from Graph:
/users?$filter=assignedLicenses/any(x:x/skuId ne null)
Configuration
Preferred: full JSON config
Use AUTOMATION_SYNCCONTACTS_CONFIG_JSON:
{
"MicrosoftTenants": [
{
"TenantId": "tenant-id-or-domain",
"ClientId": "app-client-id",
"ClientSecret": "app-client-secret",
"GlobalAddressBookUserId": "shared.contacts@contoso.com",
"TestUserList": ["user1@contoso.com"]
}
],
"DryRun": "true"
}
Multi-tenant via separate env var
Use AUTOMATION_SYNCCONTACTS_MICROSOFT_TENANTS_JSON with tenant array.
Backward-compatible single-tenant env vars
AUTOMATION_SYNCCONTACTS_TENANT_IDAUTOMATION_SYNCCONTACTS_CLIENT_IDAUTOMATION_SYNCCONTACTS_CLIENT_SECRETAUTOMATION_SYNCCONTACTS_GLOBAL_ADDRESS_BOOK_USER_IDAUTOMATION_SYNCCONTACTS_DRY_RUN(true/false)
Optional tenant test-user map env var
Works even with full config JSON:
AUTOMATION_SYNCCONTACTS_TEST_USER_LIST_BY_TENANT_JSON
Example:
{
"contoso.onmicrosoft.com": ["user1@contoso.com", "user2@contoso.com"],
"fabrikam.onmicrosoft.com": ["user3@fabrikam.com"]
}
Lookup keys include tenant identifiers like TenantId, PrimaryDomain, TenantDomain.
Prerequisites
Required Microsoft Graph Permissions
Application permissions:
Contacts.ReadWriteUser.Read.AllorDirectory.Read.All
Dry Run
Set DryRun / AUTOMATION_SYNCCONTACTS_DRY_RUN to true to simulate actions without writes.
The script logs what would be created/updated and prints creation summaries.
CI/CD
Designed for automation pipelines (for example GitLab CI/CD). Store secrets as protected CI/CD variables.
Troubleshooting
- No tenants configured: set
AUTOMATION_SYNCCONTACTS_CONFIG_JSONorAUTOMATION_SYNCCONTACTS_MICROSOFT_TENANTS_JSON. - Insufficient privileges: missing admin consent or Graph permissions.
- Shared mailbox not found: incorrect
GlobalAddressBookUserId. - No licensed users found: tenant has no users matching license filter.
- Config JSON parse errors: invalid JSON in env vars.
Security Notes
- Keep
ClientSecretin secure secret storage. - Restrict app permissions to minimum required.
- Limit and audit access to shared mailbox contact data.