Problema
Per motivi legati ai permessi, ESG non riesce a gestire la mappatura degli utenti delegati di Microsoft365 né la mappatura come indirizzi email secondari per gli utenti.
ESG si integra con Microsoft 365 tramite le Graph API per la sincronizzazione degli utenti e il Single Sign-On (SSO). Questo processo crea le Shared Mailbox come 'Functional Users' all'interno del sistema di gestione utenti di ESG. Un limite delle Graph API è la mancanza di supporto per informazioni dettagliate sui permessi relativi all'accesso alle Shared Mailbox. Di conseguenza, ESG non è in grado di determinare quali utenti dispongano dei permessi per utilizzare specifiche Shared Mailbox e pertanto non può mapparle correttamente come indirizzi secondari all'interno della piattaforma ESG.
Soluzione:
Prerequisiti
Per automatizzare la mappatura delle mailbox condivise ai rispettivi membri in ESG, devono essere soddisfatti alcuni prerequisiti:- L'integrazione di ESG con Microsoft 365 deve essere configurata e funzionante
- Deve essere stato eseguito almeno 1 import in ESG tramite l'integrazione M365
- Le Shared Mailbox sono presenti in ESG nella sezione User Management
Mappatura automatica tramite Powershell ed ESG API
#Import Exchange Online module, it must be installed first here
Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline -UserPrincipalName <M365 Tenant admin>
# Get all shared mailboxes
$sharedMailboxes = Get-Mailbox -RecipientTypeDetails SharedMailbox -ResultSize Unlimited
$shared_mailbox_list = @()
Get-Mailbox -RecipientTypeDetails SharedMailbox -ResultSize Unlimited | ForEach-Object {
$mailbox = $_
# Retrieve the permissions for the mailbox: filter for FullAccess (non-inherited and non-system accounts)
$permissions = Get-MailboxPermission -Identity $mailbox.PrimarySmtpAddress | Where-Object {
$_.AccessRights -contains "FullAccess" -and `
-not $_.IsInherited -and `
$_.User -notmatch "NT AUTHORITY|S-1-5-|SELF|SYSTEM"
}
# Process each permission entry
foreach ($perm in $permissions) {
$delegatedUser = $perm.User.ToString()
$isGroup = $false
# Try to determine if the delegated user is a group using Get-Recipient
try {
$recipient = Get-Recipient -Identity $delegatedUser -ErrorAction Stop
if ($recipient.RecipientType -match "Group") {
$isGroup = $true
}
}
catch {
# If not found or error occurs, assume it's not a group.
$isGroup = $false
}
if ($isGroup) {
# Expand the group: get each member using Get-DistributionGroupMember
$groupMembers = Get-DistributionGroupMember -Identity $delegatedUser -ErrorAction SilentlyContinue
if ($groupMembers) {
foreach ($member in $groupMembers) {
$shared_mailbox_list += [PSCustomObject]@{
SharedMailbox = $mailbox.DisplayName
PrimarySmtpAddress = $mailbox.PrimarySmtpAddress
ExternalObjectId = $mailbox.ExternalDirectoryObjectId
User = $member.PrimarySmtpAddress.ToString()
AccessRights = ($perm.AccessRights -join ", ")
}
}
}
}
else {
# Not a group: add one object with the delegated user identity
$shared_mailbox_list += [PSCustomObject]@{
SharedMailbox = $mailbox.DisplayName
PrimarySmtpAddress = $mailbox.PrimarySmtpAddress
ExternalObjectId = $mailbox.ExternalDirectoryObjectId
User = $delegatedUser
AccessRights = ($perm.AccessRights -join ", ")
}
}
}
}
# Display the results in a formatted table could be uncommented to see all the shared mailbox mapped.
#$shared_mailbox_list
$shared_unique = $shared_mailbox_list.PrimarySmtpAddress | Group-Object
# Number of users returned per page in each GET, 150 is the maximum chunk.
$page_size = 150
$page_num = 1
# Define the ESG API endpoint to get the list of users with maximum page size (150)
$apiUrl = "<youresgAddress>/api/v2/user?page="+$page_num+"&itemsPerPage=150"
# Define headers for the api call, use your apiToken generated
$headers = @{
'Accept' = 'application/hal+json'
'X-ESG-Auth-Token' = 'apiToken'
}
# Send the GET request
$get_users_response = Invoke-RestMethod -Uri $apiUrl -Method Get -Headers $headers
$users_list_body = $get_users_response._embedded.item
# Number of total users extracted from the GET call
$total_users = $get_users_response.totalItems
Write-Host "Total users" $total_users
# Check if number of total users is < than page size, if yes no multiple calls are needed to loop through pages
if ($total_users -lt 50){
Write-Host "user page page > total"
$users_list = $users_list_body
} else {
# Number of total users is > than page size, looping through pages to get all users
Write-Host "users per page < total, looping through pages"
while ($true){
# Check if GET request for page_num is empty, meaning we reached end of user list
if ($get_users_response.PSObject.Properties.Name -notcontains '_embedded') {
break
} else {
# Go to next page
$page_num += 1
# GET request of current page is not empty
$apiUrl = "<youresgAddress>/api/v2/user?page=" + $page_num +"&itemsPerPage=150"
# Send the GET request
$get_users_response = Invoke-RestMethod -Uri $apiUrl -Method Get -Headers $headers
# Appending list of users of each page
$users_list_body += $get_users_response._embedded.item
}
}
}
# Filter only users that are members of the Shared mailbox list
$users_list = $users_list_body| Where-Object {$shared_mailbox_list.User -contains $_.username}
Write-Host "total number of users to map: " $users_list.count
#Filter only shared mailbox , this could be also -> $shared_mailbox_list.PrimarySmtpAddress
$mailbox_list_values = $users_list_body| Where-Object {$shared_mailbox_list.SharedMailbox -contains $_.username}
Write-Host "total number of Mailbox to remap: " $mailbox_list_values.count
#delete all shared mailbox saved as functional_users
# Loop all users and check if the shared mailbox is already present as an alias, if not add the Shared Mailbox as alias
foreach ($user in $users_list){
# ID of the ESG user
$id = $user.id
Write-Host "Esg user id:" $id
$apiUrl = "<youresgAddress>/api/v2/user/$id"
# Loop each Shared mailbox
foreach($shared in $shared_mailbox_list){
$match = $user.emailAddresses | Where-Object {
$_.PSObject.Properties['address'].Value -eq $shared.User
}
if ($match){
$shared_mailbox = [PSCustomObject]@{
address = $shared.PrimarySmtpAddress
active = $true
primary = $false
}
$headers = @{
"Accept" = "application/hal+json"
"Content-Type" = "application/merge-patch+json"
"X-ESG-Auth-Token" = "apiToken"
}
$user.emailAddresses = $user.emailAddresses + $shared_mailbox
$body = [PSCustomObject] @{ emailAddresses = $user.emailAddresses } | ConvertTo-Json
$patch_response = Invoke-RestMethod -Uri $apiUrl -Method Patch -Headers $headers -Body $body
}
}
}
#delete all the shared mailbox that are functional users. Iterate all functional_users
foreach($functional in $mailbox_list_values)
{
$id = $functional.id
$apiUrl = "<youresgAddress>/api/v2/user/$id"
# Define headers
$headers = @{
'Accept' = 'application/json'
'X-ESG-Auth-Token' = 'apiToken'
}
$response = Invoke-RestMethod -Uri $apiUrl -Method Get -Headers $headers
#Functional user role, is it needed to check if the retrieved user is functional, il that case it will remove it (After having remapped it)
$functionalUser = "/api/v2/user-role/499"
$otherFunctionalUser = "/api/v2/user-role/400"
if($response.role -eq $functionalUser)
{
$apiUrl = "<youresgAddress>/api/v2/user/$id"
$apiUrl
# Define headers
$headers = @{
'Accept' = 'application/json'
'X-ESG-Auth-Token' = 'apiToken'
}
$response = Invoke-RestMethod -Uri $apiUrl -Method DELETE -Headers $headers
Write-Host "Deleting the user ..."
}
if($response.role -eq $otherFunctionalUser)
{
$apiUrl = "<youresgAddress>/api/v2/user/$id"
$apiUrl
# Define headers
$headers = @{
'Accept' = 'application/json'
'X-ESG-Auth-Token' = 'apiToken'
}
$response = Invoke-RestMethod -Uri $apiUrl -Method DELETE -Headers $headers
Write-Host "Deleting the user ..."
}
}
Write-Host "all shared mailbox are now mapped"