From 19d56e4d503b6a94103ebf0db5eb3225ef192113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A5re=20Overg=C3=A5rd?= Date: Sat, 28 Mar 2026 08:06:16 +0100 Subject: [PATCH 01/14] Scope check removed --- .../functions/Test-GraphConnection.ps1 | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 EntraReporter/internal/functions/Test-GraphConnection.ps1 diff --git a/EntraReporter/internal/functions/Test-GraphConnection.ps1 b/EntraReporter/internal/functions/Test-GraphConnection.ps1 new file mode 100644 index 0000000..6bb9c3b --- /dev/null +++ b/EntraReporter/internal/functions/Test-GraphConnection.ps1 @@ -0,0 +1,28 @@ +<# + .SYNOPSIS + Test Microsoft Graph connection + .DESCRIPTION + Verifies that the Microsoft.Graph.Authentication module is installed and that the session is connected to Microsoft Graph. + .EXAMPLE + Test-GraphConnection +#> +function Test-GraphConnection { + [CmdletBinding()] + param () + + # Check if Graph module is installed + if (-not (Get-Module -ListAvailable -Name Microsoft.Graph.Authentication -Verbose:$false)) { + Write-Verbose 'Microsoft.Graph.Authentication module is not installed.' + return $false + } + + # Check if connected + $mgContext = Get-MgContext -ErrorAction SilentlyContinue + if (-not $mgContext) { + Write-Verbose 'Not connected to Microsoft Graph.' + return $false + } + + Write-Verbose 'Connected to Microsoft Graph.' + return $true +} \ No newline at end of file From a91c53aa752c5ba5bcd03611cacdde1ba2866c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A5re=20Overg=C3=A5rd?= Date: Sat, 28 Mar 2026 08:06:25 +0100 Subject: [PATCH 02/14] WIP --- .../functions/Get-EntraIdRoleAssignment.ps1 | 474 ++++++++++++++++++ 1 file changed, 474 insertions(+) create mode 100644 EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 diff --git a/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 b/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 new file mode 100644 index 0000000..393ca95 --- /dev/null +++ b/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 @@ -0,0 +1,474 @@ +<# + +TODO: Comment based help + +#> +function Get-EntraIdRoleAssignment { + [CmdletBinding()] + param( + [Parameter()] + [string[]] + $RoleName + ) + + # TODO: Support tenants with without PIM / Privileged Access enabled (currently the command relies on fetching role assignments via the privileged access API, which only returns results for tenants with PIM / Privileged Access enabled) + + #region Configuiration + + # TODO: Move to module initialization when prototyping is done + + $ErrorActionPreference = 'Stop' # Always stop on errors + + #endregion + + #region Private functions + # TODO: Move these to separate files when prototyping is done + + + function Split-ArrayIntoChunks { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [object[]]$InputObject, + + [ValidateRange(1, [int]::MaxValue)] + [int]$ChunkSize = 20 + ) + + $items = @($InputObject) + # Always return a collection object + $result = New-Object System.Collections.Generic.List[object[]] + + if ($items.Count -gt 0) { + for ($i = 0; $i -lt $items.Count; $i += $ChunkSize) { + $end = [Math]::Min($i + $ChunkSize - 1, $items.Count - 1) + [void]$result.Add(@($items[$i..$end])) # ensure each chunk is an object[] + } + } + + # Emit the *collection* as a single object so callers get one wrapper + , $result + } + + function Invoke-GraphBatch { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [hashtable[]]$Requests, + + [Parameter()] + [string]$GraphVersion = 'v1.0', + + [Parameter()] + [switch]$ThrowOnAnyError + ) + + $uri = "$GraphVersion/`$batch" + $body = @{ requests = $Requests } + + $resp = Invoke-MgGraphRequest -Method POST -Uri $uri -Body $body -Verbose:$false + + if ($ThrowOnAnyError) { + $errors = $resp.responses | Where-Object { $_.status -ge 400 } + if ($errors) { + $codes = ($errors | ForEach-Object { "$($_.id):$($_.status)" }) -join ', ' + throw "One or more batch requests failed: $codes" + } + } + return $resp + } + + # Robust pagination helper + function Invoke-GraphPaged { + param( + [Parameter(Mandatory)][string] $Uri + ) + $items = @() + $next = $Uri + while ($next) { + $resp = Invoke-MgGraphRequest -Method GET -Uri $next -ErrorAction Stop + if ($resp.value) { $items += $resp.value } + $next = $resp.'@odata.nextLink' + } + return , $items + } + + #endregion + + #region Public functions + # TODO: Move these to separate files when prototyping is done + + function Get-EntraIdGroupScheduleBatch { + param( + [Parameter(Mandatory = $true)] + [string[]] + $GroupId, + + [Parameter(Mandatory = $true)] + [ValidateSet('Assigned', 'Eligible')] + [string] + $State + ) + + switch ($State) { + 'Assigned' { + $urlTemplate = "/identityGovernance/privilegedAccess/group/assignmentSchedules?`$filter=groupId eq '{Id}'&`$expand=principal" + } + 'Eligible' { + $urlTemplate = "/identityGovernance/privilegedAccess/group/eligibilitySchedules?`$filter=groupId eq '{Id}'&`$expand=principal" + } + } + + # Split the GroupId array into chunks of 20 + $chunks = Split-ArrayIntoChunks -InputObject $GroupId -ChunkSize 20 + + $allResponses = foreach ($chunk in $chunks) { + try { + $requests = foreach ($id in $chunk) { + @{ + id = $id + method = 'GET' + url = $urlTemplate.Replace('{Id}', $id) + } + } + Invoke-GraphBatch -Requests $requests -ThrowOnAnyError + } + catch { + Write-Warning ("Failed to process chunk of group IDs '{0}': {1}" -f ($chunk -join ', '), $_.Exception.Message) + } + } + + $allResponses.responses | ForEach-Object { $_.body.value } + } + + function Get-EntraIdLevel { + [CmdletBinding()] + param( + # If set, include diagnostic details (enabled/assigned counts and SKU part numbers). + [switch] $IncludeDetails + ) + + # Ensure we're using v1.0 for stability (this affects URL only, not the module profile). + $skus = Invoke-GraphPaged -Uri 'https://graph.microsoft.com/v1.0/subscribedSkus' -Verbose:$false + + # Defensive: if tenant has no SKUs (rare), treat as Free + if (-not $skus -or $skus.Count -eq 0) { + $result = [ordered]@{ + Level = 'Free' + IsP1Available = $false + IsP2Available = $false + } + if ($IncludeDetails) { + $result.P1EnabledCount = 0 + $result.P2EnabledCount = 0 + $result.P1AssignedCount = 0 + $result.P2AssignedCount = 0 + $result.P1SkuPartNumbers = @() + $result.P2SkuPartNumbers = @() + } + return [PSCustomObject]$result + } + + # Filter SKUs by service plans (provisioned and active) + $p1Skus = $skus | Where-Object { + $_.servicePlans | Where-Object { + $_.servicePlanName -eq 'AAD_PREMIUM' -and $_.provisioningStatus -eq 'Success' + } + } + $p2Skus = $skus | Where-Object { + $_.servicePlans | Where-Object { + $_.servicePlanName -eq 'AAD_PREMIUM_P2' -and $_.provisioningStatus -eq 'Success' + } + } + + # Enabled capacity (what you own) + $p1Enabled = ($p1Skus | ForEach-Object { $_.prepaidUnits.enabled } | Measure-Object -Sum).Sum + $p2Enabled = ($p2Skus | ForEach-Object { $_.prepaidUnits.enabled } | Measure-Object -Sum).Sum + + # Assigned units (how many are currently consumed) + $p1Assigned = ($p1Skus | ForEach-Object { $_.consumedUnits } | Measure-Object -Sum).Sum + $p2Assigned = ($p2Skus | ForEach-Object { $_.consumedUnits } | Measure-Object -Sum).Sum + + # Determine the “portal-equivalent” level + $level = if ($p2Enabled -gt 0) { 'P2' } + elseif ($p1Enabled -gt 0) { 'P1' } + else { 'Free' } + + $result = [ordered]@{ + Level = $level + IsP1Available = ($p1Enabled -gt 0) + IsP2Available = ($p2Enabled -gt 0) + } + + if ($IncludeDetails) { + $result.P1EnabledCount = [int]($p1Enabled | ForEach-Object { $_ } ) + $result.P2EnabledCount = [int]($p2Enabled | ForEach-Object { $_ } ) + $result.P1AssignedCount = [int]($p1Assigned | ForEach-Object { $_ } ) + $result.P2AssignedCount = [int]($p2Assigned | ForEach-Object { $_ } ) + $result.P1SkuPartNumbers = $p1Skus.skuPartNumber | Sort-Object -Unique + $result.P2SkuPartNumbers = $p2Skus.skuPartNumber | Sort-Object -Unique + } + + [PSCustomObject]$result + } + + + #endregion + + + #region Internal functions + + function Resolve-AssignmentWindow { + param( + [Nullable[datetime]] $PrincipalStart, + [Nullable[datetime]] $PrincipalEnd, + [Nullable[datetime]] $UserStart, + [Nullable[datetime]] $UserEnd + ) + + if ($null -eq $UserEnd -and $null -eq $PrincipalEnd) { + return [pscustomobject]@{ + Start = $null + End = $null + IsPermanent = $true + } + } + + if ($null -eq $PrincipalEnd) { + return [pscustomobject]@{ + Start = $UserStart + End = $UserEnd + IsPermanent = $false + } + } + + if ($null -eq $UserEnd) { + return [pscustomobject]@{ + Start = $PrincipalStart + End = $PrincipalEnd + IsPermanent = $false + } + } + + # Intersection logic + return [pscustomobject]@{ + Start = if ($PrincipalStart -gt $UserStart) { $PrincipalStart } else { $UserStart } + End = if ($PrincipalEnd -lt $UserEnd) { $PrincipalEnd } else { $UserEnd } + IsPermanent = $false + } + } + + function New-RoleAssignmentEntry { + [CmdletBinding()] + param( + [Parameter(Mandatory)] [string]$RoleId, + [Parameter(Mandatory)] [string]$RoleName, + [Parameter(Mandatory)] [string]$PrincipalId, + [Parameter(Mandatory)] [string]$PrincipalDisplayName, + [Parameter(Mandatory)] [string]$AssignmentType, + [Parameter(Mandatory)] [string]$Scope, + [Parameter(Mandatory)] [string]$PrincipalState, + [Nullable[datetime]] $PrincipalStartTime, + [Nullable[datetime]] $PrincipalEndTime, + [Parameter(Mandatory)] [string]$UserId, + [Parameter(Mandatory)] [string]$UserDisplayName, + [Parameter(Mandatory)] [string]$UserPrincipalName, + [Parameter(Mandatory)] [string]$UserState, + [Nullable[datetime]] $UserStartTime, + [Nullable[datetime]] $UserEndTime + ) + + # Resolve effective assignment window by calculating the intersection of the principal and user assignment windows. If either of the windows is permanent (i.e. has no end time), the effective window will be determined by the other window. If both windows are permanent, the effective window will also be permanent. + $window = Resolve-AssignmentWindow -PrincipalStart $PrincipalStartTime -PrincipalEnd $PrincipalEndTime -UserStart $UserStartTime -UserEnd $UserEndTime + + # Determine effective state. If both principal and user are assigned, the effective state is assigned. In all other cases (e.g. one of them is eligible or both are eligible), the effective state is eligible. + $effectiveState = if ($PrincipalState -eq 'Assigned' -and $UserState -eq 'Assigned') { + 'Assigned' + } + else { + 'Eligible' + } + + # Emit a single entry for the combination of principal and user with the resolved effective assignment window and state. + [PSCustomObject]@{ + RoleId = $RoleId + RoleName = $RoleName + PrincipalId = $PrincipalId + PrincipalDisplayName = $PrincipalDisplayName + AssignmentType = $AssignmentType + Scope = $Scope + PrincipalState = $PrincipalState + PrincipalStartTime = $PrincipalStartTime + PrincipalEndTime = $PrincipalEndTime + UserId = $UserId + UserDisplayName = $UserDisplayName + UserPrincipalName = $UserPrincipalName + UserState = $UserState + UserStartTime = $UserStartTime + UserEndTime = $UserEndTime + EffectiveState = $effectiveState + EffectiveStartTime = $window.Start + EffectiveEndTime = $window.End + IsPermanent = $window.IsPermanent + } + } + + function Resolve-RoleAssignedGroup { + param( + [Parameter(Mandatory = $true)] + [psobject] + $Schedule, + + [Parameter(Mandatory = $true)] + [ValidateSet('Assigned', 'Eligible')] + [string] + $GroupState, + + [Parameter(Mandatory = $true)] + [ValidateSet('Assigned', 'Eligible')] + [string] + $UserState + ) + + if ($UserState -eq 'Assigned') { + $scheduleEntries = $script:groupAssignmentSchedules | Where-Object { $_.groupId -eq $principal.id } + } + else { + $scheduleEntries = $script:groupEligibilitySchedules | Where-Object { $_.groupId -eq $principal.id } + } + + foreach ($scheduleEntry in $scheduleEntries ) { + $roleEntrySplat = @{ + RoleId = $Schedule.roleDefinitionId + RoleName = $Schedule.roleDefinition.displayName + PrincipalId = $Schedule.principal.id + PrincipalDisplayName = $Schedule.principal.displayName + AssignmentType = 'Group' + Scope = $Schedule.directoryScopeId + PrincipalState = $GroupState + PrincipalStartTime = if ($Schedule.scheduleInfo.expiration.endDateTime) { $Schedule.scheduleInfo.startDateTime } else { $null } + PrincipalEndTime = if ($Schedule.scheduleInfo.expiration.endDateTime) { $Schedule.scheduleInfo.expiration.endDateTime } else { $null } + UserId = $scheduleEntry.principalId + UserDisplayName = $scheduleEntry.principal.displayName + UserPrincipalName = $scheduleEntry.principal.userPrincipalName + UserState = $UserState + UserStartTime = if ($scheduleEntry.scheduleInfo.expiration.endDateTime) { $scheduleEntry.scheduleInfo.startDateTime } else { $null } + UserEndTime = if ($scheduleEntry.scheduleInfo.expiration.endDateTime) { $scheduleEntry.scheduleInfo.expiration.endDateTime } else { $null } + } + New-RoleAssignmentEntry @roleEntrySplat + } + } + + function Resolve-EntraIDRoleSchedule { + param( + [Parameter(Mandatory = $true)] + [psobject] + $Schedule, + + [Parameter(Mandatory = $true)] + [ValidateSet('Assigned', 'Eligible')] + [string] + $State + ) + + # Resolve principal to allow for individual handling of user vs group principals (e.g. resolving group members for group principals since Graph API does not currently provide a way to see effective role assignments for group members) + $principal = $Schedule.principal + + if ($principal.'@odata.type' -eq '#microsoft.graph.user') { + $roleEntrySplat = @{ + RoleId = $Schedule.roleDefinitionId + RoleName = $Schedule.roleDefinition.displayName + PrincipalId = $Schedule.principal.id + PrincipalDisplayName = $Schedule.principal.displayName + AssignmentType = 'User' + Scope = $Schedule.directoryScopeId + PrincipalState = $State + PrincipalStartTime = if ($Schedule.scheduleInfo.expiration.endDateTime) { $Schedule.scheduleInfo.startDateTime } else { $null } + PrincipalEndTime = if ($Schedule.scheduleInfo.expiration.endDateTime) { $Schedule.scheduleInfo.expiration.endDateTime } else { $null } + UserId = $Schedule.principalId + UserDisplayName = $Schedule.principal.displayName + UserPrincipalName = $Schedule.principal.userPrincipalName + UserState = $State + UserStartTime = if ($Schedule.scheduleInfo.expiration.endDateTime) { $Schedule.scheduleInfo.startDateTime } else { $null } + UserEndTime = if ($Schedule.scheduleInfo.expiration.endDateTime) { $Schedule.scheduleInfo.expiration.endDateTime } else { $null } + } + New-RoleAssignmentEntry @roleEntrySplat + } + elseif ($principal.'@odata.type' -eq '#microsoft.graph.group') { + Resolve-RoleAssignedGroup -Schedule $Schedule -GroupState $State -UserState 'Assigned' + Resolve-RoleAssignedGroup -Schedule $Schedule -GroupState $State -UserState 'Eligible' + } + else { + Write-Warning "Principal with ID '$($principal.id)' is of type $($principal.'@odata.type'), which is currently not supported by the command. Skipping entry." + continue + } + } + + #endregion Helper Functions + + #region MAIN + + # + ## Connect-mgGraph -Scopes 'RoleManagement.Read.Directory','RoleEligibilitySchedule.Read.Directory','PrivilegedEligibilitySchedule.Read.AzureADGroup', 'PrivilegedAssignmentSchedule.Read.AzureADGroup', 'GroupMember.Read.All' + # Current: + ## Connect-mgGraph -Scopes 'RoleEligibilitySchedule.Read.Directory','PrivilegedEligibilitySchedule.Read.AzureADGroup', 'PrivilegedAssignmentSchedule.Read.AzureADGroup' + # + + if (!(Test-GraphConnection)) { + throw 'No active connection to Microsoft Graph. Run Connect-MgGraph to sign in and then retry.' + } + + Get-EntraIdLevel -IncludeDetails | ConvertTo-Json -Depth 5 | Write-Verbose + + + $timer = [Diagnostics.Stopwatch]::StartNew() + $activityName = 'Fetching Entra ID role assignments' + + # Fetch all role schedules, assigned and eligible. + Write-Progress -Activity $activityName -Status 'Fetching assigned role schedules' -PercentComplete 20 + $roleAssignmentSchedules = Invoke-MgGraphRequest -Method GET -Uri "v1.0/roleManagement/directory/roleAssignmentSchedules?`$filter=assignmentType eq 'Assigned'&`$expand=principal,roleDefinition" -Verbose:$false | Select-Object -ExpandProperty value + Write-Progress -Activity $activityName -Status 'Fetching eligible role schedules' -PercentComplete 40 + $roleEligibilitySchedules = Invoke-MgGraphRequest -Method GET -Uri "v1.0/roleManagement/directory/roleEligibilitySchedules?`$expand=principal,roleDefinition" -Verbose:$false | Select-Object -ExpandProperty value + + + # TODO: Test for nested groups??? + + # TODO: Test with scopes other than the directory (e.g. administrative units) + + # TODO: Test with Azure RBAC roles (if possible - not sure if these are returned by the API when listing directory role assignments?) + + # If Rolename has been specified, filter out any assigments not in the specified role(s). + if ($RoleName) { + $roleAssignmentSchedules = $roleAssignmentSchedules | Where-Object { $_.roleDefinition.displayName -in $RoleName } + $roleEligibilitySchedules = $roleEligibilitySchedules | Where-Object { $_.roleDefinition.displayName -in $RoleName } + } + + # Extract unique group IDs from all role schedules for prefetching group schedule information in bulk to reduce number of API calls later on. + $groupIds = @() + $groupIds += $roleAssignmentSchedules | Select-Object -ExpandProperty principal | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.group' } | Select-Object -ExpandProperty Id + $groupIds += $roleEligibilitySchedules | Select-Object -ExpandProperty principal | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.group' } | Select-Object -ExpandProperty Id + $groupIds = $groupIds | Select-Object -Unique + + # Prefetch group schedules for all groups used groups + Write-Progress -Activity $activityName -Status 'Fetching assigned group schedules' -PercentComplete 60 + $script:groupAssignmentSchedules = Get-EntraIdGroupScheduleBatch -GroupId $groupIds -State 'Assigned' + Write-Progress -Activity $activityName -Status 'Fetching eligible group schedules' -PercentComplete 80 + $script:groupEligibilitySchedules = Get-EntraIdGroupScheduleBatch -GroupId $groupIds -State 'Eligible' + + Write-Progress -Activity $activityName -Status 'Consolidating results' -PercentComplete 95 + $resolvedGroupSchedules = @() + $resolvedGroupSchedules += foreach ($schedule in $roleAssignmentSchedules) { + Resolve-EntraIDRoleSchedule -Schedule $schedule -State 'Assigned' + } + $resolvedGroupSchedules += foreach ($schedule in $roleEligibilitySchedules) { + Resolve-EntraIDRoleSchedule -Schedule $schedule -State 'Eligible' + } + + Write-Progress -Activity $activityName -Completed + + $resolvedGroupSchedules | Sort-Object -Property RoleName, UserDisplayName + + Write-Verbose "Total execution time: $($timer.Elapsed.TotalSeconds) seconds" +} +#endregion \ No newline at end of file From d4d97e6a0d659abad9961b6105962748d47dd3cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A5re=20Overg=C3=A5rd?= Date: Sun, 29 Mar 2026 11:39:59 +0200 Subject: [PATCH 03/14] Default table format for roles --- ...EntraReporter.RoleAssignment.Format.ps1xml | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 EntraReporter/internal/formats/EntraReporter.RoleAssignment.Format.ps1xml diff --git a/EntraReporter/internal/formats/EntraReporter.RoleAssignment.Format.ps1xml b/EntraReporter/internal/formats/EntraReporter.RoleAssignment.Format.ps1xml new file mode 100644 index 0000000..dc74d37 --- /dev/null +++ b/EntraReporter/internal/formats/EntraReporter.RoleAssignment.Format.ps1xml @@ -0,0 +1,66 @@ + + + + + Role Assignment Table Default + + EntraReporter.RoleAssignment + + + + + 25 + + + 25 + + + 20 + + + 14 + + + 9 + + + + + 10 + + + + 10 + + + + + + + RoleName + + + UserPrincipalName + + + UserDisplayName + + + Scope + + + EffectiveState + + + $($_.EffectiveStartTime.ToShortDateString()) + + + $($_.EffectiveEndTime.ToShortDateString()) + + + + + + + + \ No newline at end of file From b9c4b8c46023b06e0bfed77776bffc3c483f7284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A5re=20Overg=C3=A5rd?= Date: Sun, 29 Mar 2026 11:40:14 +0200 Subject: [PATCH 04/14] Add formats --- EntraReporter/EntraReporter.psd1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EntraReporter/EntraReporter.psd1 b/EntraReporter/EntraReporter.psd1 index 43c90d3..28a27af 100644 --- a/EntraReporter/EntraReporter.psd1 +++ b/EntraReporter/EntraReporter.psd1 @@ -55,7 +55,9 @@ # TypesToProcess = @() # Format files (.ps1xml) to be loaded when importing this module - # FormatsToProcess = @() + FormatsToProcess = @( + 'internal\formats\EntraReporter.RoleAssignment.Format.ps1xml' + ) # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess # NestedModules = @() From 20bf6e159657d2aa7527a4c0798e8d74546884ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A5re=20Overg=C3=A5rd?= Date: Sun, 29 Mar 2026 11:40:27 +0200 Subject: [PATCH 05/14] WIP --- EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 b/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 index 393ca95..5473523 100644 --- a/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 +++ b/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 @@ -292,6 +292,7 @@ function Get-EntraIdRoleAssignment { # Emit a single entry for the combination of principal and user with the resolved effective assignment window and state. [PSCustomObject]@{ + PSTypeName = 'EntraReporter.RoleAssignment' RoleId = $RoleId RoleName = $RoleName PrincipalId = $PrincipalId @@ -376,6 +377,10 @@ function Get-EntraIdRoleAssignment { $principal = $Schedule.principal if ($principal.'@odata.type' -eq '#microsoft.graph.user') { + # if ($Schedule.directoryScopeId -ne '/') { + # $Schedule | ConvertTo-Json -Depth 4 | Write-Verbose + # throw 'STOP' + #} $roleEntrySplat = @{ RoleId = $Schedule.roleDefinitionId RoleName = $Schedule.roleDefinition.displayName @@ -425,6 +430,10 @@ function Get-EntraIdRoleAssignment { $timer = [Diagnostics.Stopwatch]::StartNew() $activityName = 'Fetching Entra ID role assignments' + Invoke-MgGraphRequest -Method GET -Uri 'v1.0/directory/administrativeUnits' -Verbose:$false | Select-Object -ExpandProperty value + + throw 'STOP' + # Fetch all role schedules, assigned and eligible. Write-Progress -Activity $activityName -Status 'Fetching assigned role schedules' -PercentComplete 20 $roleAssignmentSchedules = Invoke-MgGraphRequest -Method GET -Uri "v1.0/roleManagement/directory/roleAssignmentSchedules?`$filter=assignmentType eq 'Assigned'&`$expand=principal,roleDefinition" -Verbose:$false | Select-Object -ExpandProperty value From fa466a4403cf1988309a104f9f4bef48298e8c56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A5re=20Overg=C3=A5rd?= Date: Mon, 30 Mar 2026 06:39:55 +0200 Subject: [PATCH 06/14] Cleanup --- EntraReporter/functions/readme.md | 3 --- EntraReporter/internal/functions/readme.md | 3 --- 2 files changed, 6 deletions(-) delete mode 100644 EntraReporter/functions/readme.md delete mode 100644 EntraReporter/internal/functions/readme.md diff --git a/EntraReporter/functions/readme.md b/EntraReporter/functions/readme.md deleted file mode 100644 index 105038b..0000000 --- a/EntraReporter/functions/readme.md +++ /dev/null @@ -1,3 +0,0 @@ -# Functions - -Folder for all the functions the user is supposed to have access to. diff --git a/EntraReporter/internal/functions/readme.md b/EntraReporter/internal/functions/readme.md deleted file mode 100644 index 0643487..0000000 --- a/EntraReporter/internal/functions/readme.md +++ /dev/null @@ -1,3 +0,0 @@ -# Internal > Functions - -Folder for all the functions you want the user to not see. From 11568dbf61a1cf615d4bcb9dd1e29648c3e3532c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A5re=20Overg=C3=A5rd?= Date: Mon, 30 Mar 2026 06:40:34 +0200 Subject: [PATCH 07/14] Functions split into files --- .../Get-EntraIdGroupScheduleBatch.ps1 | 43 ++++ EntraReporter/functions/Get-EntraIdLevel.ps1 | 71 ++++++ .../functions/Get-EntraIdRoleAssignment.ps1 | 212 +----------------- .../internal/functions/Invoke-GraphBatch.ps1 | 28 +++ .../internal/functions/Invoke-GraphPaged.ps1 | 15 ++ .../functions/Split-ArrayIntoChunks.ps1 | 25 +++ 6 files changed, 186 insertions(+), 208 deletions(-) create mode 100644 EntraReporter/functions/Get-EntraIdGroupScheduleBatch.ps1 create mode 100644 EntraReporter/functions/Get-EntraIdLevel.ps1 create mode 100644 EntraReporter/internal/functions/Invoke-GraphBatch.ps1 create mode 100644 EntraReporter/internal/functions/Invoke-GraphPaged.ps1 create mode 100644 EntraReporter/internal/functions/Split-ArrayIntoChunks.ps1 diff --git a/EntraReporter/functions/Get-EntraIdGroupScheduleBatch.ps1 b/EntraReporter/functions/Get-EntraIdGroupScheduleBatch.ps1 new file mode 100644 index 0000000..97f3eb2 --- /dev/null +++ b/EntraReporter/functions/Get-EntraIdGroupScheduleBatch.ps1 @@ -0,0 +1,43 @@ +function Get-EntraIdGroupScheduleBatch { + param( + [Parameter(Mandatory = $true)] + [string[]] + $GroupId, + + [Parameter(Mandatory = $true)] + [ValidateSet('Assigned', 'Eligible')] + [string] + $State + ) + + switch ($State) { + 'Assigned' { + $urlTemplate = "/identityGovernance/privilegedAccess/group/assignmentSchedules?`$filter=groupId eq '{Id}'&`$expand=principal" + } + 'Eligible' { + $urlTemplate = "/identityGovernance/privilegedAccess/group/eligibilitySchedules?`$filter=groupId eq '{Id}'&`$expand=principal" + } + } + + # Split the GroupId array into chunks of 20 + $chunks = Split-ArrayIntoChunks -InputObject $GroupId -ChunkSize 20 + + $allResponses = foreach ($chunk in $chunks) { + try { + $requests = foreach ($id in $chunk) { + @{ + id = $id + method = 'GET' + url = $urlTemplate.Replace('{Id}', $id) + } + } + Invoke-GraphBatch -Requests $requests -ThrowOnAnyError + } + catch { + Write-Warning ("Failed to process chunk of group IDs '{0}': {1}" -f ($chunk -join ', '), $_.Exception.Message) + } + } + + $allResponses.responses | ForEach-Object { $_.body.value } +} + diff --git a/EntraReporter/functions/Get-EntraIdLevel.ps1 b/EntraReporter/functions/Get-EntraIdLevel.ps1 new file mode 100644 index 0000000..1b3e10a --- /dev/null +++ b/EntraReporter/functions/Get-EntraIdLevel.ps1 @@ -0,0 +1,71 @@ +function Get-EntraIdLevel { + [CmdletBinding()] + param( + # If set, include diagnostic details (enabled/assigned counts and SKU part numbers). + [switch] $IncludeDetails + ) + + # Ensure we're using v1.0 for stability (this affects URL only, not the module profile). + $skus = Invoke-GraphPaged -Uri 'https://graph.microsoft.com/v1.0/subscribedSkus' -Verbose:$false + + # Defensive: if tenant has no SKUs (rare), treat as Free + if (-not $skus -or $skus.Count -eq 0) { + $result = [ordered]@{ + Level = 'Free' + IsP1Available = $false + IsP2Available = $false + } + if ($IncludeDetails) { + $result.P1EnabledCount = 0 + $result.P2EnabledCount = 0 + $result.P1AssignedCount = 0 + $result.P2AssignedCount = 0 + $result.P1SkuPartNumbers = @() + $result.P2SkuPartNumbers = @() + } + return [PSCustomObject]$result + } + + # Filter SKUs by service plans (provisioned and active) + $p1Skus = $skus | Where-Object { + $_.servicePlans | Where-Object { + $_.servicePlanName -eq 'AAD_PREMIUM' -and $_.provisioningStatus -eq 'Success' + } + } + $p2Skus = $skus | Where-Object { + $_.servicePlans | Where-Object { + $_.servicePlanName -eq 'AAD_PREMIUM_P2' -and $_.provisioningStatus -eq 'Success' + } + } + + # Enabled capacity (what you own) + $p1Enabled = ($p1Skus | ForEach-Object { $_.prepaidUnits.enabled } | Measure-Object -Sum).Sum + $p2Enabled = ($p2Skus | ForEach-Object { $_.prepaidUnits.enabled } | Measure-Object -Sum).Sum + + # Assigned units (how many are currently consumed) + $p1Assigned = ($p1Skus | ForEach-Object { $_.consumedUnits } | Measure-Object -Sum).Sum + $p2Assigned = ($p2Skus | ForEach-Object { $_.consumedUnits } | Measure-Object -Sum).Sum + + # Determine the “portal-equivalent” level + $level = if ($p2Enabled -gt 0) { 'P2' } + elseif ($p1Enabled -gt 0) { 'P1' } + else { 'Free' } + + $result = [ordered]@{ + Level = $level + IsP1Available = ($p1Enabled -gt 0) + IsP2Available = ($p2Enabled -gt 0) + } + + if ($IncludeDetails) { + $result.P1EnabledCount = [int]($p1Enabled | ForEach-Object { $_ } ) + $result.P2EnabledCount = [int]($p2Enabled | ForEach-Object { $_ } ) + $result.P1AssignedCount = [int]($p1Assigned | ForEach-Object { $_ } ) + $result.P2AssignedCount = [int]($p2Assigned | ForEach-Object { $_ } ) + $result.P1SkuPartNumbers = $p1Skus.skuPartNumber | Sort-Object -Unique + $result.P2SkuPartNumbers = $p2Skus.skuPartNumber | Sort-Object -Unique + } + + [PSCustomObject]$result +} + diff --git a/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 b/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 index 5473523..73c4b97 100644 --- a/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 +++ b/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 @@ -13,210 +13,6 @@ function Get-EntraIdRoleAssignment { # TODO: Support tenants with without PIM / Privileged Access enabled (currently the command relies on fetching role assignments via the privileged access API, which only returns results for tenants with PIM / Privileged Access enabled) - #region Configuiration - - # TODO: Move to module initialization when prototyping is done - - $ErrorActionPreference = 'Stop' # Always stop on errors - - #endregion - - #region Private functions - # TODO: Move these to separate files when prototyping is done - - - function Split-ArrayIntoChunks { - [CmdletBinding()] - param( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [object[]]$InputObject, - - [ValidateRange(1, [int]::MaxValue)] - [int]$ChunkSize = 20 - ) - - $items = @($InputObject) - # Always return a collection object - $result = New-Object System.Collections.Generic.List[object[]] - - if ($items.Count -gt 0) { - for ($i = 0; $i -lt $items.Count; $i += $ChunkSize) { - $end = [Math]::Min($i + $ChunkSize - 1, $items.Count - 1) - [void]$result.Add(@($items[$i..$end])) # ensure each chunk is an object[] - } - } - - # Emit the *collection* as a single object so callers get one wrapper - , $result - } - - function Invoke-GraphBatch { - [CmdletBinding()] - param( - [Parameter(Mandatory)] - [hashtable[]]$Requests, - - [Parameter()] - [string]$GraphVersion = 'v1.0', - - [Parameter()] - [switch]$ThrowOnAnyError - ) - - $uri = "$GraphVersion/`$batch" - $body = @{ requests = $Requests } - - $resp = Invoke-MgGraphRequest -Method POST -Uri $uri -Body $body -Verbose:$false - - if ($ThrowOnAnyError) { - $errors = $resp.responses | Where-Object { $_.status -ge 400 } - if ($errors) { - $codes = ($errors | ForEach-Object { "$($_.id):$($_.status)" }) -join ', ' - throw "One or more batch requests failed: $codes" - } - } - return $resp - } - - # Robust pagination helper - function Invoke-GraphPaged { - param( - [Parameter(Mandatory)][string] $Uri - ) - $items = @() - $next = $Uri - while ($next) { - $resp = Invoke-MgGraphRequest -Method GET -Uri $next -ErrorAction Stop - if ($resp.value) { $items += $resp.value } - $next = $resp.'@odata.nextLink' - } - return , $items - } - - #endregion - - #region Public functions - # TODO: Move these to separate files when prototyping is done - - function Get-EntraIdGroupScheduleBatch { - param( - [Parameter(Mandatory = $true)] - [string[]] - $GroupId, - - [Parameter(Mandatory = $true)] - [ValidateSet('Assigned', 'Eligible')] - [string] - $State - ) - - switch ($State) { - 'Assigned' { - $urlTemplate = "/identityGovernance/privilegedAccess/group/assignmentSchedules?`$filter=groupId eq '{Id}'&`$expand=principal" - } - 'Eligible' { - $urlTemplate = "/identityGovernance/privilegedAccess/group/eligibilitySchedules?`$filter=groupId eq '{Id}'&`$expand=principal" - } - } - - # Split the GroupId array into chunks of 20 - $chunks = Split-ArrayIntoChunks -InputObject $GroupId -ChunkSize 20 - - $allResponses = foreach ($chunk in $chunks) { - try { - $requests = foreach ($id in $chunk) { - @{ - id = $id - method = 'GET' - url = $urlTemplate.Replace('{Id}', $id) - } - } - Invoke-GraphBatch -Requests $requests -ThrowOnAnyError - } - catch { - Write-Warning ("Failed to process chunk of group IDs '{0}': {1}" -f ($chunk -join ', '), $_.Exception.Message) - } - } - - $allResponses.responses | ForEach-Object { $_.body.value } - } - - function Get-EntraIdLevel { - [CmdletBinding()] - param( - # If set, include diagnostic details (enabled/assigned counts and SKU part numbers). - [switch] $IncludeDetails - ) - - # Ensure we're using v1.0 for stability (this affects URL only, not the module profile). - $skus = Invoke-GraphPaged -Uri 'https://graph.microsoft.com/v1.0/subscribedSkus' -Verbose:$false - - # Defensive: if tenant has no SKUs (rare), treat as Free - if (-not $skus -or $skus.Count -eq 0) { - $result = [ordered]@{ - Level = 'Free' - IsP1Available = $false - IsP2Available = $false - } - if ($IncludeDetails) { - $result.P1EnabledCount = 0 - $result.P2EnabledCount = 0 - $result.P1AssignedCount = 0 - $result.P2AssignedCount = 0 - $result.P1SkuPartNumbers = @() - $result.P2SkuPartNumbers = @() - } - return [PSCustomObject]$result - } - - # Filter SKUs by service plans (provisioned and active) - $p1Skus = $skus | Where-Object { - $_.servicePlans | Where-Object { - $_.servicePlanName -eq 'AAD_PREMIUM' -and $_.provisioningStatus -eq 'Success' - } - } - $p2Skus = $skus | Where-Object { - $_.servicePlans | Where-Object { - $_.servicePlanName -eq 'AAD_PREMIUM_P2' -and $_.provisioningStatus -eq 'Success' - } - } - - # Enabled capacity (what you own) - $p1Enabled = ($p1Skus | ForEach-Object { $_.prepaidUnits.enabled } | Measure-Object -Sum).Sum - $p2Enabled = ($p2Skus | ForEach-Object { $_.prepaidUnits.enabled } | Measure-Object -Sum).Sum - - # Assigned units (how many are currently consumed) - $p1Assigned = ($p1Skus | ForEach-Object { $_.consumedUnits } | Measure-Object -Sum).Sum - $p2Assigned = ($p2Skus | ForEach-Object { $_.consumedUnits } | Measure-Object -Sum).Sum - - # Determine the “portal-equivalent” level - $level = if ($p2Enabled -gt 0) { 'P2' } - elseif ($p1Enabled -gt 0) { 'P1' } - else { 'Free' } - - $result = [ordered]@{ - Level = $level - IsP1Available = ($p1Enabled -gt 0) - IsP2Available = ($p2Enabled -gt 0) - } - - if ($IncludeDetails) { - $result.P1EnabledCount = [int]($p1Enabled | ForEach-Object { $_ } ) - $result.P2EnabledCount = [int]($p2Enabled | ForEach-Object { $_ } ) - $result.P1AssignedCount = [int]($p1Assigned | ForEach-Object { $_ } ) - $result.P2AssignedCount = [int]($p2Assigned | ForEach-Object { $_ } ) - $result.P1SkuPartNumbers = $p1Skus.skuPartNumber | Sort-Object -Unique - $result.P2SkuPartNumbers = $p2Skus.skuPartNumber | Sort-Object -Unique - } - - [PSCustomObject]$result - } - - - #endregion - - #region Internal functions function Resolve-AssignmentWindow { @@ -410,7 +206,7 @@ function Get-EntraIdRoleAssignment { } } - #endregion Helper Functions + #endregion Internal Functions #region MAIN @@ -420,13 +216,15 @@ function Get-EntraIdRoleAssignment { ## Connect-mgGraph -Scopes 'RoleEligibilitySchedule.Read.Directory','PrivilegedEligibilitySchedule.Read.AzureADGroup', 'PrivilegedAssignmentSchedule.Read.AzureADGroup' # + # Always stop on errors to avoid emitting incomplete data. Errors should be handled at the command level to allow for more granular error handling (e.g. skipping individual entries that fail to resolve rather than failing the entire command). + $ErrorActionPreference = 'Stop' + if (!(Test-GraphConnection)) { throw 'No active connection to Microsoft Graph. Run Connect-MgGraph to sign in and then retry.' } Get-EntraIdLevel -IncludeDetails | ConvertTo-Json -Depth 5 | Write-Verbose - $timer = [Diagnostics.Stopwatch]::StartNew() $activityName = 'Fetching Entra ID role assignments' @@ -445,8 +243,6 @@ function Get-EntraIdRoleAssignment { # TODO: Test with scopes other than the directory (e.g. administrative units) - # TODO: Test with Azure RBAC roles (if possible - not sure if these are returned by the API when listing directory role assignments?) - # If Rolename has been specified, filter out any assigments not in the specified role(s). if ($RoleName) { $roleAssignmentSchedules = $roleAssignmentSchedules | Where-Object { $_.roleDefinition.displayName -in $RoleName } diff --git a/EntraReporter/internal/functions/Invoke-GraphBatch.ps1 b/EntraReporter/internal/functions/Invoke-GraphBatch.ps1 new file mode 100644 index 0000000..1fd9238 --- /dev/null +++ b/EntraReporter/internal/functions/Invoke-GraphBatch.ps1 @@ -0,0 +1,28 @@ +function Invoke-GraphBatch { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [hashtable[]]$Requests, + + [Parameter()] + [string]$GraphVersion = 'v1.0', + + [Parameter()] + [switch]$ThrowOnAnyError + ) + + $uri = "$GraphVersion/`$batch" + $body = @{ requests = $Requests } + + $resp = Invoke-MgGraphRequest -Method POST -Uri $uri -Body $body -Verbose:$false + + if ($ThrowOnAnyError) { + $errors = $resp.responses | Where-Object { $_.status -ge 400 } + if ($errors) { + $codes = ($errors | ForEach-Object { "$($_.id):$($_.status)" }) -join ', ' + throw "One or more batch requests failed: $codes" + } + } + return $resp +} + diff --git a/EntraReporter/internal/functions/Invoke-GraphPaged.ps1 b/EntraReporter/internal/functions/Invoke-GraphPaged.ps1 new file mode 100644 index 0000000..635e834 --- /dev/null +++ b/EntraReporter/internal/functions/Invoke-GraphPaged.ps1 @@ -0,0 +1,15 @@ +# Robust pagination helper +function Invoke-GraphPaged { + param( + [Parameter(Mandatory)][string] $Uri + ) + $items = @() + $next = $Uri + while ($next) { + $resp = Invoke-MgGraphRequest -Method GET -Uri $next -ErrorAction Stop + if ($resp.value) { $items += $resp.value } + $next = $resp.'@odata.nextLink' + } + return , $items +} + diff --git a/EntraReporter/internal/functions/Split-ArrayIntoChunks.ps1 b/EntraReporter/internal/functions/Split-ArrayIntoChunks.ps1 new file mode 100644 index 0000000..677fc84 --- /dev/null +++ b/EntraReporter/internal/functions/Split-ArrayIntoChunks.ps1 @@ -0,0 +1,25 @@ +function Split-ArrayIntoChunks { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [object[]]$InputObject, + + [ValidateRange(1, [int]::MaxValue)] + [int]$ChunkSize = 20 + ) + + $items = @($InputObject) + # Always return a collection object + $result = New-Object System.Collections.Generic.List[object[]] + + if ($items.Count -gt 0) { + for ($i = 0; $i -lt $items.Count; $i += $ChunkSize) { + $end = [Math]::Min($i + $ChunkSize - 1, $items.Count - 1) + [void]$result.Add(@($items[$i..$end])) # ensure each chunk is an object[] + } + } + + # Emit the *collection* as a single object so callers get one wrapper + , $result +} From a207817feab7ca242ee109bb25d957a6dd5bacbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A5re=20Overg=C3=A5rd?= Date: Mon, 30 Mar 2026 06:59:56 +0200 Subject: [PATCH 08/14] WIP --- .../functions/Get-EntraIdRoleAssignment.ps1 | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 b/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 index 73c4b97..b0bee26 100644 --- a/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 +++ b/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 @@ -211,9 +211,10 @@ function Get-EntraIdRoleAssignment { #region MAIN # - ## Connect-mgGraph -Scopes 'RoleManagement.Read.Directory','RoleEligibilitySchedule.Read.Directory','PrivilegedEligibilitySchedule.Read.AzureADGroup', 'PrivilegedAssignmentSchedule.Read.AzureADGroup', 'GroupMember.Read.All' + ## Connect-MgGraph -Scopes 'RoleManagement.Read.Directory','RoleEligibilitySchedule.Read.Directory','PrivilegedEligibilitySchedule.Read.AzureADGroup', 'PrivilegedAssignmentSchedule.Read.AzureADGroup', 'GroupMember.Read.All' + # Current: - ## Connect-mgGraph -Scopes 'RoleEligibilitySchedule.Read.Directory','PrivilegedEligibilitySchedule.Read.AzureADGroup', 'PrivilegedAssignmentSchedule.Read.AzureADGroup' + ## Connect-MgGraph -Scopes 'RoleEligibilitySchedule.Read.Directory','PrivilegedEligibilitySchedule.Read.AzureADGroup', 'PrivilegedAssignmentSchedule.Read.AzureADGroup' # # Always stop on errors to avoid emitting incomplete data. Errors should be handled at the command level to allow for more granular error handling (e.g. skipping individual entries that fail to resolve rather than failing the entire command). @@ -228,10 +229,6 @@ function Get-EntraIdRoleAssignment { $timer = [Diagnostics.Stopwatch]::StartNew() $activityName = 'Fetching Entra ID role assignments' - Invoke-MgGraphRequest -Method GET -Uri 'v1.0/directory/administrativeUnits' -Verbose:$false | Select-Object -ExpandProperty value - - throw 'STOP' - # Fetch all role schedules, assigned and eligible. Write-Progress -Activity $activityName -Status 'Fetching assigned role schedules' -PercentComplete 20 $roleAssignmentSchedules = Invoke-MgGraphRequest -Method GET -Uri "v1.0/roleManagement/directory/roleAssignmentSchedules?`$filter=assignmentType eq 'Assigned'&`$expand=principal,roleDefinition" -Verbose:$false | Select-Object -ExpandProperty value @@ -249,6 +246,20 @@ function Get-EntraIdRoleAssignment { $roleEligibilitySchedules = $roleEligibilitySchedules | Where-Object { $_.roleDefinition.displayName -in $RoleName } } + # If any scopes are used in the role schedules (i.e. scope is not just the entire directory), fetch information about the scopes to allow for better reporting (e.g. resolving administrative unit names). + $scopeIds = @() + $scopeIds += $roleAssignmentSchedules | Select-Object -ExpandProperty directoryScopeId + $scopeIds += $roleEligibilitySchedules | Select-Object -ExpandProperty directoryScopeId + $scopeIds = $scopeIds | Select-Object -Unique | Where-Object { $_ -ne '/' } + if ($scopeIds.Count -gt 0) { + Write-Progress -Activity $activityName -Status 'Fetching scope information' -PercentComplete 50 + $scopeIds | ConvertTo-Json -Depth 5 | Write-Verbose + throw 'STOP' + } + + Invoke-MgGraphRequest -Method GET -Uri 'v1.0/directory/administrativeUnits' -Verbose:$false | Select-Object -ExpandProperty value + throw 'STOP' + # Extract unique group IDs from all role schedules for prefetching group schedule information in bulk to reduce number of API calls later on. $groupIds = @() $groupIds += $roleAssignmentSchedules | Select-Object -ExpandProperty principal | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.group' } | Select-Object -ExpandProperty Id From 026d7095df65e1dc0fece35c56a00aefaab735c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A5re=20Overg=C3=A5rd?= Date: Mon, 30 Mar 2026 19:35:48 +0200 Subject: [PATCH 09/14] Prepare list of admin units for lookup --- .../functions/Get-AdministrativeUnit.ps1 | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 EntraReporter/internal/functions/Get-AdministrativeUnit.ps1 diff --git a/EntraReporter/internal/functions/Get-AdministrativeUnit.ps1 b/EntraReporter/internal/functions/Get-AdministrativeUnit.ps1 new file mode 100644 index 0000000..50968ea --- /dev/null +++ b/EntraReporter/internal/functions/Get-AdministrativeUnit.ps1 @@ -0,0 +1,21 @@ +# Generate a list of all administrative units in the directory for resolving scope information in role assignments. +function Get-AdministrativeUnit { + [CmdletBinding()] + + $results = @() + + # Get all administrative units from directory to avoid making multiple calls + $allAdministrativeUnits = Invoke-MgGraphRequest -Method GET -Uri 'v1.0/directory/administrativeUnits' -Verbose:$false | Select-Object -ExpandProperty value + foreach ($adminUnit in $allAdministrativeUnits) { + $results += [pscustomobject] @{ + directoryScopeId = "/administrativeUnits/$($adminUnit.id)" + displayName = $adminUnit.displayName + } + } + $results += [pscustomobject] @{ + directoryScopeId = '/' + displayName = 'Directory' + } + + return $results +} \ No newline at end of file From eddf4438631c5cf0ad3cd77f8bfd039ea690ff31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A5re=20Overg=C3=A5rd?= Date: Mon, 30 Mar 2026 19:39:37 +0200 Subject: [PATCH 10/14] WIP --- .../functions/Get-EntraIdRoleAssignment.ps1 | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 b/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 index b0bee26..327704d 100644 --- a/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 +++ b/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 @@ -142,7 +142,7 @@ function Get-EntraIdRoleAssignment { PrincipalId = $Schedule.principal.id PrincipalDisplayName = $Schedule.principal.displayName AssignmentType = 'Group' - Scope = $Schedule.directoryScopeId + Scope = $administrativeUnits | Where-Object { $_.directoryScopeId -eq $Schedule.directoryScopeId } | Select-Object -ExpandProperty displayName PrincipalState = $GroupState PrincipalStartTime = if ($Schedule.scheduleInfo.expiration.endDateTime) { $Schedule.scheduleInfo.startDateTime } else { $null } PrincipalEndTime = if ($Schedule.scheduleInfo.expiration.endDateTime) { $Schedule.scheduleInfo.expiration.endDateTime } else { $null } @@ -173,17 +173,13 @@ function Get-EntraIdRoleAssignment { $principal = $Schedule.principal if ($principal.'@odata.type' -eq '#microsoft.graph.user') { - # if ($Schedule.directoryScopeId -ne '/') { - # $Schedule | ConvertTo-Json -Depth 4 | Write-Verbose - # throw 'STOP' - #} $roleEntrySplat = @{ RoleId = $Schedule.roleDefinitionId RoleName = $Schedule.roleDefinition.displayName PrincipalId = $Schedule.principal.id PrincipalDisplayName = $Schedule.principal.displayName AssignmentType = 'User' - Scope = $Schedule.directoryScopeId + Scope = $administrativeUnits | Where-Object { $_.directoryScopeId -eq $Schedule.directoryScopeId } | Select-Object -ExpandProperty displayName PrincipalState = $State PrincipalStartTime = if ($Schedule.scheduleInfo.expiration.endDateTime) { $Schedule.scheduleInfo.startDateTime } else { $null } PrincipalEndTime = if ($Schedule.scheduleInfo.expiration.endDateTime) { $Schedule.scheduleInfo.expiration.endDateTime } else { $null } @@ -235,11 +231,8 @@ function Get-EntraIdRoleAssignment { Write-Progress -Activity $activityName -Status 'Fetching eligible role schedules' -PercentComplete 40 $roleEligibilitySchedules = Invoke-MgGraphRequest -Method GET -Uri "v1.0/roleManagement/directory/roleEligibilitySchedules?`$expand=principal,roleDefinition" -Verbose:$false | Select-Object -ExpandProperty value - # TODO: Test for nested groups??? - # TODO: Test with scopes other than the directory (e.g. administrative units) - # If Rolename has been specified, filter out any assigments not in the specified role(s). if ($RoleName) { $roleAssignmentSchedules = $roleAssignmentSchedules | Where-Object { $_.roleDefinition.displayName -in $RoleName } @@ -247,18 +240,12 @@ function Get-EntraIdRoleAssignment { } # If any scopes are used in the role schedules (i.e. scope is not just the entire directory), fetch information about the scopes to allow for better reporting (e.g. resolving administrative unit names). + Write-Progress -Activity $activityName -Status 'Fetching scope information' -PercentComplete 60 $scopeIds = @() $scopeIds += $roleAssignmentSchedules | Select-Object -ExpandProperty directoryScopeId $scopeIds += $roleEligibilitySchedules | Select-Object -ExpandProperty directoryScopeId - $scopeIds = $scopeIds | Select-Object -Unique | Where-Object { $_ -ne '/' } - if ($scopeIds.Count -gt 0) { - Write-Progress -Activity $activityName -Status 'Fetching scope information' -PercentComplete 50 - $scopeIds | ConvertTo-Json -Depth 5 | Write-Verbose - throw 'STOP' - } - - Invoke-MgGraphRequest -Method GET -Uri 'v1.0/directory/administrativeUnits' -Verbose:$false | Select-Object -ExpandProperty value - throw 'STOP' + $scopeIds = $scopeIds | Where-Object { $_.id -ne '/' } | Select-Object -Unique + $script:administrativeUnits = Get-AdministrativeUnit # Extract unique group IDs from all role schedules for prefetching group schedule information in bulk to reduce number of API calls later on. $groupIds = @() @@ -267,7 +254,7 @@ function Get-EntraIdRoleAssignment { $groupIds = $groupIds | Select-Object -Unique # Prefetch group schedules for all groups used groups - Write-Progress -Activity $activityName -Status 'Fetching assigned group schedules' -PercentComplete 60 + Write-Progress -Activity $activityName -Status 'Fetching assigned group schedules' -PercentComplete 65 $script:groupAssignmentSchedules = Get-EntraIdGroupScheduleBatch -GroupId $groupIds -State 'Assigned' Write-Progress -Activity $activityName -Status 'Fetching eligible group schedules' -PercentComplete 80 $script:groupEligibilitySchedules = Get-EntraIdGroupScheduleBatch -GroupId $groupIds -State 'Eligible' From acd39addc343f1731a734a0d57a9095cf94674d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A5re=20Overg=C3=A5rd?= Date: Fri, 3 Apr 2026 14:16:01 +0200 Subject: [PATCH 11/14] Move to internal function --- .../Get-EntraIdGroupScheduleBatch.ps1 | 43 ---------- .../Get-EntraIdGroupScheduleBatch.ps1 | 81 +++++++++++++++++++ 2 files changed, 81 insertions(+), 43 deletions(-) delete mode 100644 EntraReporter/functions/Get-EntraIdGroupScheduleBatch.ps1 create mode 100644 EntraReporter/internal/functions/Get-EntraIdGroupScheduleBatch.ps1 diff --git a/EntraReporter/functions/Get-EntraIdGroupScheduleBatch.ps1 b/EntraReporter/functions/Get-EntraIdGroupScheduleBatch.ps1 deleted file mode 100644 index 97f3eb2..0000000 --- a/EntraReporter/functions/Get-EntraIdGroupScheduleBatch.ps1 +++ /dev/null @@ -1,43 +0,0 @@ -function Get-EntraIdGroupScheduleBatch { - param( - [Parameter(Mandatory = $true)] - [string[]] - $GroupId, - - [Parameter(Mandatory = $true)] - [ValidateSet('Assigned', 'Eligible')] - [string] - $State - ) - - switch ($State) { - 'Assigned' { - $urlTemplate = "/identityGovernance/privilegedAccess/group/assignmentSchedules?`$filter=groupId eq '{Id}'&`$expand=principal" - } - 'Eligible' { - $urlTemplate = "/identityGovernance/privilegedAccess/group/eligibilitySchedules?`$filter=groupId eq '{Id}'&`$expand=principal" - } - } - - # Split the GroupId array into chunks of 20 - $chunks = Split-ArrayIntoChunks -InputObject $GroupId -ChunkSize 20 - - $allResponses = foreach ($chunk in $chunks) { - try { - $requests = foreach ($id in $chunk) { - @{ - id = $id - method = 'GET' - url = $urlTemplate.Replace('{Id}', $id) - } - } - Invoke-GraphBatch -Requests $requests -ThrowOnAnyError - } - catch { - Write-Warning ("Failed to process chunk of group IDs '{0}': {1}" -f ($chunk -join ', '), $_.Exception.Message) - } - } - - $allResponses.responses | ForEach-Object { $_.body.value } -} - diff --git a/EntraReporter/internal/functions/Get-EntraIdGroupScheduleBatch.ps1 b/EntraReporter/internal/functions/Get-EntraIdGroupScheduleBatch.ps1 new file mode 100644 index 0000000..35466da --- /dev/null +++ b/EntraReporter/internal/functions/Get-EntraIdGroupScheduleBatch.ps1 @@ -0,0 +1,81 @@ +<# + .SYNOPSIS + Retrieves membership role assignment or eligibility schedules for multiple Azure AD groups using batched Graph API requests. + + .DESCRIPTION + Fetches PIM group membership schedules for a collection of group IDs. Supports both active assignments and eligible + memberships. The function splits the input group IDs into chunks of 20 to optimize Graph API batch requests and handles + error recovery per chunk to avoid total failure if some group IDs fail. + + .PARAMETER GroupId + One or more Azure AD group IDs for which to retrieve schedules. The function will batch these requests for efficiency. + + .PARAMETER State + The type of schedule to retrieve: 'Assigned' for active membership schedules, or 'Eligible' for eligible membership schedules. + + .OUTPUTS + PSCustomObject array containing schedule records with principal expansion. Each record includes properties like principalId, + groupId, scheduleInfo, and principal (expanded user/group details). + + .EXAMPLE + Get-EntraIdGroupScheduleBatch -GroupId @('group-id-1', 'group-id-2') -State 'Assigned' + Retrieves active membership assignment schedules for the specified groups. + + .NOTES + Requires an active Microsoft Graph connection. Uses batched requests (20 IDs per batch) to optimize API throughput. + Errors on individual chunks are logged as warnings but do not halt the overall operation. + +#> +function Get-EntraIdGroupScheduleBatch { + param( + # Array of Azure AD group IDs to fetch schedules for + [Parameter(Mandatory = $true)] + [string[]] + $GroupId, + + # Type of schedule: 'Assigned' for active, 'Eligible' for eligible memberships + [Parameter(Mandatory = $true)] + [ValidateSet('Assigned', 'Eligible')] + [string] + $State + ) + + # Select the appropriate Graph API endpoint based on the requested state + switch ($State) { + 'Assigned' { + # Endpoint for active group membership assignments + $urlTemplate = "/identityGovernance/privilegedAccess/group/assignmentSchedules?`$filter=groupId eq '{Id}'&`$expand=principal" + } + 'Eligible' { + # Endpoint for eligible group membership schedules + $urlTemplate = "/identityGovernance/privilegedAccess/group/eligibilitySchedules?`$filter=groupId eq '{Id}'&`$expand=principal" + } + } + + # Split the GroupId array into chunks of 20 for batch processing (Graph API batch limit optimization) + $chunks = Split-ArrayIntoChunks -InputObject $GroupId -ChunkSize 20 + + # Process each chunk through the Graph batch API and collect all responses + $allResponses = foreach ($chunk in $chunks) { + try { + # Build batch request objects for each group ID in the current chunk + $requests = foreach ($id in $chunk) { + @{ + id = $id + method = 'GET' + url = $urlTemplate.Replace('{Id}', $id) + } + } + # Submit batch request for this chunk and retrieve responses + Invoke-GraphBatch -Requests $requests -ThrowOnAnyError + } + catch { + # Log warning if chunk processing fails, but continue with remaining chunks + Write-Warning ("Failed to process chunk of group IDs '{0}': {1}" -f ($chunk -join ', '), $_.Exception.Message) + } + } + + # Extract the actual schedule data from batch responses and flatten the results + $allResponses.responses | ForEach-Object { $_.body.value } +} + From 83adc01ab8e0d0e763363c59412d753ef1042c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A5re=20Overg=C3=A5rd?= Date: Fri, 3 Apr 2026 14:16:13 +0200 Subject: [PATCH 12/14] Add help text --- EntraReporter/functions/Get-EntraIdLevel.ps1 | 58 +++++++++++++++++--- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/EntraReporter/functions/Get-EntraIdLevel.ps1 b/EntraReporter/functions/Get-EntraIdLevel.ps1 index 1b3e10a..df7a618 100644 --- a/EntraReporter/functions/Get-EntraIdLevel.ps1 +++ b/EntraReporter/functions/Get-EntraIdLevel.ps1 @@ -1,21 +1,59 @@ -function Get-EntraIdLevel { +<# + .SYNOPSIS + Determines the Entra ID license level (Free, P1, or P2) for the connected tenant. + + .DESCRIPTION + Queries the tenant's Microsoft Graph SKU information to determine which premium Entra ID licenses are enabled. + Returns the highest available license level (P2 > P1 > Free). Optionally includes detailed information about + provisioned and consumed license units. + + .PARAMETER IncludeDetails + When specified, includes detailed license counts (enabled and assigned units) and SKU part numbers for P1 and P2 plans. + + .OUTPUTS + PSCustomObject with properties: + - Level: 'Free', 'P1', or 'P2' (highest available) + - IsP1Available: $true if P1 is licensed and active + - IsP2Available: $true if P2 is licensed and active + - P1EnabledCount: (with -IncludeDetails) Total P1 licenses enabled + - P2EnabledCount: (with -IncludeDetails) Total P2 licenses enabled + - P1AssignedCount: (with -IncludeDetails) Currently consumed P1 units + - P2AssignedCount: (with -IncludeDetails) Currently consumed P2 units + - P1SkuPartNumbers: (with -IncludeDetails) Unique P1 SKU part numbers + - P2SkuPartNumbers: (with -IncludeDetails) Unique P2 SKU part numbers + + .EXAMPLE + Get-EntraIdLevel + Returns the license level for the tenant. + + .EXAMPLE + Get-EntraIdLevel -IncludeDetails + Returns the license level along with detailed unit and SKU information. + + .NOTES + Requires an active Microsoft Graph connection. Uses v1.0 subscription API endpoint. + +#> +function Get-EntraIdLevel { [CmdletBinding()] param( - # If set, include diagnostic details (enabled/assigned counts and SKU part numbers). + # When set, includes enabled/assigned counts and SKU part numbers in output [switch] $IncludeDetails ) - # Ensure we're using v1.0 for stability (this affects URL only, not the module profile). + # Fetch all subscribed SKUs from Graph API using v1.0 endpoint for stability $skus = Invoke-GraphPaged -Uri 'https://graph.microsoft.com/v1.0/subscribedSkus' -Verbose:$false - # Defensive: if tenant has no SKUs (rare), treat as Free + # Handle edge case: if tenant has no SKUs (rare), default to Free tier if (-not $skus -or $skus.Count -eq 0) { + # No SKUs found; initialize result with Free tier $result = [ordered]@{ Level = 'Free' IsP1Available = $false IsP2Available = $false } if ($IncludeDetails) { + # Add detailed counts and SKU info (all zeros/empty for Free tier) $result.P1EnabledCount = 0 $result.P2EnabledCount = 0 $result.P1AssignedCount = 0 @@ -26,7 +64,7 @@ return [PSCustomObject]$result } - # Filter SKUs by service plans (provisioned and active) + # Identify P1 and P2 licensed SKUs (filter by service plan and provisioning status) $p1Skus = $skus | Where-Object { $_.servicePlans | Where-Object { $_.servicePlanName -eq 'AAD_PREMIUM' -and $_.provisioningStatus -eq 'Success' @@ -38,19 +76,20 @@ } } - # Enabled capacity (what you own) + # Sum total enabled licenses (capacity purchased for each tier) $p1Enabled = ($p1Skus | ForEach-Object { $_.prepaidUnits.enabled } | Measure-Object -Sum).Sum $p2Enabled = ($p2Skus | ForEach-Object { $_.prepaidUnits.enabled } | Measure-Object -Sum).Sum - # Assigned units (how many are currently consumed) + # Sum consumed/assigned units (how many are currently in use) $p1Assigned = ($p1Skus | ForEach-Object { $_.consumedUnits } | Measure-Object -Sum).Sum $p2Assigned = ($p2Skus | ForEach-Object { $_.consumedUnits } | Measure-Object -Sum).Sum - # Determine the “portal-equivalent” level + # Determine the effective license level: P2 (highest) > P1 > Free $level = if ($p2Enabled -gt 0) { 'P2' } elseif ($p1Enabled -gt 0) { 'P1' } else { 'Free' } + # Build the result object with availability flags $result = [ordered]@{ Level = $level IsP1Available = ($p1Enabled -gt 0) @@ -58,14 +97,17 @@ } if ($IncludeDetails) { + # Convert sums to int and populate unit counts $result.P1EnabledCount = [int]($p1Enabled | ForEach-Object { $_ } ) $result.P2EnabledCount = [int]($p2Enabled | ForEach-Object { $_ } ) $result.P1AssignedCount = [int]($p1Assigned | ForEach-Object { $_ } ) $result.P2AssignedCount = [int]($p2Assigned | ForEach-Object { $_ } ) + # Extract and deduplicate SKU part numbers for reporting $result.P1SkuPartNumbers = $p1Skus.skuPartNumber | Sort-Object -Unique $result.P2SkuPartNumbers = $p2Skus.skuPartNumber | Sort-Object -Unique } + # Return the result as a PowerShell custom object with all properties [PSCustomObject]$result } From f426cc85a04d1e0af73b926c641d4a45a3f7d34a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A5re=20Overg=C3=A5rd?= Date: Fri, 3 Apr 2026 14:56:30 +0200 Subject: [PATCH 13/14] Documentation updated --- .../Get-EntraIdRoleAssignment-Format-List.png | Bin 0 -> 55160 bytes Images/Get-EntraIdRoleAssignment.png | Bin 0 -> 78536 bytes README.md | 111 +++++++++++++----- 3 files changed, 79 insertions(+), 32 deletions(-) create mode 100644 Images/Get-EntraIdRoleAssignment-Format-List.png create mode 100644 Images/Get-EntraIdRoleAssignment.png diff --git a/Images/Get-EntraIdRoleAssignment-Format-List.png b/Images/Get-EntraIdRoleAssignment-Format-List.png new file mode 100644 index 0000000000000000000000000000000000000000..0f0ff21c3a5cd23bd673ef817b7790eb498cd0bd GIT binary patch literal 55160 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 P4oN(e5y1*Sef|Fcr;dEs literal 0 HcmV?d00001 diff --git a/Images/Get-EntraIdRoleAssignment.png b/Images/Get-EntraIdRoleAssignment.png new file mode 100644 index 0000000000000000000000000000000000000000..8666ce5bdbc6a07ecff185d1738c884b06136be8 GIT binary patch literal 78536 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%0D3p0GV!r-F()2S1G~u33e93 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

