Inventory/Get-MailboxInventory.ps1
|
<#
.SYNOPSIS Generates a per-mailbox inventory with size, type, and key properties. .DESCRIPTION Enumerates all mailboxes in Exchange Online (User, Shared, Room, Equipment) and collects per-mailbox detail including size, item count, forwarding rules, litigation hold, and archive status. Designed for M&A due diligence, migration planning, and tenant-wide asset inventories. Requires ExchangeOnlineManagement module and an active Exchange Online connection. .PARAMETER OutputPath Optional path to export results as CSV. If not specified, results are returned to the pipeline. .EXAMPLE PS> . .\Common\Connect-Service.ps1 PS> Connect-Service -Service ExchangeOnline PS> .\Inventory\Get-MailboxInventory.ps1 Returns per-mailbox inventory for all mailboxes in the tenant. .EXAMPLE PS> .\Inventory\Get-MailboxInventory.ps1 -OutputPath '.\mailbox-inventory.csv' Exports the full mailbox inventory to CSV. .NOTES M365 Assess — M&A Inventory #> [CmdletBinding()] param( [Parameter()] [ValidateNotNullOrEmpty()] [string]$OutputPath ) $ErrorActionPreference = 'Stop' # Verify EXO connection try { $null = Get-OrganizationConfig -ErrorAction Stop } catch { Write-Error "Not connected to Exchange Online. Run Connect-Service -Service ExchangeOnline first." return } # Retrieve all mailboxes with required properties Write-Verbose "Retrieving all mailboxes (this may take a moment in large tenants)..." try { $mailboxProperties = @( 'DisplayName' 'PrimarySmtpAddress' 'RecipientTypeDetails' 'WhenCreated' 'ForwardingAddress' 'ForwardingSmtpAddress' 'DeliverToMailboxAndForward' 'ArchiveStatus' 'LitigationHoldEnabled' 'RetentionPolicy' 'HiddenFromAddressListsEnabled' 'ExchangeObjectId' ) $allMailboxes = @(Get-EXOMailbox -ResultSize Unlimited -Properties $mailboxProperties) } catch { Write-Error "Failed to retrieve mailboxes: $_" return } if ($allMailboxes.Count -eq 0) { Write-Verbose "No mailboxes found in this tenant" return } Write-Verbose "Processing $($allMailboxes.Count) mailboxes..." $results = [System.Collections.Generic.List[PSCustomObject]]::new() $counter = 0 foreach ($mbx in $allMailboxes) { $counter++ if ($counter % 50 -eq 0 -or $counter -eq 1) { Write-Verbose "[$counter/$($allMailboxes.Count)] $($mbx.PrimarySmtpAddress)" } # Get mailbox statistics for size and item count $sizeMB = $null $itemCount = $null try { $stats = Get-EXOMailboxStatistics -Identity $mbx.ExchangeObjectId -ErrorAction Stop if ($stats.TotalItemSize -and $stats.TotalItemSize.ToString() -match '\(([0-9,]+)\s+bytes\)') { $sizeBytes = [long]($Matches[1] -replace ',', '') $sizeMB = [math]::Round($sizeBytes / 1MB, 2) } $itemCount = $stats.ItemCount } catch { Write-Warning "Could not retrieve statistics for $($mbx.PrimarySmtpAddress): $_" } $results.Add([PSCustomObject]@{ DisplayName = $mbx.DisplayName PrimarySmtpAddress = $mbx.PrimarySmtpAddress RecipientTypeDetails = $mbx.RecipientTypeDetails WhenCreated = $mbx.WhenCreated TotalItemSizeMB = $sizeMB ItemCount = $itemCount ArchiveStatus = $mbx.ArchiveStatus ForwardingAddress = $mbx.ForwardingAddress ForwardingSmtpAddress = $mbx.ForwardingSmtpAddress DeliverToMailboxAndForward = $mbx.DeliverToMailboxAndForward LitigationHoldEnabled = $mbx.LitigationHoldEnabled RetentionPolicy = $mbx.RetentionPolicy HiddenFromAddressLists = $mbx.HiddenFromAddressListsEnabled }) } Write-Verbose "Inventory complete: $($results.Count) mailboxes processed" if ($OutputPath) { $results | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8 Write-Output "Exported mailbox inventory ($($results.Count) mailboxes) to $OutputPath" } else { Write-Output $results } |