Sync-GalToMutipleTenants.ps1
<#PSScriptInfo
.VERSION 1.1 .GUID 09adead3-5a12-4461-92b5-6b0adf8fc50e .AUTHOR SHAHIN BASHEER - shahinbasheer@hotmail.com .COMPANYNAME TECHNICALLY POSSIBLE .COPYRIGHT https://technicallypossible.com .TAGS GalSync ContactSync OneToManyHYBRID ExchangeOnline .LICENSEURI https://technicallypossible.com .PROJECTURI https://technicallypossible.com .ICONURI .EXTERNALMODULEDEPENDENCIES ExchangeOnlineManagement / Onprem Exchange Shell .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES FullSync, GroupSync & UpdateGuestUser included .DESCRIPTION SINGLE EXCHANGE ONPREMISE EXCHANGE TO MULTIPLE O365 TENANT CONTACT SYNC Script reads recipients from each OU and create contacts in the respective tenants. Each OU should uniquely identify a department & domain Address Lists should be precreated in the tenant # GUIDE # https://technicallypossible.com # SCRIPT PATH # Script should be copied to C:\POWERSHELL\Sync-GalToMultipleTenants folder. Path can be changed using $ScriptPath variable # FILES # Script Requires 2 CSV files (Tenants.csv & OUSyncMaps.csv) in the \Configs Subfolder Tenants.csv # Sample File Tenant,AppID,CertThumbPrint FirstTenant.onmicrosoft.com,abcdefghi-1234-5678-abcd-123456784321,ABCD699BA1E5A5E12347B4B12ABCD7CC01FABCD SecondTenant.onmicrosoft.com,cdefghiab-1234-5678-cdef-023456784320,ABCD699BA1E5A5E12347B4B12ABCD7CC01FABCD OuSyncMaps.csv # Sample File DeptCode,ADOU,ParentTenant,GlobalHide DEPT1_CODE,OU=Sync,OU=First Department,OU=HOSTING,DC=techposs,DC=internal,FirstTenant.onmicrosoft.com DEPT2_CODE,OU=Sync,OU=Second Department,OU=HOSTING,DC=techposs,DC=internal,SecondTenant.onmicrosoft.com # MODULES # OnPremise Exchange Management Shell should be instlledin the server Latest ExchangeOnlineManagement Modules should be installed in the Server # CERTS # Certificate should be installed in the user store/ # PARAMETERS # -FullSync - Compares each contact in the tenant./ use -FullSync Switch -NoGroupSync - Do not create contact for each group object / use -NoGroupSync Switch -UpdateGuestUser - Update the mail users came from Azure AD guest user / use -UpdateGuestUser switch #> [CmdletBinding()] param ( [switch] $FullSync, [switch] $NoGroupSync, [switch] $UpdateGuestUser ) function Get-Now () { $now = (Get-Date).DateTime Return ($now).ToString() } function Add-ExContact { param ( [Parameter(Mandatory)] $Contact ) try { $Unique = Get-Random -Minimum 10000 -Maximum 99999 $Name = "" $Name = $Contact.DisplayName + "_" + $Unique if ($Name.Length -gt 62) { $Name = $Name.Substring(0,62) } New-MailContact -Name $Name -DisplayName $Contact.DisplayName -Alias ($Contact.PrimarySmtpAddress.local).tostring() -ExternalEmailAddress ($Contact.PrimarySmtpAddress).tostring() -ErrorAction Stop Start-Sleep -Seconds 0.5 if ( $Contact.Type -notlike "*Group") { Get-Contact ($Contact.PrimarySmtpAddress).tostring() | Set-Contact -Title $Contact.Title -Company $Contact.Company -Department $Contact.Department -Office $Contact.Office -Phone $Contact.Phone } $X500 = $null $X500 = "X500:" + $Contact.LegacyExchangeDN Get-MailContact ($Contact.PrimarySmtpAddress).tostring() | Set-MailContact -CustomAttribute12 $Contact.OU -CustomAttribute13 $Contact.Type -CustomAttribute14 $TimeStamp -CustomAttribute15 $DeptCode -Emailaddresses @{Add=$X500} (Get-Now) + ": Created NEW Contact :: " + $Contact.PrimarySmtpAddress | Out-File $TenantLogFile -Append } catch { (Get-Now) + ": Failed to Create or Update Contact :: " + $Contact.PrimarySmtpAddress + "`r`n" + $Error[0] | Out-File $TenantLogFile -Append } } function Update-ExContact { param ( [Parameter(Mandatory)] $Contact ) try { if ( $Contact.Type -notlike "*Group") { Get-Contact ($Contact.PrimarySmtpAddress).tostring() | Set-Contact -Title $Contact.Title -Company $Contact.Company -Department $Contact.Department -Office $Contact.Office -Phone $Contact.Phone } #Start-Sleep -Seconds 0.5 $X500 = $null $X500 = "X500:" + $Contact.LegacyExchangeDN Get-MailContact ($Contact.PrimarySmtpAddress).tostring() | Set-MailContact -DisplayName $Contact.DisplayName -CustomAttribute12 $Contact.OU -CustomAttribute13 $Contact.Type -CustomAttribute14 $TimeStamp -CustomAttribute15 $DeptCode -Emailaddresses @{Add=$X500} (Get-Now) + ": Updated the Contact :: " + $Contact.PrimarySmtpAddress | Out-File $TenantLogFile -Append } catch { (Get-Now) + ": Failed to Update Contact :: " + $Contact.PrimarySmtpAddress + "`r`n" + $Error[0] | Out-File $TenantLogFile -Append } } function Remove-ExContact { param ( [Parameter(Mandatory)] $Contact ) try { Remove-MailContact $Contact -Confirm:$false -ErrorAction Stop (Get-Now) + ": Removed Contact :: " + $Contact | Out-File $TenantLogFile -Append } catch { (Get-Now) + ": Failed to Remove Contact :: " + $Contact + "`r`n" + $Error[0] | Out-File $TenantLogFile -Append } } ########################### SET THE SCRIPT PATH ##################################### $ScriptPath = "C:\SCHEDULER\Sync-GalToMultipleTenants" Set-Location $ScriptPath ########################### BEGIN CODE ##################################### #$Rand = Get-Random -min 1000000000 $TimeStamp = Get-Now ########################### READ THE TENANTS INFO FILE ##################################### if ( $null -eq (Get-Item "$ScriptPath\Configs" -ErrorAction SilentlyContinue)) { New-Item -Path $ScriptPath -Name "Configs" -ItemType "Directory" -ErrorAction SilentlyContinue New-Item -Path "$ScriptPath\Configs\" -Name "CONFIG.txt" -ItemType "file" -Value "Tenants.CSV and OUSyncMaps.CSV Should be Placed in this Folder" -ErrorAction SilentlyContinue } $Year = ( Get-Date ).ToString('yyyy') $Name = ( Get-Date ).ToString('MM') + " " + ( Get-Date ).ToString('MMMM') $LogPath ="$ScriptPath" + "\Logs\" + $Year + "\" + $Name if ( (Test-Path $LogPath) -eq $false ) { New-Item -ItemType Directory -Path $Logpath -ErrorAction Stop } $Name = ( Get-Date ).ToString('yyMMdd-HHmmss-fff-') $LogFile = "$Logpath\" + $Name + "io.log" $ConnectLogFile = "$LogPath\" + $Name + "connections.log" $ReadOULogFile = "$LogPath\" + $Name + "ou_processing.log" try { $Tenants = $null $Tenants = Import-Csv "$ScriptPath\Configs\Tenants.csv" -ErrorAction Stop } catch { (Get-Now) + "`r`nError Reading TENANT INFORMATION File`r`n" + $Error[0] + "`r`n" | Out-File $LogFile -Append Exit } ########################### READ THE TENANTS/OU SYNC MAP FILE ##################################### try { $SyncMaps = $null $SyncMaps = Import-Csv "$ScriptPath\Configs\OUSyncMaps.csv" -ErrorAction Stop } catch { (Get-Now) + "`r`nError Reading SYNC MAP File`r`n" + $Error[0] + "`r`n" | Out-File $LogFile -Append Exit } ########################### LOAD THE EXCHANGE PS MODULES ##################################### Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue try { Add-PSSnapin Microsoft.Exchange.Management.PowerShell* -ErrorAction Stop } catch { (Get-Now) + "`r`nError Loading Exchange OnPremise Management Shell. `r`n" + $Error[0] + "`r`n" | Out-File $ConnectLogFile -Append Exit } ########################### BEGIN FOREACH - READ THE MAILOXES OF EACH OU :: SyncMaps.csv ################################### $Index = 0 foreach ( $SyncMapRow in $SyncMaps ) { ########################### SKIP OUs SET AS GLOBAL HIDE ##################################### try { $Progress = (($Index + 1) / $SyncMaps.Count) * 100 Set-Variable "OuContacts$Index" -Value $null } catch { $Progress = $Index + 1 } Write-Progress -Activity "Recipient Import from AD" -Status ("Processing " + ($Index+1) + " of " + ($SyncMaps.Count) + ": OU : " + $SyncMapRow.ADOU) -PercentComplete $Progress if ( $SyncMapRow.GlobalHide -eq "TRUE" ) { (Get-Now) + ": Skipping the Department OU :: Department set to Global Hide : " + $SyncMapRow.ADOU + "`r`n" | Out-File $ReadOULogFile -Append $Index++ Continue; } ########################### READ FROM OU FOR MAILBOXES ##################################### (Get-Now) + ": BEGIN Reading Recipient information from the Department OU : " + $SyncMapRow.ADOU | Out-File $ReadOULogFile -Append try { $OuMailboxes = $null $OuMailboxes = Get-Mailbox -ResultSize Unlimited -OrganizationalUnit $SyncMapRow.ADOU -ErrorAction Stop | Where-Object {$_.HiddenFromAddressListsEnabled -eq $false} | Select-Object DisplayName,PrimarySmtpAddress,LegacyExchangeDN,UserPrincipalName,RecipientTypeDetails,OrganizationalUnit | Sort-Object PrimarySmtpAddress #$OuMailboxes = Get-Mailbox -ResultSize Unlimited -OrganizationalUnit $SyncMapRow.ADOU -ErrorAction Stop | Where-Object {$_.HiddenFromAddressListsEnabled -eq $false} | Select-Object DisplayName,UserPrincipalName,SamAccountName,PrimarySmtpAddress,CustomAttribute10,CustomAttribute11,CustomAttribute12,CustomAttribute13,CustomAttribute14,OrganizationalUnit,Database,LegacyExchangeDN | Sort-Object PrimarySmtpAddress $OuContacts = $null $OuContacts = New-Object System.Collections.ArrayList $OuMailboxes | Foreach-Object{ [String]$Upn = "" [String]$Upn = $_.UserPrincipalName Write-Verbose "Extracting Recipient Information $Upn" $MbxUsr = $null #$AdUsr = $null if ( $Upn -eq "" -or $null -eq $Upn) { (Get-Now) + ": Error:: Invalid UserPrincipalName. Skipping the Mailbox : " + $_.DisplayName + " " + $_.PrimarySmtpAddress | Out-File $ReadOULogFile -Append } else { $Mbxusr = Get-User $Upn | Select-Object FirstName,LastName,Title,Department,Company,Office,Phone,Manager #$AdUsr = Get-AdUser -Filter { UserprincipalName -eq $Upn } -Properties * } $ContactEntry = $null if ( $null -eq $MbxUsr ) { $ContactEntry = [PSCustomObject]@{ DisplayName = $_.DisplayName PrimarySmtpAddress = $_.PrimarySmtpAddress LegacyExchangeDN = $_.LegacyExchangeDN Type = $_.RecipientTypeDetails OU = $_.OrganizationalUnit FirstName = "" LastName = "" Title = "" Department = "" Company = "" Office = "" Phone = "" } } else { $ContactEntry = [PSCustomObject]@{ DisplayName = $_.DisplayName PrimarySmtpAddress = $_.PrimarySmtpAddress LegacyExchangeDN = $_.LegacyExchangeDN Type = $_.RecipientTypeDetails OU = $_.OrganizationalUnit #Name = $_.DisplayName #UserPrincipalName = $_.UserPrincipalName #SamAccountName = $_.SamAccountName #CustomAttribute10 = $_.CustomAttribute10 #CustomAttribute11 = $_.CustomAttribute11 #CustomAttribute12 = $_.CustomAttribute12 #CustomAttribute13 = $_.CustomAttribute13 #CustomAttribute14 = $_.CustomAttribute14 FirstName = $MbxUsr.FirstName LastName = $MbxUsr.LastName Title = $MbxUsr.Title Department = $MbxUsr.Department Company = $MbxUsr.Company Office = $MbxUsr.Office Phone = $MbxUsr.Phone #Manager = $MbxUsr.Manager #$Section = $AdUsr.Division #OfficePhone = $AdUsr.OfficePhone #IPPhone = $AdUsr.IPPhone #OtherPhone = $AdUsr.otherTelephone #EmpID = $AdUsr.EmployeeID #EmpNo = $AdUsr.EmployeeNumber #Description = $AdUsr.Description #Manager = $AdUsr.Manager } } $OuContacts.Add($ContactEntry) | Out-Null } if ( ($OuMailboxes -is [System.Array]) -or ($null -eq $OuMailboxes) ) { (Get-Now) + ": Total Number of Mailboxes from OU : " + $OuMailboxes.count | Out-File $ReadOULogFile -Append } else { (Get-Now) + ": Total Number of Mailboxes from OU : 1 " | Out-File $ReadOULogFile -Append } $OuMailboxes = $null $OuMailboxes = Get-RemoteMailbox -ResultSize Unlimited -OnPremisesOrganizationalUnit $SyncMapRow.ADOU -ErrorAction Stop | Where-Object {$_.HiddenFromAddressListsEnabled -eq $false} | Select-Object DisplayName,PrimarySmtpAddress,LegacyExchangeDN,UserPrincipalName,RecipientTypeDetails,OnPremisesOrganizationalUnit | Sort-Object PrimarySmtpAddress $OuMailboxes | Foreach-Object{ [String]$Upn = "" [String]$Upn = $_.UserPrincipalName Write-Verbose "Extracting Recipient Information $Upn" $MbxUsr = $null #$AdUsr = $null if ( $Upn -eq "" -or $null -eq $Upn) { (Get-Now) + " Error: Invalid UserPrincipalName. Skipping the Remote Mailbox : " + $_.DisplayName + " " + $_.PrimarySmtpAddress | Out-File $ReadOULogFile -Append } else { $Mbxusr = Get-User $Upn | Select-Object FirstName,LastName,Title,Department,Company,Office,Phone,Manager #$AdUsr = Get-AdUser -Filter { UserprincipalName -eq $Upn } -Properties * } $ContactEntry = $null if ( $null -eq $MbxUsr ) { $ContactEntry = [PSCustomObject]@{ DisplayName = $_.DisplayName PrimarySmtpAddress = $_.PrimarySmtpAddress LegacyExchangeDN = $_.LegacyExchangeDN Type = $_.RecipientTypeDetails OU = $_.OnPremisesOrganizationalUnit FirstName = "" LastName = "" Title = "" Department = "" Company = "" Office = "" Phone = "" } } else { $ContactEntry = [PSCustomObject]@{ DisplayName = $_.DisplayName PrimarySmtpAddress = $_.PrimarySmtpAddress LegacyExchangeDN = $_.LegacyExchangeDN Type = $_.RecipientTypeDetails OU = $_.OnPremisesOrganizationalUnit FirstName = $MbxUsr.FirstName LastName = $MbxUsr.LastName Title = $MbxUsr.Title Department = $MbxUsr.Department Company = $MbxUsr.Company Office = $MbxUsr.Office Phone = $MbxUsr.Phone } } $OuContacts.Add($ContactEntry) | Out-Null } if ( ($OuMailboxes -is [System.Array]) -or ($null -eq $OuMailboxes) ) { (Get-Now) + ": Total Number of Remote Mailboxes from OU : " + $OuMailboxes.count | Out-File $ReadOULogFile -Append } else { (Get-Now) + ": Total Number of Remote Mailboxes from OU : 1 " | Out-File $ReadOULogFile -Append } if (!$NoGroupSync) { $OuGroups = $null $OuGroups = Get-DistributionGroup -ResultSize Unlimited -OrganizationalUnit $SyncMapRow.ADOU -ErrorAction Stop | Where-Object { ($_.RecipientTypeDetails -ne "RoomList") -and ($_.HiddenFromAddressListsEnabled -eq $false) } | Select-Object DisplayName,PrimarySmtpAddress,LegacyExchangeDN,RecipientTypeDetails,OrganizationalUnit | Sort-Object PrimarySmtpAddress $OuGroups | Foreach-Object{ Write-Verbose "Extracting Recipient Information $_.PrimarySMTPAddress" $ContactEntry = $null $ContactEntry = [PSCustomObject]@{ DisplayName = $_.DisplayName PrimarySmtpAddress = $_.PrimarySmtpAddress LegacyExchangeDN = $_.LegacyExchangeDN Type = $_.RecipientTypeDetails OU = $_.OrganizationalUnit FirstName = "" LastName = "" Title = "" Department = "" Company = "" Office = "" Phone = "" } $OuContacts.Add($ContactEntry) | Out-Null } if ( ($OuGroups -is [System.Array]) -or ($null -eq $OuGroups) ) { (Get-Now) + ": Total Number of Groups from OU : " + $OuGroups.count | Out-File $ReadOULogFile -Append } else { (Get-Now) + ": Total Number of Groups from OU : 1 " | Out-File $ReadOULogFile -Append } } } catch { (Get-Now) + "`r`nError Reading Recipient Information from OU `r`n" + $Error[0] | Out-File $ReadOULogFile -Append } finally { if ($null -eq $OuContacts) { Set-Variable "OuContacts$Index" -Value $null (Get-Now) + ": Recipients from OU will be skipped: "| Out-File $ReadOULogFile -Append } else { Set-Variable "OuContacts$Index" -Value $OuContacts (Get-Now) + ": Total Number of Recipients from OU : " + $OuContacts.count | Out-File $ReadOULogFile -Append #(Get-Now) + ": Number of Recipients from OU : " + $OuMailboxes.count | Out-File $ReadOULogFile -Append } } (Get-Now) + ": END Reading Recipient information from the Department OU `r`n" | Out-File $ReadOULogFile -Append Remove-Variable OuMailboxes -ErrorAction SilentlyContinue Remove-Variable OuGroups -ErrorAction SilentlyContinue Remove-Variable OuContacts -ErrorAction SilentlyContinue $Index++ #Start-Sleep -Seconds .25 } Remove-PSSnapin Microsoft.Exchange.Management.PowerShell* -ErrorAction SilentlyContinue ########################### END FOREACH - READ THE MAILOXES OF EACH OU :: SyncMaps.csv ################################### ########################### LOAD THE EXCHANGE ONLINE PS MODULE ##################################### try { Import-Module ExchangeOnlineManagement -ErrorAction Stop } catch { (Get-Now) + "`r`nError Loading Exchange PS Module V2 `r`n" + $Error[0] + "`r`n" | Out-File $ConnectLogFile -Append Exit } ########################### BEGIN FOREACH - READ EACH TENANT & APPID INFO :: Tenants.csv ##################################### $TenantIndex = 0 foreach ( $Tenant in $Tenants ) { try { $Progress = (($TenantIndex + 1) / $Tenants.Count) * 100 } catch { $Progress = $TenantIndex + 1 } $ExclusionFileName = $null $ExclusionFileName = "$ScriptPath\Configs\" + $Tenant.Tenant + ".csv" $ExclusionFile = $null $ExclusionFile = Get-Item $ExclusionFileName -ErrorAction SilentlyContinue $ExclusionList = $null $TenantLogFile = $null $TenantLogFile = "$LogPath\" + $Name + $Tenant.Tenant + ".log" Write-Progress -Activity ("GAL Sync to Tenant " + ($TenantIndex+1) + " of " + ($Tenants.Count) + " : " + $Tenant.Tenant) -Status ("Verifying OUs for Sync" ) -PercentComplete $Progress "`r`n" + (Get-Now) + "`r`n[ BEGIN Processing Tenant :: " + $Tenant.Tenant + " ]" | Out-File $TenantLogFile -Append if ($FullSync) { "[ Full Sync ] is Set :: All Contacts will be compared for changes. Changes will be updated" | Out-File $TenantLogFile -Append } else { "[ Delta Sync ] is Set :: Existing Contacts in the Tenant will be skipped" | Out-File $TenantLogFile -Append } if ($NoGroupSync) { "[ No Group Sync ] is Set :: All Existing Group Contacts will be removed from the Tenant !" | Out-File $TenantLogFile -Append } else { "[ Group Sync ] is Set :: Contacts will be Created/Updated for Groups" | Out-File $TenantLogFile -Append } if ($UpdateGuestUser) { "[ Update Guest User ] is Set :: All Existing Guest Users proxy address will be cleared and New Contacts will be created !!`r`n" | Out-File $TenantLogFile -Append } else { "[ Update Guest User ] is Not Set :: All Existing Guest Users will not be modified in the Tenant `r`n" | Out-File $TenantLogFile -Append } if ( $null -ne $ExclusionFile ) { $ExclusionList = Import-Csv $ExclusionFile -ErrorAction SilentlyContinue } ########################### BEGIN FOREACH - SYNC CONTACTS TO EACH TENAT FROM OUs IN THE SYNC MAP FILE :: SyncMaps.csv ##################################### $IsSessionActive = $false $Index = 0 foreach ( $SyncMapRow in $SyncMaps ) { ########################### SKIP OUs OF THE SAME TENANT ##################################### if ( $SyncMapRow.ParentTenant -eq $Tenant.Tenant ) { (Get-Now) + ": Skipping the Department OU :: Department is the Target Tenant : " + $SyncMapRow.ADOU + "`r`n"| Out-File $TenantLogFile -Append $Index++ Continue; } ########################### SKIP OUs SET AS GLOBAL HIDE ##################################### if ( $SyncMapRow.GlobalHide -eq "TRUE" ) { (Get-Now) + ": Skipping the Department OU :: Department set to Global Hide : " + $SyncMapRow.ADOU + "`r`n"| Out-File $TenantLogFile -Append $Index++ Continue; } ########################### SKIP OUs Defined IN TENANT EXCLUSION FILE ##################################### $IsExcluded = $false if ( $null -ne $ExclusionList ) { ########################### BEGIN LOOP - EACH OU IN THE EXCLUSION FILE ##################################### foreach ($Exclusion in $ExclusionList) { if ( $SyncMapRow.ADOU -eq $Exclusion.ADOU ) { $IsExcluded = $true Break; } } ########################### BEGIN LOOP - EACH OU IN THE EXCLUSION FILE ##################################### } if ( $IsExcluded ) { (Get-Now) + ": Skipping the Department OU :: Department in Tenant Exclusion List : " + $SyncMapRow.ADOU + "`r`n"| Out-File $TenantLogFile -Append $Index++ Continue; } ########################### PROCESS THE OU CONTACT SYNC TO TENANT ##################################### try { $Progress = (($Index + 1) / $SyncMaps.Count) * 100 } catch { $Progress = $Index + 1 } Write-Progress -Activity ("GAL Sync to Tenant " + ($TenantIndex+1) + " of " + ($Tenants.Count) + " : " + $Tenant.Tenant) -Status ("Syncing OU " + ($Index+1) + " of " + ($SyncMaps.Count) + ": OU : " + $SyncMapRow.ADOU) -PercentComplete $Progress $DeptCode = $null $DeptCode = $SyncMapRow.DeptCode if ( !$IsSessionActive ) { ############################ CONNECT TO THE CURRENT TENANT - ONLY ONCE PER TENANT ##################### try { ########################### CERT with the Thumbprint SHOULD BE IN USER STORE ########################## if ( ($null -eq $Tenant.Tenant) -or ("" -eq $Tenant.Tenant) ) { Throw "Missing Tenant Name : $Tenant" } if ( ($null -eq $Tenant.CertThumbPrint) -or ("" -eq $Tenant.CertThumbPrint) ) { Throw "Missing CertThumbprint for Tenant: $Tenant" } if ( ($null -eq $Tenant.AppID) -or ("" -eq $Tenant.AppID) ) { Throw "Missing AppID for Tenant: $Tenant" } Connect-ExchangeOnline -CertificateThumbPrint $Tenant.CertThumbPrint -Organization $Tenant.Tenant -AppID $Tenant.AppID -ShowBanner:$false $IsSessionActive = $true (Get-Now) + "`r`nSuccessfully Connected to TENANT "+ $Tenant.Tenant + "`r`n" | Out-File $ConnectLogFile -Append } catch { (Get-Now) + "`r`nError Connecting to TENANT "+ $Tenant.Tenant + "`r`n" + $Error[0] + "`r`n"| Out-File $ConnectLogFile -Append (Get-Now) + ": ERROR Processing the TENANT - Terminate : `r`n" | Out-File $TenantLogFile -Append $IsSessionActive = $false Break } Write-Verbose "Session Status : $IsSessionActive" #Check if ignore Existing users are Set if ($UpdateGuestUser) { (Get-Now) + ": =BEGIN Reading Existing Guest Users from the Tenant : " + $DeptCode | Out-File $TenantLogFile -Append try { #Reading Existing Azure AD Guest User Information (ONCE Per Tenant) into Collecion than the default Fixed Size onject array $TenantGuestUsers = $null [System.Collections.ArrayList]$TenantGuestUsers = @( Get-MailUser -ResultSize Unlimited -ErrorAction Stop | Where-Object {$_.RecipientTypeDetails -eq "GuestMailUser" -and $_.PrimarySMTPAddress -ne ""} | Select-Object DisplayName, PrimarySmtpAddress | Sort-Object PrimarySmtpAddress ) #check If Guest Users are empty if ($null -eq $TenantGuestUsers) { (Get-Now) + ": No Guest Users in the Tenant with Proper Proxy Address : " | Out-File $TenantLogFile -Append } else { (Get-Now) + ": Number of Guest Users in the Tenant with Proper Proxy Address : " + $TenantGuestUsers.count | Out-File $TenantLogFile -Append } } catch { (Get-Now) + ": Error Reading Existing Guest User Information from Tenant. Update Guest User is Set to False`r`n" + $Error[0] | Out-File $TenantLogFile -Append $UpdateGuestUser = $false } (Get-Now) + ": =END Reading Guest Users from the Tenant ::::::::: `r`n" | Out-File $TenantLogFile -Append } } (Get-Now) + ": BEGIN Processing the Department OU with Dept Code $DeptCode : " + $SyncMapRow.ADOU + "`r`n"| Out-File $TenantLogFile -Append if ($IsSessionActive) { ############################ BEGIN READ THE CONTACTS OF THE DEPT FROM TENANT ##################### (Get-Now) + ": -BEGIN Reading Existing Contacts from the Tenant With Dept Code : " + $DeptCode | Out-File $TenantLogFile -Append #Read Dept Contacts from Tenant #Read Full Contact Information if Full Sync is Set if ($FullSync) { try { #Reading Existing Contacts into Collecion than the default Fixed Size onject array $TenantBaseContacts = $null $TenantBaseContacts = Get-MailContact -ResultSize Unlimited -ErrorAction Stop | Where-Object {$_.CustomAttribute15 -eq $DeptCode} | Select-Object DisplayName, PrimarySmtpAddress, CustomAttribute12, CustomAttribute13 | Sort-Object PrimarySmtpAddress $TenantContacts = $null $TenantContacts = New-Object System.Collections.ArrayList $TenantBaseContacts | Foreach-Object{ [String]$ID = "" [String]$ID = $_.PrimarySmtpAddress Write-Verbose "Extracting Recipient Information $ID" $TContact = $null if ( $ID -eq "" -or $null -eq $ID) { (Get-Now) + ": Error:: Invalid PrimarySmtpAddress. Skipping the Contact : " + $_.DisplayName + " " + $_.PrimarySmtpAddress | Out-File $TenantLogFile -Append } else { $TContact = Get-Contact $ID | Select-Object FirstName,LastName,Title,Department,Company,Office,Phone } $TenantContactEntry = $null if ( $null -eq $TContact ) { $TenantContactEntry = [PSCustomObject]@{ DisplayName = $_.DisplayName PrimarySmtpAddress = $_.PrimarySmtpAddress Type = $_.CustomAttribute13 OU = $_.CustomAttribute12 FirstName = "" LastName = "" Title = "" Department = "" Company = "" Office = "" Phone = "" } } else { $TenantContactEntry = [PSCustomObject]@{ DisplayName = $_.DisplayName PrimarySmtpAddress = $_.PrimarySmtpAddress Type = $_.CustomAttribute13 OU = $_.CustomAttribute12 FirstName = $TContact.FirstName LastName = $TContact.LastName Title = $TContact.Title Department = $TContact.Department Company = $TContact.Company Office = $TContact.Office Phone = $TContact.Phone } } $TenantContacts.Add($TenantContactEntry) | Out-Null } } catch { (Get-Now) + ": Error Reading Existing Contacts from the Tenant with Dept Code $DeptCode `r`n" + $Error[0] | Out-File $TenantLogFile -Append } } #Read Base Contact Information if Full Sync is Not Set else { try { #Reading Existing Contacts into Collecion than the default Fixed Size onject array $TenantContacts = $null #Early Filter did not work with variable in the filter block #[System.Collections.ArrayList]$TenantContacts = Get-MailContact -ResultSize Unlimited -Filter { CustomAttribute15 -eq $DeptCode} -ErrorAction Stop| Select-Object DisplayName, PrimarySmtpAddress | Sort-Object PrimarySmtpAddress [System.Collections.ArrayList]$TenantContacts = @( Get-MailContact -ResultSize Unlimited -ErrorAction Stop | Where-Object {$_.CustomAttribute15 -eq $DeptCode} | Select-Object DisplayName, PrimarySmtpAddress | Sort-Object PrimarySmtpAddress ) } catch { (Get-Now) + ": Error Reading Existing Contacts from the Tenant with Dept Code $DeptCode `r`n" + $Error[0] | Out-File $TenantLogFile -Append } } #check If Contacts are empty if ($null -eq $TenantContacts) { (Get-Now) + ": No Contacts in the Tenant with DeptCode $DeptCode : " | Out-File $TenantLogFile -Append } else { (Get-Now) + ": Number of Contacts in the Tenant with Dept Code $DeptCode : " + $TenantContacts.count | Out-File $TenantLogFile -Append } (Get-Now) + ": -END Reading Existing Contacts from the Tenant ::::::::: `r`n" | Out-File $TenantLogFile -Append ############################ END READ THE CONTACTS OF THE DEPT FROM TENANT ##################### ############################ BEGIN SYNCING OF THE DEPT CONTACTS TO TENANT ##################### (Get-Now) + ": --BEGIN Syncing Contacts from the Department OU to Tenant With Dept Code : " + $DeptCode | Out-File $TenantLogFile -Append $OuContacts = $null $OuContacts = (Get-Variable -Name OuContacts$Index -ErrorAction SilentlyContinue).value #If Recipients from OU are EMPTY :: No Sync if ($null -eq $OuContacts) { (Get-Now) + ": No Contacts from the Department OU. Check $ReadOuLogFile for more details. Contacts with DeptCode $DeptCode will be skipped: "| Out-File $TenantLogFile -Append } #If Recipients from OU are NOT EMPTY :: Check for existing Contacts in Tenant else { (Get-Now) + ": Number of Contacts from Department OU with Dept Code $DeptCode : " + $OuContacts.count | Out-File $TenantLogFile -Append (Get-Now) + ": Number of Contacts Existing in the Tenant with Dept Code $DeptCode : " + $TenantContacts.count | Out-File $TenantLogFile -Append #If Recipients are read from OU and there are NO Contacts in Tenant :: Create All Contacts if ($null -eq $TenantContacts) { (Get-Now) + ": First Time Contacts Provisioning Triggered with $DeptCode : Update Guest User is Set to False" | Out-File $TenantLogFile -Append foreach ($OuContact in $OuContacts ) { Add-ExContact($OuContact) } } #If Recipients are read from OU and there are existing Contacts in Tenant :: Create/Update/Remove else { foreach ($OuContact in $OuContacts) { $IsSynced = $false $i = 0 foreach ($TenantContact in $TenantContacts) { Write-Verbose "Verifying the Sync Status of the Contact : $OuContact.PrimarySmtpAddress" if ($TenantContact.PrimarySmtpAddress -eq ($OuContact.PrimarySmtpAddress).toString()) { $IsSynced = $true #If FullSync Specified :: Compare Each Existing Contact if ($FullSync) { if ( ($TenantContact.DisplayName -ne $OuContact.DisplayName) -or ($TenantContact.OU -ne $OuContact.OU) -or ($TenantContact.Type -ne $OuContact.Type) -or ($TenantContact.Title -ne $OuContact.Title) -or ($TenantContact.Department -ne $OuContact.Department) -or ($TenantContact.Company -ne $OuContact.Company) -or ($TenantContact.Office -ne $OuContact.Office) -or ($TenantContact.Phone -ne $OuContact.Phone) ) { Write-Verbose "Updating Existing Contact $OuContact.PrimarySmtpAddress" Update-ExContact($OuContact) } else { Write-Verbose "Skipping Contact, No Changes : $TenantContact.PrimarySmtpAddress" (Get-Now) + ": Skipped the Contact, No Changes :: " + $OuContact.PrimarySmtpAddress | Out-File $TenantLogFile -Append } } #If FullSync Not Specified :: Skip the Existing Contact else { Write-Verbose "Skipping Existing Contact : $TenantContact.PrimarySmtpAddress" (Get-Now) + ": Skipped the Existing Contact :: " + $OuContact.PrimarySmtpAddress | Out-File $TenantLogFile -Append } $TenantContacts.RemoveAt($i) break; } $i++ } # Contact Not in Tenant if (!$IsSynced) { # Check If Update Guest User is Specified : Update the proxy address of the Existing Mail User if ($UpdateGuestUser) { $i = 0 foreach ($TenantGuestUser in $TenantGuestUsers) { Write-Verbose "Verifying the Contact $OuContact.PrimarySmtpAddress is a Guest Mail User" if ($TenantGuestUser.PrimarySmtpAddress -eq ($OuContact.PrimarySmtpAddress).toString()) { try { $x = $null $x = ($OuContact.PrimarySmtpAddress).tostring() Set-MailUser $x -EmailAddresses $null -ErrorAction Stop (Get-Now) + ": The Email Addresss " + $OuContact.PrimarySmtpAddress + " is a Guest User. Cleared the proxy address" | Out-File $TenantLogFile -Append Start-Sleep -Seconds 15 } catch { (Get-Now) + ": Error Clearing Proxy Address of the mail user. " + $OuContact.PrimarySmtpAddress + "`r`n" + $Error[0] | Out-File $TenantLogFile -Append } $TenantGuestUsers.RemoveAt($i) break; } $i++ } } # Create New Contact Add-ExContact($OuContact) } } } #If Tenant Contact Collection is NOT Empty :: Remove the Contacts, Since the Recipient is Removed from EX OnPremise if ($TenantContacts.Count -ne 0) { foreach ($TenantContact in $TenantContacts) { Remove-ExContact ($TenantContact.PrimarySmtpAddress) } } } (Get-Now) + ": --END Syncing Contacts to Tenant ::::::::: `r`n" | Out-File $TenantLogFile -Append ############################ END SYNCING OF THE DEPT CONTACTS TO TENANT ##################### Remove-Variable OuContacts -ErrorAction SilentlyContinue Remove-Variable TenantContacts -ErrorAction SilentlyContinue Remove-Variable TenantBaseContacts -ErrorAction SilentlyContinue } Write-Verbose "OU $Index : $SyncMapRow.ADOU" (Get-Now) + ": END Processing the Department OU with Dept Code $DeptCode : `r`n" | Out-File $TenantLogFile -Append #Start-Sleep -Seconds 1 $Index++ } Remove-Variable TenantGuestUsers -ErrorAction SilentlyContinue if ( $IsSessionActive ) { Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue (Get-Now) + "`r`nDisonnected from TENANT "+ $Tenant.Tenant + "`r`n" | Out-File $ConnectLogFile -Append } Write-Verbose "$TenantIndex Completed OUs for Tenant $Tenant.Tenant" "`r`n" + (Get-Now) + "`r`n[ END Processing Tenant :: " + $Tenant.Tenant + " ]" | Out-File $TenantLogFile -Append #Start-Sleep -Seconds 1 $TenantIndex++ ########################### END FOREACH - SYNC CONTACTS TO EACH TENAT FROM OUS IN THE SYNC MAP FILE :: SyncMaps.csv ##################################### } ########################### END FOREACH - READ EACH TENANT & APPID INFO :: Tenants.csv ##################################### for ( $i=0;$i -lt $SyncMaps.count; $i++) { Remove-Variable OuContacts$i } Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue Remove-Module ExchangeOnlineManagement -ErrorAction SilentlyContinue $FullSync = $null $NoGroupSync = $null $UpdateGuestUser = $null ########################### END CODE ##################################### |