Introduction
During some courses I have been working with the AzureAD PowerShell Module to retrieve information from Users, Groups, Roles etc. While working with these cmdlets it was annoying that it doesn’t have a functionality to recursivly search through group objects. So I wrote two modules to recursivly search through group objects and roles and only return the user objects. Then I also wanted to get a overview of the Azure AD roles and privileged roles and their users? This blogpost will be the first of the series about priviliged identities. In this blogpost I will describe and share the PowerShell cmdlets I created which will help with enumerating Azure AD Group and Role user memberships.
In Azure there are three identities that can have access to resources:
- Users
- Groups
- Can have users, serviceprincipals or other groups as members
- ServicePrincipals
In the first blog we will focus on retrieving users out of groups and roles.
The problem with Azure AD cmdlets when querying for memberships
When quering members of a group with the cmdlet Get-AzureADGroupMember
the cmdlet returns user, serviceprincipal and group objects. In the output below an example is shown where it returns the user GroupUser
and the group NestedGroup
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Get-AzureADGroupMember -ObjectId f5108639-9aca-4694-864e-c4e00186706b
ObjectId DisplayName UserPrincipalName UserType
-------- ----------- ----------------- --------
eb815e66-31a5-45ca-bed8-2b0f5e24f62f GroupUser GroupUser@jonyschats.nl Member
DeletionTimestamp :
ObjectId : 50c16bf1-5016-4dfe-8bcc-d273c3b02f02
ObjectType : Group
Description :
DirSyncEnabled :
DisplayName : NestedGroup
LastDirSyncTime :
Mail :
MailEnabled : False
MailNickName : 295cb161-8
OnPremisesSecurityIdentifier :
ProvisioningErrors : {}
ProxyAddresses : {}
SecurityEnabled : True
But it is missing the user that is part of the group NestedGroup
and I would like to only retrieve the userobjects. The same happens for roles since you can assign groups and users to a role.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Get-AzureADDirectoryRoleMember -ObjectId 598a6cfe-5d1a-42a7-81b6-76f4ab077152
ObjectId DisplayName UserPrincipalName UserType
-------- ----------- ----------------- --------
fb8a7905-e32c-4431-9e66-2968013f924f SecurityReader SecurityReader@jonyschats.nl Member
DeletionTimestamp :
ObjectId : 15c39279-6020-4a08-891f-8d76b6b86036
ObjectType : Group
Description :
DirSyncEnabled :
DisplayName : Security Reader AD Group
LastDirSyncTime :
Mail :
MailEnabled : False
MailNickName : 62ac515e-a
OnPremisesSecurityIdentifier :
ProvisioningErrors : {}
ProxyAddresses : {}
SecurityEnabled : True
The output is missing the user of the group Security Reader AD Group
.
New cmdlets for recursively searching through groups and roles
Get-AzureADGroupMemberRecursive
So I created the PowerShell cmdlet Get-AzureADGroupMemberRecursive
which takes a group object as input and recursivly searches through all group objects and returns user objects. The code of the cmdlet is:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Function Get-AzureADGroupMemberRecursive{
<#
.SYNOPSIS
Author: Jony Schats - 0xjs
Required Dependencies: Get-AzureADGroupMember
Optional Dependencies: None
.DESCRIPTION
Recursively search through groups and only return unique user objects. Requires the Get-AzureADGroup as input.
.EXAMPLE
Get-AzureADGroup -ObjectId <ID> | Get-AzureADGroupMemberRecursive
.EXAMPLE
Get-AzureADGroup | Where-Object -Property Displayname -eq "<GROUP>" | Get-AzureADGroupMemberRecursive
#>
[cmdletbinding()]
param(
[parameter(Mandatory=$True,ValueFromPipeline=$true)]
$AzureGroup
)
Begin{
If(-not(Get-AzureADCurrentSessionInfo)){Connect-AzureAD}
$Output = @()
}
Process {
Write-Verbose -Message "Enumerating $($AzureGroup.DisplayName)"
$Members = Get-AzureADGroupMember -ObjectId $AzureGroup.ObjectId -All $true
$UserMembers = $Members | Where-Object{$_.ObjectType -eq 'User'}
$Output += $UserMembers
$GroupMembers = $Members | Where-Object{$_.ObjectType -eq 'Group'}
If($GroupMembers){
$UserMembers = $GroupMembers | ForEach-Object{ Get-AzureADGroupMemberRecursive -AzureGroup $_}
$Output += $UserMembers
}
}
end {
Return $Output | Sort-Object -Unique
}
}
Below is example output where it retrieves the user GroupUser
of the group test group
, but also the user NestedGroupUser
from the group NestedGroup
which is member of the group test group
. The membership structure is as follows:
- Test group
- GroupUser@jonyschats.nl
- NestedGroup
- NestedGroupUser@jonyschats.nl
1
2
3
4
5
6
Get-AzureADGroup -ObjectId f5108639-9aca-4694-864e-c4e00186706b | Get-AzureADGroupMemberRecursive
ObjectId DisplayName UserPrincipalName UserType
-------- ----------- ----------------- --------
1a9a26f4-297a-4dec-95b3-e502ec8e9dfc NestedGroupUser NestedGroupUser@jonyschats.nl Member
eb815e66-31a5-45ca-bed8-2b0f5e24f62f GroupUser GroupUser@jonyschats.nl Member
Get-AzureADDirectoryRoleMemberRecursive
Roles can also have user and group objects as members. So I created the PowerShell cmdlet Get-AzureADDirectoryRoleMemberRecursive
which uses the Get-AzureADGroupMemberRecursive
cmdlet to recursivly search through all the groups assigned to the role and returns all the users. The code of the cmdlet is:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Function Get-AzureADDirectoryRoleMemberRecursive{
<#
.SYNOPSIS
Author: Jony Schats - 0xjs
Required Dependencies: Get-AzureADDirectoryRoleMember, Get-AzureADGroupMember, Get-AzureADGroupMemberRecursive
Optional Dependencies: None
.DESCRIPTION
Recursively search through roles and only return unique user objects. Requires the Get-AzureADDirectoryRole as input.
.EXAMPLE
Get-AzureADDirectoryRole -ObjectId <ID> | Get-AzureADDirectoryRoleMemberRecursive
.EXAMPLE
Get-AzureADDirectoryRole | Where-Object -Property Displayname -eq "<ROLE>" | Get-AzureADDirectoryRoleMemberRecursive
#>
[cmdletbinding()]
param(
[parameter(Mandatory=$True,ValueFromPipeline=$true)]
$RoleGroup
)
Begin{
If(-not(Get-AzureADCurrentSessionInfo)){Connect-AzureAD}
$Output = @()
}
Process {
Write-Verbose -Message "Enumerating $($RoleGroup.DisplayName)"
$Members = Get-AzureADDirectoryRoleMember -ObjectId $RoleGroup.ObjectId
$UserMembers = $Members | Where-Object{$_.ObjectType -eq 'User'}
$Output += $UserMembers
$GroupMembers = $Members | Where-Object{$_.ObjectType -eq 'Group'}
If($GroupMembers){
$UserMembers = $GroupMembers | ForEach-Object{ Get-AzureADGroupMemberRecursive -AzureGroup $_}
$Output += $UserMembers
}
}
end {
Return $Output | Sort-Object -Unique
}
}
Below is example output where it retrieves the user SecurityReader
of the role Security Reader
but also the user NestedGroupUser
from the group NestedGroup
which is member of the role. The membership structure is as follows:
- Security Reader
- SecurityReader@jonyschats.nl
- NestedGroup
- NestedGroupUser@jonyschats.nl
1
2
3
4
5
6
Get-AzureADDirectoryRole -ObjectId 598a6cfe-5d1a-42a7-81b6-76f4ab077152 | Get-AzureADDirectoryRoleMemberRecursive
ObjectId DisplayName UserPrincipalName UserType
-------- ----------- ----------------- --------
1a9a26f4-297a-4dec-95b3-e502ec8e9dfc NestedGroupUser NestedGroupUser@jonyschats.nl Member
fb8a7905-e32c-4431-9e66-2968013f924f SecurityReader SecurityReader@jonyschats.nl Member
New cmdlets for enumerating roles
Get-AzureADDirectoryRoleOverview
AzureAD has a lot of built-in roles but the AzureAD cmdlet Get-AzureADDirectoryRole
only returns the roles which are activated. Using this information we can easily request all activated roles and retrieve its users with the cmdlet I created earlier. But I woud like to get an overview of how many users each role has and who the users are. So I created the cmdlet Get-AzureADDirectoryRoleOverview
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Function Get-AzureADDirectoryRoleOverview{
<#
.SYNOPSIS
Author: Jony Schats - 0xjs
Required Dependencies: Get-AzureADDirectoryRole, Get-AzureADDirectoryRoleMember, Get-AzureADGroupMember, Get-AzureADGroupMemberRecursive
Optional Dependencies: None
.DESCRIPTION
Recursively search through all active Azure AD roles and return a overview of the amount of members a role has and the members itself.
.EXAMPLE
Get-AzureADDirectoryRoleOverview
#>
Begin{
If(-not(Get-AzureADCurrentSessionInfo)){Connect-AzureAD}
$Output = @()
}
Process {
$AzureADRoles = Get-AzureADDirectoryRole
foreach ($AzureADRole in $AzureADRoles) {
Write-Verbose -Message "Enumerating $($AzureADRole.DisplayName)"
# Retrieve members of the AdminRole
$RoleMembers = Get-AzureADDirectoryRole -ObjectId $AzureADRole.ObjectId | Get-AzureADDirectoryRoleMemberRecursive
$RoleMembersCount = $RoleMembers | Sort-Object -Unique | Measure-Object
$item = New-Object PSObject
$item | Add-Member -type NoteProperty -Name 'Role' -Value $AzureADRole.DisplayName
$item | Add-Member -type NoteProperty -Name 'UserCount' -Value $RoleMembersCount.Count
$item | Add-Member -type NoteProperty -Name 'Members' -Value $RoleMembers.UserPrincipalName
$Output += $item
}
}
end {
Return $Output | Sort-Object -Property UserCount -Descending
}
}
The cmdlet retrieves all active roles and their memberships, counts the userobjects after searching recursivly and creates new PowerShell objects with the attributes Rolename
, Usercount
and Members
.
1
2
3
4
5
6
7
Get-AzureADDirectoryRoleOverview
Role UserCount Members
---- --------- -------
Security Reader 2 {NestedGroupUser@jonyschats.nl, SecurityReader@jonyschats.nl}
Global Reader 1 SecurityReader@jonyschats.nl
Global Administrator 1 0xjs@jonyschats.nl
Get-AzureADPrivilegedRolesMembers
We got an overview of all roles used within Azure, the member count and listing the members, but what about privileged roles? There is a warning in Microsoft Security Center which recommends MFA for atleast these privileged roles. So I created the PowerShell cmdlet Get-AzureADPrivilegedRolesMembers
which searches recursivly through all these roles and return all user object:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Function Get-AzureADPrivilegedRolesMembers{
<#
.SYNOPSIS
Author: Jony Schats - 0xjs
Required Dependencies: Get-AzureADDirectoryRole, Get-AzureADDirectoryRoleMember, Get-AzureADGroupMember, Get-AzureADGroupMemberRecursive
Optional Dependencies: None
.DESCRIPTION
Recursively search through privileged roles and only return unique user objects.
.EXAMPLE
Get-AzureADPrivilegedRolesMembers
#>
Begin{
If(-not(Get-AzureADCurrentSessionInfo)){Connect-AzureAD}
$AdminRoles = "Global administrator", "Application administrator", "Authentication Administrator", "Billing administrator", "Cloud application administrator", "Conditional Access administrator", "Exchange administrator", "Helpdesk administrator", "Password administrator", "Privileged authentication administrator", "Privileged Role Administrator", "Security administrator", "SharePoint administrator", "User administrator"
$Output = @()
}
Process {
foreach ($AdminRole in $AdminRoles) {
$AdminRoleData = Get-AzureADDirectoryRole | Where-Object -Property Displayname -eq $AdminRole
Write-Verbose -Message "Enumerating $($AdminRoleData.DisplayName)"
# If the role is populated
if ($AdminRoleData -ne $null){
# Retrieve members of the AdminRole
$AdminRoleMembers = Get-AzureADDirectoryRole -ObjectId $AdminRoleData.ObjectId | Get-AzureADDirectoryRoleMemberRecursive
$Output += $AdminRoleMembers
}
}
}
end {
Return $Output | Sort-Object -Unique
}
}
I only have one Global Administrator
in my tenant so it returns just one object. But it will go through all 14 roles and recursivly search for all users.
1
2
3
4
5
Get-AzureADPrivilegedRolesMembers
ObjectId DisplayName UserPrincipalName UserType
-------- ----------- ----------------- --------
766787e8-82c1-4062-bfa9-5d4a4ca300f3 0xjs 0xjs@jonyschats.nl Member
Get-AzureADPrivilegedRolesOverview
We already created a overview of all active roles and its members, but I also wanted to have this overview for the privileged roles which we discussed earlier. So I created Get-AzureADPrivilegedRolesOverview
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
Function Get-AzureADPrivilegedRolesOverview{
<#
.SYNOPSIS
Author: Jony Schats - 0xjs
Required Dependencies: Get-AzureADDirectoryRole, Get-AzureADDirectoryRoleMember, Get-AzureADGroupMember, Get-AzureADGroupMemberRecursive
Optional Dependencies: None
.DESCRIPTION
Recursively search through privileged Azure AD roles and return a overview of the amount of members a role has and the members itself.
.EXAMPLE
Get-AzureADPrivilegedRolesOverview
#>
Begin{
If(-not(Get-AzureADCurrentSessionInfo)){Connect-AzureAD}
$AdminRoles = "Global administrator", "Application administrator", "Authentication Administrator", "Billing administrator", "Cloud application administrator", "Conditional Access administrator", "Exchange administrator", "Helpdesk administrator", "Password administrator", "Privileged authentication administrator", "Privileged Role Administrator", "Security administrator", "SharePoint administrator", "User administrator"
$Output = @()
}
Process {
foreach ($AdminRole in $AdminRoles) {
$AdminRoleData = Get-AzureADDirectoryRole | Where-Object -Property Displayname -eq $AdminRole
Write-Verbose -Message "Enumerating $($AdminRoleData.DisplayName)"
# If the role is populated
if ($AdminRoleData -ne $null){
# Retrieve members of the AdminRole
$AdminRoleMembers = Get-AzureADDirectoryRole -ObjectId $AdminRoleData.ObjectId | Get-AzureADDirectoryRoleMemberRecursive
$AdminRoleMembersCount = $AdminRoleMembers | Sort-Object -Unique | Measure-Object
$item = New-Object PSObject
$item | Add-Member -type NoteProperty -Name 'Role' -Value $AdminRoleData.DisplayName
$item | Add-Member -type NoteProperty -Name 'UserCount' -Value $AdminRoleMembersCount.Count
$item | Add-Member -type NoteProperty -Name 'Members' -Value $AdminRoleMembers.UserPrincipalName
$Output += $item
}
else {
$item = New-Object PSObject
$item | Add-Member -type NoteProperty -Name 'Role' -Value $AdminRole
$item | Add-Member -type NoteProperty -Name 'UserCount' -Value "0"
$Output += $item
}
}
}
end {
Return $Output | Sort-Object -Property UserCount -Descending
}
}
The PowerShell cmdlet also adds a object for the role even if its empty, but you can easily filter this by piping it to a where-object statement such as | Where-Object -Property UserCount -NE 0
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Get-AzureADPrivilegedRolesOverview
Role UserCount Members
---- --------- -------
Global Administrator 1 0xjs@jonyschats.nl
Privileged Role Administrator 0
Privileged authentication administrator 0
Password administrator 0
User administrator 0
SharePoint administrator 0
Security administrator 0
Helpdesk administrator 0
Billing administrator 0
Authentication Administrator 0
Application administrator 0
Exchange administrator 0
Conditional Access administrator 0
Cloud application administrator 0
Get-AzureADPrivilegedRolesOverview | Where-Object -Property UserCount -NE 0
Role UserCount Members
---- --------- -------
Global Administrator 1 0xjs@jonyschats.nl
GitHub
All the cmdlets can be found in my GitHub project AzurePowerCommand.
The next blog in the series will be about GroupOwners.