msgraph-sdk-powershell
msgraph-sdk-powershell copied to clipboard
Automatic request batching
I want to be able to easily retrieve bulk data sets from commands that normally return single data records by using batching.
For example, consider the Get-MgUserManager cmdlet. This cmdlet takes in a single UserId string and retrieves the manager for that user. That's good, but since it only works for a single user, you need to make many calls back and forth to get managers for a set of users, or to get managers for all users in your organization.
JSON batching addresses this problem by allowing you to batch multiple requests into a single call, so that you can get a larger data set in one invocation.
It would be very, very useful if the cmdlets generated using AutoRest had automatic support for JSON batching internally, and supported multiple inputs externally, such that you could invoke Get-MgUserManager and pass in multiple UserId values and internally it would batch the request and pass it along to the back end, returning the entire set of managers that I want in one go. This would make the cmdlets work more like other PowerShell cmdlets do, allowing you to request a single record or an entire dataset depending on what you pass in.
Please consider this enhancement request for the Graph PowerShell module bundle. AB#7432
Another option here would be to generate cmdlets that wrap the $batch endpoint, and include a common switch parameter for all graph cmdlets (where it makes sense) called -OutputRequestParameters that would output a hashtable containing the request parameters (all but id) for the command without invoking that command. Then I could mix and match my requests myself, and do something like this:
$managerRequest = Get-MgUserManager -UserId $userId -OutputRequestParameters
$updateUserRequest = Update-MgUserSetting -UserId $userId -ContributionToContentDiscoveryAsOrganizationDisabled -OutputRequestParameters
$removeGroupRequest = Remove-MgGroup -GroupId $groupId -OutputRequestParameters
Invoke-MgBatchRequest -Request $managerRequest,$updateUserRequest,$removeGroupRequest
Internally the Invoke-MgBatchRequest would assign batch IDs based on the order in which I passed in the batch request parameters. That would solve this problem nicely, and generically for any Graph command.
To support batching, all generated cmdlets will need to have a parameterSet (overload) that:
- Does not serialize request bodies.
- Does not make HTTP requests.
- Returns native HTTP request objects instead of models.
AutoREST PowerShell does not currently support this. We will need to add this as an AutoREST feature request.
@peombwa couldn't this be done with the HTTPPipelinePrepend portion? You could append a middleware that outputs as @KirkMunro mentions and exits early without running the actual command.
Example: Azure/autorest.powershell#21
Obviously you would do this at the C# level but you get the idea.
$GLOBAL:BatchRequests = @()
Get-MgGroup -ErrorAction SilentlyContinue -Search 'Something' -Top 50 -ExpandProperty 'Members' -ConsistencyLevel Eventual -HttpPipelinePrepend {
param($req, $callback, $next)
$reqJson = $req | ConvertTo-Json
Write-Host -Fore Magenta $reqJson
$GLOBAL:BatchRequests += $reqJson
return #Insert Faked Empty Body Here?
}
Get-MgGroup -ErrorAction SilentlyContinue -Search 'SomethingElse' -Top 25 -ExpandProperty 'Members' -ConsistencyLevel Eventual -HttpPipelinePrepend {
param($req, $callback, $next)
$reqJson = $req | ConvertTo-Json
Write-Host -Fore Magenta $reqJson
$GLOBAL:BatchRequests += $reqJson
return #Insert Faked Empty Body Here?
}
Write-Host -fore green "Starting Batch Request with:"
Write-Host -fore magenta $BatchRequests
Result
Starting Batch Request with:
{
"Version": {
"Major": 1,
"Minor": 1,
"Build": -1,
"Revision": -1,
"MajorRevision": -1,
"MinorRevision": -1
},
"VersionPolicy": 0,
"Content": null,
"Method": {
"Method": "GET"
},
"RequestUri": "https://graph.microsoft.com/v1.0/groups?$top=50&$search=%22Something%22&Expand=Members",
"Headers": [
{
"Key": "ConsistencyLevel",
"Value": "Eventual"
}
],
"Properties": [],
"Options": []
} {
"Version": {
"Major": 1,
"Minor": 1,
"Build": -1,
"Revision": -1,
"MajorRevision": -1,
"MinorRevision": -1
},
"VersionPolicy": 0,
"Content": null,
"Method": {
"Method": "GET"
},
"RequestUri": "https://graph.microsoft.com/v1.0/groups?$top=25&$search=%22SomethingElse%22&Expand=Members",
"Headers": [
{
"Key": "ConsistencyLevel",
"Value": "Eventual"
}
],
"Properties": [],
"Options": []
}
You might have an issue with AssemblyLoadContext, as trying get-mguser after get-mggroup I get what I like to call "the most confusing error message ever" that I know is associated with assemblyloadcontext type conflicts.
Cannot bind parameter 'HttpPipelinePrepend'. Cannot convert the "Microsoft.Graph.PowerShell.Runtime.SendAsyncStep" value of type "Microsoft.Graph.PowerShell.Runtime.SendAsyncStep" to type "Microsoft.Graph.PowerShell.Runtime.SendAsyncStep".
If you swap them, user works, then group fails. In a C# context you can probably build the request in the context of the command and avoid the conflict.
@peombwa 1 month follow-up
Sorry for the late response.
@JustinGrote, this is still an open feature request for AutoREST.PowerShell. The code generator will need to provide us with a parameterSet that returns an HttpRequestMessage object without making a call to the service.
-HTTPPipelinePrepend is for scenarios where you need to intercept a call but let the command continue with the request, e.g., playing recorded tests when mocking - https://github.com/Azure/autorest.powershell/blob/main/powershell/resources/psruntime/HttpPipelineMocking.ps1#L110. The suggested approach throws a null reference exception because you are not returning the response task. One can still access the suppressed error in the $Error variable despite the SilentlyContinue ErrorAction.
The type error is expected since this isn't how -HTTPPipelinePrepend is expected to be used. Each module/dll has its own unique implementation of SendAsync (under the same namespace). Loading them all at once in one session will result in an error.
@peombwa Would you share the swagger of the batch API with me?
@dolauli, there isn't a swagger representation for the Microsoft Graph batch; it is just available as a REST endpoint https://docs.microsoft.com/en-us/graph/json-batching#first-json-batch-request.
As a reminder, graph supports -scope process, so you can use foreach -parallel (or other runspace techniques) and share the same session pretty effectively with minimal state hydration. This will get you 90% of the performance of using batching anyways but without all the implementation headache.
With the newly available support for HTTP/2 on Graph, I don't think this will dramatically improve the experience. I would close as not planned.