Public/Export-M365SharedMailboxReport.ps1
|
function Export-M365SharedMailboxReport { <# .SYNOPSIS Generates an HTML report of shared mailboxes and who has Full Access. .DESCRIPTION Lists shared mailboxes via Exchange Online and their Full Access delegates. Read-only. Requires an Exchange Online connection (Connect-ExchangeOnline); if missing, the report is created empty with a note. .PARAMETER Path Target path of the HTML file. Default: .\SharedMailbox-Report.html .EXAMPLE Export-M365SharedMailboxReport -BrandName 'CloudNest365' #> [CmdletBinding()] [OutputType([pscustomobject])] param( [string]$Path = (Join-Path (Get-Location) 'SharedMailbox-Report.html'), [string]$BrandName = 'TenantToolbox', [string]$BrandTagline = 'M365 Tenant Administration', [switch]$Csv, [switch]$Excel, [string]$DataPath, [switch]$NoHtml, [switch]$PassThru, [switch]$NoOpen ) $data = @() $exOk = $true try { Assert-TTExchange } catch { $exOk = $false; Write-Warning $_ } if ($exOk) { Write-TTLog -Level INFO -Message "Reading shared mailboxes (Exchange Online) ..." $boxes = Get-EXOMailbox -RecipientTypeDetails SharedMailbox -ResultSize Unlimited -ErrorAction Stop $data = foreach ($b in $boxes) { $delegates = @() try { $delegates = @(Get-EXOMailboxPermission -Identity $b.Identity -ErrorAction Stop | Where-Object { $_.AccessRights -contains 'FullAccess' -and -not $_.IsInherited -and $_.User -notlike 'NT AUTHORITY*' } | ForEach-Object { $_.User }) } catch { } [pscustomobject]@{ Mailbox = $b.DisplayName; Email = $b.PrimarySmtpAddress; Delegates = $delegates } } $data = @($data) } $total = $data.Count $noDel = @($data | Where-Object { @($_.Delegates).Count -eq 0 }).Count $genAt = Get-Date -Format 'yyyy-MM-dd HH:mm' $tenant = try { (Get-MgContext).TenantId } catch { '' } $rows = foreach ($m in ($data | Sort-Object Mailbox)) { $fNo = if (@($m.Delegates).Count -eq 0) { '1' } else { '0' } $delChips = if (@($m.Delegates).Count) { (@($m.Delegates) | ForEach-Object { "<span class='chip'>$(TTEnc $_)</span>" }) -join ' ' } else { "<span class='b b-warn'>none</span>" } $searchAttr = TTEnc ("$($m.Mailbox) $($m.Email)".ToLower()); $nameAttr = TTEnc ([string]$m.Mailbox).ToLower() @" <tr class="item" data-f-nodelegate="$fNo" data-name="$nameAttr" data-search="$searchAttr"> <td><div class="u"><b>$(TTEnc $m.Mailbox)</b><span class="upn">$(TTEnc $m.Email)</span></div></td> <td><div class="chips">$delChips</div></td> </tr> "@ } $kpiHtml = New-TTKpis @( @{ n = $total; l = 'Shared mailboxes'; filter = 'all' } @{ n = $noDel; l = 'Without delegate'; kind = 'warn'; filter = 'nodelegate' } ) $toolbar = New-TTToolbar -SearchPlaceholder 'Search mailbox ...' -Filters @( @{ label = 'All'; key = 'all' }, @{ label = 'Without delegate'; key = 'nodelegate' } ) $note = if (-not $exOk) { '<p class="muted" style="padding:0 0 12px">Not connected to Exchange Online. Run Connect-ExchangeOnline, then re-run this report.</p>' } else { '' } $body = @" $note <div class="panel"><table class="tbl"><thead><tr><th data-sort="name">Shared mailbox</th><th>Full Access delegates</th></tr></thead> <tbody> $($rows -join "`n") </tbody></table><div class="empty" id="empty">No mailbox matches the filters.</div></div> "@ $html = New-TTHtmlPage -Title 'Shared Mailboxes' -Heading 'Shared Mailboxes' -Sub "Tenant $tenant · generated $genAt · $total shared mailboxes" ` -BrandName $BrandName -BrandTagline $BrandTagline -KpiHtml $kpiHtml -ToolbarHtml $toolbar -BodyHtml $body Write-TTLog -Level INFO -Message "Shared mailbox report created: $Path ($total mailboxes)." $flat = $data | Select-Object Mailbox, Email, @{N = 'Delegates'; E = { $_.Delegates -join '; ' } } Complete-TTReport -Html $html -Path $Path -Data $flat -Csv:$Csv -Excel:$Excel -DataPath $DataPath -NoHtml:$NoHtml -NoOpen:$NoOpen -Kind 'Shared-Mailbox-Report' if ($PassThru) { $data } } |