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 .\Invoke-AADAssessmentDataCollection -OutputDirectory "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 ) Start-AppInsightsRequest $MyInvocation.MyCommand.Name try { $LookupCache = New-LookupCache $referencedIds = [PSCustomObject]@{ user = New-Object 'System.Collections.Generic.HashSet[guid]' group = New-Object 'System.Collections.Generic.HashSet[guid]' servicePrincipal = New-Object 'System.Collections.Generic.HashSet[guid]' } function Add-ReferencesToHash { param ( # [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [object] $InputObject, # [Parameter(Mandatory = $true)] [Alias('Type')] [ValidateSet('directoryRoles', 'servicePrincipal', 'oauth2PermissionGrants')] [string] $ObjectType, # [Parameter(Mandatory = $false)] [switch] $PassThru ) process { switch ($ObjectType) { directoryRoles { foreach ($member in $InputObject.members) { $MemberType = $member.'@odata.type' -replace '#microsoft.graph.', '' [void] $referencedIds.$MemberType.Add($member.id) } break } servicePrincipal { foreach ($appRoleAssignment in $InputObject.appRoleAssignedTo) { [void] $referencedIds.servicePrincipal.Add($appRoleAssignment.resourceId) [void] $referencedIds.$($appRoleAssignment.principalType).Add($appRoleAssignment.principalId) } break } oauth2PermissionGrants { [void] $referencedIds.servicePrincipal.Add($InputObject.clientId) [void] $referencedIds.servicePrincipal.Add($InputObject.resourceId) if ($InputObject.principalId) { [void] $referencedIds.user.Add($InputObject.principalId) } break } } if ($PassThru) { return $InputObject } } } 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.zip" ### Organization Data Write-Progress -Id 0 -Activity 'Microsoft Azure AD Assessment Data Collection' -Status 'Organization Details' -PercentComplete 0 $OrganizationData = Get-MsGraphResults 'organization?$select=id,verifiedDomains,technicalNotificationMails' -ErrorAction Stop $InitialTenantDomain = $OrganizationData.verifiedDomains | Where-Object isInitial -EQ $true | Select-Object -ExpandProperty name -First 1 $PackagePath = $PackagePath.Replace("AzureADAssessmentData.zip", "AzureADAssessmentData-$InitialTenantDomain.zip") $OutputDirectoryAAD = Join-Path $OutputDirectoryData "AAD-$InitialTenantDomain" Assert-DirectoryExists $OutputDirectoryAAD #Export-Clixml -InputObject $OrganizationData -Depth 10 -Path (Join-Path $OutputDirectoryAAD "OrganizationData.xml") ConvertTo-Json -InputObject $OrganizationData -Depth 10 | Set-Content (Join-Path $OutputDirectoryAAD "OrganizationData.json") ### Generate Assessment Data Assert-DirectoryExists $OutputDirectoryData ConvertTo-Json -InputObject @{ AssessmentId = if ($script:AppInsightsRuntimeState.OperationStack.Count -gt 0) { $script:AppInsightsRuntimeState.OperationStack.Peek().Id } else { New-Guid } AssessmentVersion = $MyInvocation.MyCommand.Module.Version.ToString() AssessmentTenantId = $OrganizationData.id AssessmentTenantDomain = $InitialTenantDomain } | Set-Content $AssessmentDetailPath ### Directory Role Data Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment Data Collection - {0}' -f $InitialTenantDomain) -Status 'Directory Roles' -PercentComplete 10 #[array] $DirectoryRoleData = Get-MsGraphResults 'directoryRoles?$select=id,displayName&$expand=members' | Where-Object members -NE $null Get-MsGraphResults 'directoryRoles?$select=id,displayName&$expand=members' ` | Where-Object members -NE $null ` | Add-ReferencesToHash -Type directoryRoles -PassThru ` | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "DirectoryRoleData.xml") #| ConvertTo-Json -Depth 10 -Compress | Set-Content (Join-Path $OutputDirectoryAAD "DirectoryRoleData.json") ### Application Data Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment Data Collection - {0}' -f $InitialTenantDomain) -Status 'Applications' -PercentComplete 20 #[array] $ApplicationData = Get-MsGraphResults 'applications?$select=id,appId,displayName,appRoles,keyCredentials,passwordCredentials' -Top 999 | Where-Object { $_.passwordCredentials -ne $null -or $_.keyCredentials -ne $null } Get-MsGraphResults 'applications?$select=id,appId,displayName,appRoles,keyCredentials,passwordCredentials' -Top 999 ` | Where-Object { $_.passwordCredentials -ne $null -or $_.keyCredentials -ne $null } ` | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "ApplicationData.xml") #| ConvertTo-Json -Depth 10 -Compress | Set-Content (Join-Path $OutputDirectoryAAD "ApplicationData.json") ### Service Principal Data Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment Data Collection - {0}' -f $InitialTenantDomain) -Status 'Service Principals' -PercentComplete 30 #[array] $ServicePrincipalData = Get-MsGraphResults 'serviceprincipals?$select=id,servicePrincipalType,appId,displayName,accountEnabled,appOwnerOrganizationId,appRoles,oauth2PermissionScopes,keyCredentials,passwordCredentials' -Top 999 Get-MsGraphResults 'serviceprincipals?$select=id,servicePrincipalType,appId,displayName,accountEnabled,appOwnerOrganizationId,appRoles,oauth2PermissionScopes,keyCredentials,passwordCredentials&$expand=appRoleAssignedTo' -Top 999 -OutVariable ServicePrincipalData ` | Add-ReferencesToHash -Type servicePrincipal -PassThru ` | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "ServicePrincipalData.xml") #| ConvertTo-Json -Depth 10 -Compress | Set-Content (Join-Path $OutputDirectoryAAD "ServicePrincipalData.json") #Import-Clixml -Path (Join-Path $OutputDirectoryAAD "ServicePrincipalData.xml") -OutVariable ServicePrincipalData ` #| Add-ReferencesToHash -Type servicePrincipal ` ### App Role Assignments Data Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment Data Collection - {0}' -f $InitialTenantDomain) -Status 'App Role Assignments' -PercentComplete 40 #[array] $AppRoleAssignmentData = Get-MsGraphResults 'serviceprincipals/{0}/appRoleAssignedTo?$select=id,appRoleId,createdDateTime,principalId,principalType,principalDisplayName,resourceId,resourceDisplayName' -UniqueId $ServicePrincipalData.id -Top 999 #$ServicePrincipalData.appRoleAssignedTo ` #| Export-Clixml -Path (Join-Path $OutputDirectoryAAD "AppRoleAssignmentData.xml") #| ConvertTo-Json -Depth 10 -Compress | Set-Content (Join-Path $OutputDirectoryAAD "AppRoleAssignmentData.json") ### OAuth2 Permission Grants Data Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment Data Collection - {0}' -f $InitialTenantDomain) -Status 'OAuth2 Permission Grants' -PercentComplete 50 ## https://graph.microsoft.com/v1.0/oauth2PermissionGrants cannot be used for large tenants because it eventually fails with "Service is temorarily unavailable." #[array] $OAuth2PermissionGrantData = Get-MsGraphResults 'oauth2PermissionGrants' -Top 999 #[array] $OAuth2PermissionGrantData = Get-MsGraphResults 'serviceprincipals/{0}/oauth2PermissionGrants' -UniqueId $ServicePrincipalData.id -Top 999 Get-MsGraphResults 'serviceprincipals/{0}/oauth2PermissionGrants' -UniqueId $ServicePrincipalData.id -Top 999 ` | Add-ReferencesToHash -Type oauth2PermissionGrants -PassThru ` | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "OAuth2PermissionGrantData.xml") #| ConvertTo-Json -Depth 10 -Compress | Set-Content (Join-Path $OutputDirectoryAAD "OAuth2PermissionGrantData.json") #Import-Clixml -Path (Join-Path $OutputDirectoryAAD "OAuth2PermissionGrantData.xml") ` #| Add-ReferencesToHash -Type oauth2PermissionGrants ` ### Remove unnessessary Service Principals Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment Data Collection - {0}' -f $InitialTenantDomain) -Status 'Filtering Service Principals' -PercentComplete 55 $ServicePrincipalData | Where-Object { $_.passwordCredentials -ne $null -or $_.keyCredentials -ne $null -or $referencedIds.servicePrincipal.Contains($_.id) } -OutVariable ServicePrincipalData ` | Add-AadObjectToLookupCache -Type servicePrincipal -LookupCache $LookupCache -PassThru ` | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "ServicePrincipalData.xml") #| ConvertTo-Json -Depth 10 -Compress | Set-Content (Join-Path $OutputDirectoryAAD "ServicePrincipalData.json") ### User Data Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment Data Collection - {0}' -f $InitialTenantDomain) -Status 'Users' -PercentComplete 60 [array] $UserData = Get-MsGraphResults 'users?$select=id,userPrincipalName,displayName,mail,otherMails,proxyAddresses' -UniqueId $referencedIds.user -Top 999 if ($OrganizationData) { foreach ($technicalNotificationMail in $OrganizationData.technicalNotificationMails) { $user = Get-MsGraphResults 'users?$select=id,userPrincipalName,displayName,mail,otherMails,proxyAddresses' -Filter "proxyAddresses/any(c:c eq 'smtp:$technicalNotificationMail') or otherMails/any(c:c eq '$technicalNotificationMail')" | Select-Object -First 1 if ($user -and !($UserData | Where-Object id -EQ $user.id)) { $UserData += $user } } } $UserData | Add-AadObjectToLookupCache -Type user -LookupCache $LookupCache -PassThru ` | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "UserData.xml") #ConvertTo-Json -InputObject $UserData -Depth 10 -Compress | Set-Content (Join-Path $OutputDirectoryAAD "UserData.json") ### Group Data Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment Data Collection - {0}' -f $InitialTenantDomain) -Status 'Groups' -PercentComplete 70 [array] $GroupData = Get-MsGraphResults 'groups?$select=id,displayName,mail,proxyAddresses' -UniqueId $referencedIds.group -Top 999 if ($OrganizationData) { foreach ($technicalNotificationMail in $OrganizationData.technicalNotificationMails) { $group = Get-MsGraphResults 'groups?$select=id,displayName,mail,proxyAddresses' -Filter "proxyAddresses/any(c:c eq 'smtp:$technicalNotificationMail')" | Select-Object -First 1 if ($group -and !($GroupData | Where-Object id -EQ $group.id)) { $GroupData += $group } } } $GroupData | Add-AadObjectToLookupCache -Type group -LookupCache $LookupCache -PassThru ` | Export-Clixml -Path (Join-Path $OutputDirectoryAAD "GroupData.xml") #ConvertTo-Json -InputObject $GroupData -Depth 10 -Compress | Set-Content (Join-Path $OutputDirectoryAAD "GroupData.json") ### Conditional Access Data Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment Data Collection - {0}' -f $InitialTenantDomain) -Status 'Groups' -PercentComplete 80 Export-AADAssessConditionalAccessData -OutputDirectory $OutputDirectoryAAD ### Generate Reports if (!$SkipReportOutput) { Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment Data Collection - {0}' -f $InitialTenantDomain) -Status 'Generating Reports' -PercentComplete 90 $DirectoryRoleData = Import-Clixml -Path (Join-Path $OutputDirectoryAAD "DirectoryRoleData.xml") Get-AADAssessNotificationEmailsReport -OrganizationData $OrganizationData -UserData $LookupCache.user -GroupData $LookupCache.group -DirectoryRoleData $DirectoryRoleData | Export-Csv -Path (Join-Path $OutputDirectoryAAD "NotificationsEmailsReport.csv") -NoTypeInformation #$ServicePrincipalData = Import-Clixml -Path (Join-Path $OutputDirectoryAAD "ServicePrincipalData.xml") Get-AADAssessAppAssignmentReport -ServicePrincipalData $LookupCache.servicePrincipal | Export-Csv -Path (Join-Path $OutputDirectoryAAD "AppAssignmentsReport.csv") -NoTypeInformation $ApplicationData = Import-Clixml -Path (Join-Path $OutputDirectoryAAD "ApplicationData.xml") Get-AADAssessAppCredentialExpirationReport -ApplicationData $ApplicationData -ServicePrincipalData $LookupCache.servicePrincipal | Export-Csv -Path (Join-Path $OutputDirectoryAAD "AppCredentialsReport.csv") -NoTypeInformation $OAuth2PermissionGrantData = Import-Clixml -Path (Join-Path $OutputDirectoryAAD "OAuth2PermissionGrantData.xml") Get-AADAssessConsentGrantReport -UserData $LookupCache.user -ServicePrincipalData $LookupCache.servicePrincipal -OAuth2PermissionGrantData $OAuth2PermissionGrantData | Export-Csv -Path (Join-Path $OutputDirectoryAAD "ConsentGrantReport.csv") -NoTypeInformation } ### Complete Write-Progress -Id 0 -Activity ('Microsoft Azure AD Assessment Data Collection - {0}' -f $InitialTenantDomain) -Completed ### Write Custom Event Write-AppInsightsEvent 'AAD Assessment Data Collection Complete' -OverrideProperties -Properties @{ AssessmentId = if ($script:AppInsightsRuntimeState.OperationStack.Count -gt 0) { $script:AppInsightsRuntimeState.OperationStack.Peek().Id } else { New-Guid } AssessmentVersion = $MyInvocation.MyCommand.Module.Version.ToString() AssessmentTenantId = $OrganizationData.id } ### Package Output Compress-Archive (Join-Path $OutputDirectoryData '\*') -DestinationPath $PackagePath -Force -ErrorAction Stop ### Clean-Up Data Files Remove-Item $OutputDirectoryData -Recurse -Force ### Open Directory Invoke-Item $OutputDirectory } catch { if ($MyInvocation.CommandOrigin -eq 'Runspace') { Write-AppInsightsException $_.Exception }; throw } finally { Complete-AppInsightsRequest $MyInvocation.MyCommand.Name -Success $? } } |