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