pnpframework icon indicating copy to clipboard operation
pnpframework copied to clipboard

[BUG] Tenant Template with Team Site with group is not working with App Only Context.

Open kavaghela opened this issue 3 years ago • 0 comments

Tenant Template with Team Site Group is not working with App Only Context.

I am using below configuration for Site Collection Node.

<?xml version="1.0"?>
<pnp:Provisioning xmlns:pnp="http://schemas.dev.office.com/PnP/2021/03/ProvisioningSchema">
  <pnp:Preferences Generator="PnP.Framework, Version=1.7.3.0, Culture=neutral, PublicKeyToken=0d501f89f11b748c">
    <pnp:Parameters>
      <pnp:Parameter Key="SITEURL"></pnp:Parameter>
      <pnp:Parameter Key="SITETITLE"></pnp:Parameter>
	  <pnp:Parameter Key="SITEOWNER"></pnp:Parameter>
    </pnp:Parameters>
  </pnp:Preferences>       
  <pnp:Sequence ID="TENANTSEQUENCE">
    <pnp:SiteCollections>
      <pnp:SiteCollection d4p1:type="pnp:TeamSite" ProvisioningId="TEAM.SITE.01" Title="{parameter:SITETITLE}" Description="" IsHubSite="false" Alias="{parameter:SITEURL}" DisplayName="{parameter:SITETITLE}" IsPublic="false" Language="1031" xmlns:d4p1="http://www.w3.org/2001/XMLSchema-instance">        
      </pnp:SiteCollection>
    </pnp:SiteCollections>
  </pnp:Sequence>  
</pnp:Provisioning>

I am using below method to apply pnp tenant template

                var xMLTemplateProvider = new XMLOpenXMLTemplateProvider(openXMLConnector);
                var tenantProvisioningHierarchy = xMLTemplateProvider.GetHierarchy();
                tenantProvisioningHierarchy.Connector = xMLTemplateProvider.Connector;
                using (var pnpProvisioningContext = new PnPProvisioningContext(
                    (resource, scope) =>
                    {
                        if (resource == string.Format("{0}.sharepoint.com", tenantName).ToLower())
                        {
                            log.LogInformation("{0} - SharePoint - {1}", resource, correlationid);
                            return Task.FromResult(sharePointAccessToken);
                        }
                        else
                        {
                            log.LogInformation("{0} - Graph - {1}", resource, correlationid);
                            return Task.FromResult(graphAccessToken);
                        }

                    }))
                {

                    tenantProvisioningHierarchy.Parameters["SITETITLE"] = workspace.GroupTitle;                 
                    tenantProvisioningHierarchy.Parameters["SITEURL"] = workspace.FullUrl;
                    tenantProvisioningHierarchy.Parameters["SITEOWNER"] = workspace.Owner.LoginName;                        

                    


                    Tenant tenant = new Tenant(adminContext);
                    var applyConfiguration = new ApplyConfiguration();
                    applyConfiguration.FileConnector = xMLTemplateProvider.Connector;
                    applyConfiguration.SiteProvisionedDelegate = (title, url) =>
                    {
                        log.LogInformation("{0} - {1} - {2}", title, url, correlationid);
                    };

                    applyConfiguration.MessagesDelegate = (message, type) =>
                    {
                        log.LogInformation("{0} - {1} - {2}", type.ToString(), message, correlationid);
                    };
                    applyConfiguration.ProgressDelegate = (message, step, total) =>
                    {
                        log.LogInformation("{0} - {1} - {2} - {3}", step, message, total, correlationid);
                    };
                    var provisioningTemplates = tenantProvisioningHierarchy.Templates;
                    int count = provisioningTemplates.Count;                    

                    log.LogInformation("Apply template is in progress... - {0}", correlationid);
                    foreach (var seq in tenantProvisioningHierarchy.Sequences)
                    {
                        try
                        {
                            var tenantProvisioningHierarchySite = tenantProvisioningHierarchy;
                            tenant.ApplyTenantTemplate(tenantProvisioningHierarchy, seq.ID, applyConfiguration);
                        }
                        catch (Exception ex)
                        {
                            if (ex != null)
                            {
                                tenant.ApplyTenantTemplate(tenantProvisioningHierarchy, seq.ID, applyConfiguration);
                            }
                        }
                    }

                    log.LogInformation("Apply template is in progress is done - {0}", correlationid);                                       
                }

Expected behavior

It should apply pnp tenant template in first try only.

Actual behavior

Appy PnP Tenant Template fails in first try

What is the version of the Cmdlet module you are running?

PnP.Framwork 1.10.0

