diff --git a/CHANGELOG.md b/CHANGELOG.md index 12c76d1f6..8c2df5195 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - Added `-Contributors` and `-Managers` parameters to `New-PnPTermGroup` and `Set-PnPTermGroup` cmdlets. - Added `-Files` parameter for `Send-PnPMail` cmdlet to allow files to be downloaded from SharePoint and then sent as attachments. - Added `-Force` parameter to `Set-PnPPropertyBagValue` cmdlet to toggle NoScript status of the site. +- Added `-Batch` parameter to `Invoke-PnPGraphMethod` cmdlet to allow adding request in a batch. ### Changed diff --git a/documentation/Invoke-PnPGraphMethod.md b/documentation/Invoke-PnPGraphMethod.md index 3b2a405fc..a4d1372d3 100644 --- a/documentation/Invoke-PnPGraphMethod.md +++ b/documentation/Invoke-PnPGraphMethod.md @@ -54,6 +54,19 @@ Invoke-PnPGraphMethod -Url [-Verbose] ``` +### Batch +```powershell +Invoke-PnPGraphMethod -Url + [-AdditionalHeaders GraphAdditionalHeadersPipeBind] + [[-Method] ] + [-Content ] + [-ContentType ] + [-ConsistencyLevelEventual] + [-Connection ] + [-Batch ] + [-Verbose] +``` + ## DESCRIPTION Invokes a REST request towards the Microsoft Graph API. It will take care of potential throttling retries that are needed to retrieve the data. @@ -120,6 +133,18 @@ Invoke-PnPGraphMethod -Url "https://graph.microsoft.com/v1.0/planner/tasks/23fas This example retrieves a Planner task to find the etag value which is required to update the task. In order to update the task through call to the Microsoft Graph API we need to include an If-Match header with the value of the etag. It then creates the content to update, in this case the title of the task, and calls the PATCH method on the Graph end-point to update the specific task. +### EXAMPLE 9 +```powershell +$batch = New-PnPBatch -RetainRequests +Invoke-PnPSPRestMethod -Method Get -Url "https://graph.microsoft.com/v1.0/users" -Batch $batch +Invoke-PnPSPRestMethod -Method Get -Url "https://graph.microsoft.com/v1.0/groups" -Batch $batch +$response = Invoke-PnPBatch $batch -Details +$response +``` + +This example executes a GET request to get all users and a groups in a single batch request. +It is necessary to create and invoke batch requests in the manner specified here if you want to process something later on with the response object. + ## PARAMETERS ### -AdditionalHeaders @@ -290,6 +315,21 @@ Accept pipeline input: True (ByValue) Accept wildcard characters: False ``` +### -Batch + +The batch to add this request to. + +```yaml +Type: PnPBatch +Parameter Sets: Batched + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ## RELATED LINKS [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) \ No newline at end of file diff --git a/documentation/Invoke-PnPSPRestMethod.md b/documentation/Invoke-PnPSPRestMethod.md index 87ce65259..f7a0c0341 100644 --- a/documentation/Invoke-PnPSPRestMethod.md +++ b/documentation/Invoke-PnPSPRestMethod.md @@ -190,6 +190,21 @@ Position: Named Accept pipeline input: False ``` +### -Batch + +The batch to add this request to. + +```yaml +Type: PnPBatch +Parameter Sets: Batched + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ## RELATED LINKS [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) diff --git a/src/Commands/Base/InvokeSPRestMethod.cs b/src/Commands/Base/InvokeSPRestMethod.cs index 9ca54ee44..675f070f0 100644 --- a/src/Commands/Base/InvokeSPRestMethod.cs +++ b/src/Commands/Base/InvokeSPRestMethod.cs @@ -38,17 +38,17 @@ public class InvokeSPRestMethod : PnPSharePointCmdlet [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Parsed)] [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Raw)] - [Parameter(Mandatory = false, Position = 0, ParameterSetName = PARAMETERSET_Batch)] + [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Batch)] public object Content; [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Parsed)] [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Raw)] - [Parameter(Mandatory = false, Position = 0, ParameterSetName = PARAMETERSET_Batch)] + [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Batch)] public string ContentType = "application/json"; [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Parsed)] [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Raw)] - [Parameter(Mandatory = false, Position = 0, ParameterSetName = PARAMETERSET_Batch)] + [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Batch)] public string Accept = "application/json;odata=nometadata"; [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Raw)] @@ -58,7 +58,7 @@ public class InvokeSPRestMethod : PnPSharePointCmdlet [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Raw)] public string ResponseHeadersVariable; - [Parameter(Mandatory = false, Position = 0, ParameterSetName = PARAMETERSET_Batch)] + [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Batch)] public PnPBatch Batch; protected override void ExecuteCmdlet() diff --git a/src/Commands/Graph/InvokeGraphMethod.cs b/src/Commands/Graph/InvokeGraphMethod.cs index e0222682a..feaca61c8 100644 --- a/src/Commands/Graph/InvokeGraphMethod.cs +++ b/src/Commands/Graph/InvokeGraphMethod.cs @@ -1,15 +1,18 @@ -using PnP.Framework.Utilities; +using PnP.Core.Model; +using PnP.Core.Services; +using PnP.Framework.Utilities; +using PnP.PowerShell.Commands.Base.PipeBinds; using PnP.PowerShell.Commands.Enums; +using PnP.PowerShell.Commands.Model; using PnP.PowerShell.Commands.Utilities.REST; using System; -using System.Management.Automation; -using System.Text.Json; using System.Collections.Generic; +using System.IO; +using System.Management.Automation; using System.Net.Http; using System.Net.Http.Headers; +using System.Text.Json; using System.Text.Json.Serialization; -using System.IO; -using PnP.PowerShell.Commands.Base.PipeBinds; namespace PnP.PowerShell.Commands.Base { @@ -19,9 +22,11 @@ public class InvokeGraphMethod : PnPGraphCmdlet private const string ParameterSet_TOSTREAM = "Out to stream"; private const string ParameterSet_TOFILE = "Out to file"; private const string ParameterSet_TOCONSOLE = "Out to console"; + public const string PARAMETERSET_Batch = "Batch"; [Parameter(Mandatory = false, ParameterSetName = ParameterSet_TOFILE)] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_TOCONSOLE)] + [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Batch)] public HttpRequestMethod Method = HttpRequestMethod.Get; private string _url; @@ -29,6 +34,7 @@ public class InvokeGraphMethod : PnPGraphCmdlet [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ParameterSetName = ParameterSet_TOFILE)] [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ParameterSetName = ParameterSet_TOCONSOLE)] [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ParameterSetName = ParameterSet_TOSTREAM)] + [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ParameterSetName = PARAMETERSET_Batch)] public string Url { get { return _url; } @@ -53,25 +59,29 @@ public string Url [Parameter(Mandatory = false, ParameterSetName = ParameterSet_TOFILE)] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_TOCONSOLE)] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_TOSTREAM)] + [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Batch)] public object Content; [Parameter(Mandatory = false, ParameterSetName = ParameterSet_TOFILE)] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_TOCONSOLE)] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_TOSTREAM)] + [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Batch)] public string ContentType = "application/json"; [Parameter(Mandatory = false, ParameterSetName = ParameterSet_TOFILE)] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_TOCONSOLE)] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_TOSTREAM)] + [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Batch)] public GraphAdditionalHeadersPipeBind AdditionalHeaders = new(new Dictionary()); [Parameter(Mandatory = false, ParameterSetName = ParameterSet_TOFILE)] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_TOCONSOLE)] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_TOSTREAM)] + [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Batch)] public SwitchParameter ConsistencyLevelEventual; [Parameter(Mandatory = false, ParameterSetName = ParameterSet_TOCONSOLE)] - public SwitchParameter Raw; + public SwitchParameter Raw; [Parameter(Mandatory = false, ParameterSetName = ParameterSet_TOCONSOLE)] public SwitchParameter All; @@ -82,11 +92,21 @@ public string Url [Parameter(Mandatory = true, ParameterSetName = ParameterSet_TOSTREAM)] public SwitchParameter OutStream; + [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Batch)] + public PnPBatch Batch; + protected override void ExecuteCmdlet() { try { - SendRequest(); + if (ParameterSpecified(nameof(Batch))) + { + CallBatchRequest(new HttpMethod(Method.ToString().ToUpper()), Url); + } + else + { + SendRequest(); + } } catch (Exception ex) { @@ -123,7 +143,7 @@ private void SendRequest() switch (Method) { case HttpRequestMethod.Get: - if(ParameterSetName == ParameterSet_TOCONSOLE) + if (ParameterSetName == ParameterSet_TOCONSOLE) { GetRequestWithPaging(); } @@ -235,7 +255,7 @@ private void GetRequestWithPaging() WriteObject(rootObj); } } - + private void GetRequestWithoutPaging() { WriteVerbose($"Sending HTTP GET to {Url}"); @@ -298,7 +318,7 @@ private void HandleResponse(HttpResponseMessage response) case ParameterSet_TOSTREAM: var responseStream = response.Content.ReadAsStream(); - + WriteVerbose($"Writing {responseStream.Length} bytes response to outputstream"); var memoryStream = new MemoryStream(); @@ -315,5 +335,40 @@ private void HandleResponse(HttpResponseMessage response) throw new Exception($"Parameter set {ParameterSetName} not supported"); } } + + private void CallBatchRequest(HttpMethod method, string requestUrl) + { + var web = Connection.PnPContext.Web; + string contentString = null; + if (ParameterSpecified(nameof(Content))) + { + contentString = Content is string ? Content.ToString() : + JsonSerializer.Serialize(Content, new JsonSerializerOptions() { ReferenceHandler = ReferenceHandler.IgnoreCycles, WriteIndented = true }); + + } + + Dictionary extraHeaders = AdditionalHeaders?.GetHeaders(ConsistencyLevelEventual.IsPresent); + extraHeaders.Add("Content-Type", ContentType); + + ApiRequestType apiRequestType = ApiRequestType.Graph; + if (requestUrl.IndexOf("/beta/", StringComparison.InvariantCultureIgnoreCase) > -1) + { + apiRequestType = ApiRequestType.GraphBeta; + } + + if (requestUrl.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase)) + { + if (requestUrl.IndexOf("/v1.0/", StringComparison.InvariantCultureIgnoreCase) > -1) + { + requestUrl = requestUrl.Replace($"https://{Connection.GraphEndPoint}/v1.0/", "", StringComparison.InvariantCultureIgnoreCase); + } + else if (requestUrl.IndexOf("/beta/", StringComparison.InvariantCultureIgnoreCase) > -1) + { + requestUrl = requestUrl.Replace($"https://{Connection.GraphEndPoint}/beta/", "", StringComparison.InvariantCultureIgnoreCase); + } + } + + web.WithHeaders(extraHeaders).ExecuteRequestBatch(Batch.Batch, new ApiRequest(method, apiRequestType, requestUrl, contentString)); + } } } \ No newline at end of file