Skip to content

Commit 1f20339

Browse files
simplified batch specification
1 parent 5f0adf9 commit 1f20339

File tree

3 files changed

+163
-37
lines changed

3 files changed

+163
-37
lines changed

MiniGraph/MiniGraph.psd1

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
RootModule = 'MiniGraph.psm1'
55

66
# Version number of this module.
7-
ModuleVersion = '1.3.12'
7+
ModuleVersion = '1.3.13'
88

99
# Supported PSEditions
1010
# CompatiblePSEditions = @()

MiniGraph/functions/Invoke-GraphRequestBatch.ps1

+158-36
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,40 @@
22
<#
33
.SYNOPSIS
44
Invoke a batch request against the graph API
5+
56
.DESCRIPTION
67
Invoke a batch request against the graph API in batches of twenty.
7-
Breaking with the easy queries from Invoke-GraphRequest, this function
8-
requires you to provide a list of batches consisting of url, method and id.
8+
99
.PARAMETER Request
10-
A list of batches consisting of url, method and id.
10+
A list of requests to batch.
11+
Each entry should either be ...
12+
- A relative uri to query (what you would send to Invoke-GraphRequest)
13+
- A hashtable consisting of url (mandatory), method (optional), id (optional), body (optional), headers (optional) and dependsOn (optional).
14+
15+
.PARAMETER Method
16+
The method to use with requests, that do not specify their method.
17+
Defaults to "GET"
18+
19+
.PARAMETER Body
20+
The body to add to requests that do not specify their own body.
21+
22+
.PARAMETER Header
23+
The header to add to requests that do not specify their own header.
24+
25+
.EXAMPLE
26+
$servicePrincipals = Invoke-GraphRequest -Query "servicePrincipals?&`$filter=accountEnabled eq true"
27+
$requests = @($servicePrincipals).ForEach{ "/servicePrincipals/$($_.id)/appRoleAssignments" }
28+
Invoke-GraphRequestBatch -Request $requests
29+
30+
Retrieve the role assignments for all enabled service principals
31+
32+
.EXAMPLE
33+
$servicePrincipals = Invoke-GraphRequest -Query "servicePrincipals?&`$filter=accountEnabled eq false"
34+
$requests = @($servicePrincipals).ForEach{ "/servicePrincipals/$($_.id)" }
35+
Invoke-GraphRequestBatch -Request $requests -Body { accountEnabled = $true } -Method PATCH
36+
37+
Enables all disabled service principals
38+
1139
.EXAMPLE
1240
$servicePrincipals = Invoke-GraphRequest -Query "servicePrincipals?&`$filter=accountEnabled eq true"
1341
$araCounter = 1
@@ -22,56 +50,150 @@
2250
$idToSp[$araCounter] = $sp
2351
$araCounter++
2452
}
53+
Invoke-GraphRequestBatch -Request $appRoleAssignmentsRequest
54+
55+
Retrieve the role assignments for all enabled service principals
2556
#>
2657
[CmdletBinding()]
2758
param (
2859
[Parameter(Mandatory = $true)]
29-
[hashtable[]]
30-
$Request
60+
[object[]]
61+
$Request,
62+
63+
[Microsoft.PowerShell.Commands.WebRequestMethod]
64+
$Method = 'Get',
65+
66+
[hashtable]
67+
$Body,
68+
69+
[hashtable]
70+
$Header
3171
)
3272

