pnp / pnp/pnpframework

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

Open
#697 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C#
Stars
259
Forks
161
Avg merge
10h 33m
Merged PRs (30d)
1

Description

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?
  • Windows
  • 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
                        }
                    }
                }
            }

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at CreateTeamSiteViaGraphAsync and follow the Owners value into AddUnifiedGroupMembers and UpdateMembers, where the reported app-only tenant-template failure occurs. Reproduce the TeamSite provisioning flow and verify that the tenant template applies successfully on its first attempt without the null-member failure.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
api, backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.