diff --git a/CHANGELOG.md b/CHANGELOG.md index e7fa14d50..ce8d1fe1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - Added `RequestFilesLinkEnabled` and `RequestFilesLinkExpirationInDays` to the output of `Get-PnPSite` [#3557](https://github.com/pnp/powershell/pull/3557) - Added `CoreRequestFilesLinkEnabled`, `CoreRequestFilesLinkExpirationInDays`, `OneDriveRequestFilesLinkEnabled`, `OneDriveRequestFilesLinkExpirationInDays`, `BusinessConnectivityServiceDisabled` to the output of `Get-PnPTenant` [#3557](https://github.com/pnp/powershell/pull/3557) - Added `-BusinessConnectivityServiceDisabled` parameter to `Set-PnPTenant` cmdlt to allow disabling the Business Connectivity Service [#3562](https://github.com/pnp/powershell/pull/3562) +- Added support for executing the 'Invoke-PnPSPRestMethod' cmdlet in a batch [#3565](https://github.com/pnp/powershell/pull/3565) - Added `Get-PnPSiteSetVersionPolicyProgress` cmdlet which allows for getting the progress of setting a version policy for existing document libraries on a site [#3564](https://github.com/pnp/powershell/pull/3564) ### Fixed diff --git a/documentation/Invoke-PnPSPRestMethod.md b/documentation/Invoke-PnPSPRestMethod.md index 6728791dd..87ce65259 100644 --- a/documentation/Invoke-PnPSPRestMethod.md +++ b/documentation/Invoke-PnPSPRestMethod.md @@ -22,6 +22,7 @@ Invoke-PnPSPRestMethod -Url [-Raw] [-Connection ] [-ResponseHeadersVariable ] + [-Batch ] ``` ## DESCRIPTION @@ -79,6 +80,19 @@ This example executes a GET request towards the current site collection and retu It will also store the response headers values in the PowerShell variable name that you specify. Enter a variable name without the dollar sign ($) symbol. +### EXAMPLE 7 +```powershell +$batch = New-PnPBatch -RetainRequests +Invoke-PnPSPRestMethod -Method Get -Url "https://tenant.sharepoint.com/sites/mysite/_api/web/lists" -Batch $batch +$item = "{'Title':'Test'}" +Invoke-PnPSPRestMethod -Method Post -Url "https://tenant.sharepoint.com/sites/mysite/_api/web/lists/GetByTitle('Test')/items" -Content $item -Batch $batch +$response = Invoke-PnPBatch $batch -Details +$response +``` + +This example executes a GET request to get all lists and a POST request to add an item to a list 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 ### -Content diff --git a/src/Commands/Base/InvokeSPRestMethod.cs b/src/Commands/Base/InvokeSPRestMethod.cs index 82bfad13d..8ad164b6b 100644 --- a/src/Commands/Base/InvokeSPRestMethod.cs +++ b/src/Commands/Base/InvokeSPRestMethod.cs @@ -1,7 +1,10 @@ using Microsoft.SharePoint.Client; +using PnP.Core.Model; +using PnP.Core.Services; using PnP.Framework.Http; using PnP.Framework.Utilities; using PnP.PowerShell.Commands.Enums; +using PnP.PowerShell.Commands.Model; using System; using System.Collections.Generic; using System.Linq; @@ -16,29 +19,36 @@ namespace PnP.PowerShell.Commands.Admin [Cmdlet(VerbsLifecycle.Invoke, "PnPSPRestMethod", DefaultParameterSetName = PARAMETERSET_Parsed)] [OutputType(typeof(PSObject), ParameterSetName = new[] { PARAMETERSET_Parsed })] [OutputType(typeof(string), ParameterSetName = new[] { PARAMETERSET_Raw })] + [OutputType(typeof(void), ParameterSetName = new[] { PARAMETERSET_Batch })] public class InvokeSPRestMethod : PnPSharePointCmdlet { public const string PARAMETERSET_Parsed = "Parsed"; public const string PARAMETERSET_Raw = "Raw"; + public const string PARAMETERSET_Batch = "Batch"; [Parameter(Mandatory = false, Position = 0, ParameterSetName = PARAMETERSET_Parsed)] [Parameter(Mandatory = false, Position = 0, ParameterSetName = PARAMETERSET_Raw)] + [Parameter(Mandatory = false, Position = 0, ParameterSetName = PARAMETERSET_Batch)] public HttpRequestMethod Method = HttpRequestMethod.Get; [Parameter(Mandatory = true, Position = 0, ParameterSetName = PARAMETERSET_Parsed)] [Parameter(Mandatory = true, Position = 0, ParameterSetName = PARAMETERSET_Raw)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = PARAMETERSET_Batch)] public string Url; [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Parsed)] [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Raw)] + [Parameter(Mandatory = false, Position = 0, 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)] 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)] public string Accept = "application/json;odata=nometadata"; [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Raw)] @@ -48,6 +58,9 @@ public class InvokeSPRestMethod : PnPSharePointCmdlet [Parameter(Mandatory = false, ParameterSetName = PARAMETERSET_Raw)] public string ResponseHeadersVariable; + [Parameter(Mandatory = false, Position = 0, ParameterSetName = PARAMETERSET_Batch)] + public PnPBatch Batch; + protected override void ExecuteCmdlet() { if (Url.StartsWith("/")) @@ -56,21 +69,37 @@ protected override void ExecuteCmdlet() Url = UrlUtility.Combine(ClientContext.Url, Url); } - var method = new HttpMethod(Method.ToString()); - - var httpClient = PnPHttpClient.Instance.GetHttpClient(ClientContext); + var method = new HttpMethod(Method.ToString().ToUpper()); var requestUrl = Url; + if (string.IsNullOrEmpty(Accept)) + { + Accept = "application/json;odata=nometadata"; + } + + if (string.IsNullOrEmpty(ContentType)) + { + ContentType = "application/json"; + } + + if (ParameterSpecified(nameof(Batch))) + { + CallBatchRequest(method, requestUrl); + } + else + { + CallSingleRequest(method, requestUrl); + } + } + + private void CallSingleRequest(HttpMethod method, string requestUrl) + { + var httpClient = PnPHttpClient.Instance.GetHttpClient(ClientContext); bool isResponseHeaderRequired = !string.IsNullOrEmpty(ResponseHeadersVariable); using (HttpRequestMessage request = new HttpRequestMessage(method, requestUrl)) { - if (string.IsNullOrEmpty(Accept)) - { - Accept = "application/json;odata=nometadata"; - } - request.Headers.Add("accept", Accept); if (Method == HttpRequestMethod.Merge) @@ -88,10 +117,7 @@ protected override void ExecuteCmdlet() if (Method == HttpRequestMethod.Post || Method == HttpRequestMethod.Merge || Method == HttpRequestMethod.Put || Method == HttpRequestMethod.Patch) { - if (string.IsNullOrEmpty(ContentType)) - { - ContentType = "application/json"; - } + var contentString = Content is string ? Content.ToString() : JsonSerializer.Serialize(Content, new JsonSerializerOptions() { ReferenceHandler = ReferenceHandler.IgnoreCycles, WriteIndented = true }); request.Content = new StringContent(contentString, System.Text.Encoding.UTF8); @@ -150,5 +176,32 @@ protected override void ExecuteCmdlet() } } } + + private void CallBatchRequest(HttpMethod method, string requestUrl) + { + var web = 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 = new() { { "Accept", Accept } }; + + if (Method == HttpRequestMethod.Merge) + { + extraHeaders.Add("X-HTTP-Method", "MERGE"); + } + + if (Method == HttpRequestMethod.Merge || Method == HttpRequestMethod.Delete) + { + extraHeaders.Add("IF-MATCH", "*"); + } + extraHeaders.Add("Content-Type", ContentType); + + web.WithHeaders(extraHeaders).ExecuteRequestBatch(Batch.Batch, new ApiRequest(method, ApiRequestType.SPORest, requestUrl, contentString)); + } } } \ No newline at end of file