Skip to content

Commit

Permalink
Fixes Copy-PnPList throwing unauthorized exception when used with a…
Browse files Browse the repository at this point in the history
… non SPO admin user (#2054)

* Fixes #1927

* Adding PR reference

Co-authored-by: = <=>
  • Loading branch information
KoenZomers authored Jun 29, 2022
1 parent c8c9f86 commit d0cbcc3
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
- Fixed `Add-PnPListItem` not showing field name when it has an improper value assigned to it [#2002](https://github.com/pnp/powershell/pull/2002)
- Fixed connecting using `Connect-PnPOnline -Interactive -ClientId` not working well when already having an App-Only connection using the same ClientId [#2035](https://github.com/pnp/powershell/pull/2035)
- Fixed cmdlets inheriting from PnPAdminCmdlet not working well on vanity domain SharePoint Online tenants [#2052](https://github.com/pnp/powershell/pull/2052)
- Fixed `Copy-PnPList` throwing an unauthorized exception when using it with a non SharePoint Online administrator user [#2054](https://github.com/pnp/powershell/pull/2054)

### Removed

Expand Down
1 change: 0 additions & 1 deletion src/Commands/Base/PnPWebCmdlet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Management.Automation;
using Microsoft.SharePoint.Client;


namespace PnP.PowerShell.Commands
{
public abstract class PnPWebCmdlet : PnPSharePointCmdlet
Expand Down
33 changes: 14 additions & 19 deletions src/Commands/Lists/CopyList.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System.Management.Automation;
using Microsoft.SharePoint.Client;
using PnP.PowerShell.Commands.Base;
using PnP.PowerShell.Commands.Base.PipeBinds;
using Microsoft.Online.SharePoint.TenantAdministration;
using System;
using System.Text.RegularExpressions;
using System.Linq;
using PnP.PowerShell.Commands.Utilities.REST;
using PnP.PowerShell.Commands.Model.SharePoint;

namespace PnP.PowerShell.Commands.Lists
{
Expand Down Expand Up @@ -62,18 +62,10 @@ protected override void ExecuteCmdlet()

// Generate a site script from the list that needs to be copied
WriteVerbose($"Generating script from list at {SourceListUrl}");
var scriptRequest = Tenant.GetSiteScriptFromList(ClientContext, SourceListUrl);
try
{
ClientContext.ExecuteQueryRetry();
}
catch (Microsoft.SharePoint.Client.ServerException e) when (e.ServerErrorTypeName == "System.IO.FileNotFoundException")
{
throw new PSArgumentException($"List provided through {nameof(SourceListUrl)} could not be found", nameof(SourceListUrl));
}
var generatedScript = RestHelper.PostAsync<RestResult<string>>(Connection.HttpClient, $"{Connection.Url}/_api/Microsoft.Sharepoint.Utilities.WebTemplateExtensions.SiteScriptUtility.GetSiteScriptFromList()", ClientContext, new { listUrl = SourceListUrl}).GetAwaiter().GetResult();

// Take the site script of the list to copy
var script = scriptRequest.Value;
var script = generatedScript.Content;

if (ParameterSpecified(nameof(Title)) && !string.IsNullOrWhiteSpace(Title))
{
Expand All @@ -96,31 +88,34 @@ protected override void ExecuteCmdlet()

// Execute site script on destination site so the list will be created
WriteVerbose($"Executing site script to site at {DestinationWebUrl}");
var actionResults = PnP.PowerShell.Commands.Utilities.SiteTemplates.InvokeSiteScript(Connection, AccessToken, script, DestinationWebUrl).GetAwaiter().GetResult().Items.ToArray();
var actionResults = RestHelper.PostAsync<RestResultCollection<InvokeSiteScriptActionResponse>>(Connection.HttpClient, $"{Connection.Url}/_api/Microsoft.Sharepoint.Utilities.WebTemplateExtensions.SiteScriptUtility.ExecuteTemplateScript()", ClientContext, new { script = script}).GetAwaiter().GetResult();

// Ensure site script actions have been executed
if(actionResults.Length == 0)
if(actionResults.Items.Count() == 0)
{
throw new PSInvalidOperationException($"List copy failed. No site script actions have been executed.");
}

// Display the results of each action in verbose
foreach(var actionResult in actionResults)
foreach(var actionResult in actionResults.Items)
{
WriteVerbose($"Action {actionResult.Title} {(actionResult.ErrorCode != 0 ? $"failed: {actionResult.OutcomeText}" : "succeeded")}");
}

// Ensure the list creation succeeded
if(actionResults[0].ErrorCode != 0)
if(actionResults.Items.ElementAt(0).ErrorCode != 0)
{
throw new PSInvalidOperationException($"List copy failed with error {actionResults[0].OutcomeText}");
throw new PSInvalidOperationException($"List copy failed with error {actionResults.Items.ElementAt(0).OutcomeText}");
}

// Create a ClientContext to the web where the list has been created
//Create a ClientContext to the web where the list has been created
var destinationContext = ClientContext.Clone(DestinationWebUrl);

// Retrieve the newly created list
var createdList = destinationContext.Web.Lists.GetById(Guid.Parse(actionResults[0].TargetId));
var newListId = actionResults.Items.ElementAt(0).TargetId;

WriteVerbose($"Retrieving newly created list hosted in {DestinationWebUrl} with ID {newListId}");
var createdList = destinationContext.Web.Lists.GetById(Guid.Parse(newListId));
destinationContext.Load(createdList, l => l.Id, l => l.BaseTemplate, l => l.OnQuickLaunch, l => l.DefaultViewUrl, l => l.Title, l => l.Hidden, l => l.ContentTypesEnabled, l => l.RootFolder.ServerRelativeUrl);
destinationContext.ExecuteQuery();

Expand Down
13 changes: 13 additions & 0 deletions src/Commands/Model/SharePoint/SiteScriptFromList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace PnP.PowerShell.Commands.Model.SharePoint
{
/// <summary>
/// Contains the information regarding a site script which was generated from an existing list
/// </summary>
public class SiteScriptFromList
{
/// <summary>
/// The site script
/// </summary>
public string GetSiteScriptFromList { get; set; }
}
}
5 changes: 3 additions & 2 deletions src/Commands/Utilities/REST/RestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading;
Expand Down Expand Up @@ -223,7 +224,7 @@ public static async Task<string> PostAsync(HttpClient httpClient, string url, st
HttpRequestMessage message = null;
if (payload != null)
{
var content = new StringContent(JsonSerializer.Serialize(payload, new JsonSerializerOptions() { IgnoreNullValues = true }));
var content = new StringContent(JsonSerializer.Serialize(payload, new JsonSerializerOptions { IgnoreNullValues = true }));
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
message = GetMessage(url, HttpMethod.Post, accessToken, accept, content);
}
Expand Down Expand Up @@ -591,7 +592,7 @@ private static HttpRequestMessage GetMessage(string url, HttpMethod method, Clie
var message = new HttpRequestMessage();
message.Method = method;
message.RequestUri = new Uri(url);
message.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue(accept));
message.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse(accept));
PnP.Framework.Http.PnPHttpClient.AuthenticateRequestAsync(message, clientContext).GetAwaiter().GetResult();
if (method == HttpMethod.Post || method == HttpMethod.Put || method.Method == "PATCH")
{
Expand Down
17 changes: 17 additions & 0 deletions src/Commands/Utilities/REST/RestResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Text.Json.Serialization;

namespace PnP.PowerShell.Commands.Utilities.REST
{
/// <summary>
/// Contains a single result
/// </summary>
/// <typeparam name="T">Model type to map the content to</typeparam>
public class RestResult<T>
{
/// <summary>
/// The content contained in the results
/// </summary>
[JsonPropertyName("value")]
public T Content { get; set; }
}
}

0 comments on commit d0cbcc3

Please sign in to comment.