SHELL/5.3.1.ps1

$CheckId = "5.3.1"
$Title = "Ensure 'Privileged Identity Management' is used to manage roles"
$Level = "L2"
$BenchmarkType = "Automated"

function Get-PropValue {
    param(
        [AllowNull()]$Object,
        [string]$Name
    )

    if ($null -eq $Object) {
        return $null
    }

    if ($Object -is [hashtable]) {
        foreach ($Key in $Object.Keys) {
            if ([string]$Key -ieq $Name) {
                return $Object[$Key]
            }
        }
    }

    if ($Object.PSObject -and $Object.PSObject.Properties) {
        foreach ($Property in $Object.PSObject.Properties) {
            if ([string]$Property.Name -ieq $Name) {
                return $Property.Value
            }
        }
    }

    return $null
}

function Get-GraphPagedValue {
    param([string]$Uri)

    $Items = [System.Collections.Generic.List[object]]::new()
    $Next = $Uri

    while (-not [string]::IsNullOrWhiteSpace($Next)) {
        $Response = Invoke-MgGraphRequest -Uri $Next -Method GET -ErrorAction Stop
        foreach ($Item in @(Get-PropValue -Object $Response -Name "value")) {
            $Items.Add($Item)
        }
        $Next = [string](Get-PropValue -Object $Response -Name "@odata.nextLink")
    }

    return @($Items)
}

try {
    $SensitiveRoleDefinitionIds = @(
        "62e90394-69f5-4237-9190-012177145e10", # Global Administrator
        "e8611ab8-c189-46e8-94e1-60213ab1f814", # Privileged Role Administrator
        "194ae4cb-b126-40b2-bd5b-6091b380977d", # Security Administrator
        "29232cdf-9323-42fd-ade2-1d097af3e4de", # Exchange Administrator
        "f28a1f50-f6e7-4571-818b-6a12f2af6b6c", # SharePoint Administrator
        "fe930be7-5e62-47db-91af-98c3a49a38b1", # User Administrator
        "c4e39bd9-1100-46d3-8c65-fb160da0071f", # Authentication Administrator
        "9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3"  # Application Administrator
    )

    $RoleDefinitions = @(Get-GraphPagedValue -Uri "https://graph.microsoft.com/beta/roleManagement/directory/roleDefinitions?`$select=id,displayName")
    $RoleNameById = @{}
    foreach ($Role in $RoleDefinitions) {
        $RoleId = [string](Get-PropValue -Object $Role -Name "id")
        $RoleName = [string](Get-PropValue -Object $Role -Name "displayName")
        if ($RoleId) {
            $RoleNameById[$RoleId] = $RoleName
        }
    }

    $EligibilityInstances = @(Get-GraphPagedValue -Uri "https://graph.microsoft.com/beta/roleManagement/directory/roleEligibilityScheduleInstances?`$top=500")
    $AssignmentInstances = @(Get-GraphPagedValue -Uri "https://graph.microsoft.com/beta/roleManagement/directory/roleAssignmentScheduleInstances?`$top=500")

    $SensitiveEligibility = @($EligibilityInstances | Where-Object {
            ([string](Get-PropValue -Object $_ -Name "roleDefinitionId")) -in $SensitiveRoleDefinitionIds
        })
    $SensitiveAssignments = @($AssignmentInstances | Where-Object {
            ([string](Get-PropValue -Object $_ -Name "roleDefinitionId")) -in $SensitiveRoleDefinitionIds
        })

    $Pass = $SensitiveEligibility.Count -gt 0
    $Status = if ($Pass) { "PASS" } else { "FAIL" }

    $EligibilitySummary = @(
        $SensitiveEligibility | ForEach-Object {
            $RoleId = [string](Get-PropValue -Object $_ -Name "roleDefinitionId")
            [pscustomobject]@{
                RoleDefinitionId = $RoleId
                RoleDisplayName = if ($RoleNameById.ContainsKey($RoleId)) { $RoleNameById[$RoleId] } else { $null }
                PrincipalId = [string](Get-PropValue -Object $_ -Name "principalId")
                MemberType = [string](Get-PropValue -Object $_ -Name "memberType")
            }
        }
    )

    $AssignmentSummary = @(
        $SensitiveAssignments | ForEach-Object {
            $RoleId = [string](Get-PropValue -Object $_ -Name "roleDefinitionId")
            [pscustomobject]@{
                RoleDefinitionId = $RoleId
                RoleDisplayName = if ($RoleNameById.ContainsKey($RoleId)) { $RoleNameById[$RoleId] } else { $null }
                PrincipalId = [string](Get-PropValue -Object $_ -Name "principalId")
                AssignmentType = [string](Get-PropValue -Object $_ -Name "assignmentType")
                MemberType = [string](Get-PropValue -Object $_ -Name "memberType")
            }
        }
    )

    [pscustomobject]@{
        CheckId = $CheckId
        Title = $Title
        Level = $Level
        BenchmarkType = $BenchmarkType
        Status = $Status
        Pass = $Pass
        Evidence = [pscustomobject]@{
            SensitiveRoleDefinitionIds = $SensitiveRoleDefinitionIds
            SensitiveEligibilityCount = $SensitiveEligibility.Count
            SensitiveAssignmentCount = $SensitiveAssignments.Count
            SensitiveEligibility = $EligibilitySummary
            SensitiveAssignments = $AssignmentSummary
            ComplianceCriteria = "At least one sensitive Entra role has eligible (PIM) assignments."
            SourceDocument = "CIS_Microsoft_365_Foundations_Benchmark_v6.0.1"
        }
        Error = if ($Pass) { $null } else { "No eligible (PIM) assignments were found for the minimum sensitive Entra roles list." }
        Timestamp = Get-Date
    }
}
catch {
    [pscustomobject]@{
        CheckId = $CheckId
        Title = $Title
        Level = $Level
        BenchmarkType = $BenchmarkType
        Status = "ERROR"
        Pass = $null
        Evidence = [pscustomobject]@{
            SourceDocument = "CIS_Microsoft_365_Foundations_Benchmark_v6.0.1"
        }
        Error = $_.Exception.Message
        Timestamp = Get-Date
    }
}