[BUG] Tenant Template with Team Site with group is not working with App Only Context.
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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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