Azure Active Directory Assessment | Part III
In the previous post, Azure Active Directory Assessment | Part II, an App registration with the correct permissions was created. Via PowerShell the connection details where saved in a XML file on the desktop. Via the portal the details needed to be written down.
In this post, the assessment will start with Users and there properties. After that the Groups will be exported included the members and sharepoint/teams url and at last the Devices will be exported.
Connection
After the previous part, $ConnectionDetails is filled with the necessary information for the connection to Graph API.
# retrieve an access token
do {
try{
$accessToken = (Get-MSALToken -Clientid $ConnectionDetails.ClientId -ClientSecret $connectionDetails.ClientSecret -TenantId $ConnectionDetails.TenantId).AccessToken
$mustRetry = 0
}
catch {
$webError = $_
$mustRetry = 1
Start-Sleep -seconds 2
}
} while (
$mustRetry -eq 1
)
When the token is aquired, build the Header.
$header = @{
'Authorization' = "BEARER $accesstoken"
'Content-type' = "application/json"
}
Function for Running query to Graph API
function RunQueryAndProcess {
Try {
$Results = (Invoke-RestMethod -Headers $header -Uri $url -Method Get)
}
catch {
$webError = $_
$mustRetry = 1
}
# if token is expired renew token
If ($mustRetry -and ($weberror.ErrorDetails.message -like "*Access token has expired or is not yet valid.*")) {
#region connection
# Get an access token for the Microsoft Graph API
do {
Try {
$accessToken = (Get-MSALToken -Clientid $ConnectionDetails.ClientId -ClientSecret $connectionDetails.ClientSecret -TenantId $ConnectionDetails.TenantId -ForceRefresh).AccessToken
$header = @{
'Authorization' = "BEARER $accesstoken"
'Content-type' = "application/json"
}
}
Catch {
write-host "Unable to acquire access token, check the parameters are correct`n$($Error[0])"
exit
}
Start-Sleep -seconds 2
} while (
$AccessToken -eq $null
)
#endregion connection
$Results = (Invoke-RestMethod -Headers $header -Uri $url -Method Get)
}
#Output Results for Debugging
#write-host $results
#Begin populating results
[array]$ResultsValue = $Results.value
#If there is a next page, query the next page until there are no more pages and append results to existing set
if ($results."@odata.nextLink" -ne $null) {
$NextPageUri = $results."@odata.nextLink"
##While there is a next page, query it and loop, append results
While ($NextPageUri -ne $null) {
$NextPageRequest = (Invoke-RestMethod -Headers $header -Uri $NextPageURI -Method Get)
$NxtPageData = $NextPageRequest.Value
$NextPageUri = $NextPageRequest."@odata.nextLink"
$ResultsValue = $ResultsValue + $NxtPageData
}
}
##Return completed results
return $ResultsValue
}
Users
When getting information about the users in Azure Active Directory, you need to determine which information you would like to retrieve.
In this post we have chosen for the following attributes:
- ID
- Status
- Type
- lastSignInActivity
- UserPrincipalName
- DisplayName
- createdDateTime
- lastPasswordChangeDateTime
- GivenName
- SurName
- employeeHireDate
- employeeId
- employeeType
- JobTitle
- Company
- Manager
- licenseSkuId
- licenseSkuPartNumber
- licenseServicePlan
- Usage Location
- mailNickname
- proxyAddresses
- OnPremisesSyncEnabled
- OnPremisesLastSyncDateTime
- OnPremisesDistinguishedName
- OnPremisesImmutableId
- OnPremisesSamAccountName
- OnPremisesDomainName
- OnPremisesUserPrincipalName
- OnPremisesSecurityIdentifier
- mailbox statistics
To achieve this information in a single export, we need to run some query’s and combine those to the end result. Let’s start with the easy information.
# setting url
$url = "https://graph.microsoft.com/v1.0/users?`$select=lastPasswordChangeDateTime,signInActivity,createdDateTime,licenseAssignmentStates,id,AccountEnabled,UserprincipalName,proxyAddresses,UserType,DisplayName,GivenName,Surname,EmployeeHireDate,EmployeeID,EmployeeType,Identities,JobTitle,Company,manager,usagelocation,mailnickname,onpremisesdistinguishedname,onpremisesdomainname,OnPremisesImmutableId,OnPremisesLastSyncDateTime,OnPremisesSamAccountName,OnPremisesSecurityIdentifier,OnPremisesSyncEnabled,OnPremisesUserPrincipalName"
# getting the users
$Users = RunQueryAndProcess
# adding additional placeholders to array
foreach ($user in $users) {
$user.proxyaddresses = $user.proxyaddresses -join ';'
$user | Add-Member -MemberType NoteProperty -Name "License SKUs" -Value ($user.licenseAssignmentStates.skuid -join ";") -Force
$user | Add-Member -MemberType NoteProperty -Name "Group License Assignments" -Value ($user.licenseAssignmentStates.assignedByGroup -join ";") -Force
$user | Add-Member -MemberType NoteProperty -Name "Disabled Plan IDs" -Value ($user.licenseAssignmentStates.disabledplans -join ";") -Force
$user | Add-Member -MemberType NoteProperty -Name "Mailbox Items" -Value "" -force
$user | Add-Member -MemberType NoteProperty -Name "Mailbox isDeleted" -Value "" -force
$user | Add-Member -MemberType NoteProperty -Name "Last Activity Date" -Value "" -force
$user | Add-Member -MemberType NoteProperty -Name "Storage Used" -Value "" -force
$user | Add-Member -MemberType NoteProperty -Name "Deleted Item Count" -Value "" -force
$user | Add-Member -MemberType NoteProperty -Name "Deleted Item Size" -Value "" -force
$user | Add-Member -MemberType NoteProperty -Name "Archive" -Value "" -force
}
# getting mailbox reports
$url = "https://graph.microsoft.com/v1.0/reports/getMailboxUsageDetail(period='D30')"
$url = "https://graph.microsoft.com/v1.0/reports/getMailboxUsageDetail(period='D30')"
try {
$MailboxStatsReport = ((Invoke-Restmethod -Headers $Header -Uri $url -Method GET) | ConvertFrom-Csv)}
catch {
$webError = $_
$mustRetry = 1
}
If ($mustRetry -and ($weberror.ErrorDetails.message -like "*Access token has expired or is not yet valid.*")) {
#region connection
# Get an access token for the Microsoft Graph API
do {
$accessToken = (Get-MSALToken -Clientid $ConnectionDetails.ClientId -ClientSecret $connectionDetails.ClientSecret -TenantId $ConnectionDetails.TenantId -ForceRefresh).AccessToken
Start-Sleep -seconds 2
} while (
$accessToken -eq $null
)
$resource = "https://graph.microsoft.com/"
$header = @{
'Authorization' = "BEARER $accesstoken"
'Content-type' = "application/json"
}
#endregion connection
$MailboxStatsReport = ((Invoke-Restmethod -Headers $Header -Uri $url -Method GET) | ConvertFrom-Csv)
}
# getting groups
$url = "https://graph.microsoft.com/v1.0/groups"
$Groups = RunQueryAndProcess
# getting licenses information
$url = "https://graph.microsoft.com/v1.0/subscribedskus"
$SKUs = RunQueryAndProcess
# processing data to user table
foreach ($user in $users) {
foreach ($Group in $Groups) {
$user.'Group License Assignments' = $user.'Group License Assignments'.replace($group.id, $group.displayName)
}
foreach ($SKU in $SKUs) {
$user.'License SKUs' = $user.'License SKUs'.replace($SKU.skuid, $SKU.skuPartNumber)
}
foreach ($SKUplan in $SKUs.servicePlans) {
$user.'Disabled Plan IDs' = $user.'Disabled Plan IDs'.replace($SKUplan.servicePlanId, $SKUplan.servicePlanName)
}
$Stats = $null
$stats = $MailboxStatsReport | Where-Object {$_.'User Principal Name' -eq $user.userPrincipalName}
if ($Stats){
$User.'Mailbox Items' = $Stats.'Item Count'
$user.'Mailbox isDeleted' = $Stats.'Is Deleted'
$user.'Last Activity Date' = $stats.'Last Activity Date'
$user.'Storage Used (MB)' = $stats.'Storage Used (Byte)' /1024 /1024
$user.'Storage Used (MB)' = [math]::Round($user.'Storage Used (MB)' , 2)
$user.'Deleted Item Count' = $Stats.'Deleted Item Count'
$user.'Deleted Item Size (MB)' = $Stats.'Deleted Item Size (Byte)' /1024 /1024
$user.'Deleted Item Size (MB)' = [math]::Round($user.'Deleted Item Size (MB)' , 2)
$user.Archive = $Stats.'Has Archive'
}
}
Groups
There are different type of groups and some of them are also related to teams or sharepoint. To get a good overview over the groups, we need to get all the information there is about a group. With the Users we al ready pulled all the groups from GraphAPI. Now we need to check what is more behind.
# getting team groups
$TeamGroups = $Groups | Where-Object { ($_.grouptypes -Contains "unified") -and ($_.resourceProvisioningOptions -contains "Team") }
# getting all the TEAMS information for each group
foreach ($teamgroup in $TeamGroups) {
$url = "https://graph.microsoft.com/beta/teams/$($teamgroup.id)/allchannels"
$Teamchannels = RunQueryAndProcess
$standardchannels = ($teamchannels | Where-Object { $_.membershipType -eq "standard" })
$privatechannels = ($teamchannels | Where-Object { $_.membershipType -eq "private" })
$outgoingsharedchannels = ($teamchannels | Where-Object { ($_.membershipType -eq "shared") -and (($_."@odata.id") -like "*$($teamgroup.id)*") })
$incomingsharedchannels = ($teamchannels | Where-Object { ($_.membershipType -eq "shared") -and ($_."@odata.id" -notlike "*$($teamgroup.id)*") })
$teamgroup | Add-Member -MemberType NoteProperty -Name "StandardChannels" -Value $standardchannels.id.count -Force
$teamgroup | Add-Member -MemberType NoteProperty -Name "PrivateChannels" -Value $privatechannels.id.count -Force
$teamgroup | Add-Member -MemberType NoteProperty -Name "SharedChannels" -Value $outgoingsharedchannels.id.count -Force
$teamgroup | Add-Member -MemberType NoteProperty -Name "IncomingSharedChannels" -Value $incomingsharedchannels.id.count -Force
$privatechannelSize = 0
foreach ($Privatechannel in $privatechannels) {
$PrivateChannelObject = $null
$apiuri = "https://graph.microsoft.com/beta/teams/$($teamgroup.id)/channels/$($Privatechannel.id)/FilesFolder"
Try {
$PrivateChannelObject = RunQueryAndProcess
$Privatechannelsize += $PrivateChannelObject.size
}
catch {
$Privatechannelsize += 0
}
}
$sharedchannelSize = 0
foreach ($sharedchannel in $outgoingsharedchannels) {
$sharedChannelObject = $null
$apiuri = "https://graph.microsoft.com/beta/teams/$($teamgroup.id)/channels/$($Sharedchannel.id)/FilesFolder"
Try {
$SharedChannelObject = RunQueryAndProcess
$Sharedchannelsize += $SharedChannelObject.size
}
Catch {
$Sharedchannelsize += 0
}
}
$teamgroup | Add-Member -MemberType NoteProperty -Name "PrivateChannelsSize" -Value $privatechannelSize -Force
$teamgroup | Add-Member -MemberType NoteProperty -Name "SharedChannelsSize" -Value $sharedchannelSize -Force
$TeamDetails = $null
$url = "https://graph.microsoft.com/v1.0/groups/$($teamgroup.id)/drive/"
Try {
$TeamDetails = RunQueryAndProcess
}
catch {
}
$teamgroup | Add-Member -MemberType NoteProperty -Name "DataSize" -Value $TeamDetails.quota.used -Force
if ($TeamDetails.weburl){
$teamgroup | Add-Member -MemberType NoteProperty -Name "URL" -Value $TeamDetails.webUrl.replace("/Shared%20Documents", "") -Force -ErrorAction SilentlyContinue
}
}
Now every group is checked if it is a teams group or not and gathered the information, we going to check who are the members of these groups
# creating a table for membership
$GroupMembersObject = @()
# getting group memberships
foreach ($group in $groups) {
$url = "https://graph.microsoft.com/v1.0/groups/$($group.id)/members"
$Members = RunQueryAndProcess
foreach ($member in $members) {
$MemberEntry = [PSCustomObject]@{
GroupID = $group.id
GroupName = $group.displayname
MemberID = $member.id
MemberName = $member.displayname
MemberUserPrincipalName = $member.userprincipalname
MemberType = "Member"
MemberObjectType = $member.'@odata.type'.replace('#microsoft.graph.', '')
}
$GroupMembersObject += $memberEntry
}
$url = "https://graph.microsoft.com/v1.0/groups/$($group.id)/owners"
$Owners = RunQueryAndProcess
foreach ($member in $Owners) {
$MemberEntry = [PSCustomObject]@{
GroupID = $group.id
GroupName = $group.displayname
MemberID = $member.id
MemberName = $member.displayname
MemberUserPrincipalName = $member.userprincipalname
MemberType = "Owner"
MemberObjectType = $member.'@odata.type'.replace('#microsoft.graph.', '')
}
$GroupMembersObject += $memberEntry
}
}
Devices
These days more and more devices are connected to Azure AD. Let’s inventory them.
# setting url
$url = "https://graph.microsoft.com/v1.0/devices?`$select=id,accountEnabled,DisplayName,OperatingSystem,Operaringsystemversion,profiletype,registrationDateTime,approximateLastSignInDateTime,complianceExpirationDateTime,enrollmentProfileName,enrollmentType,deviceOwnership,domainName,isCompliant,isManaged,isRooted,manufacturer,mdmAppid,model"
# getting device information
$Devices = RunQueryAndProcess