frW3m1HCZpu!mOKzs$~KF%%kkr5id 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 XOzhL1m;Vj{{39(P|DZ@r-|v3|9bY^Y literal 0 HcmV?d00001 diff --git a/README.md b/README.md index 029ce55..27148c7 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,99 @@ # EntraReporter -ADD DESCRIPTION HERE +EntraReporter is a PowerShell module designed to report on information from Entra ID (Azure Active Directory), including role assignments and eligibility schedules. It leverages Microsoft Graph API to provide detailed insights into privileged access management (PAM) configurations, including role assignments for users, groups, and service principals. The module is particularly useful for compliance auditing, security reviews, and administrative reporting. -## Project Setup +Key features include: +- Comprehensive role assignment reporting with effective date ranges, with support for group-based role assignments and eligibility +- License level detection for Entra ID tiers +- Batch processing for efficient API calls +- Detailed output with customizable formatting -> TODO: Delete this section from the readme, once you are done with it +## Installation -> Setup step 1: Configuring the project +To install the module, run: -In the root folder - right beside this readme file - you can find a `config.psd1` file. -You can find and adjust settings there, such as whether you want the version automatically updated or the `FunctionsToExport` be auto-generated. +```powershell +Install-Module -Name 'EntraReporter' -Scope CurrentUser +``` -Each setting has a description, explaining what it does. -If this is your first module project, you may want to enable `ExportFunction`, to have one less thing to deal with. +Alternatively, clone the repository and import the module manually: -> What you need to know & update +```powershell +git clone https://github.com/kovergard/EntraReporter.git +cd EntraReporter +Import-Module .\EntraReporter\EntraReporter.psd1 +``` -Essentially, your module is ready to roll, just needing your content, so these are the things you need to update: +## Functions -```text -readme.md -EntraReporter\EntraReporter.psd1 -EntraReporter\functions -EntraReporter\internal\functions -EntraReporter\internal\scripts -``` +### Get-EntraIdRoleAssignment -+ `readme.md`: Add some description and examples on how to use your project, then delete the "Project Setup" section, which is for you only. -+ `EntraReporter.psd1`: The module manifest. You may need to register your public functions here, maintain the version number or declare dependencies your module uses. -+ `functions`: The folder your public functions go. That is, functions your users should have access to. One function per file, file should have the same name as the function. -+ `internal\functions`: The folder where your internal functions should be placed, that users should not directly use. One function per file, file should have the same name as the function. -+ `internal\scripts`: The folder where scripts go, that are run on module import only. Use for declaring module-wide variables, do some cleanup, or whatever else needs to happen only once per session. +**Description:** +This cmdlet queries Entra ID role assignments and eligibility information for users, groups, and service principals. The function queries Microsoft Graph Privileged Identity Management (PIM) APIs to consolidate role schedules into a unified report, resolving group memberships and calculating effective assignment windows. -## Installation +**Parameters:** +- `RoleName` (optional): Filter by one or more role names (e.g., 'Global Administrator', 'User Administrator'). -To install the module, run: +**Output:** +Returns a collection of PSCustomObjects with properties including RoleId, RoleName, PrincipalId, AssignmentType, Scope, EffectiveState, EffectiveStartTime, EffectiveEndTime, and more. -```powershell -Install-Module -Name 'EntraReporter' -Scope CurrentUser -``` +**Examples:** -Alternatively, if you have any trouble getting modules installed, this might work instead: +1. **Retrieve all role assignments and eligibilities:** + ```powershell + Get-EntraIdRoleAssignment + ``` + This command fetches all role assignments in the tenant, and shows the most important properties. + ![image](Images/Get-EntraIdRoleAssignment.png) +2. **Filter by specific role with all details:** + ```powershell + Get-EntraIdRoleAssignment -RoleName "User Administrator" | Format-List + ``` + This retrieves assignments only for the 'User Administrator' role, and show all returned properties. + ![image](Images/Get-EntraIdRoleAssignment-Format-List.png) + +**Notes:** +Requires Entra P2 license level. Ensure you have connected to Microsoft Graph with appropriate scopes (e.g., `Connect-MgGraph -Scopes 'RoleEligibilitySchedule.Read.Directory','PrivilegedEligibilitySchedule.Read.AzureADGroup', 'PrivilegedAssignmentSchedule.Read.AzureADGroup'`). + +### Get-EntraIdLevel + +**Description:** +Determines the Entra ID license level (Free, P1, or P2) for the connected tenant by querying subscribed SKUs via Microsoft Graph. + +**Parameters:** +- `IncludeDetails` (switch): Includes detailed license counts and SKU part numbers. + +**Output:** +PSCustomObject with Level, IsP1Available, IsP2Available, and optionally detailed counts. + +**Example:** ```powershell -Invoke-WebRequest 'https://raw.githubusercontent.com/PowershellFrameworkCollective/PSFramework.NuGet/refs/heads/master/bootstrap.ps1' -UseBasicParsing | Invoke-Expression -Install-PSFModule -Name 'EntraReporter' +Get-EntraIdLevel -IncludeDetails ``` +Output example: +``` +Level : P2 +IsP1Available : True +IsP2Available : True +P1EnabledCount: 50 +P2EnabledCount: 25 +P1AssignedCount: 45 +P2AssignedCount: 20 +P1SkuPartNumbers: {AAD_PREMIUM} +P2SkuPartNumbers: {AAD_PREMIUM_P2} +``` + +### Other Functions + +- **Get-EntraIdGroupScheduleBatch:** Retrieves group membership schedules in batches for efficiency. +- **Get-AdministrativeUnit:** Internal function for resolving administrative unit names. +- **Invoke-GraphBatch / Invoke-GraphPaged:** Internal utilities for Graph API calls. + +## Contributing + +Contributions are welcome! Please submit issues and pull requests on GitHub. -## Profit +## License -ADD EXAMPLES HERE +This project is licensed under the MIT License - see the LICENSE file for details. From e3e55919b1a46671e31cf9795a8f1b93d7ed6f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A5re=20Overg=C3=A5rd?= Date: Fri, 3 Apr 2026 14:56:50 +0200 Subject: [PATCH 14/14] Initial version --- .../functions/Get-EntraIdRoleAssignment.ps1 | 189 ++++++++++++------ 1 file changed, 132 insertions(+), 57 deletions(-) diff --git a/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 b/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 index 327704d..b530e7a 100644 --- a/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 +++ b/EntraReporter/functions/Get-EntraIdRoleAssignment.ps1 @@ -1,7 +1,27 @@ <# + .SYNOPSIS + Retrieves Entra ID role assignments and eligibility information for users, groups, and service principals. -TODO: Comment based help + .DESCRIPTION + The command queries Microsoft Graph Privileged Identity Management (PIM) role assignment and role eligibility schedules, resolves group member assignments, and returns a consolidated set of role assignment records. + .PARAMETER RoleName + Optional filter for one or more role names. If provided, only role assignments and eligibilities matching the specified role names are returned. + + .EXAMPLE + Get-EntraIdRoleAssignment + Retrieves all role assignments and eligibilities in the connected tenant (requires Entra P2). + + .EXAMPLE + Get-EntraIdRoleAssignment -RoleName 'Global Administrator' + Retrieves assignments and eligibilities for the Global Administrator role only. + + .NOTES + Requires an active connection to Microsoft Graph (`Connect-MgGraph`) and Entra P2 license level. + This module does not yet support nested group role assignments completely; a warning is emitted when nested groups are present. + + .LINK + https://learn.microsoft.com/graph/api/rolemanagement-root #> function Get-EntraIdRoleAssignment { [CmdletBinding()] @@ -11,19 +31,20 @@ function Get-EntraIdRoleAssignment { $RoleName ) - # TODO: Support tenants with without PIM / Privileged Access enabled (currently the command relies on fetching role assignments via the privileged access API, which only returns results for tenants with PIM / Privileged Access enabled) - #region Internal functions + # Resolve-AssignmentWindow: compute the effective assignment window from principal and user time windows function Resolve-AssignmentWindow { + [CmdletBinding()] param( - [Nullable[datetime]] $PrincipalStart, - [Nullable[datetime]] $PrincipalEnd, - [Nullable[datetime]] $UserStart, - [Nullable[datetime]] $UserEnd + [Nullable[datetime]] $PrincipalStart, # Start datetime from principal assignment schedule + [Nullable[datetime]] $PrincipalEnd, # End datetime from principal assignment schedule + [Nullable[datetime]] $UserStart, # Start datetime from user assignment schedule + [Nullable[datetime]] $UserEnd # End datetime from user assignment schedule ) if ($null -eq $UserEnd -and $null -eq $PrincipalEnd) { + # Both are permanent (no end date); assignment is permanent return [pscustomobject]@{ Start = $null End = $null @@ -32,6 +53,7 @@ function Get-EntraIdRoleAssignment { } if ($null -eq $PrincipalEnd) { + # Principal is permanent; use user window return [pscustomobject]@{ Start = $UserStart End = $UserEnd @@ -40,6 +62,7 @@ function Get-EntraIdRoleAssignment { } if ($null -eq $UserEnd) { + # User is permanent; use principal window return [pscustomobject]@{ Start = $PrincipalStart End = $PrincipalEnd @@ -47,7 +70,7 @@ function Get-EntraIdRoleAssignment { } } - # Intersection logic + # Both have end dates; return intersection (most restrictive window) return [pscustomobject]@{ Start = if ($PrincipalStart -gt $UserStart) { $PrincipalStart } else { $UserStart } End = if ($PrincipalEnd -lt $UserEnd) { $PrincipalEnd } else { $UserEnd } @@ -55,24 +78,25 @@ function Get-EntraIdRoleAssignment { } } + # New-RoleAssignmentEntry: build and normalize a role assignment output record function New-RoleAssignmentEntry { [CmdletBinding()] param( - [Parameter(Mandatory)] [string]$RoleId, - [Parameter(Mandatory)] [string]$RoleName, - [Parameter(Mandatory)] [string]$PrincipalId, - [Parameter(Mandatory)] [string]$PrincipalDisplayName, - [Parameter(Mandatory)] [string]$AssignmentType, - [Parameter(Mandatory)] [string]$Scope, - [Parameter(Mandatory)] [string]$PrincipalState, - [Nullable[datetime]] $PrincipalStartTime, - [Nullable[datetime]] $PrincipalEndTime, - [Parameter(Mandatory)] [string]$UserId, - [Parameter(Mandatory)] [string]$UserDisplayName, - [Parameter(Mandatory)] [string]$UserPrincipalName, - [Parameter(Mandatory)] [string]$UserState, - [Nullable[datetime]] $UserStartTime, - [Nullable[datetime]] $UserEndTime + [Parameter(Mandatory)] [string]$RoleId, # Role definition ID + [Parameter(Mandatory)] [string]$RoleName, # Role display name + [Parameter(Mandatory)] [string]$PrincipalId, # Assigned principal ID (user/group/servicePrincipal) + [Parameter(Mandatory)] [string]$PrincipalDisplayName,# Assigned principal display name + [Parameter(Mandatory)] [string]$AssignmentType, # Assignment type (User/Group/Service principal) + [Parameter(Mandatory)] [string]$Scope, # Scope/administrative unit for assignment + [Parameter(Mandatory)] [string]$PrincipalState, # Principal state (Assigned/Eligible) + [Nullable[datetime]] $PrincipalStartTime, # Principal assignment start + [Nullable[datetime]] $PrincipalEndTime, # Principal assignment end + [Parameter(Mandatory)] [string]$UserId, # User ID (expanded member if group schedule) + [Parameter(Mandatory)] [string]$UserDisplayName, # User display name + [Parameter(Mandatory)] [string]$UserPrincipalName, # User principal name/appId + [Parameter(Mandatory)] [string]$UserState, # User state (Assigned/Eligible) + [Nullable[datetime]] $UserStartTime, # User assignment start + [Nullable[datetime]] $UserEndTime # User assignment end ) # Resolve effective assignment window by calculating the intersection of the principal and user assignment windows. If either of the windows is permanent (i.e. has no end time), the effective window will be determined by the other window. If both windows are permanent, the effective window will also be permanent. @@ -111,23 +135,33 @@ function Get-EntraIdRoleAssignment { } } + # Resolve-RoleAssignedGroup: expand group principal schedules into per-member entries + # This function reads the preloaded group schedule entries, maps each member to user-level + # role assignment/eligibility and then calls New-RoleAssignmentEntry to normalize output. function Resolve-RoleAssignedGroup { + [CmdletBinding()] param( + # Graph schedule object for group role assignment/eligibility [Parameter(Mandatory = $true)] [psobject] - $Schedule, + $Schedule, + # Group state context being processed [Parameter(Mandatory = $true)] [ValidateSet('Assigned', 'Eligible')] [string] - $GroupState, + $GroupState, + # Resulting user-level state for group members [Parameter(Mandatory = $true)] [ValidateSet('Assigned', 'Eligible')] [string] - $UserState + $UserState ) + $principal = $Schedule.principal + + # Choose group schedule entries based on whether we are resolving assigned or eligible members if ($UserState -eq 'Assigned') { $scheduleEntries = $script:groupAssignmentSchedules | Where-Object { $_.groupId -eq $principal.id } } @@ -136,43 +170,60 @@ function Get-EntraIdRoleAssignment { } foreach ($scheduleEntry in $scheduleEntries ) { - $roleEntrySplat = @{ - RoleId = $Schedule.roleDefinitionId - RoleName = $Schedule.roleDefinition.displayName - PrincipalId = $Schedule.principal.id - PrincipalDisplayName = $Schedule.principal.displayName - AssignmentType = 'Group' - Scope = $administrativeUnits | Where-Object { $_.directoryScopeId -eq $Schedule.directoryScopeId } | Select-Object -ExpandProperty displayName - PrincipalState = $GroupState - PrincipalStartTime = if ($Schedule.scheduleInfo.expiration.endDateTime) { $Schedule.scheduleInfo.startDateTime } else { $null } - PrincipalEndTime = if ($Schedule.scheduleInfo.expiration.endDateTime) { $Schedule.scheduleInfo.expiration.endDateTime } else { $null } - UserId = $scheduleEntry.principalId - UserDisplayName = $scheduleEntry.principal.displayName - UserPrincipalName = $scheduleEntry.principal.userPrincipalName - UserState = $UserState - UserStartTime = if ($scheduleEntry.scheduleInfo.expiration.endDateTime) { $scheduleEntry.scheduleInfo.startDateTime } else { $null } - UserEndTime = if ($scheduleEntry.scheduleInfo.expiration.endDateTime) { $scheduleEntry.scheduleInfo.expiration.endDateTime } else { $null } + if ($scheduleEntry.principal.'@odata.type' -eq '#microsoft.graph.user') { + # User member found; create normalized assignment entry + $roleEntrySplat = @{ + RoleId = $Schedule.roleDefinitionId + RoleName = $Schedule.roleDefinition.displayName + PrincipalId = $Schedule.principal.id + PrincipalDisplayName = $Schedule.principal.displayName + AssignmentType = 'Group' + Scope = $administrativeUnits | Where-Object { $_.directoryScopeId -eq $Schedule.directoryScopeId } | Select-Object -ExpandProperty displayName + PrincipalState = $GroupState + PrincipalStartTime = if ($Schedule.scheduleInfo.expiration.endDateTime) { $Schedule.scheduleInfo.startDateTime } else { $null } + PrincipalEndTime = if ($Schedule.scheduleInfo.expiration.endDateTime) { $Schedule.scheduleInfo.expiration.endDateTime } else { $null } + UserId = $scheduleEntry.principalId + UserDisplayName = $scheduleEntry.principal.displayName + UserPrincipalName = $scheduleEntry.principal.userPrincipalName + UserState = $UserState + UserStartTime = if ($scheduleEntry.scheduleInfo.expiration.endDateTime) { $scheduleEntry.scheduleInfo.startDateTime } else { $null } + UserEndTime = if ($scheduleEntry.scheduleInfo.expiration.endDateTime) { $scheduleEntry.scheduleInfo.expiration.endDateTime } else { $null } + } + New-RoleAssignmentEntry @roleEntrySplat + } + elseif ($scheduleEntry.principal.'@odata.type' -eq '#microsoft.graph.group') { + # Nested group found; recursion not yet supported + Write-Verbose "Skipping nested group with ID '$($scheduleEntry.principal.id)' since nested groups are currently not supported by the command." + } + else { + Write-Warning "Principal with ID '$($scheduleEntry.principal.id)' is of type $($scheduleEntry.principal.'@odata.type'), which is currently not supported by the command. Skipping entry." + $scheduleEntry | ConvertTo-Json -Depth 5 | Write-Verbose } - New-RoleAssignmentEntry @roleEntrySplat } } + # Resolve-EntraIDRoleSchedule: normalize a schedule entry into 1+ role assignment rows + # Handles user/servicePrincipal directly and delegates group principals to Resolve-RoleAssignedGroup. function Resolve-EntraIDRoleSchedule { param( + # The Graph role schedule to resolve into output entries [Parameter(Mandatory = $true)] [psobject] - $Schedule, + $Schedule, + # The schedule state to apply for this resolution pass [Parameter(Mandatory = $true)] [ValidateSet('Assigned', 'Eligible')] [string] - $State + $State ) - # Resolve principal to allow for individual handling of user vs group principals (e.g. resolving group members for group principals since Graph API does not currently provide a way to see effective role assignments for group members) + # Resolve principal to allow handling of different principal types + # (user, servicePrincipal, group) and convert each to normalized rows. $principal = $Schedule.principal if ($principal.'@odata.type' -eq '#microsoft.graph.user') { + # User principal; create entry with user as both principal and assignee $roleEntrySplat = @{ RoleId = $Schedule.roleDefinitionId RoleName = $Schedule.roleDefinition.displayName @@ -192,13 +243,36 @@ function Get-EntraIdRoleAssignment { } New-RoleAssignmentEntry @roleEntrySplat } + elseif ($principal.'@odata.type' -eq '#microsoft.graph.servicePrincipal') { + # Service principal; create entry with appId as principal name + $roleEntrySplat = @{ + RoleId = $Schedule.roleDefinitionId + RoleName = $Schedule.roleDefinition.displayName + PrincipalId = $Schedule.principal.id + PrincipalDisplayName = $Schedule.principal.displayName + AssignmentType = 'Service principal' + Scope = $administrativeUnits | Where-Object { $_.directoryScopeId -eq $Schedule.directoryScopeId } | Select-Object -ExpandProperty displayName + PrincipalState = $State + PrincipalStartTime = if ($Schedule.scheduleInfo.expiration.endDateTime) { $Schedule.scheduleInfo.startDateTime } else { $null } + PrincipalEndTime = if ($Schedule.scheduleInfo.expiration.endDateTime) { $Schedule.scheduleInfo.expiration.endDateTime } else { $null } + UserId = $Schedule.principalId + UserDisplayName = $Schedule.principal.displayName + UserPrincipalName = $Schedule.principal.appId + UserState = $State + UserStartTime = if ($Schedule.scheduleInfo.expiration.endDateTime) { $Schedule.scheduleInfo.startDateTime } else { $null } + UserEndTime = if ($Schedule.scheduleInfo.expiration.endDateTime) { $Schedule.scheduleInfo.expiration.endDateTime } else { $null } + } + New-RoleAssignmentEntry @roleEntrySplat + } + elseif ($principal.'@odata.type' -eq '#microsoft.graph.group') { + # Group principal; expand into per-member entries, handling both assigned and eligible Resolve-RoleAssignedGroup -Schedule $Schedule -GroupState $State -UserState 'Assigned' Resolve-RoleAssignedGroup -Schedule $Schedule -GroupState $State -UserState 'Eligible' } else { Write-Warning "Principal with ID '$($principal.id)' is of type $($principal.'@odata.type'), which is currently not supported by the command. Skipping entry." - continue + $Schedule | ConvertTo-Json -Depth 5 | Write-Verbose } } @@ -206,13 +280,6 @@ function Get-EntraIdRoleAssignment { #region MAIN - # - ## Connect-MgGraph -Scopes 'RoleManagement.Read.Directory','RoleEligibilitySchedule.Read.Directory','PrivilegedEligibilitySchedule.Read.AzureADGroup', 'PrivilegedAssignmentSchedule.Read.AzureADGroup', 'GroupMember.Read.All' - - # Current: - ## Connect-MgGraph -Scopes 'RoleEligibilitySchedule.Read.Directory','PrivilegedEligibilitySchedule.Read.AzureADGroup', 'PrivilegedAssignmentSchedule.Read.AzureADGroup' - # - # Always stop on errors to avoid emitting incomplete data. Errors should be handled at the command level to allow for more granular error handling (e.g. skipping individual entries that fail to resolve rather than failing the entire command). $ErrorActionPreference = 'Stop' @@ -220,7 +287,11 @@ function Get-EntraIdRoleAssignment { throw 'No active connection to Microsoft Graph. Run Connect-MgGraph to sign in and then retry.' } - Get-EntraIdLevel -IncludeDetails | ConvertTo-Json -Depth 5 | Write-Verbose + $EntraIdLevel = Get-EntraIdLevel + if ($EntraIdLevel.Level -ne 'P2') { + throw 'This command requires at P2 level to run since it relies on APIs that are not available for Entra P1 or Free tenants.' + # TODO: Add support for P1 tenants by falling back to fetching role assignments via the standard directory role assignments API for tenants that do not have PIM / Privileged Access enabled. This will likely require significant changes to the command logic since the standard directory role assignments API does not return future-dated assignments or eligible assignments, so it will require fetching all role assignments and then checking each assignment against the role eligibility schedules to determine eligibility and future-dated status. In the meantime, we will throw an error for non-P2 tenants to avoid emitting incomplete data. + } $timer = [Diagnostics.Stopwatch]::StartNew() $activityName = 'Fetching Entra ID role assignments' @@ -231,8 +302,6 @@ function Get-EntraIdRoleAssignment { Write-Progress -Activity $activityName -Status 'Fetching eligible role schedules' -PercentComplete 40 $roleEligibilitySchedules = Invoke-MgGraphRequest -Method GET -Uri "v1.0/roleManagement/directory/roleEligibilitySchedules?`$expand=principal,roleDefinition" -Verbose:$false | Select-Object -ExpandProperty value - # TODO: Test for nested groups??? - # If Rolename has been specified, filter out any assigments not in the specified role(s). if ($RoleName) { $roleAssignmentSchedules = $roleAssignmentSchedules | Where-Object { $_.roleDefinition.displayName -in $RoleName } @@ -259,6 +328,12 @@ function Get-EntraIdRoleAssignment { Write-Progress -Activity $activityName -Status 'Fetching eligible group schedules' -PercentComplete 80 $script:groupEligibilitySchedules = Get-EntraIdGroupScheduleBatch -GroupId $groupIds -State 'Eligible' + + if (($groupAssignmentSchedules | Where-Object { $_.principal.'@odata.type' -eq '#microsoft.graph.group' }) -or ($groupEligibilitySchedules | Where-Object { $_.principal.'@odata.type' -eq '#microsoft.graph.group' })) { + Write-Warning 'One or more role assignment uses nested groups, which is currently not supported by the command. This may lead to incomplete reporting.' + # TODO: Add support for nested groups by recursively resolving group memberships until only user principals are left. This will likely require a significant increase in the number of API calls, so it should be implemented with caution and ideally with some form of caching to avoid hitting API limits. In the meantime, we will emit a warning to alert users of potential incompleteness in the reporting. + } + Write-Progress -Activity $activityName -Status 'Consolidating results' -PercentComplete 95 $resolvedGroupSchedules = @() $resolvedGroupSchedules += foreach ($schedule in $roleAssignmentSchedules) {