Which operating system/environment are you running PnP PowerShell on?

  • [X] Windows
  • [X] Azure Functions

Note: I am using Self-signed certificate for getting access token

I have checked code for site provisioning and below are the finding In Site Collection Node we can't pass Owner as it will not work for template type "TeamSite". As per Site Creation code, it tries to add Owner after creating O365 group and there it is passing Owner object which will be always null.

Code snippet for Site Creation from PnP.Framework

public static async Task<ClientContext> CreateTeamSiteViaGraphAsync(ClientContext clientContext, TeamSiteCollectionCreationInformation siteCollectionCreationInformation,
            int delayAfterCreation = 0,
            int maxRetryCount = 12, // Maximum number of retries (12 x 10 sec = 120 sec = 2 mins)
            int retryDelay = 1000 * 10, // Wait time default to 10sec,
            bool noWait = false,
            string graphAccessToken = null,
            AzureEnvironment azureEnvironment = AzureEnvironment.Production
            )
        {
            ClientContext responseContext = null;


            Guid sensitivityLabelId = Guid.Empty;
            if (siteCollectionCreationInformation.SensitivityLabelId != Guid.Empty)
            {
                sensitivityLabelId = siteCollectionCreationInformation.SensitivityLabelId;
            }
            else if (!string.IsNullOrEmpty(siteCollectionCreationInformation.SensitivityLabel))
            {
                sensitivityLabelId = await GetSensitivityLabelId(clientContext, siteCollectionCreationInformation.SensitivityLabel);
            }

            var group = Graph.UnifiedGroupsUtility.CreateUnifiedGroup(
                siteCollectionCreationInformation.DisplayName,
                siteCollectionCreationInformation.Description,
                siteCollectionCreationInformation.Alias,
                graphAccessToken,
                **siteCollectionCreationInformation.Owners**,
                null, // No members
                isPrivate: !siteCollectionCreationInformation.IsPublic,
                createTeam: false,
                retryCount: maxRetryCount,
                delay: retryDelay,
                azureEnvironment: azureEnvironment,
                preferredDataLocation: siteCollectionCreationInformation.PreferredDataLocation,
                assignedLabels: new Guid[] { sensitivityLabelId });

            if (group != null && !string.IsNullOrEmpty(group.SiteUrl))
            {
                Graph.UnifiedGroupsUtility.**AddUnifiedGroupMembers**(group.GroupId, siteCollectionCreationInformation.**Owners**, graphAccessToken);
                // Try to configure the site/group classification, if any
                if (!string.IsNullOrEmpty(siteCollectionCreationInformation.Classification))
                {
                    await SetTeamSiteClassification(
                        siteCollectionCreationInformation.Classification,
                        group.GroupId,
                        graphAccessToken
                        );
                }

                responseContext = clientContext.Clone(group.SiteUrl);
            }

            return responseContext;
        }
public static void AddUnifiedGroupMembers(string groupId, string[] members, string accessToken, bool removeExistingMembers = false, int retryCount = 10, int delay = 500, AzureEnvironment azureEnvironment = AzureEnvironment.Production)
       {
           if (String.IsNullOrEmpty(accessToken))
           {
               throw new ArgumentNullException(nameof(accessToken));
           }

           try
           {
               Task.Run(async () =>
               {
                   var graphClient = CreateGraphClient(accessToken, retryCount, delay, azureEnvironment);

                   await UpdateMembers(**members**, graphClient, groupId, removeExistingMembers);

               }).GetAwaiter().GetResult();
           }
           catch (ServiceException ex)
           {
               Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message);
               throw;
           }
       }

And below method does not check null condition before using members object hence it fails.

private static async Task UpdateMembers(string[] members, GraphServiceClient graphClient, string groupId, bool removeOtherMembers)
        {
            foreach (var m in **members**)
            {
                // Search for the user object
                var memberQuery = await graphClient.Users
                    .Request()
                    .Filter($"userPrincipalName eq '{Uri.EscapeDataString(m.Replace("'", "''"))}'")
                    .GetAsync();

                var member = memberQuery.FirstOrDefault();

                if (member != null)
                {
                    try
                    {
                        // And if any, add it to the collection of group's owners
                        await graphClient.Groups[groupId].Members.References.Request().AddAsync(member);
                    }
                    catch (Exception ex)
                    {
                        if (ex.Message.Contains("Request_BadRequest") &&
                            ex.Message.Contains("added object references already exist"))
                        {
                            // Skip any already existing member
                        }
                        else
                        {
#pragma warning disable CA2200
                            throw ex;
#pragma warning restore CA2200
                        }
                    }
                }
            }

kavaghela avatar Jul 20 '22 08:07 kavaghela