Invoke-AADAssessmentDataCollection.ps1
<# .SYNOPSIS Produces the Azure AD Configuration reports required by the Azure AD assesment .DESCRIPTION This cmdlet reads the configuration information from the target Azure AD Tenant and produces the output files in a target directory .EXAMPLE PS C:\> Invoke-AADAssessmentDataCollection Collect and package assessment data to "C:\AzureADAssessment". .EXAMPLE PS C:\> Invoke-AADAssessmentDataCollection -OutputDirectory "C:\Temp" Collect and package assessment data to "C:\Temp". #> function Invoke-AADAssessmentDataCollection { [CmdletBinding()] param ( # Full path of the directory where the output files will be generated. [Parameter(Mandatory = $false)] [string] $OutputDirectory = (Join-Path $env:SystemDrive 'AzureADAssessment'), # Generate Reports [Parameter(Mandatory = $false)] [switch] $SkipReportOutput, # Skip Packaging [Parameter(Mandatory = $false)] [switch] $SkipPackaging, [Parameter(Mandatory = $false)] # Skip getting user assigned plans [switch] $NoAssignedPlans ) Start-AppInsightsRequest $MyInvocation.MyCommand.Name try { $ReferencedIdCache = New-AadReferencedIdCache #$ReferencedIdCacheCA = New-AadReferencedIdCache function Get-ReferencedIdCacheDetail { param ( # ReferencedIdCache Object [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [psobject] $ReferencedIdCache ) process { $Output = [ordered]@{} foreach ($Property in $ReferencedIdCache.psobject.Properties) { $Output.Add(('RefIdCacheCount: {0}' -f $Property.Name), $Property.Value.Count) } Write-Output $Output } } function Extract-AppRoleAssignments { param ( # [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [psobject] $InputObject, # [Parameter(Mandatory = $true)] [psobject] $ListVariable, # [Parameter(Mandatory = $false)] [switch] $PassThru ) process { [PSCustomObject[]] $AppRoleAssignment = $InputObject.appRoleAssignedTo $ListVariable.AddRange($AppRoleAssignment) if ($PassThru) { return $InputObject } } } if ($MyInvocation.CommandOrigin -eq 'Runspace') { ## Reset Parent Progress Bar New-Variable -Name stackProgressId -Scope Script -Value (New-Object 'System.Collections.Generic.Stack[int]') -ErrorAction SilentlyContinue $stackProgressId.Clear() $stackProgressId.Push(0) } ### Initalize Directory Paths #$OutputDirectory = Join-Path $OutputDirectory "AzureADAssessment" $OutputDirectoryData = Join-Path $OutputDirectory "AzureADAssessmentData" $AssessmentDetailPath = Join-Path $OutputDirectoryData "AzureADAssessment.json" $PackagePath = Join-Path $OutputDirectory "AzureADAssessmentData.aad" ### Start Output Log #Start-Transcript -OutputDirectory $OutputDirectoryData -Force -IncludeInvocationHeader | Out-Null #$ErrorStartCount = $Error.Count ### Organization Data - 0 Write-AppInsightsTrace ("{0} - Organization Details" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties (Get-ReferencedIdCacheDetail $ReferencedIdCache) Write-Progress -Id 0 -Activity 'Microsoft Azure AD Assessment' -Status 'Organization Details' -PercentComplete 0 $OrganizationData = Get-MsGraphResults 'organization?$select=id,displayName,verifiedDomains,technicalNotificationMails' -ErrorAction Stop $InitialTenantDomain = $OrganizationData.verifiedDomains | Where-Object isInitial -EQ $true | Select-Object -ExpandProperty name -First 1 $PackagePath = $PackagePath.Replace("AzureADAssessmentData.aad", "AzureADAssessmentData-$InitialTenantDomain.aad") $OutputDirectoryAAD = Join-Path $OutputDirectoryData "AAD-$InitialTenantDomain" Assert-DirectoryExists $OutputDirectoryAAD ConvertTo-Json -InputObject $OrganizationData -Depth 10 | Set-Content (Join-Path $OutputDirectoryAAD "organization.json") ### Generate Assessment Data $AssessmentData = [PSCustomObject]@{ AssessmentDateTime = Get-Date AssessmentId = if ($script:AppInsightsRuntimeState.OperationStack.Count -gt 0) { $script:AppInsightsRuntimeState.OperationStack.Peek().Id.ToString() } else { (New-Guid).ToString() } AssessmentVersion = $MyInvocation.MyCommand.Module.Version.ToString() AssessmentTenantId = $OrganizationData.id AssessmentTenantDomain = $InitialTenantDomain } Assert-DirectoryExists $OutputDirectoryData ConvertTo-Json -InputObject $AssessmentData | Set-Content $AssessmentDetailPath ### Licenses - 1 Write-AppInsightsTrace ("{0} - Subscribed SKU" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties (Get-ReferencedIdCacheDetail $ReferencedIdCache) Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'Subscribed SKU' -PercentComplete 5 Get-MsGraphResults "subscribedSkus" -Select "prepaidunits", "consumedunits", "skuPartNumber", "servicePlans" -OutVariable skus ` | Export-JsonArray (Join-Path $OutputDirectoryAAD "subscribedSkus.json") -Depth 5 -Compress # Check tenant license status $licenseType = "Free" if ($skus | Where-Object { $_.prepaidUnits.enabled -gt 0 -and ($_.servicePlans | Where-Object { $_.servicePlanId -eq "41781fb2-bc02-4b7c-bd55-b576c07bb09d" })}) { $licenseType = "P2" } elseif ($skus | Where-Object { $_.prepaidUnits.enabled -gt 0 -and ($_.servicePlans | Where-Object { $_.servicePlanId -eq "eec0eb4f-6444-4f95-aba0-50c24d67f998" })}) { $licenseType = "P1" } Remove-Variable skus ### Conditional Access policies - 2 Write-AppInsightsTrace ("{0} - Conditional Access Policies" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties (Get-ReferencedIdCacheDetail $ReferencedIdCache) Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'Conditional Access Policies' -PercentComplete 10 #Get-MsGraphResults "identity/conditionalAccess/policies" -ErrorAction Stop ` Get-MsGraphResults "identity/conditionalAccess/policies" ` | Add-AadReferencesToCache -Type conditionalAccessPolicy -ReferencedIdCache $ReferencedIdCache -PassThru ` | Export-JsonArray (Join-Path $OutputDirectoryAAD "conditionalAccessPolicies.json") -Depth 5 -Compress ### Named location - 3 Write-AppInsightsTrace ("{0} - Conditional Access Named locations" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties (Get-ReferencedIdCacheDetail $ReferencedIdCache) Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'Conditional Access Named locations' -PercentComplete 15 Get-MsGraphResults "identity/conditionalAccess/namedLocations" ` | Export-JsonArray (Join-Path $OutputDirectoryAAD "namedLocations.json") -Depth 5 -Compress ### EOTP Policy - 4 Write-AppInsightsTrace ("{0} - Email Auth Method Policy" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties (Get-ReferencedIdCacheDetail $ReferencedIdCache) Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'Email Auth Method Policy' -PercentComplete 20 Get-MsGraphResults "policies/authenticationMethodsPolicy/authenticationMethodConfigurations/email" ` | ConvertTo-Json -Depth 5 -Compress | Set-Content -Path (Join-Path $OutputDirectoryAAD "emailOTPMethodPolicy.json") ### Directory Role Data - 5 (Remove from next release) # Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'Directory Roles' -PercentComplete 21 # ## $expand on directoryRole members caps results at 20 members with no NextLink so call members endpoint for each. # Get-MsGraphResults 'directoryRoles?$select=id,displayName,roleTemplateId' -DisableUniqueIdDeduplication ` # | Expand-MsGraphRelationship -ObjectType directoryRoles -PropertyName members -References ` # | Add-AadReferencesToCache -Type directoryRole -ReferencedIdCache $ReferencedIdCache -PassThru ` # | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "directoryRoleData.xml") ### Directory Role Definitions - 6 Write-AppInsightsTrace ("{0} - Directory Role Definitions" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties (Get-ReferencedIdCacheDetail $ReferencedIdCache) Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'Directory Role Definitions' -PercentComplete 25 Get-MsGraphResults 'roleManagement/directory/roleDefinitions' -Select 'id,templateId,displayName,isBuiltIn,isEnabled' -ApiVersion 'v1.0' -OutVariable roleDefinitions ` | Where-Object { $_.isEnabled } ` | Select-Object id, templateId, displayName, isBuiltIn, isEnabled ` | Export-Csv (Join-Path $OutputDirectoryAAD "roleDefinitions.csv") -NoTypeInformation if ($licenseType -eq "P2") { ### Directory Role Assignments - 7 Write-AppInsightsTrace ("{0} - Directory Role Assignments" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties (Get-ReferencedIdCacheDetail $ReferencedIdCache) Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'Directory Role Assignments' -PercentComplete 30 ## Getting role assignments via unified role API # Get-MsGraphResults 'roleManagement/directory/roleAssignmentScheduleInstances' -Select 'id,directoryScopeId,assignmentType,memberType,principalId,startDateTime,endDateTime' -QueryParameters @{ '$expand' = 'principal($select=id),roleDefinition($select=id,templateId,displayName)' } -ApiVersion 'v1.0' ` $roleDefinitions | Get-MsGraphResults 'roleManagement/directory/roleAssignmentScheduleInstances' -Select 'id,directoryScopeId,assignmentType,memberType,principalId,startDateTime,endDateTime' -Filter "roleDefinitionId eq '{0}'" -QueryParameters @{ '$expand' = 'principal($select=id),roleDefinition($select=id,templateId,displayName)' } -TotalRequests $roleDefinitions.Count -ApiVersion 'v1.0' ` | Add-AadReferencesToCache -Type roleAssignmentScheduleInstances -ReferencedIdCache $ReferencedIdCache -PassThru ` | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "roleAssignmentScheduleInstancesData.xml") ### Directory Role Eligibility - 8 Write-AppInsightsTrace ("{0} - Directory Role Eligibility" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties (Get-ReferencedIdCacheDetail $ReferencedIdCache) Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'Directory Role Eligibility' -PercentComplete 35 # Getting role eligibility via unified role API #Get-MsGraphResults 'roleManagement/directory/roleEligibilityScheduleInstances' -Select 'id,directoryScopeId,memberType,principalId,startDateTime,endDateTime' -QueryParameters @{ '$expand' = 'principal($select=id),roleDefinition($select=id,templateId,displayName)' } -ApiVersion 'v1.0' ` $roleDefinitions | Get-MsGraphResults 'roleManagement/directory/roleEligibilityScheduleInstances' -Select 'id,directoryScopeId,memberType,principalId,startDateTime,endDateTime' -Filter "roleDefinitionId eq '{0}'" -QueryParameters @{ '$expand' = 'principal($select=id),roleDefinition($select=id,templateId,displayName)' } -TotalRequests $roleDefinitions.Count -ApiVersion 'v1.0' ` | Add-AadReferencesToCache -Type roleAssignmentScheduleInstances -ReferencedIdCache $ReferencedIdCache -PassThru ` | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "roleEligibilityScheduleInstancesData.xml") #| Export-JsonArray (Join-Path $OutputDirectoryAAD "roleEligibilityScheduleInstances.json") -Depth 5 -Compress } else { ### Directory Role Assignments - 7 Write-AppInsightsTrace ("{0} - Directory Role Assignments" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties (Get-ReferencedIdCacheDetail $ReferencedIdCache) Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'Directory Role Assignments' -PercentComplete 30 Get-MsGraphResults 'roleManagement/directory/roleAssignments' -Select 'id,directoryScopeId,principalId' -QueryParameters @{ '$expand' = 'roleDefinition($select=id,templateId,displayName)' } ` | Add-AadReferencesToCache -Type roleAssignments -ReferencedIdCache $ReferencedIdCache -PassThru ` | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "roleAssignmentsData.xml") } Remove-Variable roleDefinitions # Lookup ObjectIds with Unknown Types $ReferencedIdCache.unknownType | Get-MsGraphResults 'directoryObjects' -Select 'id' ` | ForEach-Object { $ObjectType = $_.'@odata.type' -replace '#microsoft.graph.', '' [void] $ReferencedIdCache.$ObjectType.Add($_.id) if ($ObjectType -eq 'group') { [void] $ReferencedIdCache.roleGroup.Add($_.id) } } $ReferencedIdCache.unknownType.Clear() ### Application Data - 9 Write-AppInsightsTrace ("{0} - Applications" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties (Get-ReferencedIdCacheDetail $ReferencedIdCache) Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'Applications' -PercentComplete 40 Get-MsGraphResults 'applications?$select=id,appId,displayName,appRoles,keyCredentials,passwordCredentials' -Top 999 -ApiVersion 'v1.0' ` | Where-Object { $_.keyCredentials.Count -or $_.passwordCredentials.Count -or $ReferencedIdCache.application.Contains($_.id) -or $ReferencedIdCache.appId.Contains($_.appId) } ` | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "applicationData.xml") ### Service Principal Data - 10 Write-AppInsightsTrace ("{0} - Service Principals" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties (Get-ReferencedIdCacheDetail $ReferencedIdCache) Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'Service Principals' -PercentComplete 45 ## Option 1: Get servicePrincipal objects without appRoleAssignments. Get appRoleAssignments # $servicePrincipalsCount = Get-MsGraphResults 'servicePrincipals/$count' ` # ## Although much more performant, $expand on servicePrincipal appRoleAssignedTo appears to miss certain appRoleAssignments. # Get-MsGraphResults 'servicePrincipals?$select=id,appId,servicePrincipalType,displayName,accountEnabled,appOwnerOrganizationId,appRoles,oauth2PermissionScopes,keyCredentials,passwordCredentials' -Top 999 ` # | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "servicePrincipalData.xml") ## Option 2: Expand appRoleAssignedTo when retrieving servicePrincipal object. This is at least 50x faster but appears to miss some appRoleAssignments. $listAppRoleAssignments = New-Object 'System.Collections.Generic.List[psobject]' Get-MsGraphResults 'servicePrincipals?$select=id,appId,servicePrincipalType,displayName,accountEnabled,appOwnerOrganizationId,appRoles,oauth2PermissionScopes,keyCredentials,passwordCredentials&$expand=appRoleAssignedTo' -Top 999 -ApiVersion 'v1.0' ` | Extract-AppRoleAssignments -ListVariable $listAppRoleAssignments -PassThru ` | Select-Object -Property "*" -ExcludeProperty 'appRoleAssignedTo', 'appRoleAssignedTo@odata.context' ` | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "servicePrincipalData.xml") ### App Role Assignments Data - 11 Write-AppInsightsTrace ("{0} - App Role Assignments" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties (Get-ReferencedIdCacheDetail $ReferencedIdCache) Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'App Role Assignments' -PercentComplete 50 ## Option 1: Loop through all servicePrincipals to get appRoleAssignments # Import-Clixml -Path (Join-Path $OutputDirectoryAAD "servicePrincipalData.xml") ` # | Get-MsGraphResults 'servicePrincipals/{0}/appRoleAssignedTo' -Top 999 -TotalRequests $servicePrincipalsCount -DisableUniqueIdDeduplication ` # | Add-AadReferencesToCache -Type appRoleAssignment -ReferencedIdCache $ReferencedIdCache -PassThru ` # | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "appRoleAssignmentData.xml") ## Option 2: Use expanded appRoleAssignedTo from servicePrincipals. This is at least 50x faster but appears to miss some appRoleAssignments. $listAppRoleAssignments ` | Add-AadReferencesToCache -Type appRoleAssignment -ReferencedIdCache $ReferencedIdCache -PassThru ` | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "appRoleAssignmentData.xml") Remove-Variable listAppRoleAssignments ### OAuth2 Permission Grants Data - 12 Write-AppInsightsTrace ("{0} - OAuth2 Permission Grants" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties (Get-ReferencedIdCacheDetail $ReferencedIdCache) Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'OAuth2 Permission Grants' -PercentComplete 55 ## https://graph.microsoft.com/v1.0/oauth2PermissionGrants fails with "Service is temorarily unavailable" if too much data is returned in a single request. 600 works on microsoft.onmicrosoft.com. Get-MsGraphResults 'oauth2PermissionGrants' -Top 600 ` | Add-AadReferencesToCache -Type oauth2PermissionGrant -ReferencedIdCache $ReferencedIdCache -PassThru ` | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "oauth2PermissionGrantData.xml") ### Filter Service Principals - 13 Write-AppInsightsTrace ("{0} - Filtering Service Principals" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties (Get-ReferencedIdCacheDetail $ReferencedIdCache) Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'Filtering Service Principals' -PercentComplete 60 Remove-Item (Join-Path $OutputDirectoryAAD "servicePrincipalData-Unfiltered.xml") -ErrorAction Ignore Rename-Item (Join-Path $OutputDirectoryAAD "servicePrincipalData.xml") -NewName "servicePrincipalData-Unfiltered.xml" Import-Clixml -Path (Join-Path $OutputDirectoryAAD "servicePrincipalData-Unfiltered.xml") ` | Where-Object { $_.keyCredentials.Count -or $_.passwordCredentials.Count -or $ReferencedIdCache.servicePrincipal.Contains($_.id) -or $ReferencedIdCache.appId.Contains($_.appId) } ` | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "servicePrincipalData.xml") Remove-Item (Join-Path $OutputDirectoryAAD "servicePrincipalData-Unfiltered.xml") -Force $ReferencedIdCache.servicePrincipal.Clear() ### Administrative units data - 14 Write-AppInsightsTrace ("{0} - Administrative Units" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties (Get-ReferencedIdCacheDetail $ReferencedIdCache) Set-Content -Path (Join-Path $OutputDirectoryAAD "administrativeUnits.csv") -Value 'id,displayName,visibility' Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'Administrative Units' -PercentComplete 65 Get-MsGraphResults 'directory/administrativeUnits' -Select 'id,displayName,visibility' ` | Export-Csv (Join-Path $OutputDirectoryAAD "administrativeUnits.csv") -NoTypeInformation -Append ### Registration details data - 15 if ($licenseType -ne "Free") { Write-AppInsightsTrace ("{0} - Registration Details" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties (Get-ReferencedIdCacheDetail $ReferencedIdCache) Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'Registration Details' -PercentComplete 70 Get-MsGraphResults 'reports/authenticationMethods/userRegistrationDetails' -ApiVersion 'beta' ` | Export-JsonArray (Join-Path $OutputDirectoryAAD "userRegistrationDetails.json") -Depth 5 -Compress } ### Group Data - 16 Write-AppInsightsTrace ("{0} - Groups" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties (Get-ReferencedIdCacheDetail $ReferencedIdCache) Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'Groups' -PercentComplete 75 # add technical notifications groups if ($OrganizationData) { $OrganizationData.technicalNotificationMails | Get-MsGraphResults 'groups?$select=id' -Filter "proxyAddresses/any(c:c eq 'smtp:{0}')" ` | ForEach-Object { [void]$ReferencedIdCache.group.Add($_.id) } } # Add nested groups if ($ReferencedIdCache.roleGroup.Count -gt 0) { $ReferencedIdCache.roleGroup.guid | Get-MsGraphResults 'groups/{0}/transitiveMembers/microsoft.graph.group?$count=true&$select=id' -Top 999 -TotalRequests $ReferencedIdCache.roleGroup.Count -DisableUniqueIdDeduplication ` | ForEach-Object { [void]$ReferencedIdCache.group.Add($_.id) } } ## Option 1: Populate direct members on groups (including nested groups) and calculate transitiveMembers later. ## $expand on group members caps results at 20 members with no NextLink so call members endpoint for each. $ReferencedIdCache.group | Get-MsGraphResults 'groups?$select=id,groupTypes,displayName,mail,proxyAddresses,mailEnabled,securityEnabled,onPremisesSyncEnabled' -TotalRequests $ReferencedIdCache.group.Count -DisableUniqueIdDeduplication -BatchSize 1 -GetByIdsBatchSize 20 ` | Expand-MsGraphRelationship -ObjectType groups -PropertyName members -References -Top 999 -SkipRelationshipThreshold 100 ` | Add-AadReferencesToCache -Type group -ReferencedIdCache $ReferencedIdCache -ReferencedTypes '#microsoft.graph.user', '#microsoft.graph.servicePrincipal' -PassThru ` | Select-Object -Property "*" -ExcludeProperty '@odata.type' ` | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "groupData.xml") # | ForEach-Object { # foreach ($Object in $_.member) { # if ($Object.'@odata.type' -in ('#microsoft.graph.user', '#microsoft.graph.servicePrincipal')) { # $ObjectType = $Object.'@odata.type' -replace '#microsoft.graph.', '' # [void] $ReferencedIdCache.$ObjectType.Add($Object.id) # } # } # } ## Option 2: Get groups without member data and let Azure AD calculate transitiveMembers. # $ReferencedIdCache.group | Get-MsGraphResults 'groups?$select=id,groupTypes,displayName,mail,proxyAddresses,mailEnabled,securityEnabled' -TotalRequests $ReferencedIdCache.group.Count -DisableUniqueIdDeduplication ` # | Select-Object -Property "*" -ExcludeProperty '@odata.type' ` # | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "groupData.xml") # ### Group Transitive members - 16 # Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'Group Transitive Membership' -PercentComplete 75 # $ReferencedIdCache.group | Get-MsGraphResults 'groups/{0}/transitiveMembers/$ref' -Top 999 -TotalRequests $ReferencedIdCache.group.Count -IncapsulateReferenceListInParentObject -DisableUniqueIdDeduplication ` # | ForEach-Object { # $group = $_ # #[array] $directMembers = Get-MsGraphResults 'groups/{0}/members/$ref' -UniqueId $_.id -Top 999 -DisableUniqueIdDeduplication | Expand-ODataId | Select-Object -ExpandProperty id # $group.transitiveMembers | Expand-ODataId | ForEach-Object { # if ($_.'@odata.type' -eq '#microsoft.graph.user') { [void]$ReferencedIdCache.user.Add($_.id) } # [PSCustomObject]@{ # id = $group.id # #'@odata.type' = $group.'@odata.type' # memberId = $_.id # memberType = $_.'@odata.type' -replace '#microsoft.graph.', '' # #direct = $directMembers -and $directMembers.Contains($_.id) # } # } # } ` # | Export-Csv (Join-Path $OutputDirectoryAAD "groupTransitiveMembers.csv") -NoTypeInformation $ReferencedIdCache.group.Clear() ### User Data - 17 Write-AppInsightsTrace ("{0} - Users" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties (Get-ReferencedIdCacheDetail $ReferencedIdCache) Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'Users' -PercentComplete 80 # add technical notifications users if ($OrganizationData) { $OrganizationData.technicalNotificationMails | Get-MsGraphResults 'users?$select=id' -Filter "proxyAddresses/any(c:c eq 'smtp:{0}') or otherMails/any(c:c eq '{0}')" ` | ForEach-Object { [void]$ReferencedIdCache.user.Add($_.id) } } # get user information #$ReferencedIdCache.user | Get-MsGraphResults 'users/{0}?$select=id,userPrincipalName,userType,displayName,accountEnabled,onPremisesSyncEnabled,onPremisesImmutableId,mail,otherMails,proxyAddresses,assignedPlans,signInActivity' -TotalRequests $ReferencedIdCache.user.Count -DisableUniqueIdDeduplication -ApiVersion 'beta' ` $userQuery = 'users/{0}?$select=id,userPrincipalName,userType,displayName,accountEnabled,onPremisesSyncEnabled,onPremisesImmutableId,mail,otherMails,proxyAddresses' if (!$NoAssignedPlans) { $userQuery += ",assignedPlans" } $ReferencedIdCache.user | Get-MsGraphResults $userQuery -TotalRequests $ReferencedIdCache.user.Count -DisableUniqueIdDeduplication -BatchSize 20 -ApiVersion 'beta' ` | Select-Object -Property "*" -ExcludeProperty '@odata.type' ` | Select-Object -Property "*", @{ Name = "assignedPlans"; Expression = { Write-Output @($_.assignedPlans | Where-Object service -EQ 'AADPremiumService') -NoEnumerate } } -ExcludeProperty 'assignedPlans' ` | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "userData.xml") $ReferencedIdCache.user.Clear() ### Generate Reports if (!$SkipReportOutput) { Write-AppInsightsTrace ("{0} - Output Reports" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties (Get-ReferencedIdCacheDetail $ReferencedIdCache) Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'Output Report Data' -PercentComplete 85 Export-AADAssessmentReportData -SourceDirectory $OutputDirectoryAAD -LicenseType $licenseType -Force ## Remove Raw Data Output Remove-Item -Path (Join-Path $OutputDirectoryAAD "*") -Include "*Data.xml" -ErrorAction Ignore Remove-Item -Path (Join-Path $OutputDirectoryAAD "*") -Include "*Data.csv" -ErrorAction Ignore } ### Package Output if (!$SkipPackaging) { Write-AppInsightsTrace ("{0} - Package Output" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties (Get-ReferencedIdCacheDetail $ReferencedIdCache) Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Status 'Packaging Data' -PercentComplete 95 ### Remove pre existing package (zip) if it exists if (Test-Path -Path $PackagePath) { Remove-Item $PackagePath -Force } ### Package Output #Compress-Archive (Join-Path $OutputDirectoryData '\*') -DestinationPath $PackagePath -Force -ErrorAction Stop [System.IO.Compression.ZipFile]::CreateFromDirectory($OutputDirectoryData, $PackagePath) $PackageFileInfo = Get-Item $PackagePath Write-AppInsightsTrace ("{0} - Package Complete" -f $MyInvocation.MyCommand.Name) -SeverityLevel Verbose -IncludeProcessStatistics -OrderedProperties ((Get-ReferencedIdCacheDetail $ReferencedIdCache) + [ordered]@{ PackageSize = Format-NumberWithUnit $PackageFileInfo.Length 'B'; PackageSizeInBytes = $PackageFileInfo.Length }) Remove-Item $OutputDirectoryData -Recurse -Force } ### Complete Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment - {0}' -f $InitialTenantDomain) -Completed ### Write Custom Event Write-AppInsightsEvent 'AAD Assessment Data Collection Complete' -OverrideProperties -Properties @{ AssessmentId = $AssessmentData.AssessmentId AssessmentVersion = $MyInvocation.MyCommand.Module.Version.ToString() AssessmentTenantId = $OrganizationData.id } ### Stop Transcript #Stop-Transcript #$Error | Select-Object -Last ($Error.Count - $ErrorStartCount) | Export-Clixml -Path (Join-Path $OutputDirectoryData "PowerShell_errors.xml") -Depth 10 ### Open Directory try { Invoke-Item $OutputDirectory -ErrorAction SilentlyContinue } catch {} } catch { if ($MyInvocation.CommandOrigin -eq 'Runspace') { Write-AppInsightsException -ErrorRecord $_ -IncludeProcessStatistics }; throw } finally { ## Stop transcript if not already #try { Stop-Transcript | Out-Null } #catch {} # check generated package and issue warning $issue = $false if (!(Test-Path -PathType Leaf -Path $PackagePath) -and !$SkipPackaging) { Write-Warning "The export package has not been generated" $issue = $true } elseif (!$SkipPackaging) { if (!(Test-AADAssessmentPackage -Path $PackagePath -SkippedReportOutput $SkipReportOutput)) { Write-Warning "The generated package is missing some data" $issue = $true } } if ($issue) { Write-Warning "If you are working with microsoft or a provider on the assessment please warn them" Write-Warning "Please check GitHub issues and fill a new one or reply on existing ones mentionning the errors seen" Write-warning "https://github.com/AzureAD/AzureADAssessment/issues" } Complete-AppInsightsRequest $MyInvocation.MyCommand.Name -Success $? } } # SIG # Begin signature block # MIInuwYJKoZIhvcNAQcCoIInrDCCJ6gCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDqyHozX/D0+ZGK # yZOmo/0L7oPI53iQnhL+aHtkhTOX2qCCDYUwggYDMIID66ADAgECAhMzAAACzfNk # v/jUTF1RAAAAAALNMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NjAyWhcNMjMwNTExMjA0NjAyWjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDrIzsY62MmKrzergm7Ucnu+DuSHdgzRZVCIGi9CalFrhwtiK+3FIDzlOYbs/zz # HwuLC3hir55wVgHoaC4liQwQ60wVyR17EZPa4BQ28C5ARlxqftdp3H8RrXWbVyvQ # aUnBQVZM73XDyGV1oUPZGHGWtgdqtBUd60VjnFPICSf8pnFiit6hvSxH5IVWI0iO # nfqdXYoPWUtVUMmVqW1yBX0NtbQlSHIU6hlPvo9/uqKvkjFUFA2LbC9AWQbJmH+1 # uM0l4nDSKfCqccvdI5l3zjEk9yUSUmh1IQhDFn+5SL2JmnCF0jZEZ4f5HE7ykDP+ # oiA3Q+fhKCseg+0aEHi+DRPZAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU0WymH4CP7s1+yQktEwbcLQuR9Zww # VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh # dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ3MDUzMDAfBgNVHSMEGDAW # gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw # MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx # XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB # AE7LSuuNObCBWYuttxJAgilXJ92GpyV/fTiyXHZ/9LbzXs/MfKnPwRydlmA2ak0r # GWLDFh89zAWHFI8t9JLwpd/VRoVE3+WyzTIskdbBnHbf1yjo/+0tpHlnroFJdcDS # MIsH+T7z3ClY+6WnjSTetpg1Y/pLOLXZpZjYeXQiFwo9G5lzUcSd8YVQNPQAGICl # 2JRSaCNlzAdIFCF5PNKoXbJtEqDcPZ8oDrM9KdO7TqUE5VqeBe6DggY1sZYnQD+/ # LWlz5D0wCriNgGQ/TWWexMwwnEqlIwfkIcNFxo0QND/6Ya9DTAUykk2SKGSPt0kL # tHxNEn2GJvcNtfohVY/b0tuyF05eXE3cdtYZbeGoU1xQixPZAlTdtLmeFNly82uB # VbybAZ4Ut18F//UrugVQ9UUdK1uYmc+2SdRQQCccKwXGOuYgZ1ULW2u5PyfWxzo4 # BR++53OB/tZXQpz4OkgBZeqs9YaYLFfKRlQHVtmQghFHzB5v/WFonxDVlvPxy2go # a0u9Z+ZlIpvooZRvm6OtXxdAjMBcWBAsnBRr/Oj5s356EDdf2l/sLwLFYE61t+ME # iNYdy0pXL6gN3DxTVf2qjJxXFkFfjjTisndudHsguEMk8mEtnvwo9fOSKT6oRHhM # 9sZ4HTg/TTMjUljmN3mBYWAWI5ExdC1inuog0xrKmOWVMIIHejCCBWKgAwIBAgIK # YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm # aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw # OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD # VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG # 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la # UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc # 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D # dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ # lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk # kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 # A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd # X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL # 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd # sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 # T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS # 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI # bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL # BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD # uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv # c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF # BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h # cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA # YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn # 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 # v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b # pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ # KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy # CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp # mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi # hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb # BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS # oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL # gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX # cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGYwwghmIAgEBMIGVMH4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p # Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAALN82S/+NRMXVEAAAAA # As0wDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw # HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIKqv # MFwYQDHiJ1FGXQMm110o9damAVNZtPnDJhAER7IjMEIGCisGAQQBgjcCAQwxNDAy # oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20wDQYJKoZIhvcNAQEBBQAEggEAisJrQF/SVxkrpti3Abw1DJIQACINhXOAbmSK # ua4TVu0A+0PRoeTocHRFstxSYp6PSOirurWrC+4bkyXACUzyQMvyfHizlLy87zJU # Peze+0IjuXp2SH/cYH5Ja3w9dh+/qo2vDpapSvi+UAQh927qtYfNR59JSMEdcBBg # HDqjuQWBQKcT523o+KFpZlsPZvrFvXACAC1g/GphNhm6ocPPg0l9rpeFx2tQ70Zg # BcWmnS9JpmbqCmPlGgWKkAfX2XzIFU47A6wbOuap3xMbeOuBMS/81KSm2Bp7Yj2/ # VSNyWTGZDi2GSopUMvB8d5iKEm9l/39Lc3+ICQ9CE8NZkhM6X6GCFxYwghcSBgor # BgEEAYI3AwMBMYIXAjCCFv4GCSqGSIb3DQEHAqCCFu8wghbrAgEDMQ8wDQYJYIZI # AWUDBAIBBQAwggFZBgsqhkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGE # WQoDATAxMA0GCWCGSAFlAwQCAQUABCBlDsu18TuKjM9y/rooczVrSSVGnmc6dSzK # PFnmofLC2wIGYxIQr/+KGBMyMDIyMDkwNjIxNDAyOC40MjhaMASAAgH0oIHYpIHV # MIHSMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQL # EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsT # HVRoYWxlcyBUU1MgRVNOOkQwODItNEJGRC1FRUJBMSUwIwYDVQQDExxNaWNyb3Nv # ZnQgVGltZS1TdGFtcCBTZXJ2aWNloIIRZTCCBxQwggT8oAMCAQICEzMAAAGP81Go # 61py3cwAAQAAAY8wDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg # UENBIDIwMTAwHhcNMjExMDI4MTkyNzQ2WhcNMjMwMTI2MTkyNzQ2WjCB0jELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9z # b2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMg # VFNTIEVTTjpEMDgyLTRCRkQtRUVCQTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt # U3RhbXAgU2VydmljZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJlX # Pv61zxcehZOPgqS67mw6y02t0LgB0VV7svs7MV8JKIJE9Gvl1rgWm8B8qo/EUYmU # EL3b2zquTURMTnh4mgrZFLENNhEgHvQs/paATbbHwqvOOrt6LVhwiZapLw60q+5j # AasHEWO3H4QBny75aTEqI6AJ5O0Xo/o3CZ2MSHjd+Bd4fScADWN+tKBmAiEu6Szq # LFlfm8boPbok2WBP13JcmDRel3c2f8w/+FOacU+DGUJswRrw7PvHA3QP7LWX4/68 # votF1GDRT4bqnPlCpMJv1zRwfgg7BkJfmUtBnG1FLp+FT04RyZupkQMC+cvM6bVh # CKHG03kUk5mZ1GtomB9hDgVe3go8dEnW+pC3NcXRUXJA3kBkeCdchcsm7mbFD/Md # HTrBBKC0LjobipQy0BIOw+mcZmSZ0vAdN3sJP0qVS6rG+ulNqsheAcA7dbmZIxGe # 34pyKFIEs+Ae31i2CHjtjgmMSBNF78LFaKfT70102bRj885h1O+dxmqysrjOqGv6 # mk82L6wH1G+ymIb1UCsRlD5C/fniojOxtKnpyQha182T8EVqHHAEd9z4TRLrs8ym # RSeA3mkwi4P/LitEOEIxUXn+Z+B/tikCBIm2e8yHgV944LKyAm880ptEF90kVZmR # //wKqfGMZMHKCNVggYs7/OM/XqsEQXUOB2HDW0DDAgMBAAGjggE2MIIBMjAdBgNV # HQ4EFgQU8wbmdGuuSc7ioc6Fm9uX+zcjcbwwHwYDVR0jBBgwFoAUn6cVXQBeYl2D # 9OXSZacbUzUZ6XIwXwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3Nv # ZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUy # MDIwMTAoMSkuY3JsMGwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1l # LVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUE # DDAKBggrBgEFBQcDCDANBgkqhkiG9w0BAQsFAAOCAgEAzX/TqPc8oQuZ9YwvIlOz # SWN/RYs44cWcCWyWP2LcJ+t6ZTJU0mgaXq2p+eun7kaIxiUr9xMGbPka7jlNk/2U # Q8eFR3rCx7XJRPBpjDNakzGmTy/CNha0Zn+TqKeBqnMTXTRAgQpVWZp9CsxXTzKk # Wuf9EegpoKiYdJrryJopIB7m76IbGrzxMsh0GveBw+PyzSnf2CGgiij8/UgOXwGu # KYUOBL89mrmPUlJbiHeTCvR+XI1lcAcQr2AA/tQlvc+xrISZTY6gb1rSjuidAHpn # 4Os9vIO6nOxv7Qra5R+P8tu8vrLbsFlzb8HbEndZbweDOtwLjJfWKemrv1xZJxsy # Txep/7lkeiYUL84eNCC4Hk4S5mIX/M/mg2+K9jgSxtT9gemgk1tmEJLe06VJ8yNH # Chq9tdwmyrRpPzjiB0rAzsWrJnhifhYlCQoldecId2mU/1U/z5C/ROIQwQMBrePR # vPIEgjtMfY33Q2VnVhmxC15UpgNxD+Hk2Ku0a6JWNOBvHxrRdKc7mbuNwNvc2iPZ # SK+bpSkc/BKEB1OnLtD8VMNAfR/HAJL0MWjLpkWf+Hwm6jW+E3D5D3FjiNuEGJb6 # W7U/ad9X5WBJZnOcIxqZQJMv55CXE9B2RDV3p8VrT77eIHKKqaXiTwN0v9Q+eyi9 # +uu3qSK9MldvdBNqLWWwFvAwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAA # AAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBB # dXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwx # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1p # Y3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOC # Ag8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YB # f2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKD # RLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus # 9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTj # kY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56 # KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39 # IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHo # vwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJo # LhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMh # XV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREd # cu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEA # AaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqn # Uv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnp # cjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRw # Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0w # EwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEw # CwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/o # olxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNy # b3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYt # MjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5t # aWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5j # cnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+ # TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2Y # urYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4 # U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJ # w7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb # 30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ # /gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGO # WhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFE # fnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJ # jXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rR # nj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUz # WLOhcGbyoYIC1DCCAj0CAQEwggEAoYHYpIHVMIHSMQswCQYDVQQGEwJVUzETMBEG # A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj # cm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBP # cGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOkQwODIt # NEJGRC1FRUJBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl # oiMKAQEwBwYFKw4DAhoDFQA+TS+CBHbnSAcHRqAmldFgW0GaaqCBgzCBgKR+MHwx # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1p # Y3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUAAgUA5sHV # FzAiGA8yMDIyMDkwNjIyMTc1OVoYDzIwMjIwOTA3MjIxNzU5WjB0MDoGCisGAQQB # hFkKBAExLDAqMAoCBQDmwdUXAgEAMAcCAQACAh3vMAcCAQACAhFoMAoCBQDmwyaX # AgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSCh # CjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAhRzRIEApGYgLr+SSWTwWtYq2 # w/tw83eHCKblfvqnRfxCiy7AB125TRgEfbg9yAcFZ3UkqjPK0WwuC8GTjM9Mh1by # 971wdtwd7R1C2UGKJ2Ch0tl9Kgwh+YmNKfwfd5d+zPTFKTzMYfglHUXa4AZCdLjb # aJyldyykgKtte0YhFm8xggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEG # A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj # cm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFt # cCBQQ0EgMjAxMAITMwAAAY/zUajrWnLdzAABAAABjzANBglghkgBZQMEAgEFAKCC # AUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCDd # EIfnAxNceSXYxxZQUXVgO0HmrHCvEKdUWIMruS6jfTCB+gYLKoZIhvcNAQkQAi8x # geowgecwgeQwgb0EIJdyBU/ixsYLlY4ipttiLZjunRQ1lvJ0obujuoPMhccfMIGY # MIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV # BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG # A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAGP81Go61py # 3cwAAQAAAY8wIgQgC4CZFSoKUCk5NH4yKHz5Yjim9GV22nbRXXrtDLKqJMowDQYJ # KoZIhvcNAQELBQAEggIAiC7sD6bRj+TObW4Aeibr55QLNGG5USJHo0bJ2OeI3HH1 # yr//+yl2WyoZI/bsdRU9E+B8sOo9CtDX0exnDaXeXshs27SkrY8esQXv7rLG7nVL # 1/Si6a7GHdt3zhqp7ahT4UTuLtzL5lMnpdafXl9UcTPfSHFGTXCEyDaJJOX71zij # LvWT3QUqlf4yITA+17/XS6hsROEuJ42wANqg3lMzI1a8gclA8PrRVOQZi+ClceKf # 0pYtEzsGFLwjUMwh15rxhRKfi0AANZ364sArIDd09bMwERO+q39nTNZqAzsAdMLJ # dNkI2OaXQfCY6K//Sprmoh4/YNjB69lsxxSwqFBkitL3IKm28k1FIs0uRb/c9z1x # URHJ8zBP6qnd2ClOqL7E0D9hre1/D1byRkrDIXbzeMdK1JdpsVXC0ixTGCgzHSSO # f0zDvZHsNdgHaz6yUnNASxe27qR9FA9TXszxFH8GdTb3HJMjgQsZXGru0TpJgX1N # JHwq4MKVPbUfWB+lB4XBK29MB11A2/LAvkhzn9EFiSX0HBdrYVcfPgTlQE7VlCsy # VcaHO78A0zBSri5CqCPYIxARFVpBO5XcNQU167t4ODa6YlogRjRQFZpb5enZfA2T # qx3h7uEJ7n2nubSQBR7jEYpeliBvkW/Zf6RmA6Zy1FHsvQ3FmDON+bwe5s6SYh4= # SIG # End signature block |