Entra / Microsoft 365 · Compliance & audit
Report adaptive scopes
Demonstrate the use of the Get-AdaptiveScope and Get-AdaptiveScopeMembers cmdlets to report on adaptive scopes for Purview.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
Connect-ExchangeOnline -ShowBanner:$false
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
[array]$Modules = Get-Module | Select-Object -ExpandProperty NameIf (!($Modules -contains "ExchangeOnlineManagement")) {Connect-ExchangeOnline -ShowBanner:$false}Write-Output "Finding adaptive scopes in the organization..."[array]$AdaptiveScopes = Get-AdaptiveScope | Sort-Object NameIf ($AdaptiveScopes) {Write-Output ("Preparing to analyze {0} adaptive scopes..." -f $AdaptiveScopes.count)} Else {Write-Output "No adaptive scopes found in the organization - exiting"Break}$Report = [System.Collections.Generic.List[Object]]::new()$ReportDetail = [System.Collections.Generic.List[Object]]::new()ForEach ($Scope in $AdaptiveScopes) {Write-Output ("Processing scope {0}..." -f $Scope.Name)[array]$Members = Get-AdaptiveScopeMembers -Identity $Scope.Name -PageResultSize Unlimited -State "Added" -ErrorAction SilentlyContinueIf ($Members) {# Only take active (added) members into account for the count, and drop 1 to account for the summary entry$MemberCount = ($Members | Where-Object {$_.State -ne "Removed"} | Measure-Object | Select-Object -ExpandProperty Count) - 1} Else {$MemberCount = 0}If ($Scope.FilterConditions) {$Filter = $Scope.FilterConditions} Else {$Filter = $Scope.RawQuery}$ReportLine = [PSCustomObject] @{Name = $Scope.NameWorkload = $Scope.WorkloadDescription = $Scope.Commentfilter = $FilterCreatedBy = $Scope.CreatedByCreatedDate = Get-Date $Scope.WhenCreated -format 'dd-MMM-yyyy HH:mm'LastModifiedBy = $Scope.LastModifiedByLastModifiedDate = Get-Date $Scope.WhenChanged -format 'dd-MMM-yyyy HH:mm'MemberCount = $MemberCountId = $Scope.ImmutableId}$Report.Add($ReportLine)[int]$i = 0ForEach ($Member in $Members) {If ($i -eq 0 -or $Member.State -eq "Removed") {# Skip the first entry (it's the summary) and any removed members$i++Continue} Else {$Warning = $null# If a mailbox or group, check that it still existsSwitch ($Member.LocationType) {"Group" {$Group = Get-UnifiedGroup -Identity $Member.ObjectId -ErrorAction SilentlyContinueIf (!($Group)) {# Group no longer exists$Warning = "Microsoft 365 Group cannot be found"}}"User" {$Mailbox = Get-Mailbox -Identity $Member.ObjectId -ErrorAction SilentlyContinueIf (!($Mailbox)) {# Mailbox no longer exists$Warning = "Mailbox cannot be found"}}Default {# Do nothing for other types}}# Get the timestamp and make it into a date[string]$AddedDateString = $Member.EventDateTime.Split("+")[0].trim()[datetime]$ParsedEventDate = [DateTime]::ParseExact($AddedDateString, 'MM/dd/yyyy HH:mm:ss', [System.Globalization.CultureInfo]::InvariantCulture)$DetailLine = [PSCustomObject] @{ScopeName = $Scope.NameMemberType = $Member.LocationTypeMemberIdentity = $Member.ObjectIdDisplayName = $Member.DisplayNameUserPrincipalName = $Member.UpnDateAdded = Get-Date $ParsedEventDate -format 'dd-MMM-yyyy HH:mm'Notes = $Warning}$ReportDetail.Add($DetailLine)}}}$ReportFile = ((New-Object -ComObject Shell.Application).Namespace('shell:Downloads').Self.Path) + "\AdaptiveScopesReport.html"$Rundate = Get-Date -format 'dd-MMM-yyyy HH:mm'$Orgname = (Get-OrganizationConfig).Name$HTMLHead="<html><style>BODY{font-family: Arial; font-size: 8pt;}H1{font-size: 32px; font-family: 'Segoe UI Light','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;}H2{font-size: 26px; font-family: 'Segoe UI Light','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;}H3{font-size: 20px; font-family: 'Segoe UI Light','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;}H4{font-size: 16px; font-family: 'Segoe UI Light','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;}TABLE{border: 1px solid black; border-collapse: collapse; font-size: 8pt;}TH{border: 1px solid #969595; background: #dddddd; padding: 5px; color: #000000;}TD{border: 1px solid #969595; padding: 5px; }td.admin{background: #B7EB83;}</style><body><div align=center><p><h1>Adaptive Scopes Report</h1></p><p><h2><b>For the " + $Orgname + " tenant</b></h2></p><p><h3>Generated: " + $RunDate + "</h3></p></div>"$HtmlReport = "<body>"ForEach ($Scope in $Report) {# Generate a HTML section for each scope$HtmlScope = "<h2>Scope: " + $Scope.Name + " (Members: " + $Scope.MemberCount + ")</h2>"$HtmlScope = $HtmlScope + "<p><b><h3>Adaptive Scope Properties:</h3></b><p>Workload: " + $Scope.Workload + `"<br>Description: " + $Scope.Description + "<br>Created by: <b>" + $Scope.CreatedBy + "</b> on <b>" + $($Scope.CreatedDate) + `"</b><br>Last Modified By: <b>" + $Scope.LastModifiedBy + "</b> on <b>" + $($Scope.LastModifiedDate) + "</b><br>Filter Conditions: " + $Scope.filter + "</p>"# Include the membership details$ScopeMembers = $ReportDetail | Where-Object {$_.ScopeName -eq $Scope.Name}If ($ScopeMembers) {$HtmlScope = $HtmlScope + "<br><b>Members:</b><br>"$HtmlScope = $HtmlScope + ($ScopeMembers | Select-Object MemberType, DisplayName, UserPrincipalName, DateAdded, Notes | ConvertTo-Html -Fragment -As Table)} Else {$HtmlScope = $HtmlScope + "<br><i>No members found for this adaptive scope</i><br>"}$HtmlReport = $HtmlReport + $HtmlScope}$HtmlReport = $HtmlHead + $HtmlReport + "</body></html>"$HtmlReport | Out-File -FilePath $ReportFile -Encoding UTF8Write-Output ("Report saved to {0}" -f $ReportFile)
Attribution
Author
Office365itpros