Added Privileged Roles Memebership Exporter
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
.DS_Store
|
||||
MDE_OffboardDevices.parameters.json
|
||||
MDE/MDE_OffboardDevices.parameters.json
|
||||
Entra/Entra-PrivilegedRoleMemberships.csv
|
||||
251
Entra/Export-PrivilegedRolesMembership.ps1
Normal file
251
Entra/Export-PrivilegedRolesMembership.ps1
Normal file
@@ -0,0 +1,251 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Export all active memberships in privileged Microsoft Entra roles,
|
||||
including expansion of group-assigned roles to transitive group members.
|
||||
|
||||
.NOTES
|
||||
- Uses Microsoft Graph PowerShell only (no Microsoft.Graph.Beta module import)
|
||||
- Uses Invoke-MgGraphRequest against the beta endpoint only for role definition discovery
|
||||
- Exports CSV to .\Entra-PrivilegedRoleMemberships.csv
|
||||
|
||||
.REQUIREMENTS
|
||||
Install-Module Microsoft.Graph -Scope CurrentUser
|
||||
|
||||
.PERMISSIONS
|
||||
Suggested delegated scopes:
|
||||
RoleManagement.Read.Directory
|
||||
Directory.Read.All
|
||||
GroupMember.Read.All
|
||||
#>
|
||||
|
||||
param(
|
||||
[string]$CsvPath = ".\Entra-PrivilegedRoleMemberships.csv"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Get-ObjectType {
|
||||
param([object]$Object)
|
||||
|
||||
if ($null -eq $Object.AdditionalProperties.'@odata.type') {
|
||||
return $Object.GetType().Name
|
||||
}
|
||||
|
||||
return ($Object.AdditionalProperties.'@odata.type' -replace '^#microsoft.graph\.', '')
|
||||
}
|
||||
|
||||
function Get-DisplayNameSafe {
|
||||
param([object]$Object)
|
||||
|
||||
if ($Object.PSObject.Properties.Name -contains 'DisplayName' -and $Object.DisplayName) {
|
||||
return $Object.DisplayName
|
||||
}
|
||||
|
||||
if ($Object.AdditionalProperties -and $Object.AdditionalProperties.ContainsKey('displayName')) {
|
||||
return $Object.AdditionalProperties['displayName']
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-UpnSafe {
|
||||
param([object]$Object)
|
||||
|
||||
if ($Object.PSObject.Properties.Name -contains 'UserPrincipalName' -and $Object.UserPrincipalName) {
|
||||
return $Object.UserPrincipalName
|
||||
}
|
||||
|
||||
if ($Object.AdditionalProperties -and $Object.AdditionalProperties.ContainsKey('userPrincipalName')) {
|
||||
return $Object.AdditionalProperties['userPrincipalName']
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-MailSafe {
|
||||
param([object]$Object)
|
||||
|
||||
if ($Object.PSObject.Properties.Name -contains 'Mail' -and $Object.Mail) {
|
||||
return $Object.Mail
|
||||
}
|
||||
|
||||
if ($Object.AdditionalProperties -and $Object.AdditionalProperties.ContainsKey('mail')) {
|
||||
return $Object.AdditionalProperties['mail']
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Invoke-GraphGetAllPages {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$Uri
|
||||
)
|
||||
|
||||
$items = @()
|
||||
$nextLink = $Uri
|
||||
|
||||
while ($nextLink) {
|
||||
$response = Invoke-MgGraphRequest -Method GET -Uri $nextLink -OutputType PSObject
|
||||
|
||||
if ($response.value) {
|
||||
$items += $response.value
|
||||
}
|
||||
else {
|
||||
$items += $response
|
||||
break
|
||||
}
|
||||
|
||||
$nextLink = $response.'@odata.nextLink'
|
||||
}
|
||||
|
||||
return $items
|
||||
}
|
||||
|
||||
Write-Host "Connecting to Microsoft Graph..." -ForegroundColor Cyan
|
||||
|
||||
Import-Module Microsoft.Graph.Authentication
|
||||
Import-Module Microsoft.Graph.Identity.Governance
|
||||
Import-Module Microsoft.Graph.DirectoryObjects
|
||||
Import-Module Microsoft.Graph.Groups
|
||||
|
||||
Connect-MgGraph -Scopes @(
|
||||
"RoleManagement.Read.Directory",
|
||||
"Directory.Read.All",
|
||||
"GroupMember.Read.All"
|
||||
) -NoWelcome
|
||||
|
||||
Write-Host "Getting privileged role definitions from Graph beta endpoint..." -ForegroundColor Cyan
|
||||
|
||||
$roleDefsUri = "https://graph.microsoft.com/beta/roleManagement/directory/roleDefinitions?`$filter=isPrivileged eq true"
|
||||
$privilegedRoles = Invoke-GraphGetAllPages -Uri $roleDefsUri
|
||||
|
||||
if (-not $privilegedRoles) {
|
||||
Write-Warning "No privileged roles were returned."
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "Getting active role assignments..." -ForegroundColor Cyan
|
||||
$assignments = Get-MgRoleManagementDirectoryRoleAssignment -All
|
||||
|
||||
$privilegedRoleIds = @($privilegedRoles | ForEach-Object { $_.id })
|
||||
$privilegedAssignments = $assignments | Where-Object { $_.RoleDefinitionId -in $privilegedRoleIds }
|
||||
|
||||
if (-not $privilegedAssignments) {
|
||||
Write-Warning "No active assignments found for privileged roles."
|
||||
return
|
||||
}
|
||||
|
||||
$roleMap = @{}
|
||||
foreach ($role in $privilegedRoles) {
|
||||
$roleMap[$role.id] = $role.displayName
|
||||
}
|
||||
|
||||
$principalCache = @{}
|
||||
$groupMemberCache = @{}
|
||||
$results = New-Object System.Collections.Generic.List[object]
|
||||
|
||||
foreach ($assignment in $privilegedAssignments) {
|
||||
$roleName = $roleMap[$assignment.RoleDefinitionId]
|
||||
$principalId = $assignment.PrincipalId
|
||||
$directoryScopeId = $assignment.DirectoryScopeId
|
||||
|
||||
if (-not $principalCache.ContainsKey($principalId)) {
|
||||
try {
|
||||
$principalCache[$principalId] = Get-MgDirectoryObjectById -Ids $principalId
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Failed to resolve principal $principalId"
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
$principal = $principalCache[$principalId]
|
||||
$principalType = Get-ObjectType -Object $principal
|
||||
$principalName = Get-DisplayNameSafe -Object $principal
|
||||
|
||||
switch ($principalType.ToLower()) {
|
||||
"group" {
|
||||
if (-not $groupMemberCache.ContainsKey($principalId)) {
|
||||
try {
|
||||
$groupMemberCache[$principalId] = Get-MgGroupTransitiveMember -GroupId $principalId -All
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Failed to expand group members for group $principalName ($principalId)"
|
||||
$groupMemberCache[$principalId] = @()
|
||||
}
|
||||
}
|
||||
|
||||
$members = $groupMemberCache[$principalId]
|
||||
|
||||
if (-not $members -or $members.Count -eq 0) {
|
||||
$results.Add([pscustomobject]@{
|
||||
RoleName = $roleName
|
||||
AssignmentType = "GroupAssignment"
|
||||
AssignmentPrincipalType = $principalType
|
||||
AssignmentPrincipal = $principalName
|
||||
AssignmentPrincipalId = $principalId
|
||||
ExpandedMemberType = $null
|
||||
ExpandedMemberName = $null
|
||||
ExpandedMemberUPN = $null
|
||||
ExpandedMemberMail = $null
|
||||
ExpandedMemberId = $null
|
||||
DirectoryScopeId = $directoryScopeId
|
||||
Notes = "Group assigned, but no transitive members returned"
|
||||
})
|
||||
}
|
||||
else {
|
||||
foreach ($member in $members) {
|
||||
$memberType = Get-ObjectType -Object $member
|
||||
$memberName = Get-DisplayNameSafe -Object $member
|
||||
$memberUpn = Get-UpnSafe -Object $member
|
||||
$memberMail = Get-MailSafe -Object $member
|
||||
|
||||
$results.Add([pscustomobject]@{
|
||||
RoleName = $roleName
|
||||
AssignmentType = "GroupAssignmentExpanded"
|
||||
AssignmentPrincipalType = $principalType
|
||||
AssignmentPrincipal = $principalName
|
||||
AssignmentPrincipalId = $principalId
|
||||
ExpandedMemberType = $memberType
|
||||
ExpandedMemberName = $memberName
|
||||
ExpandedMemberUPN = $memberUpn
|
||||
ExpandedMemberMail = $memberMail
|
||||
ExpandedMemberId = $member.Id
|
||||
DirectoryScopeId = $directoryScopeId
|
||||
Notes = "Expanded from group assignment"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default {
|
||||
$results.Add([pscustomobject]@{
|
||||
RoleName = $roleName
|
||||
AssignmentType = "DirectAssignment"
|
||||
AssignmentPrincipalType = $principalType
|
||||
AssignmentPrincipal = $principalName
|
||||
AssignmentPrincipalId = $principalId
|
||||
ExpandedMemberType = $principalType
|
||||
ExpandedMemberName = $principalName
|
||||
ExpandedMemberUPN = Get-UpnSafe -Object $principal
|
||||
ExpandedMemberMail = Get-MailSafe -Object $principal
|
||||
ExpandedMemberId = $principalId
|
||||
DirectoryScopeId = $directoryScopeId
|
||||
Notes = "Direct role assignment"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$results |
|
||||
Sort-Object RoleName, AssignmentType, ExpandedMemberName |
|
||||
Export-Csv -Path $CsvPath -NoTypeInformation -Encoding UTF8
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Exported to: $CsvPath" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
$results |
|
||||
Sort-Object RoleName, ExpandedMemberName |
|
||||
Format-Table RoleName, AssignmentType, AssignmentPrincipal, ExpandedMemberName, ExpandedMemberUPN, ExpandedMemberType -AutoSize
|
||||
Reference in New Issue
Block a user