Public/Export-M365DynamicGroupReport.ps1
|
function Export-M365DynamicGroupReport { <# .SYNOPSIS Generates an HTML report of dynamic groups and their membership rules. .DESCRIPTION Lists groups with dynamic membership via Graph and shows the membership rule and its processing state (On / Paused). Read-only. .PARAMETER Path Target path of the HTML file. Default: .\DynamicGroup-Report.html .EXAMPLE Export-M365DynamicGroupReport -BrandName 'CloudNest365' #> [CmdletBinding()] [OutputType([pscustomobject])] param( [string]$Path = (Join-Path (Get-Location) 'DynamicGroup-Report.html'), [string]$BrandName = 'TenantToolbox', [string]$BrandTagline = 'M365 Tenant Administration', [switch]$Csv, [switch]$Excel, [string]$DataPath, [switch]$NoHtml, [switch]$PassThru, [switch]$NoOpen ) Assert-TTGraph Write-TTLog -Level INFO -Message "Reading dynamic groups ..." $groups = Get-TTGraphCollection "https://graph.microsoft.com/v1.0/groups?`$filter=groupTypes/any(c:c eq 'DynamicMembership')&`$select=displayName,membershipRule,membershipRuleProcessingState,securityEnabled,groupTypes&`$top=100" $data = foreach ($g in $groups) { $kind = if (@($g.groupTypes) -contains 'Unified') { 'Microsoft 365' } elseif ($g.securityEnabled) { 'Security' } else { 'Other' } [pscustomobject]@{ Group = $g.displayName; Kind = $kind; Rule = $g.membershipRule State = $g.membershipRuleProcessingState; Paused = ($g.membershipRuleProcessingState -eq 'Paused') } } $data = @($data) $total = $data.Count $paused = @($data | Where-Object Paused).Count $genAt = Get-Date -Format 'yyyy-MM-dd HH:mm'; $tenant = (Get-MgContext).TenantId $rows = foreach ($g in ($data | Sort-Object @{E = { -[int][bool]$_.Paused } }, Group)) { $fPaused = if ($g.Paused) { '1' } else { '0' } $st = if ($g.Paused) { "<span class='b b-warn'>Paused</span>" } else { "<span class='b b-ok'>On</span>" } $searchAttr = TTEnc ("$($g.Group) $($g.Rule)".ToLower()); $nameAttr = TTEnc ([string]$g.Group).ToLower() @" <tr class="item" data-f-paused="$fPaused" data-name="$nameAttr" data-search="$searchAttr"> <td><b>$(TTEnc $g.Group)</b></td><td>$(TTEnc $g.Kind)</td><td>$st</td> <td><code style="font-size:11px">$(TTEnc $g.Rule)</code></td> </tr> "@ } $kpiHtml = New-TTKpis @( @{ n = $total; l = 'Dynamic groups'; filter = 'all' } @{ n = $paused; l = 'Paused'; kind = 'warn'; filter = 'paused' } @{ n = ($total - $paused); l = 'Active'; kind = 'ok' } ) $toolbar = New-TTToolbar -SearchPlaceholder 'Search group or rule ...' -Filters @( @{ label = 'All'; key = 'all' }, @{ label = 'Paused'; key = 'paused' } ) $body = @" <div class="panel"><table class="tbl"><thead><tr><th data-sort="name">Group</th><th>Type</th><th>State</th><th>Membership rule</th></tr></thead> <tbody> $($rows -join "`n") </tbody></table><div class="empty" id="empty">No group matches the filters.</div></div> "@ $html = New-TTHtmlPage -Title 'Dynamic Groups' -Heading 'Dynamic Groups' -Sub "Tenant $tenant · generated $genAt · $total dynamic groups" ` -BrandName $BrandName -BrandTagline $BrandTagline -KpiHtml $kpiHtml -ToolbarHtml $toolbar -BodyHtml $body Write-TTLog -Level INFO -Message "Dynamic group report created: $Path ($total groups, $paused paused)." $flat = $data | Select-Object Group, Kind, State, Rule Complete-TTReport -Html $html -Path $Path -Data $flat -Csv:$Csv -Excel:$Excel -DataPath $DataPath -NoHtml:$NoHtml -NoOpen:$NoOpen -Kind 'Dynamic-Group-Report' if ($PassThru) { $data } } |