Getting all direct reports from Azure AD using the batch endpoint

A quick script to get stuff from Azure AD using the batch endpoint, that can essentially let you run your scripts 10 to 20 times as fast in certain circumstances.

Example usages of the script (needs modification of course)

  • Get direct reports for all users in Azure AD
  • Fetch owner of all groups in Azure AD
  • Get manager of all users in Azure AD (or a subset of users, but many…)
  • Get all owned devices of many users in Azure AD
function Get-AccessTokenFromGraphExplorerUrlOnClipboard
{
    [CmdletBinding()]
    [Alias()]
    Param
    ([String[]] $RequiredScopes = @())
  
    Process
    {
        $token = $null
        $first = $true
        do {
            if(!$first) {
                Sleep -Seconds 1   
            }
            $first = $false
  
            Write-Verbose "Trying to get Graph Explorer URL from clipboard"
            $url = Get-Clipboard
            if($url -ne $null -and $url.StartsWith("https://developer.microsoft.com/en-us/graph/graph-explorer#access_token=")) {
                $token = $url -split "[=&]" | Select -Index 1
            }
        } while($token -eq $null -or !$token.StartsWith("ey"))
  
        $token1 = $token.Split(".")[1]
        if(($token1.Length % 4) -gt 0) {
            $token1 = $token1.PadRight($token1.Length + (4 - ($token1.Length % 4)), "=")
        }
        $tokenAsObject = $token1 |ConvertFrom-Base64 | ConvertFrom-Json
        $RequiredScopes | Where{$_ -notin ($tokenAsObject.scp -split " ")} | Foreach {
            Write-Warning "Missing scope: $($_)"
        }

        $token
    }
}

function ConvertTo-Buckets
{
    [CmdletBinding()]
    [Alias()]
    [OutputType([int])]
    Param
    (
        # Total number of buckets, bucket size will vary
        [Parameter(Mandatory=$true,
                    Position=0,
                    ParameterSetName="NumberOfBuckets")]
        [ValidateRange(2, 9999)]
        [int] $NumberOfBuckets,
                   
        # Size of each bucket, last bucket will have different size of the rest
        [Parameter(Mandatory=$true,
                    Position=1,
                    ParameterSetName="BucketSize")]
        [ValidateRange(2, 99999)]
        [int]  $BucketSize,
 
        # Input object to put into bucket
        [Parameter(Mandatory=$true,
                    Position=2, ValueFromPipeline=$true)]
        $InputObject
    )
                   
    Begin
    {
        $Buckets = New-Object System.Collections.ArrayList<Object>
        if($NumberOfBuckets -gt 0) {
            # Add numberofbuckets number of array lists to create a multi dimensional array list
            1..$NumberOfBuckets | Foreach {
                $Buckets.Add((New-Object System.Collections.ArrayList<Object>)) | Out-Null
            }   
        } else {
            # Add a single bucket as our first bucket
            $Buckets.Add((New-Object System.Collections.ArrayList<Object>)) | Out-Null
        }
        $index = 0
    }
    Process
    {
        if($NumberOfBuckets -gt 0) {
            $index = ($index + 1) % $NumberOfBuckets
            $Buckets[$index].Add($InputObject) | Out-Null
        } else {
            $Buckets[$index].Add($InputObject) | Out-Null
            if($Buckets[$index].Count -ge $BucketSize) {
                $Buckets.Add((New-Object System.Collections.ArrayList<Object>)) | Out-Null
                $index += 1
            }
        }
         
    }
    End
    {
        $Buckets
    }
}

# Get the access token
Write-Host -ForegroundColor Green "Sign into graph explorer and copy the url with the access token - https://developer.microsoft.com/en-us/graph/graph-explorer"
$token = Get-AccessTokenFromGraphExplorerUrlOnClipboard -Verbose -RequiredScopes @("User.ReadWrite.All")
$restparams = @{headers = @{Authorization = "Bearer $token"}}

# Read all users
$users = @()
$uri = "https://graph.microsoft.com/v1.0/users?`$select=id&`$top=999"
do {
    $result = Invoke-RestMethod -Uri $uri @restparams
    $uri = $result.'@odata.nextLink'
    $users += $result.value
    Write-Verbose "User count: $($users.Count)" -Verbose
} while($uri -ne $null)

$report = $users | ConvertTo-Buckets -BucketSize 20 | Foreach {
    Write-Verbose "Processing bucket of 20 users" -Verbose
    
    # Generate request body
    $inc = 1
    $requestIdToObjectId = @{}
    $requestBody = @{
        requests = @($_ | Foreach {
            @{
                id = "$inc"
                method = "GET"
                url = "/users/$($_.id)/directReports?`$top=999&`$select=id"
            }
            $requestIdToObjectId["$inc"] = $_.id
            $inc += 1
        })
    } | ConvertTo-Json
    
    # Send request
    $result = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/`$batch" @restparams -ContentType "application/json" -Method Post -Body $requestBody
    $result.responses | Foreach {
        [PSCustomObject] @{
            Id = $requestIdToObjectId[$_.id]
            DirectReports = $_.body.value.id
        }
    }
}

# Just out gridview the whole thing
$report | Out-GridView

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s