Public/PrivilegedAccess/Get-EntraOpsPrivilegedEAMIntune.ps1
|
<# .SYNOPSIS Get a list in schema of EntraOps with all privileged principals in Microsoft Intune and assigned roles and classifications. .DESCRIPTION Get a list in schema of EntraOps with all privileged principals in Microsoft Intune and assigned roles and classifications. .PARAMETER TenantId Tenant ID of the Microsoft Entra ID tenant. Default is the current tenant ID. .PARAMETER FolderClassification Folder path to the classification definition files. Default is "./Classification". .PARAMETER SampleMode Use sample data for testing or offline mode. Default is $False. Default sample data is stored in "./Samples" .PARAMETER GlobalExclusion Use global exclusion list for classification. Default is $true. Global exclusion list is stored in "./Classification/Global.json". .PARAMETER IncludeJustification Include the Justification property (documenting a manual classification overwrite) on all Classification entries of the returned objects. Default is $false, so the property is not present in the output at all. .PARAMETER IncludeObjectDetails Include descriptive object details in warning output. Defaults to ConsoleOutput.IncludeObjectDetails from EntraOpsConfig.json. Object IDs are always shown. #> function Get-EntraOpsPrivilegedEAMIntune { [cmdletbinding()] param ( [Parameter(Mandatory = $false)] [System.String]$TenantId = (Get-EntraOpsAzContextValue -Property TenantId) , [Parameter(Mandatory = $false)] [System.String]$FolderClassification = "$DefaultFolderClassification" , [Parameter(Mandatory = $false)] [System.Boolean]$SampleMode = $False , [Parameter(Mandatory = $false)] [System.Boolean]$GlobalExclusion = $true , [Parameter(Mandatory = $false)] [System.Boolean]$EnableParallelProcessing = $true , [Parameter(Mandatory = $false)] [System.Int32]$ParallelThrottleLimit = 10 , [Parameter(Mandatory = $false)] [switch]$IncludeJustification , [Parameter(Mandatory = $false)] [System.Boolean]$IncludeObjectDetails = [bool]$Global:EntraOpsIncludeObjectDetails ) $WarningMessages = New-Object -TypeName "System.Collections.Generic.List[psobject]" # Check if classification file custom and/or template file exists, choose custom template for tenant if available $IntuneClassificationFilePath = Resolve-EntraOpsClassificationPath -ClassificationFileName "Classification_DeviceManagement.json" -FolderClassification $FolderClassification # Load group → device member mapping for BloodHound edge enrichment (created by Update-EntraOpsClassificationControlPlaneScope) $DeviceMembersFile = Join-Path (Split-Path $IntuneClassificationFilePath -Parent) "DeviceManagement_ScopeGroupDeviceMembers.json" $DeviceMembersData = if (Test-Path $DeviceMembersFile) { Get-Content $DeviceMembersFile -Raw | ConvertFrom-Json } else { $null } #region Get all role assignments and global exclusions #region Stage 1: Fetch Device Management Roles $Stage1Start = Get-Date Write-Host "" Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host " Stage 1/4: Fetching Device Management Roles" -ForegroundColor Cyan Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host "Retrieving Intune device management role assignments and definitions..." -ForegroundColor Gray Write-Progress -Activity "Stage 1/4: Fetching Device Management Roles" -Status "Loading role assignments and global exclusions..." -PercentComplete 10 if ($SampleMode -ne $True) { $DeviceMgmtRbacAssignments = Get-EntraOpsPrivilegedDeviceRoles -TenantId $TenantId -WarningMessages $WarningMessages } else { $WarningMessages.Add([PSCustomObject]@{Type = "Stage1"; Message = "SampleMode currently not supported!" }) } $GlobalExclusionList = Import-EntraOpsGlobalExclusions -Enabled $GlobalExclusion $Stage1Duration = ((Get-Date) - $Stage1Start).TotalSeconds Write-Host "✓ Stage 1 completed in $([Math]::Round($Stage1Duration, 2)) seconds ($($DeviceMgmtRbacAssignments.Count) role assignments retrieved)" -ForegroundColor Green Write-Progress -Activity "Stage 1/4: Fetching Device Management Roles" -Completed #endregion # Return early if no role assignments found to prevent null index errors if ($null -eq $DeviceMgmtRbacAssignments -or @($DeviceMgmtRbacAssignments).Count -eq 0) { Write-Warning "No Device Management role assignments found. Returning empty result." return @() } #region Get scope tages and assignments #region Stage 2: Fetch Scope Tags $Stage2Start = Get-Date Write-Host "" Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host " Stage 2/4: Fetching Scope Tags" -ForegroundColor Cyan Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host "Retrieving role scope tags and their assignments for Intune device management..." -ForegroundColor Gray Write-Progress -Activity "Stage 2/4: Fetching Scope Tags" -Status "Loading scope tags and assignments..." -PercentComplete 25 $ScopeTags = (Invoke-EntraOpsMsGraphQuery -Method GET -Uri https://graph.microsoft.com/beta/deviceManagement/roleScopeTags -OutputType PSObject) # Build scope tag name lookup for display name resolution $ScopeTagNameLookup = @{} foreach ($ScopeTag in $ScopeTags) { $ScopeTagNameLookup["$($ScopeTag.Id)"] = $ScopeTag.DisplayName } # Resolve display names for directoryScopeIds from role assignments # Scope tags are only for visibility, not scope enforcement — directoryScopeIds per assignment is authoritative Write-Host "Resolving display names for role assignment directoryScopeIds..." -ForegroundColor Gray $ScopeGroupNameCache = @{} $AllDirectoryScopeIds = @($DeviceMgmtRbacAssignments | Where-Object { $null -ne $_.DirectoryScopeIds } | ForEach-Object { $_.DirectoryScopeIds } | Where-Object { -not [string]::IsNullOrEmpty($_) } | Select-Object -Unique) $AllGroupIdsToResolve = @($AllDirectoryScopeIds | Where-Object { -not [string]::IsNullOrEmpty($_) } | Select-Object -Unique) if ($AllGroupIdsToResolve.Count -gt 0) { try { $Body = @{ ids = @($AllGroupIdsToResolve) } | ConvertTo-Json -Depth 3 $GroupObjects = Invoke-EntraOpsMsGraphQuery -Method POST -Uri "https://graph.microsoft.com/beta/directoryObjects/getByIds" -Body $Body -OutputType PSObject foreach ($GroupObj in $GroupObjects) { $ScopeGroupNameCache[$GroupObj.id] = $GroupObj.displayName } } catch { Write-Warning "Failed to batch-resolve directory object display names: $($_.Exception.Message)" } # Fallback for any IDs not resolved foreach ($GroupId in $AllGroupIdsToResolve) { if (-not $ScopeGroupNameCache.ContainsKey($GroupId)) { $ScopeGroupNameCache[$GroupId] = $GroupId } } } Write-Host "Resolved display names for $($ScopeGroupNameCache.Count) directory object(s)." -ForegroundColor Gray $Stage2Duration = ((Get-Date) - $Stage2Start).TotalSeconds Write-Host "✓ Stage 2 completed in $([Math]::Round($Stage2Duration, 2)) seconds ($($ScopeTags.Count) scope tags retrieved)" -ForegroundColor Green Write-Progress -Activity "Stage 2/4: Fetching Scope Tags" -Completed #endregion #region Check if RBAC role action and scope is defined in JSON classification #region Stage 3: Classify Role Actions $Stage3Start = Get-Date Write-Host "" Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host " Stage 3/4: Classifying Role Actions" -ForegroundColor Cyan Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host "Checking if RBAC role action and scope is defined in JSON classification..." -ForegroundColor Gray Write-Progress -Activity "Stage 3/4: Classifying Role Actions" -Status "Mapping JSON classifications..." -PercentComplete 50 # Optimization: Pre-fetch all role definitions to avoid N+1 API calls $IntuneRoleDefinitionsCache = @{} if ($SampleMode -ne $True) { Write-Host "Pre-fetching all Intune role definitions..." -ForegroundColor Gray $AllIntuneRoles = Invoke-EntraOpsMsGraphQuery -Method GET -Uri "https://graph.microsoft.com/beta/roleManagement/deviceManagement/roleDefinitions" -OutputType PSObject foreach ($Role in $AllIntuneRoles) { if ($null -ne $Role.id) { # Ensure ID is string for consistent lookup $IntuneRoleDefinitionsCache["$($Role.id)"] = $Role } } Write-Host "Cached $($IntuneRoleDefinitionsCache.Count) role definitions." -ForegroundColor Gray } # Build lookup: (RoleDefinitionId, RoleAssignmentScopeId) -> directoryScopeIds for scope matching and TaggedBy enrichment # Scoped per role assignment to avoid cross-contamination between different role assignments sharing the same scope tag $DirectoryScopesByRoleAndScope = @{} foreach ($assignment in $DeviceMgmtRbacAssignments | Where-Object { $_.RoleAssignmentScopeId -ne "/" -and $null -ne $_.DirectoryScopeIds }) { $key = "$($assignment.RoleDefinitionId)|$($assignment.RoleAssignmentScopeId)" if (-not $DirectoryScopesByRoleAndScope.ContainsKey($key)) { $DirectoryScopesByRoleAndScope[$key] = [System.Collections.Generic.List[string]]::new() } foreach ($dsId in $assignment.DirectoryScopeIds) { if (-not [string]::IsNullOrEmpty($dsId) -and -not $DirectoryScopesByRoleAndScope[$key].Contains($dsId)) { $DirectoryScopesByRoleAndScope[$key].Add($dsId) } } } $IntuneResourcesByClassificationJSON = Expand-EntraOpsPrivilegedEAMJsonFile -FilePath $IntuneClassificationFilePath | select-object EAMTierLevelName, EAMTierLevelTagValue, Category, Service, RoleAssignmentScopeName, ExcludedRoleAssignmentScopeName, RoleDefinitionActions, ExcludedRoleDefinitionActions # Load classification overwrites (down-/upgrade of entire role definitions) from tenant-specific folder or Templates fallback. # Role action overwrites are already baked into the classification file by Update-EntraOpsClassificationControlPlaneScope. $ClassificationOverwrites = Import-EntraOpsClassificationOverwrites -RbacSystem "DeviceManagement" -FolderClassification $FolderClassification $DeviceMgmtRbacClassificationsByJSON = @() $DeviceMgmtRbacClassificationsByJSON += foreach ($DeviceMgmtRbacAssignment in $DeviceMgmtRbacAssignments | Select-Object -Unique RoleDefinitionId, RoleAssignmentScopeId) { if ($DeviceMgmtRbacAssignment.RoleAssignmentScopeId -ne "/") { $DeviceMgmtRbacAssignment.RoleAssignmentScopeId = "$($DeviceMgmtRbacAssignment.RoleAssignmentScopeId)" } # Role actions are defined for scope and role definition contains an action of the role, otherwise all role actions within role assignment scope will be applied if ($SampleMode -eq $True) { $WarningMessages.Add([PSCustomObject]@{Type = "Stage3"; Message = "SampleMode currently not supported!" }) } else { $IntuneRoleActions = $IntuneRoleDefinitionsCache["$($DeviceMgmtRbacAssignment.RoleDefinitionId)"] } # Scope tags are for visibility only — use directoryScopeIds of this specific assignment for scope matching $AppScopeId = "$($DeviceMgmtRbacAssignment.RoleAssignmentScopeId)" # Get directoryScopeIds specific to this role definition + scope combination $IsScoped = ($AppScopeId -ne "/") $RoleAndScopeKey = "$($DeviceMgmtRbacAssignment.RoleDefinitionId)|$AppScopeId" $AssignmentDirectoryScopes = @() if ($IsScoped -and $DirectoryScopesByRoleAndScope.ContainsKey($RoleAndScopeKey)) { $AssignmentDirectoryScopes = @($DirectoryScopesByRoleAndScope[$RoleAndScopeKey]) } $MatchedClassificationByScope = @() # Check if RBAC scope is listed in JSON classification. # For scoped assignments, the assignment's own directoryScopeIds are matched against # RoleAssignmentScopeName (which contains group GUIDs from classification parameters). # "/*" matches any scoped (non-root) assignment. # "/" matches tenant-wide assignments. # Specific GUIDs match against the assignment's directoryScopeIds. $MatchedClassificationByScope += foreach ($ClassEntry in $IntuneResourcesByClassificationJSON) { $ScopeName = $ClassEntry.RoleAssignmentScopeName # Determine scope match # Note: Intune uses numeric scope tag IDs (e.g. "2") not path-like scopes, # so -like with "/*" pattern does not work here (unlike Defender/EntraID). $ScopeMatch = $false if ($AppScopeId -eq "/" -and $ScopeName -eq "/") { $ScopeMatch = $true } elseif ($AppScopeId -ne "/" -and $ScopeName -eq "/*") { $ScopeMatch = $true } elseif ($AppScopeId -ne "/" -and $ScopeName -ne "/" -and $ScopeName -ne "/*" -and $AssignmentDirectoryScopes.Count -gt 0) { # Group GUID match: check if classification scope name is among this assignment's directoryScopeIds if ($ScopeName -in $AssignmentDirectoryScopes) { $ScopeMatch = $true } } if ($ScopeMatch) { # Exclusion check against this assignment's directoryScopeIds (not all scope tag groups) $IsExcluded = $false if ($AppScopeId -eq "/") { $IsExcluded = ($ClassEntry.ExcludedRoleAssignmentScopeName -contains "/") } elseif ($ScopeName -eq "/*" -and $IsScoped -and $AssignmentDirectoryScopes.Count -gt 0) { # Wildcard match: only exclude if ALL of this assignment's directoryScopeIds are excluded $NonExcludedGroups = @($AssignmentDirectoryScopes | Where-Object { $_ -notin $ClassEntry.ExcludedRoleAssignmentScopeName }) $IsExcluded = ($NonExcludedGroups.Count -eq 0) } elseif ($ScopeName -ne "/*" -and $ScopeName -ne "/") { # GUID match: check if the specific matched group is excluded $IsExcluded = ($ScopeName -in $ClassEntry.ExcludedRoleAssignmentScopeName) } if (-not $IsExcluded) { $ClassEntry } } } # Guard: does any allowed role action match an in-scope classification? Matchers are # precomputed once (loop-invariant) and the probe short-circuits on the first hit instead # of materializing the full match set only to test .Count -gt 0. $GuardMatchers = @(foreach ($ClassEntry in $MatchedClassificationByScope) { Build-EntraOpsClassificationActionMatcher -RoleDefinitionActions $ClassEntry.RoleDefinitionActions -ExcludedRoleDefinitionActions $ClassEntry.ExcludedRoleDefinitionActions }) $HasClassifiedRoleAction = $false foreach ($Action in $IntuneRoleActions.rolePermissions.allowedResourceActions) { if ([string]::IsNullOrEmpty($Action)) { continue } foreach ($Matcher in $GuardMatchers) { $AllowedMatch = $Matcher.AllowedExact.Contains($Action) if (-not $AllowedMatch) { foreach ($Pattern in $Matcher.AllowedWildcards) { if ($Action -like $Pattern) { $AllowedMatch = $true; break } } } if (-not $AllowedMatch) { continue } $ExcludedMatch = $Matcher.ExcludedExact.Contains($Action) if (-not $ExcludedMatch) { foreach ($Pattern in $Matcher.ExcludedWildcards) { if ($Action -like $Pattern) { $ExcludedMatch = $true; break } } } if (-not $ExcludedMatch) { $HasClassifiedRoleAction = $true; break } } if ($HasClassifiedRoleAction) { break } } if ($HasClassifiedRoleAction) { # Track which scope name (group GUID or wildcard) triggered each classification match, # and which specific groups from this assignment are relevant (non-excluded) # Matchers precomputed once per classification entry (loop-invariant across the action # loop). This site deliberately checks allowed actions only, like the helper call it # replaces - exclusions here are scope-based, handled below per match type. $IntuneClassificationMatchers = @(foreach ($ClassEntry in $IntuneResourcesByClassificationJSON) { [pscustomobject]@{ Entry = $ClassEntry Matcher = Build-EntraOpsClassificationActionMatcher -RoleDefinitionActions $ClassEntry.RoleDefinitionActions } }) $ClassifiedWithMatchedScope = @() foreach ($IntuneRoleAction in $IntuneRoleActions.rolePermissions.allowedResourceActions) { if ([string]::IsNullOrEmpty($IntuneRoleAction)) { continue } $ClassifiedWithMatchedScope += foreach ($MatcherEntry in $IntuneClassificationMatchers) { $ClassEntry = $MatcherEntry.Entry $ScopeName = $ClassEntry.RoleAssignmentScopeName # Same scope matching logic as above $ScopeMatch = $false $MatchType = $null if ($AppScopeId -eq "/" -and $ScopeName -eq "/") { $ScopeMatch = $true $MatchType = "root" } elseif ($AppScopeId -ne "/" -and $ScopeName -eq "/*") { $ScopeMatch = $true $MatchType = "wildcard" } elseif ($AppScopeId -ne "/" -and $ScopeName -ne "/" -and $ScopeName -ne "/*" -and $AssignmentDirectoryScopes.Count -gt 0 -and $ScopeName -in $AssignmentDirectoryScopes) { $ScopeMatch = $true $MatchType = "guid" } $ActionMatch = $false if ($ScopeMatch) { $ActionMatch = $MatcherEntry.Matcher.AllowedExact.Contains($IntuneRoleAction) if (-not $ActionMatch) { foreach ($Pattern in $MatcherEntry.Matcher.AllowedWildcards) { if ($IntuneRoleAction -like $Pattern) { $ActionMatch = $true; break } } } } if ($ScopeMatch -and $ActionMatch) { $IsExcluded = $false $EntryMatchedGroups = @() if ($AppScopeId -eq "/") { $IsExcluded = ($ClassEntry.ExcludedRoleAssignmentScopeName -contains "/") } elseif ($MatchType -eq "wildcard" -and $IsScoped -and $AssignmentDirectoryScopes.Count -gt 0) { # Wildcard: filter to only the non-excluded directoryScopeIds from this assignment $EntryMatchedGroups = @($AssignmentDirectoryScopes | Where-Object { $_ -notin $ClassEntry.ExcludedRoleAssignmentScopeName }) $IsExcluded = ($EntryMatchedGroups.Count -eq 0) } elseif ($MatchType -eq "guid") { # GUID match: check if the specific matched group is excluded $IsExcluded = ($ScopeName -in $ClassEntry.ExcludedRoleAssignmentScopeName) if (-not $IsExcluded -and $ScopeName -in $AssignmentDirectoryScopes) { $EntryMatchedGroups = @($ScopeName) } } if (-not $IsExcluded) { [PSCustomObject]@{ EAMTierLevelName = $ClassEntry.EAMTierLevelName EAMTierLevelTagValue = $ClassEntry.EAMTierLevelTagValue Service = $ClassEntry.Service MatchedScopeName = $ScopeName MatchType = $MatchType MatchedGroupIds = $EntryMatchedGroups MatchedAction = $IntuneRoleAction } } } } } # Group by unique classification and compute per-classification TaggedBy $UniqueClassifications = $ClassifiedWithMatchedScope | Select-Object -Unique EAMTierLevelName, EAMTierLevelTagValue, Service $Classification = foreach ($UniqueClass in $UniqueClassifications) { # Aggregate matched entries for this classification $MatchedEntries = @($ClassifiedWithMatchedScope | Where-Object { $_.EAMTierLevelName -eq $UniqueClass.EAMTierLevelName -and $_.EAMTierLevelTagValue -eq $UniqueClass.EAMTierLevelTagValue -and $_.Service -eq $UniqueClass.Service }) # Aggregate matched actions across all entries [array]$MatchedActions = @($MatchedEntries.MatchedAction | Where-Object { -not [string]::IsNullOrEmpty($_) } | Select-Object -Unique) if ($IsScoped) { [array]$TaggedByIds = @($MatchedEntries | ForEach-Object { $_.MatchedGroupIds } | Where-Object { -not [string]::IsNullOrEmpty($_) } | Select-Object -Unique) if ($TaggedByIds.Count -gt 0) { [array]$TaggedByNames = @($TaggedByIds | ForEach-Object { $ScopeGroupNameCache[$_] }) $TaggedBySystem = "ScopeTagAssignedGroup" } else { $TaggedByIds = $null $TaggedByNames = $null $TaggedBySystem = $null } } else { $TaggedByIds = $null $TaggedByNames = $null $TaggedBySystem = $null } # Resolve scoped devices from group → device member mapping $ScopedObjects = @() if ($null -ne $DeviceMembersData) { if ($TaggedByIds.Count -gt 0) { # Scoped assignment: devices from matched groups $ScopedObjects = @($TaggedByIds | ForEach-Object { $grpData = $DeviceMembersData.groupDeviceMembers.$_ if ($grpData) { $grpData.deviceMembers } } | Where-Object { $null -ne $_ }) } elseif (-not $IsScoped) { # Tenant-wide assignment: all classified devices $ScopedObjects = @($DeviceMembersData.allClassifiedDevices | Where-Object { $null -ne $_ }) } } [PSCustomObject]@{ 'AdminTierLevel' = $UniqueClass.EAMTierLevelTagValue 'AdminTierLevelName' = $UniqueClass.EAMTierLevelName 'MatchedActions' = if ($MatchedActions.Count -gt 0) { , @($MatchedActions) } else { $null } 'ScopedObjects' = if ($ScopedObjects.Count -gt 0) { $ScopedObjects } else { $null } 'Service' = $UniqueClass.Service 'TaggedBy' = "JSONwithAction" 'TaggedByObjectIds' = $TaggedByIds 'TaggedByObjectDisplayNames' = $TaggedByNames 'TaggedByRoleSystem' = $TaggedBySystem } } [PSCustomObject]@{ 'RoleDefinitionId' = $DeviceMgmtRbacAssignment.RoleDefinitionId 'RoleAssignmentScopeId' = $DeviceMgmtRbacAssignment.RoleAssignmentScopeId 'Classification' = $Classification } } else { $ClassifiedWithMatchedScope = @() } } #endregion #region Classify all assigned privileged users and groups in Device Management $DeviceMgmtRbacClassifications = foreach ($DeviceMgmtRbacAssignment in $DeviceMgmtRbacAssignments) { $DeviceMgmtRbacAssignment = $DeviceMgmtRbacAssignment | Select-Object -ExcludeProperty Classification, DirectoryScopeIds $Classification = @() $Classification += ($DeviceMgmtRbacClassificationsByJSON | Where-Object { $_.RoleAssignmentScopeId -eq $DeviceMgmtRbacAssignment.RoleAssignmentScopeId -and $_.RoleDefinitionId -eq $DeviceMgmtRbacAssignment.RoleDefinitionId }).Classification $Classification = $Classification | select-object -Unique AdminTierLevel, AdminTierLevelName, Service, TaggedBy, TaggedByObjectIds, TaggedByObjectDisplayNames, TaggedByRoleSystem, MatchedActions, ScopedObjects, Justification | Sort-Object AdminTierLevel, AdminTierLevelName, Service, TaggedBy $DeviceMgmtRbacAssignment | Add-Member -NotePropertyName "Classification" -NotePropertyValue $Classification -Force $DeviceMgmtRbacAssignment } # Apply role definition classification overwrites (down-/upgrade by RoleDefinitionId or RoleDefinitionName) if ($ClassificationOverwrites.RoleDefinitionOverwrites.Count -gt 0) { Write-Host "Applying $($ClassificationOverwrites.RoleDefinitionOverwrites.Count) role definition classification overwrite(s) from Classification_RoleDefinitionOverwrites.json..." -ForegroundColor Yellow $DeviceMgmtRbacClassifications = Invoke-EntraOpsClassificationRoleOverwrite -RbacClassifications $DeviceMgmtRbacClassifications -RoleDefinitionOverwrites $ClassificationOverwrites.RoleDefinitionOverwrites -RoleSystem "DeviceManagement" } $Stage3Duration = ((Get-Date) - $Stage3Start).TotalSeconds Write-Host "✓ Stage 3 completed in $([Math]::Round($Stage3Duration, 2)) seconds ($($DeviceMgmtRbacClassifications.Count) role assignments classified)" -ForegroundColor Green Write-Progress -Activity "Stage 3/4: Classifying Role Actions" -Completed #endregion #region Apply classification to all assigned privileged users and groups in Device Management #region Stage 4: Resolve and Finalize Objects $Stage4Start = Get-Date Write-Host "" Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host " Stage 4/4: Resolving Object Details and Finalizing" -ForegroundColor Cyan Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host "Enriching principals with detailed attributes and applying exclusions..." -ForegroundColor Gray # Group assignments by ObjectId for efficient lookup $DeviceMgmtRbacByObject = $DeviceMgmtRbacClassifications | Group-Object ObjectId -AsHashTable -AsString # Collect unique objects and resolve details # Case-insensitive dedup (mirrors Get-EntraOpsPrivilegedEAMAzure.ps1): Select-Object -Unique compares # ObjectId case-sensitively and would emit the same principal twice when ids differ only in casing. $UniqueObjects = @( $DeviceMgmtRbacAssignments | Where-Object { $null -ne $_.ObjectId } | Group-Object -Property { "$($_.ObjectId)".ToLowerInvariant() } | ForEach-Object { $_.Group[0] | Select-Object ObjectId, ObjectType } ) $ObjectDetailsCache = Invoke-EntraOpsParallelObjectResolution ` -UniqueObjects $UniqueObjects ` -TenantId $TenantId ` -EnableParallelProcessing $EnableParallelProcessing ` -ParallelThrottleLimit $ParallelThrottleLimit # Aggregate classifications and build output objects $DeviceMgmtRbacClassifiedObjects = Invoke-EntraOpsEAMClassificationAggregation ` -UniqueObjects $UniqueObjects ` -ObjectDetailsCache $ObjectDetailsCache ` -RbacClassificationsByObject $DeviceMgmtRbacByObject ` -RoleSystem "DeviceManagement" ` -EnableParallelProcessing $EnableParallelProcessing ` -ParallelThrottleLimit $ParallelThrottleLimit ` -WarningMessages $WarningMessages #endregion Write-Host "Applying global exclusions and finalizing results..." $FilteredIntuneObjects = $DeviceMgmtRbacClassifiedObjects | Where-Object { $GlobalExclusionList -notcontains $_.ObjectId } Write-Host "Completed processing $($FilteredIntuneObjects.Count) privileged objects." Show-EntraOpsWarningSummary -WarningMessages $WarningMessages -IncludeObjectDetails $IncludeObjectDetails $FilteredIntuneObjects | Where-Object { $null -ne $_.ObjectType -and $null -ne $_.ObjectId } | Set-EntraOpsEAMClassificationJustification -IncludeJustification:$IncludeJustification | Sort-Object ObjectAdminTierLevel, ObjectDisplayName } # SIG # Begin signature block # MIIniAYJKoZIhvcNAQcCoIIneTCCJ3UCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBszoZATYJ10ioA # SkTCboY6BOk9VW7kIEunlTVMs8/AW6CCIDwwggXJMIIEsaADAgECAhAbtY8lKt8j # AEkoya49fu0nMA0GCSqGSIb3DQEBDAUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK # ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy # dGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5l # dHdvcmsgQ0EwHhcNMjEwNTMxMDY0MzA2WhcNMjkwOTE3MDY0MzA2WjCBgDELMAkG # A1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAl # BgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMb # Q2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMIICIjANBgkqhkiG9w0BAQEFAAOC # Ag8AMIICCgKCAgEAvfl4+ObVgAxknYYblmRnPyI6HnUBfe/7XGeMycxca6mR5rlC # 5SBLm9qbe7mZXdmbgEvXhEArJ9PoujC7Pgkap0mV7ytAJMKXx6fumyXvqAoAl4Va # qp3cKcniNQfrcE1K1sGzVrihQTib0fsxf4/gX+GxPw+OFklg1waNGPmqJhCrKtPQ # 0WeNG0a+RzDVLnLRxWPa52N5RH5LYySJhi40PylMUosqp8DikSiJucBb+R3Z5yet # /5oCl8HGUJKbAiy9qbk0WQq/hEr/3/6zn+vZnuCYI+yma3cWKtvMrTscpIfcRnNe # GWJoRVfkkIJCu0LW8GHgwaM9ZqNd9BjuiMmNF0UpmTJ1AjHuKSbIawLmtWJFfzcV # WiNoidQ+3k4nsPBADLxNF8tNorMe0AZa3faTz1d1mfX6hhpneLO/lv403L3nUlbl # s+V1e9dBkQXcXWnjlQ1DufyDljmVe2yAWk8TcsbXfSl6RLpSpCrVQUYJIP4ioLZb # MI28iQzV13D4h1L92u+sUS4Hs07+0AnacO+Y+lbmbdu1V0vc5SwlFcieLnhO+Nqc # noYsylfzGuXIkosagpZ6w7xQEmnYDlpGizrrJvojybawgb5CAKT41v4wLsfSRvbl # jnX98sy50IdbzAYQYLuDNbdeZ95H7JlI8aShFf6tjGKOOVVPORa5sWOd/7cCAwEA # AaOCAT4wggE6MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLahVDkCw6A/joq8 # +tT4HKbROg79MB8GA1UdIwQYMBaAFAh2zcsH/yT2xc3tu5C84oQ3RnX3MA4GA1Ud # DwEB/wQEAwIBBjAvBgNVHR8EKDAmMCSgIqAghh5odHRwOi8vY3JsLmNlcnR1bS5w # bC9jdG5jYS5jcmwwawYIKwYBBQUHAQEEXzBdMCgGCCsGAQUFBzABhhxodHRwOi8v # c3ViY2Eub2NzcC1jZXJ0dW0uY29tMDEGCCsGAQUFBzAChiVodHRwOi8vcmVwb3Np # dG9yeS5jZXJ0dW0ucGwvY3RuY2EuY2VyMDkGA1UdIAQyMDAwLgYEVR0gADAmMCQG # CCsGAQUFBwIBFhhodHRwOi8vd3d3LmNlcnR1bS5wbC9DUFMwDQYJKoZIhvcNAQEM # BQADggEBAFHCoVgWIhCL/IYx1MIy01z4S6Ivaj5N+KsIHu3V6PrnCA3st8YeDrJ1 # BXqxC/rXdGoABh+kzqrya33YEcARCNQOTWHFOqj6seHjmOriY/1B9ZN9DbxdkjuR # mmW60F9MvkyNaAMQFtXx0ASKhTP5N+dbLiZpQjy6zbzUeulNndrnQ/tjUoCFBMQl # lVXwfqefAcVbKPjgzoZwpic7Ofs4LphTZSJ1Ldf23SIikZbr3WjtP6MZl9M7JYjs # NhI9qX7OAo0FmpKnJ25FspxihjcNpDOO16hO0EoXQ0zF8ads0h5YbBRRfopUofbv # n3l6XYGaFpAP4bvxSgD5+d2+7arszgowggZrMIIEU6ADAgECAhBX5gfP8LabvnxC # XoItEBa/MA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhB # c3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBDb2RlIFNp # Z25pbmcgMjAyMSBDQTAeFw0yNjEwMDYwODI4MjNaFw0yNzEwMDYwODI4MjJaMIGQ # MQswCQYDVQQGEwJERTEdMBsGA1UECAwUUmhpbmVsYW5kLVBhbGF0aW5hdGUxEjAQ # BgNVBAcMCUxhaG5zdGVpbjEeMBwGA1UECgwVT3BlbiBTb3VyY2UgRGV2ZWxvcGVy # MS4wLAYDVQQDDCVPcGVuIFNvdXJjZSBEZXZlbG9wZXIgVGhvbWFzIE5hdW5oZWlt # MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAphp2ljSmVsRB6gvkEq7A # DUMENpKZiDFoAfF1ktD1nCwaVyI+bJ0ROjHwAn4iJgS+vlBDPOUIw2GLHkiqUhOg # HSwzg6gj45GNmt6zxsKP0zPlhMB/fb0zmz7/F7e3yfKCXLfhbHuk0Es9gJ+8Wmet # 9StVwwSE/K0MpW06GKA9nPWyE4N/ADwigDIj9rsTKJR4cve9w8G1b3zohpUc/qMr # onab+HVw5r9b3sOBnnaXC6zvrX4fovRz1ztwfWjRjILLoFG6mW/TuK5F2ICD3Rhl # 9R2rJWWWhzTDMsQlzvX+/SaM74q3OFpIVNdhvtmWU9pKUBRAyYMijcDqKEFGSzrc # 8JFmGMpP5TuroWF+N21d3px2eARvW1mV0DCfFAH+Og20SH/gVW6/ltQQEkH/z/Zt # 49ARyJrgESLOQtlMCIYSoKISdkk4ml4VUS1E/PcbzXEYj3a7m6DOxcd3WuY03UT9 # cnk3p0LMN6LeLYAZgVY+HRloZlH+kW2SkOHNuWgtXvZhAgMBAAGjggF4MIIBdDAM # BgNVHRMBAf8EAjAAMD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly9jY3NjYTIwMjEu # Y3JsLmNlcnR1bS5wbC9jY3NjYTIwMjEuY3JsMHMGCCsGAQUFBwEBBGcwZTAsBggr # BgEFBQcwAYYgaHR0cDovL2Njc2NhMjAyMS5vY3NwLWNlcnR1bS5jb20wNQYIKwYB # BQUHMAKGKWh0dHA6Ly9yZXBvc2l0b3J5LmNlcnR1bS5wbC9jY3NjYTIwMjEuY2Vy # MB8GA1UdIwQYMBaAFN10XUwA23ufoHTKsW73PMAywHDNMB0GA1UdDgQWBBTBvGyH # MXj1dMbVnxuof9oP4PY7cDBLBgNVHSAERDBCMAgGBmeBDAEEATA2BgsqhGgBhvZ3 # AgUBBDAnMCUGCCsGAQUFBwIBFhlodHRwczovL3d3dy5jZXJ0dW0ucGwvQ1BTMBMG # A1UdJQQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsF # AAOCAgEAkC165c7/r6y3VeEEpNXas9bQs6ZpKPdJffHkf9xoPi4kWvAIWGc4ocG1 # DJLb18RXiDYShDmT5sb/6I08FeipKi2DNJkuOq6yOTJ3S7+hNFIZCyd6i7WKO8ea # P5I+FAbyJP4+5coZoMg45bUa9JoocaJCm+5n7WiCfN7/EQpClW5Trm4+ZMbp7EMN # F1uLJgGCMnN0SsoUarp7PHlqupMxMC4Uhhe+lAP2ytcSIU81L97/qjrADbLWqICk # 9il5iGM+aZndguq/cmfH9rJj4GRZHWnG6WwnzYD9+KgYlRI6em+/9dq+IRiXe2Bs # thUuIaFW7U2gWw8tBTEYm1UBLHdJx+L1gjJCLs5KT/wy5wU32wZxNDqPUQejUC1x # GvU3cKqmLgRq+yNf3gFVfzqIoggyM3U9wQvnk7nBtCkpjXS2hdx4yxzcmrlWsDHr # z6+6/jeyQySzbYUlFmKnc4n9Mw2soHOajEua5poFARVRS5KgUBuiiQ21YFNYzWBo # qGuECaaAI/C5rGxapMJ3dAOBnHw6NLqBet+ZXBJEccX5y89MLlOQu+jDmJP8HieR # qDLlwfhVZnw4RY8Rus43NZBwhNlVuOUXqocHKmJhKNlekda3qsJj93eQBQOxKJcc # w5O08q9jukpH2+yzFAj+7ll33DuNUgWptogc4Awtbx3glzcEvJQwggaCMIIEaqAD # AgECAhAo8HfBHDa9/l90MkdwJy4DMA0GCSqGSIb3DQEBDAUAMFYxCzAJBgNVBAYT # AlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMT # G0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQTAeFw0yNjAzMTEwNzM0NTRaFw0z # NjAyMjcwNzM0NTRaMFAxCzAJBgNVBAYTAlBMMSEwHwYDVQQKDBhBc3NlY28gRGF0 # YSBTeXN0ZW1zIFMuQS4xHjAcBgNVBAMMFUNlcnR1bSBUaW1lc3RhbXAgMjAyNjCC # AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi9zfBw+xI12hi2F9uNQBNO # STkTIi13fuh3DiWybI5392bBfJw6tO7zDs7UqP/rejVf4xFTU3XNOPsuOFkP5SPF # snWwn3VJL+NhNhsuujvYsQtaQnvNo1wk46reTgws1L8OFEdKaxgyqT4zOQx81mLw # NZSqXNU9Rrb9oAJWJF4Mydz+C1ebu0D0+vgM3tqAQ8TU203fqgKCPqIdHloaFqH9 # eyR+8tnpmy2hnnN59ZOtmsuHeRwf5iTOhlhf0d/qqquLOSIGPgGVL1Hj5XBUox2O # E7tQRr+l/xxNbL6OuwBO4Aa2EbuxYg37cpXv5i2/lTL4NASB/duf9W4fjA3Ro+GP # +bC54n2OYiM3Cq8z92ejx8F5HWO2e4/75eC95B9tMw/eq7109GDyW2+2ut5Bn0o8 # MXJTERkyystWbqcnUkomA97qfiDCwvpWuOMdfTOksXe9S+xDxOJaCbiyTivgPulx # cT+tfQTRXBr5UwtPvc+INnafOEilzVHA11tv+pHcylryxBc08cUihf/ttqkZS0bm # x3VxQay8uohaTp0//ECA9vYGW51SKdJMJhXvKyIdBLy8WYNchTt8yRITv9Adxg9J # qGujh+TnP1bW3aQjFtlEl7SzgNeI8OVA/CJAWWUuRT3Au9ToOpHy/flrrknhN4he # Ew7/6ILMv4HSsj0YZ4otAgMBAAGjggFQMIIBTDB1BggrBgEFBQcBAQRpMGcwOwYI # KwYBBQUHMAKGL2h0dHA6Ly9zdWJjYS5yZXBvc2l0b3J5LmNlcnR1bS5wbC9jdHNj # YTIwMjEuY2VyMCgGCCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2NzcC1jZXJ0dW0u # Y29tMB8GA1UdIwQYMBaAFL5UAi+/QGxzQ86sCSVOnkNEGu7gMAwGA1UdEwEB/wQC # MAAwOQYDVR0fBDIwMDAuoCygKoYoaHR0cDovL3N1YmNhLmNybC5jZXJ0dW0ucGwv # Y3RzY2EyMDIxLmNybDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8E # BAMCB4AwIgYDVR0gBBswGTAIBgZngQwBBAIwDQYLKoRoAYb2dwIFAQswHQYDVR0O # BBYEFCM5aiirmhKnpKfyH/lFmr+N/oIDMA0GCSqGSIb3DQEBDAUAA4ICAQBm/pOb # rAveTqYFPCSthxNVwBHTW4TfuBQY2KGCdMlH6ALjVPYf2XARNGMTXScR0JQ2/c2L # xLpoHHhRwxFf3d933DhFS165rYj6iPSQKm41rc9AMpTCjMdmai1aK9o870pOzTzL # RhWFVIxm+5Qf+t4V5z14tPoUsAWdfRKD1Zzvzfe5AHlE9rxOZcGROiULxs3Edf7Z # J8RZI0jzD9juj2oEX2XS7BVKuNCK1W0h1mjapv028yqo8UtZh5/1Ib48Yyp88BDY # Shh5PSGUUuS5tYXVfcduIPBe3PdNTYRYdT5VNxkwLgjMzIcr/6SH2fejFJDjnCxj # vXLuarErJg/IdI+hXdGIBDVOGv1zrkF4ktoQvFZIp5DIAYh0GwyUhc87qqn5UaYT # j2+iyiBDSNh7aGCrkuAnqg7Qh7frhX+QhMGeJ9SpLpnG76qN85hLyjvYR6gRYiLT # j8G4fxjqV400/buPw0rBo9yJZyq/8cGdCOLPOYf9qpefTcrZKeO9pg+UJHx8UABb # aTqizRxm8sUSEycccCCFhIit3AI/3wGuSOADeiA676NcQ7+dsP04YvzvdN7pTZNy # g672WpBDJdg0eItHl4yomvNeIOWh1qs4QqOW6TI8Ocsi4uSDTLorxLUDnBTwKQzz # pxd1YUctEgRh32R0J9FkFkeEruJU94RljFr1uDCCBrkwggShoAMCAQICEQCZo4AK # JlU7ZavcboSms+o5MA0GCSqGSIb3DQEBDAUAMIGAMQswCQYDVQQGEwJQTDEiMCAG # A1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVt # IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3Rl # ZCBOZXR3b3JrIENBIDIwHhcNMjEwNTE5MDUzMjE4WhcNMzYwNTE4MDUzMjE4WjBW # MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu # MSQwIgYDVQQDExtDZXJ0dW0gQ29kZSBTaWduaW5nIDIwMjEgQ0EwggIiMA0GCSqG # SIb3DQEBAQUAA4ICDwAwggIKAoICAQCdI88EMCM7wUYs5zNzPmNdenW6vlxNur3r # Lfi+5OZ+U3iZIB+AspO+CC/bj+taJUbMbFP1gQBJUzDUCPx7BNLgid1TyztVLn52 # NKgxxu8gpyTr6EjWyGzKU/gnIu+bHAse1LCitX3CaOE13rbuHbtrxF2tPU8f253Q # gX6eO8yTbGps1Mg+yda3DcTsOYOhSYNCJiL+5wnjZ9weoGRtvFgMHtJg6i671OPX # IciiHO4Lwo2p9xh/tnj+JmCQEn5QU0NxzrOiRna4kjFaA9ZcwSaG7WAxeC/xoZSx # F1oK1UPZtKVt+yrsGKqWONoK6f5EmBOAVEK2y4ATDSkb34UD7JA32f+Rm0wsr5aj # zftDhA5mBipVZDjHpwzv8bTKzCDUSUuUmPo1govD0RwFcTtMXcfJtm1i+P2UNXad # PyYVKRxKQATHN3imsfBiNRdN5kiVVeqP55piqgxOkyt+HkwIA4gbmSc3hD8ke66t # 9MjlcNg73rZZlrLHsAIV/nJ0mmgSjBI/TthoGJDydekOQ2tQD2Dup/+sKQptalDl # ui59SerVSJg8gAeV7N/ia4mrGoiez+SqV3olVfxyLFt3o/OQOnBmjhKUANoKLYlK # mUpKEFI0PfoT8Q1W/y6s9LTI6ekbi0igEbFUIBE8KDUGfIwnisEkBw5KcBZ3XwnH # mfznwlKo8QIDAQABo4IBVTCCAVEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU # 3XRdTADbe5+gdMqxbvc8wDLAcM0wHwYDVR0jBBgwFoAUtqFUOQLDoD+Oirz61Pgc # ptE6Dv0wDgYDVR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMDAGA1Ud # HwQpMCcwJaAjoCGGH2h0dHA6Ly9jcmwuY2VydHVtLnBsL2N0bmNhMi5jcmwwbAYI # KwYBBQUHAQEEYDBeMCgGCCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2NzcC1jZXJ0 # dW0uY29tMDIGCCsGAQUFBzAChiZodHRwOi8vcmVwb3NpdG9yeS5jZXJ0dW0ucGwv # Y3RuY2EyLmNlcjA5BgNVHSAEMjAwMC4GBFUdIAAwJjAkBggrBgEFBQcCARYYaHR0 # cDovL3d3dy5jZXJ0dW0ucGwvQ1BTMA0GCSqGSIb3DQEBDAUAA4ICAQB1iFgP5Y9Q # KJpTnxDsQ/z0O23JmoZifZdEOEmQvo/79PQg9nLF/GJe6ZiUBEyDBHMtFRK0mXj3 # Qv3gL0sYXe+PPMfwmreJHvgFGWQ7XwnfMh2YIpBrkvJnjwh8gIlNlUl4KENTK5DL # qsYPEtRQCw7R6p4s2EtWyDDr/M58iY2UBEqfUU/ujR9NuPyKk0bEcEi62JGxauFY # zZ/yld13fHaZskIoq2XazjaD0pQkcQiIueL0HKiohS6XgZuUtCKA7S6CHttZEsOb # QJ1j2s0urIDdqF7xaXFVaTHKtAuMfwi0jXtF3JJphrJfc+FFILgCbX/uYBPBlbBI # P4Ht4xxk2GmfzMn7oxPITpigQFJFWuzTMUUgdRHTxaTSKRJ/6Uh7ki/pFjf9sUAS # WgxT69QF9Ki4JF5nBIujxZ2sOU9e1HSCJwOfK07t5nnzbs1LbHuAIGJsRJiQ6HX/ # DW1XFOlXY1rc9HufFhWU+7Uk+hFkJsfzqBz3pRO+5aI6u5abI4Qws4YaeJH7H7M8 # X/YNoaArZbV4Ql+jarKsE0+8XvC4DJB+IVcvC9Ydqahi09mjQse4fxfef0L7E3hh # o2O3bLDM6v60rIRUCi2fJT2/IRU5ohgyTch4GuYWefSBsp5NPJh4QRTP9DC3gc5Q # EKtbrTY0Ka87Web7/zScvLmvQBm8JDFpDjCCBrkwggShoAMCAQICEQDn/2nHOzXO # S5Em2HR8aKWHMA0GCSqGSIb3DQEBDAUAMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UE # ChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl # cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBO # ZXR3b3JrIENBIDIwHhcNMjEwNTE5MDUzMjA3WhcNMzYwNTE4MDUzMjA3WjBWMQsw # CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQw # IgYDVQQDExtDZXJ0dW0gVGltZXN0YW1waW5nIDIwMjEgQ0EwggIiMA0GCSqGSIb3 # DQEBAQUAA4ICDwAwggIKAoICAQDpEh8ENe25XXrFppVBvoplf0530W0lddNmjtv4 # YSh/f7eDQKFaIqc7tHj7ox+u8vIsJZlroakUeMS3i3T8aJRC+eQs4FF0GqvkM6+W # ZO8kmzZfxmZaBYmMLs8FktgFYCzywmXeQ1fEExflee2OpbHVk665eXRHjH7MYZIz # Nnjl2m8Hy8ulB9mR8wL/W0v0pjKNT6G0sfrx1kk+3OGosFUb7yWNnVkWKU4qSxLv # 16kJ6oVJ4BSbZ4xMak6JLeB8szrK9vwGDpvGDnKCUMYL3NuviwH1x4gZG0JAXU3x # 2pOAz91JWKJSAmRy/l0s0l5bEYKolg+DMqVhlOANd8Yh5mkQWaMEvBRE/kAGzIqg # WhwzN2OsKIVtO8mf5sPWSrvyplSABAYa13rMYnzwfg08nljZHghquCJYCa/xHK9a # cev9UD7Y+usr15d7mrszzxhF1JOr1Mpup2chNSBlyOObhlSO16rwrffVrg/SzaKf # SndS5swRhr8bnDqNJY9TNyEYvBYpgF95K7p0g4LguR4A++Z1nFIHWVY5v0fNVZmg # zxD9uVo/gta3onGOQj3JCxgYx0KrCXu4yc9QiVwTFLWbNdHFSjBCt5/8Q9pLuRhV # ocdCunhcHudMS1CGQ/Rn0+7P+fzMgWdRKfEOh/hjLrnQ8BdJiYrZNxvIOhM2aa3z # EDHNwwIDAQABo4IBVTCCAVEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUvlQC # L79AbHNDzqwJJU6eQ0Qa7uAwHwYDVR0jBBgwFoAUtqFUOQLDoD+Oirz61PgcptE6 # Dv0wDgYDVR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMDAGA1UdHwQp # MCcwJaAjoCGGH2h0dHA6Ly9jcmwuY2VydHVtLnBsL2N0bmNhMi5jcmwwbAYIKwYB # BQUHAQEEYDBeMCgGCCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2NzcC1jZXJ0dW0u # Y29tMDIGCCsGAQUFBzAChiZodHRwOi8vcmVwb3NpdG9yeS5jZXJ0dW0ucGwvY3Ru # Y2EyLmNlcjA5BgNVHSAEMjAwMC4GBFUdIAAwJjAkBggrBgEFBQcCARYYaHR0cDov # L3d3dy5jZXJ0dW0ucGwvQ1BTMA0GCSqGSIb3DQEBDAUAA4ICAQC4k1l3yUwV/ZQH # CKCneqAs8EGTnwEUJLdDpokN/dMhKjK0rR5qX8nIIHzxpQR3TAw2IRw1Uxsr2Pli # G3bCFqSdQTUbfaTq6V3vBzEebDru9QFjqlKnxCF2h1jhLNFFplbPJiW+JSnJTh1f # KEqEdKdxgl9rVTvlxfEJ7exOn25MGbd/wGPwuSmMxRJVO0wnqgS7kmoJjNF9zqee # hFSDDP8ZVkWg4EZ2tIS0M3uZmByRr+1Lkwjjt8AtW83mVnZTyTsOb+FNfwJY7DS4 # FmWhkRbgcHRetreoTirPOr/ozyDKhT8MTSTf6Lttg6s6T/u08mDWw6HK04ZRDfQ9 # sb77QV8mKgO44WGP31vXnVKoWVJpFBjPvjL8/Zck/5wXX2iqjOaLStFOR/IQki+E # hn4zlcgVm22ZVCBPF+l8nAwUUShCtKuSU7GmZLKCmmxQMkSiWILTm8EtVD6AxnJh # oq8EnhjEEyUoflkeRF2WhFiVQOmWTwZRr44IxWGkNJC6tTorW5rl2Zl+2e9JLPYf # 3pStAPMDoPKIjVXd6NW2+fZrNUBeDo2eOa5Fn7Brs/HLQff5Xgris5MeUbdVgDrF # 8uxO6cLPvZPo63j62SsNg55pTWk9fUIF9iPoRbb4QurjoY/woI1RAOKtYtTic6aA # Jq3u83RIPpGXBSJKwx4KJAOZnCDCtTGCBqIwggaeAgEBMGowVjELMAkGA1UEBhMC # UEwxITAfBgNVBAoTGEFzc2VjbyBEYXRhIFN5c3RlbXMgUy5BLjEkMCIGA1UEAxMb # Q2VydHVtIENvZGUgU2lnbmluZyAyMDIxIENBAhBX5gfP8LabvnxCXoItEBa/MA0G # CWCGSAFlAwQCAQUAoIGEMBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZI # hvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcC # ARUwLwYJKoZIhvcNAQkEMSIEIFiLD4Vds8RXZJ4U+sFS0C60X3iFzTBIAp6ET8B1 # wDLqMA0GCSqGSIb3DQEBAQUABIIBgAVR5BLEYy+9cUt+kghZOCFGS5B6yfWI5g2u # U4fUmwnxtkhBXC+LqbbW8wCNTeWwWH57lDZg3yi4I+CHwXLzcypX+aa1Dkh0Xa03 # NYOZ1zo/T/RvNInzLzhsXLdE5mZwiyphwusiPrk4zATq15mdpo1m0YeGS7agGzdL # hO4nE5i4c1gw2dU4+V3Yc9tUKNiRHj4V/uxpokmtVP9l2KRX8BGtHqXIXNDrXv8g # ziyJuKeXCPW961Zh87+4DymF9XM6AwHWgYggHXpyUZbIwD5x/7X/i8ntAcPfbqSN # u4Yc7G/dY2CNlNcgDvW7MBafdVTNmd2bYBxIBRmp+3stXj6ej9fJSR25q0pKKoqL # VPWwRVkNkeGobqCosketmzV5UqSe+jz9gLeXPmOu6BcxjJXg3ztJ2yu9NS1ASPS3 # DFDEsjcY5exZFukcquBRPeXwPxq1YAfPzPGiZ7TD5ODPrrZUocHHKwpP/jMAmMtc # yS1p6xvHd6UT6TK5cddTN2yTFoBCfaGCBAIwggP+BgkqhkiG9w0BCQYxggPvMIID # 6wIBATBqMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0 # ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQQIQ # KPB3wRw2vf5fdDJHcCcuAzANBglghkgBZQMEAgIFAKCCAVYwGgYJKoZIhvcNAQkD # MQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNjEwMTMwOTQxMjFaMDcG # CyqGSIb3DQEJEAIvMSgwJjAkMCIEIIW+kOEK0kONfMkotq9IsJqyCBd87PiwEmxY # 05EFJcQ8MD8GCSqGSIb3DQEJBDEyBDDOuxCnmmYVDuusT7MBbULC+xXsuQIhI2Di # pKeviB775C6OYk1hpUPq8MB4gv4K3mMwgZ8GCyqGSIb3DQEJEAIMMYGPMIGMMIGJ # MIGGBBRXFGhBDKha80JO+RZKUTYQ9NONmDBuMFqkWDBWMQswCQYDVQQGEwJQTDEh # MB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQwIgYDVQQDExtDZXJ0 # dW0gVGltZXN0YW1waW5nIDIwMjEgQ0ECECjwd8EcNr3+X3QyR3AnLgMwDQYJKoZI # hvcNAQEBBQAEggIAKBCKuORyIPFcmkbG4PQl/J3C3SrGZLHb6KINxkNmlul339Mu # AgEL4K0vOdB/oaQkIh/9gm8PLgMKKMziIMU6Jq5hhNTkqCqeCOmXrcdlzfRQ2+2H # EjOZm7EjQcqg1tiEbmFxEA5uK8UEn/vzfwG04r+FDyEWaU1WC6rGE28MhYWHyWVT # 5A9CGItMPZkXibKzLEyWoFWvybcKWg/X84BaS6TjRZSG1OzeWxiL2lTMBL2pOex2 # R7CoatP/ZjhnhnaUj7K8VQjOQhxe04bjDifIGDiIWB5dQa0r/dP8HhVMAVE8DCpz # XN6FVBGdT7aykndrbX6EYYzbW5TvqH71c2LBQ1Q6ZLBuyTnNX4GH+eGgLxLnJyR6 # xDQKOKbha0R3toTbf4bg8ZAB7AS3XVq8n8RrszkLFTjt66CgSh2pK5mTUYB4zfss # qHZVboknxTFW1Nc6J/hsgrXZNZE329P/RhUzIJdFx7yq2hluyQPcVovMnj9R0ziZ # T+7bzqsN9JzP6QLdAq00HMRm8oImQ3XsGthZQ2hG+34xOl2Q78AiDmCRwOaj4FFx # +qRjzoSJE78DY/wR2rFKIeDf1g40fDSeR+RhUhSr88Yd9KLFOcLhePnwRRT73lsH # sMmqh6vaHyoflo4/UadqeUXifWj7bgLMobRe0alOUuM9Btk6QQgkbKvKoxA= # SIG # End signature block |