Workloads/Get-ExchangeData.ps1
|
# Get-ExchangeData.ps1 # Collects mailboxes, sizes, archives, connectors, transport rules, # distribution groups, contacts, and hybrid signals. # Part of the M365-QuickAssess module -- not exported. function Get-ExchangeData { param ( $Assessment ) Write-Log "Collecting Exchange data" # ------------------------------------------------------------------- # Mailboxes # ------------------------------------------------------------------- try { $mailboxes = Get-EXOMailbox -ResultSize Unlimited -PropertySets Minimum,Archive -ErrorAction Stop $total = $mailboxes.Count $userMailboxCount = ( $mailboxes | Where-Object { $_.RecipientTypeDetails -eq "UserMailbox" } ).Count $sharedMailboxCount = ( $mailboxes | Where-Object { $_.RecipientTypeDetails -eq "SharedMailbox" } ).Count $resourceMailboxCount = ( $mailboxes | Where-Object { $_.RecipientTypeDetails -in @( "RoomMailbox", "EquipmentMailbox" ) } ).Count $archiveMailboxCount = ( $mailboxes | Where-Object { $_.ArchiveStatus -eq "Active" } ).Count Write-Log "Mailboxes: Total=$total User=$userMailboxCount Shared=$sharedMailboxCount Resource=$resourceMailboxCount Archive=$archiveMailboxCount" } catch { Write-Log "Mailbox collection failed: $( $_.Exception.Message )" "ERROR" return } # ------------------------------------------------------------------- # Mailbox Sizes # Large tenant sampling logic kicks in above 5000 mailboxes # ------------------------------------------------------------------- $totalSizeGB = 0 $largeMailboxCount = 0 try { Write-Log "Collecting mailbox statistics" if ( $total -le 5000 ) { $stats = foreach ( $mbx in $mailboxes ) { try { $stat = Get-EXOMailboxStatistics -Identity $mbx.PrimarySmtpAddress -ErrorAction Stop [PSCustomObject]@{ DisplayName = $mbx.DisplayName UserPrincipalName = $mbx.UserPrincipalName PrimarySmtpAddress = $mbx.PrimarySmtpAddress TotalItemSize = $stat.TotalItemSize } } catch { Write-Log "Failed stats for $( $mbx.PrimarySmtpAddress )" "WARN" } } $scale = 1 } else { Write-Log "Large tenant detected ($total mailboxes) -- using sample of 500 for size estimation" $sample = $mailboxes | Get-Random -Count 500 $stats = foreach ( $mbx in $sample ) { try { $stat = Get-EXOMailboxStatistics -Identity $mbx.PrimarySmtpAddress -ErrorAction Stop [PSCustomObject]@{ DisplayName = $mbx.DisplayName UserPrincipalName = $mbx.UserPrincipalName PrimarySmtpAddress = $mbx.PrimarySmtpAddress TotalItemSize = $stat.TotalItemSize } } catch { Write-Log "Failed stats for $( $mbx.PrimarySmtpAddress )" "WARN" } } $scale = $total / 500 if ( $scale -le 0 ) { $scale = 1 } } foreach ( $stat in $stats ) { $bytes = 0 try { if ( $stat.TotalItemSize -and $stat.TotalItemSize.Value ) { $bytes = $stat.TotalItemSize.Value.ToBytes() } } catch { Write-Log "Failed to read size for $( $stat.DisplayName )" "WARN" } if ( $bytes -gt 0 ) { $gb = $bytes / 1GB $totalSizeGB += $gb if ( $gb -gt 80 ) { $largeMailboxCount++ $Assessment.Findings += New-Finding ` -Type "LargeMailbox" ` -Summary "Mailbox exceeds 80 GB size threshold" ` -Category "Exchange" ` -Severity "High" ` -Details @( "$( $stat.UserPrincipalName ) - $( [math]::Round( $gb, 2 ) ) GB" ) ` -Impact "Large mailboxes increase migration time and risk of failure." ` -Recommendation "Enable online archiving or run cleanup before migration." } } } $totalSizeGB = [math]::Round( $totalSizeGB * $scale, 2 ) $largeMailboxCount = [math]::Round( $largeMailboxCount * $scale ) Write-Log "Mailbox sizes: TotalGB=$totalSizeGB Large=$largeMailboxCount" } catch { Write-Log "Mailbox statistics collection failed: $( $_.Exception.Message )" "WARN" } # ------------------------------------------------------------------- # Archive Mailbox Finding # ------------------------------------------------------------------- if ( $archiveMailboxCount -gt 0 ) { $archiveMailboxes = $mailboxes | Where-Object { $_.ArchiveStatus -eq "Active" } $upns = ( $archiveMailboxes | Select-Object -First 10 -ExpandProperty PrimarySmtpAddress ) -join ", " if ( $archiveMailboxCount -gt 10 ) { $upns += " ... and $( $archiveMailboxCount - 10 ) more" } $Assessment.Findings += New-Finding ` -Type "ArchiveMailboxes" ` -Summary "$archiveMailboxCount archive mailboxes detected" ` -Category "Exchange" ` -Severity "High" ` -Details @( $upns ) ` -Impact "Archive mailboxes must be migrated separately and may require additional tooling or licensing." ` -Recommendation "Confirm archive migration approach and tooling before beginning migration." } # ------------------------------------------------------------------- # Connectors, Transport Rules, Accepted Domains # ------------------------------------------------------------------- try { Write-Log "Collecting connectors, transport rules, and accepted domains" $connectors = Get-InboundConnector -ErrorAction SilentlyContinue $outConnectors = Get-OutboundConnector -ErrorAction SilentlyContinue $transportRules = Get-TransportRule -ErrorAction SilentlyContinue $acceptedDomains = Get-AcceptedDomain -ErrorAction SilentlyContinue $connectorCount = ( ( $connectors | Measure-Object ).Count + ( $outConnectors | Measure-Object ).Count ) Write-Log "Connectors=$connectorCount TransportRules=$( ( $transportRules | Measure-Object ).Count ) AcceptedDomains=$( ( $acceptedDomains | Measure-Object ).Count )" # ------------------------------------------------------------------- # Finding: Connectors # ------------------------------------------------------------------- if ( $connectorCount -gt 0 ) { $Assessment.Findings += New-Finding ` -Type "ExchangeConnectors" ` -Summary "$connectorCount mail connectors detected" ` -Category "Exchange" ` -Severity "Medium" ` -Impact "Custom connectors for mail routing or third-party filtering will need to be recreated in the target tenant." ` -Recommendation "Document all inbound and outbound connectors and plan recreation in the target tenant." } # ------------------------------------------------------------------- # Finding: Transport Rules # ------------------------------------------------------------------- if ( ( $transportRules | Measure-Object ).Count -gt 0 ) { $Assessment.Findings += New-Finding ` -Type "TransportRules" ` -Summary "$( ( $transportRules | Measure-Object ).Count ) transport rules detected" ` -Category "Exchange" ` -Severity "Medium" ` -Impact "Transport rules will not migrate automatically and must be manually recreated in the target tenant." ` -Recommendation "Export and document all transport rules before migration." } } catch { Write-Log "Connector and transport rule collection failed: $( $_.Exception.Message )" "WARN" } # ------------------------------------------------------------------- # Hybrid Detection # ------------------------------------------------------------------- try { $remoteMailboxCount = ( $mailboxes | Where-Object { $_.RecipientTypeDetails -eq "RemoteUserMailbox" } ).Count $dirSync = ( Get-MgOrganization ).OnPremisesSyncEnabled $isExchangeHybrid = ( $remoteMailboxCount -gt 0 -or $dirSync ) Write-Log "Exchange Hybrid: $isExchangeHybrid RemoteMailboxes=$remoteMailboxCount DirSync=$dirSync" if ( $isExchangeHybrid ) { $Assessment.Findings += New-Finding ` -Type "ExchangeHybrid" ` -Summary "Exchange hybrid configuration detected" ` -Category "Exchange" ` -Severity "High" ` -Impact "Hybrid Exchange environments require additional steps to decommission and migrate cleanly. On-premises mailboxes must be migrated before hybrid can be removed." ` -Recommendation "Engage an Exchange hybrid migration specialist. Plan hybrid decommission as part of the migration project." } } catch { Write-Log "Hybrid detection failed: $( $_.Exception.Message )" "WARN" } # ------------------------------------------------------------------- # Contacts, Mail Users, Distribution Groups # ------------------------------------------------------------------- try { $mailContacts = Get-MailContact -ResultSize Unlimited -ErrorAction SilentlyContinue $mailUsers = Get-MailUser -ResultSize Unlimited -ErrorAction SilentlyContinue $distributionGroups = Get-DistributionGroup -ResultSize Unlimited -ErrorAction SilentlyContinue $mailEnabledSecurityGroups = $distributionGroups | Where-Object { $_.RecipientTypeDetails -eq "MailUniversalSecurityGroup" } $dynamicDistributionGroups = Get-DynamicDistributionGroup -ResultSize Unlimited -ErrorAction SilentlyContinue $mailboxWithDelegatesCount = ( $mailboxes | Where-Object { ( $_.GrantSendOnBehalfTo | Measure-Object ).Count -gt 0 } ).Count Write-Log "Contacts=$( ( $mailContacts | Measure-Object ).Count ) MailUsers=$( ( $mailUsers | Measure-Object ).Count ) DGs=$( ( $distributionGroups | Measure-Object ).Count ) DDGs=$( ( $dynamicDistributionGroups | Measure-Object ).Count ) Delegates=$mailboxWithDelegatesCount" # ------------------------------------------------------------------- # Finding: Dynamic Distribution Groups # ------------------------------------------------------------------- if ( ( $dynamicDistributionGroups | Measure-Object ).Count -gt 0 ) { $Assessment.Findings += New-Finding ` -Type "DynamicDistributionGroups" ` -Summary "$( ( $dynamicDistributionGroups | Measure-Object ).Count ) dynamic distribution groups detected" ` -Category "Exchange" ` -Severity "Medium" ` -Impact "Dynamic distribution groups use recipient filters that must be manually recreated in the target tenant." ` -Recommendation "Document all dynamic distribution group filters and plan recreation in the target tenant." } # ------------------------------------------------------------------- # Finding: Mailboxes with delegates # ------------------------------------------------------------------- if ( $mailboxWithDelegatesCount -gt 0 ) { $Assessment.Findings += New-Finding ` -Type "MailboxDelegates" ` -Summary "$mailboxWithDelegatesCount mailboxes have Send on Behalf delegates configured" ` -Category "Exchange" ` -Severity "Medium" ` -Impact "Delegate permissions must be reconfigured after migration as they do not migrate automatically." ` -Recommendation "Document all delegate configurations and plan post-migration remediation." } } catch { Write-Log "Contacts and distribution group collection failed: $( $_.Exception.Message )" "WARN" } # ------------------------------------------------------------------- # Populate Schema # ------------------------------------------------------------------- $Assessment.Exchange.MailboxCount = $total $Assessment.Exchange.UserMailboxCount = $userMailboxCount $Assessment.Exchange.SharedMailboxCount = $sharedMailboxCount $Assessment.Exchange.ResourceMailboxCount = $resourceMailboxCount $Assessment.Exchange.LargeMailboxCount = $largeMailboxCount $Assessment.Exchange.ArchiveMailboxCount = $archiveMailboxCount $Assessment.Exchange.TotalMailboxSizeGB = $totalSizeGB $Assessment.Exchange.HasConnectors = ( $connectorCount -gt 0 ) $Assessment.Exchange.ConnectorCount = $connectorCount $Assessment.Exchange.HasTransportRules = ( ( $transportRules | Measure-Object ).Count -gt 0 ) $Assessment.Exchange.TransportRuleCount = ( $transportRules | Measure-Object ).Count $Assessment.Exchange.AcceptedDomainCount = ( $acceptedDomains | Measure-Object ).Count $Assessment.Exchange.MailboxWithDelegatesCount = $mailboxWithDelegatesCount $Assessment.Exchange.IsExchangeHybrid = $isExchangeHybrid $Assessment.Exchange.HasRemoteMailboxes = ( $remoteMailboxCount -gt 0 ) $Assessment.Exchange.RemoteMailboxCount = $remoteMailboxCount $Assessment.Exchange.HasOnPremMailboxes = $isExchangeHybrid $Assessment.Exchange.MailContactCount = ( $mailContacts | Measure-Object ).Count $Assessment.Exchange.MailUserCount = ( $mailUsers | Measure-Object ).Count $Assessment.Exchange.HasContacts = ( ( $mailContacts | Measure-Object ).Count -gt 0 ) $Assessment.Exchange.DistributionGroupCount = ( $distributionGroups | Measure-Object ).Count $Assessment.Exchange.MailEnabledSecurityGroupCount = ( $mailEnabledSecurityGroups | Measure-Object ).Count $Assessment.Exchange.DynamicDistributionGroupCount = ( $dynamicDistributionGroups | Measure-Object ).Count $Assessment.Exchange.HasDistributionLists = ( ( $distributionGroups | Measure-Object ).Count -gt 0 ) $Assessment.Summary.Mailboxes = $total $Assessment.Summary.MailboxCount = $total $Assessment.Summary.TotalMailboxSizeGB = $totalSizeGB } |