Src/Private/Get-AbrExoHybrid.ps1
|
function Get-AbrExoHybrid { <# .SYNOPSIS Documents Exchange hybrid configuration, identity synchronisation source, and on-premises integration status for tenant $TenantId. If no hybrid is configured, explicitly documents that this is a cloud-only deployment. .NOTES Version: 0.1.0 Author: Pai Wei Sing #> [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory)] [string]$TenantId ) begin { Write-PScriboMessage -Message "Collecting Exchange Online Hybrid/Identity Integration data for $TenantId." Show-AbrDebugExecutionTime -Start -TitleMessage 'Hybrid' } process { Section -Style Heading2 'Identity Integration' { Paragraph "The following section documents the hybrid Exchange configuration and identity synchronisation model for tenant $TenantId." BlankLine #region Hybrid Configuration Detection $IsHybrid = $false $IsDirSynced = $false $HybridConfig = $null try { Write-Host " - Checking for hybrid Exchange configuration..." $HybridConfig = Get-HybridConfiguration -ErrorAction SilentlyContinue if ($HybridConfig -and $HybridConfig.OnPremisesSmartHost) { $IsHybrid = $true Write-Host " - Hybrid configuration detected." -ForegroundColor Yellow } else { Write-Host " - No hybrid Exchange configuration detected (cloud-only deployment)." -ForegroundColor Cyan } } catch { Write-AbrDebugLog "Get-HybridConfiguration returned an error (expected in cloud-only tenants): $($_.Exception.Message)" 'DEBUG' 'Hybrid' } # Check directory sync from OrgConfig try { $OrgConfig = Get-OrganizationConfig -ErrorAction SilentlyContinue $IsDirSynced = ($OrgConfig.IsDirSynced -eq $true) } catch {} # Try Graph for Entra Connect details $DirSyncEnabled = $false $DirSyncServer = '--' $LastDirSyncTime = '--' try { $OrgResp = Invoke-MgGraphRequest -Method GET ` -Uri 'https://graph.microsoft.com/v1.0/organization?$select=id,onPremisesSyncEnabled,onPremisesLastSyncDateTime,onPremisesDomainName' ` -ErrorAction SilentlyContinue $MgOrg = if ($OrgResp.value) { $OrgResp.value[0] } else { $null } if ($MgOrg) { $DirSyncEnabled = ($MgOrg.onPremisesSyncEnabled -eq $true) $LastDirSyncTime = if ($MgOrg.onPremisesLastSyncDateTime) { [datetime]$MgOrg.onPremisesLastSyncDateTime | Get-Date -Format 'yyyy-MM-dd HH:mm UTC' } else { 'Never / Not Applicable' } } } catch {} #region Deployment Model Summary Section -Style Heading3 'Deployment Model' { $ModelObj = [System.Collections.ArrayList]::new() $DeploymentType = if ($IsHybrid) { 'Exchange Hybrid (on-premises + cloud)' } elseif ($DirSyncEnabled -or $IsDirSynced) { 'Cloud-only Exchange with Entra ID Connect sync' } else { 'Cloud-only (Entra ID native / no on-premises sync)' } $SourceOfAuthority = if ($IsHybrid -or $DirSyncEnabled -or $IsDirSynced) { 'On-premises Active Directory (synced to Entra ID)' } else { 'Entra ID (cloud-native)' } $modelInObj = [ordered] @{ 'Deployment Type' = $DeploymentType 'Source of Authority' = $SourceOfAuthority 'Directory Synchronisation Active' = ($DirSyncEnabled -or $IsDirSynced) 'Last Sync Time (Entra ID)' = $LastDirSyncTime 'Exchange Hybrid Configured' = $IsHybrid } $ModelObj.Add([pscustomobject](ConvertTo-HashToYN $modelInObj)) | Out-Null $ModelTableParams = @{ Name = "Deployment Model - $TenantId"; List = $true; ColumnWidths = 45, 55 } if ($Report.ShowTableCaptions) { $ModelTableParams['Caption'] = "- $($ModelTableParams.Name)" } $ModelObj | Table @ModelTableParams if (-not $IsHybrid -and -not $DirSyncEnabled -and -not $IsDirSynced) { BlankLine Paragraph "CONFIRMED: This tenant is a cloud-only Microsoft 365 deployment. No Exchange on-premises hybrid configuration is present. All mailboxes, identities, and policies are managed natively in Exchange Online and Entra ID." } } #endregion #region Hybrid Configuration Details (only if hybrid) if ($IsHybrid -and $HybridConfig) { Section -Style Heading3 'Exchange Hybrid Configuration' { Paragraph "Exchange Hybrid mode allows seamless coexistence between Exchange on-premises and Exchange Online." BlankLine $HybObj = [System.Collections.ArrayList]::new() $hybInObj = [ordered] @{ 'On-Premises Smart Host' = if ($HybridConfig.OnPremisesSmartHost) { $HybridConfig.OnPremisesSmartHost -join ', ' } else { 'Not Set' } 'Edge Transport' = if ($HybridConfig.EdgeTransportServers) { $HybridConfig.EdgeTransportServers -join ', ' } else { 'None (using Centralized Mail Transport)' } 'Features Enabled' = if ($HybridConfig.Features) { $HybridConfig.Features -join ', ' } else { 'None' } 'Inbound Connectors' = if ($HybridConfig.InboundConnectors) { $HybridConfig.InboundConnectors -join ', ' } else { 'Not Set' } 'Outbound Connectors' = if ($HybridConfig.OutboundConnectors) { $HybridConfig.OutboundConnectors -join ', ' } else { 'Not Set' } 'TLS Certificate Subject' = if ($HybridConfig.TlsCertificateName) { $HybridConfig.TlsCertificateName } else { 'Not Set' } 'On-Premises Organisation' = if ($HybridConfig.OnPremisesOrganization) { $HybridConfig.OnPremisesOrganization -join ', ' } else { 'Not Set' } 'Receive Connectors (on-prem)' = if ($HybridConfig.ReceivingTransportServers) { $HybridConfig.ReceivingTransportServers -join ', ' } else { 'Not Set' } } $HybObj.Add([pscustomobject]$hybInObj) | Out-Null $HybTableParams = @{ Name = "Hybrid Configuration Details - $TenantId"; List = $true; ColumnWidths = 40, 60 } if ($Report.ShowTableCaptions) { $HybTableParams['Caption'] = "- $($HybTableParams.Name)" } $HybObj | Table @HybTableParams $script:ExcelSheets['Hybrid Config'] = $HybObj } #region On-premises Organisation Details try { $OnPremOrgs = Get-OnPremisesOrganization -ErrorAction SilentlyContinue if ($OnPremOrgs) { Section -Style Heading3 'On-Premises Organisation' { Paragraph "The following on-premises Exchange organisation(s) are registered in the hybrid relationship." BlankLine $OpOrgsObj = [System.Collections.ArrayList]::new() foreach ($Org in $OnPremOrgs) { $opInObj = [ordered] @{ 'Name' = $Org.Name 'Organisation GUID' = $Org.OrganizationGuid 'HybridDomains' = ($Org.HybridDomains -join ', ') 'Inbound Connector' = if ($Org.InboundConnector) { $Org.InboundConnector } else { 'Not Set' } 'Outbound Connector' = if ($Org.OutboundConnector) { $Org.OutboundConnector } else { 'Not Set' } } $OpOrgsObj.Add([pscustomobject]$opInObj) | Out-Null } $OpTableParams = @{ Name = "On-Premises Organisations - $TenantId"; List = $false; ColumnWidths = 18, 26, 22, 17, 17 } if ($Report.ShowTableCaptions) { $OpTableParams['Caption'] = "- $($OpTableParams.Name)" } if ($OpOrgsObj.Count -gt 0) { $OpOrgsObj | Table @OpTableParams } } } } catch {} #endregion } #endregion #region Mailbox source analysis try { Write-Host " - Analysing mailbox source of authority..." $AllMailboxes = Get-Mailbox -ResultSize Unlimited -ErrorAction Stop | Where-Object { $_.RecipientTypeDetails -in @('UserMailbox','SharedMailbox') } $CloudMailboxes = @($AllMailboxes | Where-Object { -not $_.IsDirSynced }) $SyncedMailboxes = @($AllMailboxes | Where-Object { $_.IsDirSynced -eq $true }) Section -Style Heading3 'Mailbox Source of Authority' { Paragraph "The following summarises whether mailboxes are cloud-native or synced from on-premises Active Directory." BlankLine $SrcObj = [System.Collections.ArrayList]::new() $srcInObj = [ordered] @{ 'Total Mailboxes' = @($AllMailboxes).Count 'Cloud-Native (Entra ID managed)' = $CloudMailboxes.Count 'Synced from On-Premises AD' = $SyncedMailboxes.Count } $SrcObj.Add([pscustomobject]$srcInObj) | Out-Null $SrcTableParams = @{ Name = "Mailbox Source of Authority - $TenantId"; List = $true; ColumnWidths = 55, 45 } if ($Report.ShowTableCaptions) { $SrcTableParams['Caption'] = "- $($SrcTableParams.Name)" } $SrcObj | Table @SrcTableParams } } catch { Write-ExoError 'Hybrid' "Unable to retrieve mailbox source data: $($_.Exception.Message)" } #endregion } } end { Show-AbrDebugExecutionTime -End -TitleMessage 'Hybrid' } } |