backup/Backup-Roles.ps1

#Requires -Version 7.0
function Backup-Roles {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string]$BackupPath,
        [Parameter(Mandatory)] [SecureString]$Token,
        [hashtable]$ScopeTagMap = @{}
    )

    try {
        $folder = Join-Path $BackupPath 'Roles'
        $uri = '/beta/deviceManagement/roleDefinitions?$filter=isBuiltIn eq false'
        $roles = Invoke-GraphRequest2 -Uri $uri -Token $Token

        foreach ($role in $roles) {
            # fetch role assignments for this role
            $assignmentsUri = "/beta/deviceManagement/roleDefinitions/$($role.id)/roleAssignments"
            $roleAssignments = Invoke-GraphRequest2 -Uri $assignmentsUri -Token $Token

            if ($roleAssignments) {
                $enrichedAssignments = @()
                foreach ($assignment in $roleAssignments) {
                    # fetch full assignment details
                    $fullUri = "/beta/deviceManagement/roleAssignments/$($assignment.id)"
                    $fullAssignment = Invoke-GraphRequest2 -Uri $fullUri -Token $Token

                    # resolve scope member names
                    $scopeMemberNames = @()
                    if ($fullAssignment.scopeMembers) {
                        foreach ($memberId in $fullAssignment.scopeMembers) {
                            $groupUri = "/v1.0/groups/$memberId`?`$select=displayName"
                            $group = Invoke-GraphRequest2 -Uri $groupUri -Token $Token
                            if ($group.displayName) {
                                $scopeMemberNames += $group.displayName
                            }
                        }
                    }

                    # resolve member names
                    $memberNames = @()
                    if ($fullAssignment.members) {
                        foreach ($memberId in $fullAssignment.members) {
                            $groupUri = "/v1.0/groups/$memberId`?`$select=displayName"
                            $group = Invoke-GraphRequest2 -Uri $groupUri -Token $Token
                            if ($group.displayName) {
                                $memberNames += $group.displayName
                            }
                        }
                    }

                    # add resolved names
                    if ($scopeMemberNames) {
                        $fullAssignment | Add-Member -MemberType NoteProperty -Name 'scopeMemberNames' -Value $scopeMemberNames -Force
                    }
                    if ($memberNames) {
                        $fullAssignment | Add-Member -MemberType NoteProperty -Name 'memberNames' -Value $memberNames -Force
                    }

                    $enrichedAssignments += $fullAssignment
                }
                $role | Add-Member -MemberType NoteProperty -Name 'roleAssignments' -Value $enrichedAssignments -Force
            }

            # construct filename
            $odataType = $role.'@odata.type'
            $type = $odataType -replace '#microsoft\.graph\.', ''
            $fileName = "$($role.displayName)_$type"

            $clean = Remove-VolatileKeys -InputObject $role
            Save-BackupItem -Item $clean -Folder $folder -FileName $fileName -ScopeTagMap $ScopeTagMap
        }
        Write-Verbose "backed up $($roles.Count) roles to $folder"
    }
    catch {
        Write-Error "failed to backup roles: $_"
        return
    }
}

Export-ModuleMember -Function Backup-Roles