Public/PrivilegedAccess/Get-EntraOpsPrivilegedEntraObject.ps1
|
<# .SYNOPSIS Details of Entra Object which is a user, group or service principal with assigned privileged roles and memberships .DESCRIPTION Get essential information about privileged object and details of protection level, owned objects and relation to associated device or work account. .PARAMETER AadObjectId Object Id of the Microsoft Entra object to get details for. .PARAMETER TenantId Tenant ID of the Microsoft Entra ID tenant. Default is the current tenant ID. .PARAMETER CustomSecurityUserAttribute Custom security attribute for user object to get classification details. Default will be set by parameter in EntraOpsConfig.json file. .PARAMETER CustomSecurityServicePrincipalAttribute Custom security attribute for service principal object to get classification details. Default will be set by parameter in EntraOpsConfig.json file. .PARAMETER CustomSecurityUserPawAttribute Custom security attribute for user object to get relation to PAW device. Default will be set by parameter in EntraOpsConfig.json file. .PARAMETER CustomSecurityUserWorkAccountAttribute Custom security attribute for user object to get relation to work account. Default will be set by parameter in EntraOpsConfig.json file. .PARAMETER XdrHunting Boolean to indicate if ThreatHunting.Read.All permission is granted for current session to get associated work account from XDR data. Default is set by scope of MgGraph. .PARAMETER PrivilegedUserAdminTierLevelAttribute CSA field name for the admin tier level on user objects. Defaults to 'adminTierLevel'. Override via EntraOpsConfig.CustomSecurityAttributes.PrivilegedUserAdminTierLevelAttribute. .PARAMETER PrivilegedUserAdminTierLevelNameAttribute CSA field name for the admin tier level name on user objects. Defaults to 'adminTierLevelName'. Override via EntraOpsConfig.CustomSecurityAttributes.PrivilegedUserAdminTierLevelNameAttribute. .PARAMETER PrivilegedServicePrincipalAdminTierLevelAttribute CSA field name for the admin tier level on service principal and application objects. Defaults to 'adminTierLevel'. Override via EntraOpsConfig.CustomSecurityAttributes.PrivilegedServicePrincipalAdminTierLevelAttribute. .PARAMETER PrivilegedServicePrincipalAdminTierLevelNameAttribute CSA field name for the admin tier level name on service principal and application objects. Defaults to 'adminTierLevelName'. Override via EntraOpsConfig.CustomSecurityAttributes.PrivilegedServicePrincipalAdminTierLevelNameAttribute. .PARAMETER AlternateObjectTierLevelAttributes Alternate classification of User and ServicePrincipal objects by PowerShell filter expressions evaluated against the object's own resolved EntraOps details (e.g. AssignedAdministrativeUnits, ObjectDisplayName), instead of Custom Security Attributes. Default will be set by EntraOpsConfig.json section AlternateObjectTierLevelAttributes. Only takes effect when its 'Enabled' property is $true; otherwise Custom Security Attribute classification is used unchanged. See README.md "Classify privileged objects by Alternate Tier Level Attributes" for details and syntax. .EXAMPLE Details of privileged object by using ObjectId Get-EntraOpsPrivilegedEntraObject -AadObjectId "bdf10e92-30c7-4cc8-93e7-2982ea6cf371" #> function Get-EntraOpsPrivilegedEntraObject { [cmdletbinding()] param ( [Parameter(Mandatory = $True)] [ValidatePattern('^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$')] [System.String]$AadObjectId , [Parameter(Mandatory = $false)] [ValidatePattern('^$|^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$')] [System.String]$TenantId = $Global:TenantIdContext , [Parameter(Mandatory = $false)] [System.String]$CustomSecurityUserAttribute = $EntraOpsConfig.CustomSecurityAttributes.PrivilegedUserAttribute , [Parameter(Mandatory = $false)] [System.String]$CustomSecurityUserPawAttribute = $EntraOpsConfig.CustomSecurityAttributes.PrivilegedUserPawAttribute , [Parameter(Mandatory = $false)] [System.String]$CustomSecurityServicePrincipalAttribute = $EntraOpsConfig.CustomSecurityAttributes.PrivilegedServicePrincipalAttribute , [Parameter(Mandatory = $false)] [System.String]$CustomSecurityUserWorkAccountAttribute = $EntraOpsConfig.CustomSecurityAttributes.UserWorkAccountAttribute , [Parameter(Mandatory = $false)] [System.Boolean]$XdrHunting = $(if ($Global:XdrAvdHuntingAccess -is [bool]) { $Global:XdrAvdHuntingAccess } else { $false }) , [Parameter(Mandatory = $false)] [PSObject]$InputObject , [Parameter(Mandatory = $false)] [switch]$IsForeignPrincipal , [Parameter(Mandatory = $false)] [System.String]$PrivilegedUserAdminTierLevelAttribute = $(if (-not [string]::IsNullOrEmpty($EntraOpsConfig.CustomSecurityAttributes.PrivilegedUserAdminTierLevelAttribute)) { $EntraOpsConfig.CustomSecurityAttributes.PrivilegedUserAdminTierLevelAttribute } else { 'adminTierLevel' }) , [Parameter(Mandatory = $false)] [System.String]$PrivilegedUserAdminTierLevelNameAttribute = $(if (-not [string]::IsNullOrEmpty($EntraOpsConfig.CustomSecurityAttributes.PrivilegedUserAdminTierLevelNameAttribute)) { $EntraOpsConfig.CustomSecurityAttributes.PrivilegedUserAdminTierLevelNameAttribute } else { 'adminTierLevelName' }) , [Parameter(Mandatory = $false)] [System.String]$PrivilegedServicePrincipalAdminTierLevelAttribute = $(if (-not [string]::IsNullOrEmpty($EntraOpsConfig.CustomSecurityAttributes.PrivilegedServicePrincipalAdminTierLevelAttribute)) { $EntraOpsConfig.CustomSecurityAttributes.PrivilegedServicePrincipalAdminTierLevelAttribute } else { 'adminTierLevel' }) , [Parameter(Mandatory = $false)] [System.String]$PrivilegedServicePrincipalAdminTierLevelNameAttribute = $(if (-not [string]::IsNullOrEmpty($EntraOpsConfig.CustomSecurityAttributes.PrivilegedServicePrincipalAdminTierLevelNameAttribute)) { $EntraOpsConfig.CustomSecurityAttributes.PrivilegedServicePrincipalAdminTierLevelNameAttribute } else { 'adminTierLevelName' }) , [Parameter(Mandatory = $false)] [AllowNull()] [PSObject]$AlternateObjectTierLevelAttributes = $EntraOpsConfig.AlternateObjectTierLevelAttributes ) $StopwatchTotal = [System.Diagnostics.Stopwatch]::StartNew() # Ensure TenantId is always set — the parameter default ($Global:TenantIdContext) is bypassed # when an explicit $null or empty string is passed (e.g. (Get-AzContext).Tenant.Id returning null). if ([string]::IsNullOrEmpty($TenantId)) { $TenantId = $Global:TenantIdContext } # Detect cross-tenant mode: when TenantId differs from the home tenant (set by Connect-EntraOps), # the object belongs to a foreign tenant (e.g., governing tenant in TG scenarios). # The MgGraph context is expected to be already switched to the foreign tenant by the caller, # so full enrichment (RMAU, PIM roles, owners, admin units, custom security attributes) works via Graph API. # Only XDR hunting is skipped (queries home tenant's security data, not applicable cross-tenant). $IsCrossTenant = (-not [string]::IsNullOrEmpty($TenantId) -and -not [string]::IsNullOrEmpty($Global:TenantIdContext) -and $TenantId -ne $Global:TenantIdContext) if ($IsCrossTenant) { Write-Verbose "Cross-tenant mode: Object $AadObjectId belongs to tenant $TenantId (home: $($Global:TenantIdContext))" } try { $ObjectDetails = $null $AllRemoteTenantGroups = $null $ResolvedViaUsersEndpoint = $false $DirectoryObjectStatusCode = $null $UsersStatusCode = $null # Smart Fallback: Use InputObject if available and valid (contains critical properties) if ($null -ne $InputObject) { # Check for critical property usually missing in v1.0 but present in beta if ($null -ne $InputObject.isManagementRestricted) { $ObjectDetails = $InputObject Write-Verbose "Using pre-fetched object details for $AadObjectId (Skipped API call)" } else { Write-Verbose "InputObject provided but missing critical 'isManagementRestricted' property. Falling back to API fetch." } } # Fallback to API call if object details are still null if ($null -eq $ObjectDetails) { # When the object is a known foreign (tenant governance) principal, suppress the built-in # Write-Warning from Invoke-EntraOpsMsGraphQuery for expected NotFound responses. # We handle the null result ourselves with a targeted informational message below. $GraphWarningAction = if ($IsForeignPrincipal) { 'SilentlyContinue' } else { 'Continue' } try { $ObjectDetails = Invoke-EntraOpsMsGraphQuery -Method Get -Uri "/beta/directoryObjects/$($AadObjectId)?`$select=id,displayName,userPrincipalName,userType,isAssignableToRole,isManagementRestricted,onPremisesSyncEnabled,passwordPolicies" -OutputType PSObject -SuppressNotFoundWarning -ThrowOnFailure -WarningAction $GraphWarningAction } catch { $DirectoryObjectStatusCode = $_.Exception.Data['StatusCode'] } } } catch { $ObjectDetails = $null Write-Verbose "No object has been found with Id: $AadObjectId" Write-Warning $_.Exception.Message } # Agent users (#microsoft.graph.agentUser) are not addressable via /directoryObjects and 404 # there despite existing - probe /users before treating the object as unknown/not found. if ($null -eq $ObjectDetails -and -not $IsForeignPrincipal) { try { $ObjectDetails = Invoke-EntraOpsMsGraphQuery -Method Get -Uri "/beta/users/$($AadObjectId)?`$select=id,displayName,userPrincipalName,userType,isManagementRestricted,onPremisesSyncEnabled,passwordPolicies" -OutputType PSObject -SuppressNotFoundWarning -ThrowOnFailure if ($null -ne $ObjectDetails) { $ResolvedViaUsersEndpoint = $true Write-Verbose "Object $AadObjectId resolved via /users fallback (not addressable via /directoryObjects)" } } catch { $UsersStatusCode = $_.Exception.Data['StatusCode'] } } $ResolutionStatus = if ($DirectoryObjectStatusCode -eq 404 -and $UsersStatusCode -eq 404) { 'NotFound' } else { 'Unresolved' } # Agent users report '#microsoft.graph.agentUser'; normalize so the user branch and the # AAD role protection check handle them like regular users (subtype stays 'agentUser') if ($null -ne $ObjectDetails -and $ObjectDetails.'@odata.type' -like '#microsoft.graph.agentUser*') { Add-Member -InputObject $ObjectDetails -NotePropertyName '@odata.type' -NotePropertyValue '#microsoft.graph.user' -Force } # If the object is a known foreign (tenant governance) principal and was not found in this tenant, # return early with a minimal placeholder. This is expected — the object lives in the managing tenant # and will be resolved in Stage 5b. Suppresses cascading 404 warnings from subsequent Graph calls. if ($null -eq $ObjectDetails -and $IsForeignPrincipal) { $AllRemoteTenantGroups = Invoke-EntraOpsMsGraphQuery -Method Get -Uri "/beta/directory/remoteTenantGroups" -OutputType PSObject -WarningAction SilentlyContinue if ($AadObjectId -in $AllRemoteTenantGroups.remoteGroupId) { Write-Verbose "Object $AadObjectId is a known remoteTenantGroup (ForeignGroup) in this tenant." $ObjectDetails = [PSCustomObject]@{ '@odata.type' = '#microsoft.graph.remoteTenantGroup' 'id' = $AadObjectId 'displayName' = ($AllRemoteTenantGroups | Where-Object { $_.remoteGroupId -eq $AadObjectId }).remoteGroupDisplayName } } else { Write-Host "Object $AadObjectId not found in home tenant — expected for tenant governance (TG) objects, will be resolved in managing tenant context." -ForegroundColor Gray $StopwatchTotal.Stop() return [PSCustomObject]@{ 'ObjectId' = $AadObjectId 'ObjectTenantId' = $TenantId 'ObjectType' = 'unknown' 'ObjectSubType' = 'unknown' 'ObjectDisplayName' = 'Identity not found' 'ObjectSignInName' = '' 'OwnedObjects' = @() 'OwnedDevices' = @() 'Owners' = @() 'Sponsors' = @() 'IdentityParent' = $null 'AdminTierLevel' = 'Unclassified' 'AdminTierLevelName' = 'Unclassified' 'AssociatedWorkAccount' = @() 'AssociatedPawDevice' = @() 'OnPremSynchronized' = $false 'RestrictedManagementByRAG' = $false 'RestrictedManagementByAadRole' = $false 'RestrictedManagementByRMAU' = $false 'AssignedAdministrativeUnits' = @() 'PasswordPolicyAssigned' = @() 'OutsideOfHomeTenant' = $true } } } # Variables for ownership or other object relationships [System.Collections.ArrayList]$Owners = @() [System.Collections.ArrayList]$Sponsors = @() [System.Collections.ArrayList]$ObjectOwner = @() [System.Collections.ArrayList]$DeviceOwner = @() [System.Collections.ArrayList]$WorkAccount = @() [System.Collections.ArrayList]$PawDevice = @() [System.Collections.ArrayList]$AssignedAdministrativeUnits = @() #region Calculate object details common for all object types and protection by RMAU membership $StopwatchRegion = [System.Diagnostics.Stopwatch]::StartNew() try { if ($null -ne $ObjectDetails) { Write-Verbose -Message "Lookup for $($ObjectDetails.'@odata.type') - $($ObjectDetails.displayName) $($AadObjectId)" $RestrictedManagementByRMAU = $($ObjectDetails.isManagementRestricted) } } catch { Write-Warning "No group or role assignment status available" } #endregion #region Get transitive memberships of object # Skip for remote tenant groups: the object was just proven absent from the home tenant # directory, so the transitiveMemberOf call would be a guaranteed 404 round-trip. if ($ResolutionStatus -ne 'NotFound' -and $ObjectDetails.'@odata.type' -ne '#microsoft.graph.remoteTenantGroup') { try { # Objects resolved via the /users fallback are not reachable through /directoryObjects $TransitiveMemberOfUri = if ($ResolvedViaUsersEndpoint) { "/beta/users/$AadObjectId/transitiveMemberOf" } else { "/beta/directoryObjects/$AadObjectId/transitiveMemberOf" } $ObjectMemberships = (Invoke-EntraOpsMsGraphQuery -Method Get -Uri $TransitiveMemberOfUri -OutputType PSObject) } catch { Write-Warning "No transitive memberships available" } } #endregion $StopwatchRegion.Stop() Write-Verbose "[Performance] Object details and RMAU protection: $($StopwatchRegion.ElapsedMilliseconds)ms" #endregion #region Calculate protection by AAD Role assignment or eligibility (available only for user and group objects) $StopwatchRegion = [System.Diagnostics.Stopwatch]::StartNew() if ( $ObjectDetails.'@odata.type' -in @('#microsoft.graph.user', '#microsoft.graph.group') ) { if ( $ObjectDetails.'@odata.type' -eq '#microsoft.graph.group' ) { # Only Role-assignable groups (isAssignableToRole = true) can hold directory role assignments. # Regular security groups can never be assigned a directory role in Entra ID, so skip the # API call entirely and set false directly to avoid a spurious result and wasted round-trip. if ( $ObjectDetails.isAssignableToRole -eq $true ) { # For groups: direct roleAssignments only. # transitiveRoleAssignments expands upward through group membership, causing false positives # when the group is merely a member of another group that holds a role assignment. $AadRolesActiveResult = Invoke-EntraOpsMsGraphQuery -Uri "/beta/roleManagement/directory/roleAssignments?`$filter=principalId eq '$($AadObjectId)'" -OutputType PSObject $AadRolesEligibleResult = Invoke-EntraOpsMsGraphQuery -Uri "/beta/roleManagement/directory/roleEligibilitySchedules?`$filter=principalId eq '$($AadObjectId)'" -OutputType PSObject } else { $AadRolesActiveResult = $null $AadRolesEligibleResult = $null } } else { # For users: transitive is correct - role assignments inherited via group membership are real. $AadRolesActiveResult = Invoke-EntraOpsMsGraphQuery -Uri "/beta/roleManagement/directory/transitiveRoleAssignments?`$count=true&`$filter=principalId eq '$($AadObjectId)'" -ConsistencyLevel "eventual" # Direct eligible role assignments to this object only, server-side filtered. $AadRolesEligibleResult = Invoke-EntraOpsMsGraphQuery -Uri "/beta/roleManagement/directory/roleEligibilitySchedules?`$filter=principalId eq '$($AadObjectId)'" -OutputType PSObject } # Filter to real role assignment objects only - when the API returns empty results, # Invoke-EntraOpsMsGraphQuery adds the response envelope (with @odata.context, value:[]) # as a single item instead of nothing. That envelope has no 'id', so checking $null -ne $_.id # reliably excludes it while counting only genuine role assignment objects. $AadRolesActive = @($AadRolesActiveResult | Where-Object { $null -ne $_.id }) $AadRolesEligible = @($AadRolesEligibleResult | Where-Object { $null -ne $_.id }) # Set protection flag based on count of active or eligible roles $RestrictedManagementByAadRole = ($AadRolesActive.Count -gt 0 -or $AadRolesEligible.Count -gt 0) if ($RestrictedManagementByAadRole) { Write-Verbose "Object protected by AAD Role: Active=$($AadRolesActive.Count), Eligible=$($AadRolesEligible.Count)" } } else { $RestrictedManagementByAadRole = $false } $StopwatchRegion.Stop() Write-Verbose "[Performance] AAD Role protection check: $($StopwatchRegion.ElapsedMilliseconds)ms" #endregion # agentIdentityBlueprintPrincipal and agentIdentity are SP subtypes; normalize so the SP branch handles them if ($ObjectDetails.'@odata.type' -like '#microsoft.graph.agentIdentity*') { Add-Member -InputObject $ObjectDetails -NotePropertyName '@odata.type' -NotePropertyValue '#microsoft.graph.servicePrincipal' -Force } switch ( $ObjectDetails.'@odata.type' ) { #region User object details '#microsoft.graph.user' { $StopwatchRegion = [System.Diagnostics.Stopwatch]::StartNew() # odata type by directoryObject includes value of user which could be either user or agentUser $ObjectType = 'user' # Combine initial query with customSecurityAttributes to reduce API calls $UserDetails = Invoke-EntraOpsMsGraphQuery -Method Get -Uri "/beta/users/$($AadObjectId)?`$select=id,userPrincipalName,userType,displayName,customSecurityAttributes,identityParentId" -OutputType PSObject $IdentityParent = $($UserDetails.identityParentId) if ($null -ne $UserDetails.'@odata.type') { $ObjectSubType = $UserDetails.'@odata.type'.Replace("#microsoft.graph.", "") } else { $ObjectSubType = $UserDetails.UserType } # Sponsors try { Invoke-EntraOpsMsGraphQuery -Method Get -Uri "/beta/users/$AadObjectId/sponsors?`$select=id" -OutputType PSObject | ForEach-Object { $Sponsors.Add($_.id) | out-null } } catch { Write-Warning "No sponsors supported for $($AadObjectId)" } # Owned Objects Invoke-EntraOpsMsGraphQuery -Method Get -Uri ("/beta/users/$AadObjectId/ownedObjects?`$select=id") -OutputType PSObject | ForEach-Object { $ObjectOwner.Add($_.id) | out-null } # User Sign-in Name $ObjectSignInName = $ObjectDetails.UserPrincipalName if ($ObjectDetails.userType -ne "Member" -or $ObjectDetails.UserPrincipalName -like "*#EXT#@*") { $OutsideOfAadTenant = $True } else { $OutsideOfAadTenant = $False } # Force OutsideOfHomeTenant for cross-tenant objects if ($IsCrossTenant) { $OutsideOfAadTenant = $true } # Object Classification from custom security attributes try { $ObjectCustomSec = $UserDetails.customSecurityAttributes.$($CustomSecurityUserAttribute) } catch { Write-Warning "No custom security attribute for $($AadObjectId)" } $AdminTierLevel = (($ObjectCustomSec) | select-object -Unique $PrivilegedUserAdminTierLevelAttribute).$PrivilegedUserAdminTierLevelAttribute $AdminTierLevelName = (($ObjectCustomSec) | select-object -Unique $PrivilegedUserAdminTierLevelNameAttribute).$PrivilegedUserAdminTierLevelNameAttribute # Administrative Unit Assignments $RestrictedManagementByRAG = $ObjectMemberships.isAssignableToRole -contains $true Invoke-EntraOpsMsGraphQuery -Method Get -Uri "/beta/users/$($AAdObjectId)/memberOf/microsoft.graph.administrativeUnit?`$select=id,displayName" -OutputType PSObject | Select-Object id, displayName | ForEach-Object { $AssignedAdministrativeUnits.Add($_) | out-null } # Relation between PAW and user if ($null -ne $ObjectCustomSec.$($CustomSecurityUserPawAttribute)) { $ObjectCustomSec.$($CustomSecurityUserPawAttribute) | ForEach-Object { $PawDevice.Add($_) | out-null } } if ($null -ne $ObjectCustomSec.$($CustomSecurityUserWorkAccountAttribute)) { $ObjectCustomSec.$($CustomSecurityUserWorkAccountAttribute) | ForEach-Object { $WorkAccount.Add($_) | out-null } } elseif ( -not $IsCrossTenant -and $XdrHunting -eq $true ) { # XDR hunting queries the home tenant's security data - skip for cross-tenant objects try { $IdentityAccountQuery = " IdentityAccountInfo | where SourceProvider == 'AzureActiveDirectory' | where SourceProviderAccountId == '$($AadObjectId)' | summarize arg_max(TimeGenerated, *) by AccountId | where IsPrimary == false | project TimeGenerated, DisplayName, SourceProviderAccountId, IdentityId, IdentityLinkBy, IdentityLinkType, IsPrimary, AccountId | join kind = leftouter ( IdentityAccountInfo | where SourceProvider == 'AzureActiveDirectory' | summarize arg_max(TimeGenerated, *) by AccountId | where IsPrimary == true | project IdentityId, AccountObjectId = SourceProviderAccountId, AccountUpn ) on IdentityId | project AccountObjectId " $IdentityAccountResult = Invoke-EntraOpsGraphSecurityQuery -Query $IdentityAccountQuery -Timespan "P14D" $IdentityAccountResult.AccountObjectId | ForEach-Object { $WorkAccount.Add($_) | out-null } } catch { Write-Warning "Query for associated work account failed for $($AadObjectId): $($_.Exception.Message)" } } else { Write-Verbose "Custom Security Attribute not present and XDR Hunting permission not granted, skipping associated work account lookup for $($AadObjectId)" } # Device Ownership of Privileged User Invoke-EntraOpsMsGraphQuery -Method Get -Uri ("/beta/users/$AadObjectId/ownedDevices" + '?$select=id') -OutputType PSObject | ForEach-Object { $DeviceOwner.Add($_.id) | out-null } $StopwatchRegion.Stop() Write-Verbose "[Performance] User object details: $($StopwatchRegion.ElapsedMilliseconds)ms" } #endregion #region Group object details '#microsoft.graph.group' { $StopwatchRegion = [System.Diagnostics.Stopwatch]::StartNew() $ObjectType = 'group' if ($ObjectDetails.isAssignableToRole -eq $True) { $ObjectSubType = "Role-assignable" $RestrictedManagementByRAG = $true } else { $ObjectSubType = "Security" $RestrictedManagementByRAG = $false } $OutsideOfAadTenant = $false if ($IsCrossTenant) { $OutsideOfAadTenant = $true } # No support for custom security attributes on groups — emit "Unclassified" (same value # non-group objects get from the Unclassified fallback) instead of a blank bucket. $AdminTierLevel = "Unclassified" $AdminTierLevelName = "Unclassified" # Owners Invoke-EntraOpsMsGraphQuery -Method Get -Uri "/beta/groups/$AadObjectId/owners?`$select=id" -OutputType PSObject | ForEach-Object { $Owners.Add($_.id) | out-null } # Administrative Unit Assignments Invoke-EntraOpsMsGraphQuery -Method Get -Uri "/beta/groups/$($AAdObjectId)/memberOf/microsoft.graph.administrativeUnit?`$select=id,displayName" -OutputType PSObject | Select-Object id, displayName | ForEach-Object { $AssignedAdministrativeUnits.Add($_) | out-null } $StopwatchRegion.Stop() Write-Verbose "[Performance] Group object details: $($StopwatchRegion.ElapsedMilliseconds)ms" } #endregion #region Remote tenant group (cross-tenant ForeignGroup) object details '#microsoft.graph.remoteTenantGroup' { # Azure RBAC ForeignGroup assignments reference a cross-tenant group that is represented in the home # tenant only through the remoteTenantGroups collection. All ForeignGroups are role-assignable groups. # The remoteTenantGroups entry exposes the real remote group id (remoteGroupId), its display name # (remoteGroupDisplayName) and the owning tenant (remoteTenantId). These are surfaced as ObjectId, # ObjectDisplayName and ObjectTenantId so the caller can resolve full details in the owning (managing) # tenant later when remoteTenantId matches the managing tenant. $StopwatchRegion = [System.Diagnostics.Stopwatch]::StartNew() $RemoteTenantGroup = $null try { # Currently no filter is supported on remoteTenantGroups, so we fetch all and filter locally. This is expected to be a small collection. # Reuse the collection already fetched by the foreign-principal fallback above (same invocation) when available. if ($null -eq $AllRemoteTenantGroups) { $AllRemoteTenantGroups = Invoke-EntraOpsMsGraphQuery -Method Get -Uri "/beta/directory/remoteTenantGroups" -OutputType PSObject -WarningAction SilentlyContinue } $RemoteTenantGroup = $AllRemoteTenantGroups | Where-Object { $_.remoteGroupId -eq $AadObjectId -or $_.id -eq $AadObjectId } } catch { Write-Verbose "Could not resolve remoteTenantGroup details for $($AadObjectId): $($_.Exception.Message)" } $RemoteGroupId = if (-not [string]::IsNullOrEmpty($RemoteTenantGroup.remoteGroupId)) { $RemoteTenantGroup.remoteGroupId } else { $AadObjectId } $RemoteGroupTenantId = if (-not [string]::IsNullOrEmpty($RemoteTenantGroup.remoteTenantId)) { $RemoteTenantGroup.remoteTenantId } else { $TenantId } $RemoteGroupDisplayName = if (-not [string]::IsNullOrEmpty($RemoteTenantGroup.remoteGroupDisplayName)) { $RemoteTenantGroup.remoteGroupDisplayName } else { $ObjectDetails.displayName } Write-Verbose "Resolved $AadObjectId as remote tenant group (remote group $RemoteGroupId in tenant $RemoteGroupTenantId)." $StopwatchRegion.Stop() Write-Verbose "[Performance] Remote tenant group object details: $($StopwatchRegion.ElapsedMilliseconds)ms" $StopwatchTotal.Stop() return [PSCustomObject]@{ 'ObjectId' = $RemoteGroupId 'ObjectTenantId' = $RemoteGroupTenantId 'ObjectType' = 'group' 'ObjectSubType' = 'Role-assignable' 'ObjectDisplayName' = $RemoteGroupDisplayName 'ObjectSignInName' = '' 'OwnedObjects' = @() 'OwnedDevices' = @() 'Owners' = @() 'Sponsors' = @() 'IdentityParent' = $null # Groups do not support custom security attributes — "Unclassified" instead of a blank bucket. 'AdminTierLevel' = 'Unclassified' 'AdminTierLevelName' = 'Unclassified' 'AssociatedWorkAccount' = @() 'AssociatedPawDevice' = @() 'OnPremSynchronized' = $false 'RestrictedManagementByRAG' = $true 'RestrictedManagementByAadRole' = $true 'RestrictedManagementByRMAU' = $false 'AssignedAdministrativeUnits' = @() 'PasswordPolicyAssigned' = @() 'OutsideOfHomeTenant' = $true } } #endregion #region Service Principal object details '#microsoft.graph.servicePrincipal' { $StopwatchRegion = [System.Diagnostics.Stopwatch]::StartNew() # Combine initial query with customSecurityAttributes to reduce API calls $SPObject = Invoke-EntraOpsMsGraphQuery -Method Get -Uri "/beta/serviceprincipals/$($AAdObjectId)?`$select=id,appId,servicePrincipalType,appOwnerOrganizationId,customSecurityAttributes,agentAppId" -OutputType PSObject $ObjectSignInName = $SPObject.appId # Lowercase to stay consistent with the other ObjectType values ('user', 'group', 'application') # returned by this function - callers and downstream EAM files compare/store ObjectType as lowercase, # and a mismatched case here causes the same object to be treated as two different objects (e.g. # duplicate entries in ScopeReasoning/Classification files that differ only by ObjectType casing). $ObjectType = 'serviceprincipal' $ObjectSubType = $SPObject.ServicePrincipalType #region Collect Owners and Owned Objects $StopwatchRegion = [System.Diagnostics.Stopwatch]::StartNew() # Owners Invoke-EntraOpsMsGraphQuery -Method Get -Uri ("/beta/servicePrincipals/$AadObjectId/owners?`$select=id") -OutputType PSObject | ForEach-Object { $Owners.Add($_.id) | out-null } # Owned Objects Invoke-EntraOpsMsGraphQuery -Method Get -Uri ("/beta/servicePrincipals/$AadObjectId/ownedObjects?`$select=id") -OutputType PSObject | ForEach-Object { $ObjectOwner.Add($_.id) | out-null } $StopwatchRegion.Stop() Write-Verbose "[Performance] Owners and owned objects collection: $($StopwatchRegion.ElapsedMilliseconds)ms" #endregion # Restricted by Role Assignale Groups does not apply $RestrictedManagementByRAG = $false # Details of classified object from custom security attribute try { $ObjectCustomSec = $SPObject.customSecurityAttributes.$($CustomSecurityServicePrincipalAttribute) } catch { Write-Warning "No custom security attribute for $($AadObjectId)" } $AdminTierLevel = (($ObjectCustomSec) | select-object -Unique $PrivilegedServicePrincipalAdminTierLevelAttribute).$PrivilegedServicePrincipalAdminTierLevelAttribute $AdminTierLevelName = (($ObjectCustomSec) | select-object -Unique $PrivilegedServicePrincipalAdminTierLevelNameAttribute).$PrivilegedServicePrincipalAdminTierLevelNameAttribute $OutsideOfAadTenant = ($SPObject.AppOwnerOrganizationId -ne $TenantId) #region Agent identity object details$ if ( $SPObject.'@odata.type' -like "*agentIdentity*" ) { $ObjectSubType = $SPObject.'@odata.type'.Replace("#microsoft.graph.", "") if ($ObjectSubType -ne "agentIdentityBlueprintPrincipal") { $BlueprintAppId = $($SPObject.agentAppId) $AgentIdentityBlueprintPrincipalObject = Invoke-EntraOpsMsGraphQuery -Method Get -Uri "/beta/servicePrincipals(appId='$($BlueprintAppId)')/microsoft.graph.agentIdentityBlueprintPrincipal" -OutputType PSObject $IdentityParent = $AgentIdentityBlueprintPrincipalObject.appId } else { $AgentIdentityBlueprintPrincipalObject = $SPObject $ObjectType = 'serviceprincipal' $ObjectSubType = 'agentIdentityBlueprintPrincipal' } $OutsideOfAadTenant = ($AgentIdentityBlueprintPrincipalObject.AppOwnerOrganizationId -ne $TenantId) # Sponsors try { Invoke-EntraOpsMsGraphQuery -Method Get -Uri "/beta/serviceprincipals/$($AadObjectId)/sponsors?`$select=id" -OutputType PSObject | ForEach-Object { $Sponsors.Add($_.id) | out-null } } catch { Write-Warning "No sponsors supported for $($AadObjectId)" } } #endregion $StopwatchRegion.Stop() Write-Verbose "[Performance] Service Principal object details: $($StopwatchRegion.ElapsedMilliseconds)ms" } #endregion #region Application object details '#microsoft.graph.application' { $StopwatchRegion = [System.Diagnostics.Stopwatch]::StartNew() # Use $select to minimize data transfer $AppObject = Invoke-EntraOpsMsGraphQuery -Method Get -Uri "/beta/applications/$($AAdObjectId)?`$select=id,appId,displayName" -OutputType PSObject # Combine service principal query with customSecurityAttributes to reduce API calls $SPObject = Invoke-EntraOpsMsGraphQuery -Method Get -Uri "/beta/serviceprincipals(appId='$($AppObject.appId)')?`$select=id,customSecurityAttributes" -OutputType PSObject Invoke-EntraOpsMsGraphQuery -Method Get -Uri ("/beta/applications/$AadObjectId/owners?`$select=id") -OutputType PSObject | ForEach-Object { $Owners.Add($_.id) | out-null } $ObjectSignInName = $AppObject.appId $ObjectType = 'application' $ObjectSubType = "" # Administrative Units and Restricted Management does not apply to service principals $RestrictedManagementByRAG = $false # Details of classified object from custom security attribute try { $ObjectCustomSec = $SPObject.customSecurityAttributes.$($CustomSecurityServicePrincipalAttribute) } catch { Write-Warning "No custom security attribute for $($AadObjectId)" } $AdminTierLevel = (($ObjectCustomSec) | select-object -Unique $PrivilegedServicePrincipalAdminTierLevelAttribute).$PrivilegedServicePrincipalAdminTierLevelAttribute $AdminTierLevelName = (($ObjectCustomSec) | select-object -Unique $PrivilegedServicePrincipalAdminTierLevelNameAttribute).$PrivilegedServicePrincipalAdminTierLevelNameAttribute $OutsideOfAadTenant = $False if ($null -ne $SPObject) { Invoke-EntraOpsMsGraphQuery -Method Get -Uri "/beta/servicePrincipals/$($SPObject.id)/ownedObjects?`$select=id" -OutputType PSObject | ForEach-Object { $ObjectOwner.Add($_.id) | out-null } } $StopwatchRegion.Stop() Write-Verbose "[Performance] Application object details: $($StopwatchRegion.ElapsedMilliseconds)ms" #endregion } #endregion #region Unknown object '' { $ObjectDetails = [PSCustomObject]@{ 'id' = "$($AadObjectId)" 'displayName' = "Identity not found" } $ObjectType = 'unknown' $ObjectSubType = 'unknown' } #endregion #region Unhandled object type default { Write-Warning "Unhandled directory object type '$($ObjectDetails.'@odata.type')' for object $AadObjectId. Classified as 'unknown'; classification for this object may be incomplete." $ObjectType = 'unknown' $ObjectSubType = 'unknown' } #endregion } #region Collect assigned administrative units for unsupported object types $StopwatchRegion = [System.Diagnostics.Stopwatch]::StartNew() if ($ResolutionStatus -ne 'NotFound' -and $ObjectType -notin @("user", "group", "devices")) { # Administrative Unit Assignments - Optimized with hashtable lookup $Body = @{ securityEnabledOnly = "false" } | ConvertTo-Json $AssignedAdminUnitIds = Invoke-EntraOpsMsGraphQuery -Method POST -Body $Body -Uri "/beta/directoryObjects/$($AAdObjectId)/getMemberObjects" -OutputType PSObject -DisableCache # Optimization: Build hashtable lookup for O(1) access instead of O(N) Where-Object filtering $AllAdminUnits = Invoke-EntraOpsMsGraphQuery -Method GET -Uri "/beta/administrativeunits?`$select=id,displayName" -OutputType PSObject $AdminUnitLookup = @{} foreach ($AU in $AllAdminUnits) { if ($null -ne $AU.id) { $AdminUnitLookup[$AU.id] = $AU } } # Use hashtable lookup for fast filtering foreach ($AuId in $AssignedAdminUnitIds) { if ($null -ne $AuId -and $AdminUnitLookup.ContainsKey($AuId)) { $AssignedAdministrativeUnits.Add($AdminUnitLookup[$AuId]) | out-null } } } $StopwatchRegion.Stop() Write-Verbose "[Performance] Administrative units collection: $($StopwatchRegion.ElapsedMilliseconds)ms" #endregion # Set empty arrays to avoid null values for arrays in schema if ([string]::IsNullOrEmpty($RestrictedManagementByRMAU)) { $RestrictedManagementByRMAU = $false } $Owners = [System.Collections.ArrayList]@($Owners | Where-Object { -not [string]::IsNullOrWhiteSpace("$_") }) $Sponsors = [System.Collections.ArrayList]@($Sponsors | Where-Object { -not [string]::IsNullOrWhiteSpace("$_") }) $ObjectOwner = [System.Collections.ArrayList]@($ObjectOwner | Where-Object { -not [string]::IsNullOrWhiteSpace("$_") }) $DeviceOwner = [System.Collections.ArrayList]@($DeviceOwner | Where-Object { -not [string]::IsNullOrWhiteSpace("$_") }) $WorkAccount = [System.Collections.ArrayList]@($WorkAccount | Where-Object { -not [string]::IsNullOrWhiteSpace("$_") }) $PawDevice = [System.Collections.ArrayList]@($PawDevice | Where-Object { -not [string]::IsNullOrWhiteSpace("$_") }) if ([string]::IsNullOrEmpty($AssignedAdministrativeUnits.id)) { $AssignedAdministrativeUnits = @() } if ([string]::IsNullOrEmpty($ObjectSignInName)) { $ObjectSignInName = "" } if ([string]::IsNullOrEmpty($ObjectDetails.passwordPolicies)) { $PasswordPolicies = @() } else { $PasswordPolicies = $ObjectDetails.passwordPolicies } # Make sure that first character is uppercase if (![string]::IsNullOrEmpty($ObjectSubType)) { $ObjectSubType = $ObjectSubType.Substring(0, 1).ToUpper() + $ObjectSubType.Substring(1) } # Sort lists for deterministic output if ($Owners.Count -gt 0) { $Owners.Sort() } if ($Sponsors.Count -gt 0) { $Sponsors.Sort() } if ($ObjectOwner.Count -gt 0) { $ObjectOwner.Sort() } if ($DeviceOwner.Count -gt 0) { $DeviceOwner.Sort() } if ($WorkAccount.Count -gt 0) { $WorkAccount.Sort() } if ($PawDevice.Count -gt 0) { $PawDevice.Sort() } # Sort AssignedAdministrativeUnits by displayName then id if it contains items if ($AssignedAdministrativeUnits.Count -gt 0) { $SortedUnits = $AssignedAdministrativeUnits | Sort-Object displayName, id if ($SortedUnits -is [System.Collections.ArrayList]) { $AssignedAdministrativeUnits = $SortedUnits } else { $AssignedAdministrativeUnits = [System.Collections.ArrayList]@($SortedUnits) } } #region Alternate classification of User/ServicePrincipal objects by AlternateObjectTierLevelAttributes # Overrides the Custom Security Attribute-based $AdminTierLevel/$AdminTierLevelName above, only when # explicitly enabled via EntraOpsConfig.json. Groups, applications and unresolved objects are unaffected. if ($ObjectType -in @('user', 'servicePrincipal') -and $null -ne $AlternateObjectTierLevelAttributes -and $AlternateObjectTierLevelAttributes.Enabled -eq $true) { $Object = [PSCustomObject]@{ ObjectId = $ObjectDetails.Id ObjectDisplayName = $ObjectDetails.displayName ObjectSignInName = $ObjectSignInName ObjectSubType = $ObjectSubType AssignedAdministrativeUnits = $AssignedAdministrativeUnits OwnedObjects = $ObjectOwner Owners = $Owners Sponsors = $Sponsors RestrictedManagementByRAG = $RestrictedManagementByRAG RestrictedManagementByAadRole = $RestrictedManagementByAadRole RestrictedManagementByRMAU = $RestrictedManagementByRMAU OnPremSynchronized = if ($null -eq $ObjectDetails.onPremisesSyncEnabled) { $false } else { $ObjectDetails.onPremisesSyncEnabled } OutsideOfHomeTenant = $OutsideOfAadTenant } $AlternateObjectTypeName = if ($ObjectType -eq 'user') { 'User' } else { 'ServicePrincipal' } $AlternateResult = Resolve-EntraOpsAlternateObjectTierLevel -ObjectType $AlternateObjectTypeName -Object $Object -AlternateObjectTierLevelAttributes $AlternateObjectTierLevelAttributes if ($null -ne $AlternateResult) { $AdminTierLevel = $AlternateResult.AdminTierLevel $AdminTierLevelName = $AlternateResult.AdminTierLevelName } } #endregion if ($null -ne $ObjectDetails) { $StopwatchTotal.Stop() Write-Verbose "[Performance] Total execution time: $($StopwatchTotal.ElapsedMilliseconds)ms" if ([string]::IsNullOrEmpty($ObjectSubType)) { $ObjectSubType = "Unknown" } if ([string]::IsNullOrEmpty($ObjectType)) { $ObjectType = "Unknown" } [PSCustomObject]@{ 'ObjectId' = $ObjectDetails.Id 'ObjectTenantId' = $TenantId 'ObjectType' = $ObjectType 'ObjectSubType' = $ObjectSubType 'ObjectDisplayName' = $ObjectDetails.displayName 'ObjectSignInName' = $ObjectSignInName 'OwnedObjects' = $ObjectOwner 'OwnedDevices' = $DeviceOwner 'Owners' = $Owners 'Sponsors' = $Sponsors 'IdentityParent' = $IdentityParent 'AdminTierLevel' = if ($null -eq $AdminTierLevel) { "Unclassified" } else { $AdminTierLevel.ToString() } 'AdminTierLevelName' = if ($null -eq $AdminTierLevelName) { "Unclassified" } else { $AdminTierLevelName } 'AssociatedWorkAccount' = $WorkAccount 'AssociatedPawDevice' = $PawDevice 'OnPremSynchronized' = if ($null -eq $ObjectDetails.onPremisesSyncEnabled) { $false } else { $ObjectDetails.onPremisesSyncEnabled } 'RestrictedManagementByRAG' = $RestrictedManagementByRAG 'RestrictedManagementByAadRole' = $RestrictedManagementByAadRole 'RestrictedManagementByRMAU' = $RestrictedManagementByRMAU 'AssignedAdministrativeUnits' = $AssignedAdministrativeUnits 'PasswordPolicyAssigned' = $PasswordPolicies 'OutsideOfHomeTenant' = $OutsideOfAadTenant 'ResolutionStatus' = if ($ObjectType -eq 'unknown') { $ResolutionStatus } else { 'Resolved' } } } } # SIG # Begin signature block # MIIniAYJKoZIhvcNAQcCoIIneTCCJ3UCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD2IhUl+Ynje91M # cSlw7PyWCIXDp6FF7iATXcEeOkOaQKCCIDwwggXJMIIEsaADAgECAhAbtY8lKt8j # 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 # ARUwLwYJKoZIhvcNAQkEMSIEIHO9nF/MWy0iNBPzBkNu3nI0C4S6hr4ixWUgkEE4 # BELOMA0GCSqGSIb3DQEBAQUABIIBgDiUHqWoQtCh6qJ7cMqug3wYXjejDrf/27a0 # qKubpFX6CCRZeLnBrOXcXegIh7o7CkDAzT7hvh7Imvw2IvP+ccUFRBUojzCMc7wP # zeRNRwk6JbJN06SCn43CvHolY6kQGv8KU7D9xMr+ETh96GtJQ+nZb7VKvxS7GWt2 # hj+Bpfa4sEW0KNNGnO/PU1VRCMbN6HAoUgHnNl+0YlD9lnPYsc0N9S8Xd8HK4dD9 # aVkkiUvCYgkMaDnPUCl69f+cq8DZafXEjkGP7vtpfQfoLrmktzZuHJUM+36lMSEb # 7VegBvq+qO+RTFlXAK1Yd90EB3wLNF3kR3jDJpEKG636OP+tN4y3jdIrfgKj0v+7 # isvC/+GcclltJPl6YLvUkpGwokJnq0dGFcNoCbO2dH7A+qDynpimPA18tqCSZs8V # S9fsiBejii/mGxgpArQOq9fxmMTIx9LrKc9ePQYdO2uoPhHYTNszIYIj0i31Etqr # WzxSzltjPTujjQGKsMHbLLFV8Z2AKKGCBAIwggP+BgkqhkiG9w0BCQYxggPvMIID # 6wIBATBqMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0 # ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQQIQ # KPB3wRw2vf5fdDJHcCcuAzANBglghkgBZQMEAgIFAKCCAVYwGgYJKoZIhvcNAQkD # MQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNjEwMTMwOTQxMjZaMDcG # CyqGSIb3DQEJEAIvMSgwJjAkMCIEIIW+kOEK0kONfMkotq9IsJqyCBd87PiwEmxY # 05EFJcQ8MD8GCSqGSIb3DQEJBDEyBDCthNzkDjfA+aTuTpFjoI6mFytwdy5u8OHE # gJlKqn1yaOirsQ4olpaSRZ/xAE3NoCYwgZ8GCyqGSIb3DQEJEAIMMYGPMIGMMIGJ # MIGGBBRXFGhBDKha80JO+RZKUTYQ9NONmDBuMFqkWDBWMQswCQYDVQQGEwJQTDEh # MB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQwIgYDVQQDExtDZXJ0 # dW0gVGltZXN0YW1waW5nIDIwMjEgQ0ECECjwd8EcNr3+X3QyR3AnLgMwDQYJKoZI # hvcNAQEBBQAEggIAcuwEw7yyJT2mtttb0GmdpAK4/E2Xj2XO30xCDIEmK4S1fGp2 # A9RV3HJV/Tag1os6iBme5QejHy+hCfN0w2s26ZmNgIixP0SJoz+5gg1YqSIjnKHj # yzPWAnqGaeTrD3ZXuNnuzURrgjMkjw8C1zmo4w8cBtr3RE9RsqHx6kcK14ocFfHE # XMeF9j1yIoKWq1ptml9ET8Ox+KmH4z9hthj2GA23zwM6tGDT77w5SWWiCXYdUk0O # Q3Jt4nNOMdR2XazEu94gdS3XtvJezA9i/TpN/tf0CUbAJTvSJMAxelxoEKtyug1p # OVJ87OE7o7XS4ioSgk74yqeRiD5R8SSfsZ0XnQrZD2qXaUgZDHh4AzadJBDoxY+/ # YzgxkGwnLiU7cxEJv2pIrlgkC1fWgEthpnB4zS6TGfTr3XoCPBqbRLseJqqBkD64 # 1w3ZS4ofqfxmLk46E51a5fyqYC2MlhTI+vPjVLJb40mklUQbrI8YVJs2jS4P+Xrx # cWhSb47/08ab0kaqvD5J8r58JK20KOp6oc8TLWmE3m+EvLX3V7YXAI9WEiaX8r52 # VItk7ascKX6c+ycdZ2AqW0yxo2VUzftOKDb0TVstkNbkDG09QqglKeb/tjeWwyRd # rg52OLxZXhF7XdjRmVjiBwU3cokVp/qiOqbh+2vOLX2wSG10k7E/hjMDkUo= # SIG # End signature block |