33-
$batchSize = 20 # Currently hardcoded API limit
34-
$counter = [pscustomobject] @{ Value = 0 }
35-
$batches = $Request | Group-Object -Property { [math]::Floor($counter.Value++ / $batchSize) } -AsHashTable
73+
begin {
74+
function ConvertTo-BatchRequest {
75+
[CmdletBinding()]
76+
param (
77+
[object[]]
78+
$Request,
3679

37-
foreach ($batch in ($batches.GetEnumerator() | Sort-Object -Property Key)) {
38-
[array] $innerResult = try {
39-
$jsonbody = @{requests = [array]$batch.Value } | ConvertTo-Json -Depth 42 -Compress
40-
(MiniGraph\Invoke-GraphRequest -Query '$batch' -Method Post -Body $jsonbody -ErrorAction Stop).responses
41-
}
42-
catch {
43-
Write-Error -Message "Error sending batch: $($_.Exception.Message)" -TargetObject $jsonbody
44-
continue
45-
}
80+
[Microsoft.PowerShell.Commands.WebRequestMethod]
81+
$Method,
4682

47-
$throttledRequests = $innerResult | Where-Object status -EQ 429
48-
$failedRequests = $innerResult | Where-Object { $_.status -ne 429 -and $_.status -in (400..499) }
49-
$successRequests = $innerResult | Where-Object status -In (200..299)
83+
$Cmdlet,
5084

51-
foreach ($failedRequest in $failedRequests) {
52-
Write-Error -Message "Error in batch request $($failedRequest.id): $($failedRequest.body.error.message)"
53-
}
85+
[AllowNull()]
86+
[hashtable]
87+
$Body,
88+
89+
[AllowNull()]
90+
[hashtable]
91+
$Header
92+
)
93+
$defaultMethod = "$Method".ToUpper()
5494

55-
if ($successRequests) {
56-
$successRequests
57-
}
95+
$results = @{}
96+
$requests = foreach ($entry in $Request) {
97+
$newRequest = @{
98+
url = ''
99+
method = $defaultMethod
100+
id = 0
101+
}
102+
if ($Body) { $newRequest.body = $Body }
103+
if ($Header) { $newRequest.headers = $Header }
104+
if ($entry -is [string]) {
105+
$newRequest.url = $entry
106+
$newRequest
107+
continue
108+
}
58109

59-
if ($throttledRequests) {
60-
$interval = ($throttledRequests.Headers | Sort-Object 'Retry-After' | Select-Object -Last 1).'Retry-After'
61-
Write-Verbose -Message "Throttled requests detected, waiting $interval seconds before retrying"
110+
if (-not $entry.url) {
111+
Invoke-TerminatingException -Cmdlet $Cmdlet -Message "Invalid batch request: No Url found! $entry" -Category InvalidArgument
112+
}
113+
$newRequest.url = $entry.url
114+
if ($entry.Method) {
115+
$newRequest.method = "$($entry.Method)".ToUpper()
116+
}
117+
if ($entry.id -as [int]) {
118+
$newRequest.id = $entry.id -as [int]
119+
$results[($entry.id -as [int])] = $newRequest
120+
}
121+
if ($entry.body) {
122+
$newRequest.body = $entry.body
123+
}
124+
if ($entry.headers) {
125+
$newRequest.headers = $entry.headers
126+
}
127+
if ($entry.dependsOn) {
128+
$newRequest.dependsOn
129+
}
130+
$newRequest
131+
}
62132

63-
Start-Sleep -Seconds $interval
64-
$retry = $Request | Where-Object id -In $throttledRequests.id
133+
$index = 1
134+
$finalList = foreach ($requestItem in $requests) {
135+
$requestItem.id = $requestItem.id -as [string]
136+
if ($requestItem.id) {
137+
$requestItem
138+
continue
139+
}
65140

66-
if (-not $retry) {
67-
continue
141+
while ($results[$index]) {
142+
$index++
143+
}
144+
$requestItem.id = $index
145+
$results[$index] = $requestItem
146+
$requestItem
68147
}
69148

70-
try {
71-
(MiniGraph\Invoke-GraphRequestBatch -Name $Name -Request $retry -NoProgress -ErrorAction Stop).responses
149+
$finalList | Sort-Object { $_.id -as [int] }
150+
}
151+
}
152+
153+
process {
154+
$batchSize = 20 # Currently hardcoded API limit
155+
$counter = [pscustomobject] @{ Value = 0 }
156+
$batches = ConvertTo-BatchRequest -Request $Request -Method $Method -Cmdlet $PSCmdlet -Body $Body -Header $Header | Group-Object -Property { [math]::Floor($counter.Value++ / $batchSize) } -AsHashTable
157+
158+
foreach ($batch in ($batches.GetEnumerator() | Sort-Object -Property Key)) {
159+
[array] $innerResult = try {
160+
$jsonbody = @{requests = [array]$batch.Value } | ConvertTo-Json -Depth 42 -Compress
161+
(MiniGraph\Invoke-GraphRequest -Query '$batch' -Method Post -Body $jsonbody -ErrorAction Stop).responses
72162
}
73163
catch {
74-
Write-Error -Message "Error sending retry batch: $($_.Exception.Message)" -TargetObject $retry
164+
Write-Error -Message "Error sending batch: $($_.Exception.Message)" -TargetObject $jsonbody
165+
continue
166+
}
167+
168+
$throttledRequests = $innerResult | Where-Object status -EQ 429
169+
$failedRequests = $innerResult | Where-Object { $_.status -ne 429 -and $_.status -in (400..499) }
170+
$successRequests = $innerResult | Where-Object status -In (200..299)
171+
172+
foreach ($failedRequest in $failedRequests) {
173+
Write-Error -Message "Error in batch request $($failedRequest.id): $($failedRequest.body.error.message)"
174+
}
175+
176+
if ($successRequests) {
177+
$successRequests
178+
}
179+
180+
if ($throttledRequests) {
181+
$interval = ($throttledRequests.Headers | Sort-Object 'Retry-After' | Select-Object -Last 1).'Retry-After'
182+
Write-Verbose -Message "Throttled requests detected, waiting $interval seconds before retrying"
183+
184+
Start-Sleep -Seconds $interval
185+
$retry = $Request | Where-Object id -In $throttledRequests.id
186+
187+
if (-not $retry) {
188+
continue
189+
}
190+
191+
try {
192+
(MiniGraph\Invoke-GraphRequestBatch -Name $Name -Request $retry -NoProgress -ErrorAction Stop).responses
193+
}
194+
catch {
195+
Write-Error -Message "Error sending retry batch: $($_.Exception.Message)" -TargetObject $retry
196+
}
75197
}
76198
}
77199
}

changelog.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 1.3.13 (2023-12-03)
4+
5+
+ Upd: Invoke-GraphRequestBatch - simplified requests specification
6+
37
## 1.3.12 (2023-12-01)
48

59
+ New: Connect-GraphBrowser - Interactive logon using the Authorization flow and browser. Supports SSO.

0 commit comments

Comments
 (0)