/// <summary> /// Applies a Provisioning Template Hierarchy to a target tenant /// </summary> /// <param name="targetSiteUrl">The URL of the target Site Collection</param> /// <param name="tenantTemplate">The Provisioning Template Hierarchy to apply</param> /// <param name="log">The TraceWriter to log activities</param> public static void ApplyTenantTemplate(String targetSiteUrl, ProvisioningHierarchy tenantTemplate, ILogger log) { ProvisioningTemplateApplyingInformation ptai = new ProvisioningTemplateApplyingInformation(); // We exclude Term Groups because they are not supported in AppOnly ptai.HandlersToProcess = Handlers.All; ptai.HandlersToProcess ^= Handlers.TermGroups; ptai.MessagesDelegate += delegate(string message, ProvisioningMessageType messageType) { log.LogDebug($"{messageType} - {message}"); }; ptai.ProgressDelegate += delegate(string message, int step, int total) { log.LogInformation($"{step:00}/{total:00} - {message}"); }; var tenantUrl = SPOUtilities.GetTenantAdministrationUrl(targetSiteUrl); using (var tenantContext = SPOContextProvider.BuildAppOnlyClientContext(tenantUrl)) { using (var pnpTenantContext = PnPClientContext.ConvertFrom(tenantContext)) { var tenant = new Microsoft.Online.SharePoint.TenantAdministration.Tenant(pnpTenantContext); // Prepare a dictionary to hold the access tokens var accessTokens = new Dictionary <String, String>(); // Get the Microsoft Graph Access Token var clientApplication = ConfidentialClientApplicationBuilder .Create(Environment.GetEnvironmentVariable("ClientId")) .WithTenantId(Environment.GetEnvironmentVariable("Tenant")) .WithClientSecret(Environment.GetEnvironmentVariable("ClientSecret")).Build(); try { string[] graphScopes = new string[] { "https://graph.microsoft.com/.default" }; var authenticationResult = clientApplication.AcquireTokenForClient(graphScopes).ExecuteAsync().GetAwaiter().GetResult(); var graphAccessToken = authenticationResult.AccessToken; accessTokens.Add(new Uri("https://graph.microsoft.com/").Authority, graphAccessToken); } catch (Exception ex) { log.LogError(ex.Message); throw; } // Configure the OAuth Access Tokens for the PnPClientContext, too pnpTenantContext.PropertyBag["AccessTokens"] = accessTokens; ptai.AccessTokens = accessTokens; // Define a PnPProvisioningContext scope to share the security context across calls using (var pnpProvisioningContext = new PnPProvisioningContext(async(r, s) => { if (accessTokens.Any(i => i.Key.Equals(r, StringComparison.InvariantCultureIgnoreCase) || r.ToLower().Contains(i.Key))) { // In this scenario we just use the dictionary of access tokens // in fact the overall operation for sure will take less than 1 hour var item = accessTokens.FirstOrDefault(i => i.Key.Equals(r, StringComparison.InvariantCultureIgnoreCase) || r.ToLower().Contains(i.Key)); return(await Task.FromResult(item.Value)); } else { return(null); } })) { log.LogInformation($"Hierarchy Provisioning Started: {DateTime.Now:hh.mm.ss}"); tenant.ApplyProvisionHierarchy(tenantTemplate, null, ptai); log.LogInformation($"Hierarchy Provisioning Completed: {DateTime.Now:hh.mm.ss}"); } } } }
private async Task <CanProvisionResult> CanProvisionInternal(CanProvisionModel model) { var canProvisionResult = new CanProvisionResult(); String provisioningScope = ConfigurationManager.AppSettings["SPPA:ProvisioningScope"]; String provisioningEnvironment = ConfigurationManager.AppSettings["SPPA:ProvisioningEnvironment"]; var tokenId = $"{model.TenantId}-{model.UserPrincipalName.GetHashCode()}-{provisioningScope}-{provisioningEnvironment}"; var graphAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync( tokenId, "https://graph.microsoft.com/"); // Retrieve the provisioning package from the database and from the Blob Storage var context = dbContext; DomainModel.Package package = null; // Get the package if (ProvisioningAppManager.IsTestingEnvironment) { // Process all packages in the test environment package = context.Packages.FirstOrDefault(p => p.Id == new Guid(model.PackageId)); } else { // Process not-preview packages in the production environment package = context.Packages.FirstOrDefault(p => p.Id == new Guid(model.PackageId) && p.Preview == false); } if (package != null) { // Retrieve parameters from the package/template definition var packageFileUrl = new Uri(package.PackageUrl); var packageLocalFolder = packageFileUrl.AbsolutePath.Substring(1, packageFileUrl.AbsolutePath.LastIndexOf('/') - 1); var packageFileName = packageFileUrl.AbsolutePath.Substring(packageLocalFolder.Length + 2); ProvisioningHierarchy hierarchy = GetHierarchyFromStorage(packageLocalFolder, packageFileName); // If we have the hierarchy if (hierarchy != null) { var accessTokens = new Dictionary <String, String>(); AuthenticationManager authManager = new AuthenticationManager(); var ptai = new ProvisioningTemplateApplyingInformation(); // Retrieve the SPO URL for the Admin Site var rootSiteUrl = model.SPORootSiteUrl; // Retrieve the SPO Access Token for SPO var spoAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync( tokenId, rootSiteUrl, ConfigurationManager.AppSettings["ida:ClientId"], ConfigurationManager.AppSettings["ida:ClientSecret"], ConfigurationManager.AppSettings["ida:AppUrl"]); // Store the SPO Access Token for any further context cloning accessTokens.Add(new Uri(rootSiteUrl).Authority, spoAccessToken); // Define a PnPProvisioningContext scope to share the security context across calls using (var pnpProvisioningContext = new PnPProvisioningContext(async(r, s) => { if (accessTokens.ContainsKey(r)) { // In this scenario we just use the dictionary of access tokens // in fact the overall operation for sure will take less than 1 hour // (in fact, it's a matter of few seconds) return(await Task.FromResult(accessTokens[r])); } else { // Try to get a fresh new Access Token var token = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync( tokenId, $"https://{r}", ConfigurationManager.AppSettings["ida:ClientId"], ConfigurationManager.AppSettings["ida:ClientSecret"], ConfigurationManager.AppSettings["ida:AppUrl"]); accessTokens.Add(r, token); return(token); } })) { // If the user is an admin (SPO or Tenant) we run the Tenant level CanProvision rules if (model.UserIsSPOAdmin || model.UserIsTenantAdmin) { // Retrieve the SPO URL for the Admin Site var adminSiteUrl = model.SPORootSiteUrl.Replace(".sharepoint.com", "-admin.sharepoint.com"); // Retrieve the SPO Access Token for the Admin Site var spoAdminAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync( tokenId, adminSiteUrl, ConfigurationManager.AppSettings["ida:ClientId"], ConfigurationManager.AppSettings["ida:ClientSecret"], ConfigurationManager.AppSettings["ida:AppUrl"]); // Store the SPO Admin Access Token for any further context cloning accessTokens.Add(new Uri(adminSiteUrl).Authority, spoAdminAccessToken); // Connect to SPO Admin Site and evaluate the CanProvision rules for the hierarchy using (var tenantContext = authManager.GetAzureADAccessTokenAuthenticatedContext(adminSiteUrl, spoAdminAccessToken)) { using (var pnpTenantContext = PnPClientContext.ConvertFrom(tenantContext)) { // Creat the Tenant object for the current SPO Admin Site context TenantAdmin.Tenant tenant = new TenantAdmin.Tenant(pnpTenantContext); // Run the CanProvision rules against the current tenant canProvisionResult = CanProvisionRulesManager.CanProvision(tenant, hierarchy, null, ptai); } } } else { // Otherwise we run the Site level CanProvision rules // Connect to SPO Root Site and evaluate the CanProvision rules for the hierarchy using (var clientContext = authManager.GetAzureADAccessTokenAuthenticatedContext(rootSiteUrl, spoAccessToken)) { using (var pnpContext = PnPClientContext.ConvertFrom(clientContext)) { // Run the CanProvision rules against the root site canProvisionResult = CanProvisionRulesManager.CanProvision(pnpContext.Web, hierarchy.Templates[0], ptai); } } } } } } else { throw new ApplicationException("Invalid request, the requested package/template is not available!"); } return(canProvisionResult); }
internal static void ProcessFiles(ProvisioningHierarchy tenantTemplate, string templateFileName, FileConnectorBase fileSystemConnector, FileConnectorBase connector, Action <string> progress) { var templateFile = ReadTenantTemplate.LoadProvisioningHierarchyFromFile(templateFileName, null, null); if (tenantTemplate.Tenant?.AppCatalog != null) { foreach (var app in tenantTemplate.Tenant.AppCatalog.Packages) { progress($"Processing {app.Src}"); AddFile(app.Src, templateFile, fileSystemConnector, connector); } } if (tenantTemplate.Tenant?.SiteScripts != null) { foreach (var siteScript in tenantTemplate.Tenant.SiteScripts) { progress($"Processing {siteScript.JsonFilePath}"); AddFile(siteScript.JsonFilePath, templateFile, fileSystemConnector, connector); } } if (tenantTemplate.Localizations != null && tenantTemplate.Localizations.Any()) { foreach (var location in tenantTemplate.Localizations) { progress($"Processing {location.ResourceFile}"); AddFile(location.ResourceFile, templateFile, fileSystemConnector, connector); } } foreach (var template in tenantTemplate.Templates) { if (template.WebSettings != null && !String.IsNullOrEmpty(template.WebSettings.SiteLogo)) { // is it a file? var isFile = false; try { using (var fileStream = fileSystemConnector.GetFileStream(template.WebSettings.SiteLogo)) { isFile = fileStream != null; } } catch { } if (isFile) { progress($"Processing {template.WebSettings.SiteLogo}"); AddFile(template.WebSettings.SiteLogo, templateFile, fileSystemConnector, connector); } } if (template.Files.Any()) { foreach (var file in template.Files) { progress($"Processing {file.Src}"); AddFile(file.Src, templateFile, fileSystemConnector, connector); } } if (template.Lists.Any()) { foreach (var list in template.Lists) { if (list.DataRows.Any()) { foreach (var dataRow in list.DataRows) { if (dataRow.Attachments.Any()) { progress("List attachments"); foreach (var attachment in dataRow.Attachments) { AddFile(attachment.Src, templateFile, fileSystemConnector, connector); } } } } } } } if (templateFile.Connector is ICommitableFileConnector) { ((ICommitableFileConnector)templateFile.Connector).Commit(); } }
public override ProvisioningHierarchy ExtractObjects(Tenant tenant, ProvisioningHierarchy hierarchy, ProvisioningTemplateCreationInformation creationInfo) { // So far, no extraction return(hierarchy); }
public override ProvisioningHierarchy ExtractObjects(Tenant tenant, ProvisioningHierarchy hierarchy, ExtractConfiguration configuration) { throw new NotImplementedException(); }
public ProvisioningHierarchy ToProvisioningHierarchy(Stream hierarchy) { // Create a copy of the source stream MemoryStream sourceStream = new MemoryStream(); hierarchy.Position = 0; hierarchy.CopyTo(sourceStream); sourceStream.Position = 0; // Check the provided template against the XML schema if (!this.IsValid(sourceStream)) { // TODO: Use resource file throw new ApplicationException("The provided provisioning file is not valid!"); } // Prepare the output variable ProvisioningHierarchy resultHierarchy = new ProvisioningHierarchy(); // Determine if the file is a provisioning hierarchy sourceStream.Position = 0; XDocument xml = XDocument.Load(sourceStream); if (xml.Root.Name.LocalName != "Provisioning") { throw new ApplicationException("The provided provisioning file is not a Hierarchy!"); } // Determine the specific formatter needed for the current provisioning file var innerFormatter = XMLPnPSchemaFormatter.GetSpecificFormatter( xml.Root.Name.NamespaceName); // Process all the provisioning templates included in the hierarchy, if any XmlNamespaceManager nsManager = new XmlNamespaceManager(new System.Xml.NameTable()); nsManager.AddNamespace("pnp", xml.Root.Name.NamespaceName); // Start with templates embedded in the provisioning file var templates = xml.XPathSelectElements("/pnp:Provisioning/pnp:Templates/pnp:ProvisioningTemplate", nsManager).ToList(); foreach (var template in templates) { // Save the single template into a MemoryStream MemoryStream templateStream = new MemoryStream(); template.Save(templateStream); templateStream.Position = 0; // Process the single template with the classic technique var provisioningTemplate = innerFormatter.ToProvisioningTemplate(templateStream); // Add the generated template to the resulting hierarchy resultHierarchy.Templates.Add(provisioningTemplate); } // Then process any external file reference var templateFiles = xml.XPathSelectElements("/pnp:Provisioning/pnp:Templates/pnp:ProvisioningTemplateFile", nsManager).ToList(); foreach (var template in templateFiles) { var templateID = template.Attribute("ID")?.Value; var templateFile = template.Attribute("File")?.Value; if (!String.IsNullOrEmpty(templateFile) && !String.IsNullOrEmpty(templateID)) { // Process the single template file with the classic technique var provisioningTemplate = this._provider.GetTemplate(templateFile); provisioningTemplate.Id = templateID; // Add the generated template to the resulting hierarchy resultHierarchy.Templates.Add(provisioningTemplate); } } // And now process the top level children elements // using schema specific serializers using (var scope = new PnPSerializationScope(typeof(TSchemaTemplate))) { // We prepare a dummy template to leverage the existing serialization infrastructure var dummyTemplate = new ProvisioningTemplate(); dummyTemplate.Id = $"DUMMY-{Guid.NewGuid()}"; resultHierarchy.Templates.Add(dummyTemplate); // Deserialize the whole wrapper Object wrapper = null; var wrapperType = Type.GetType($"{PnPSerializationScope.Current?.BaseSchemaNamespace}.Provisioning, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}", true); XmlSerializer xmlSerializer = new XmlSerializer(wrapperType); using (var reader = xml.Root.CreateReader()) { wrapper = xmlSerializer.Deserialize(reader); } // Handle the Parameters of the schema wrapper, if any var tps = new TemplateParametersSerializer(); tps.Deserialize(wrapper, dummyTemplate); // Handle the Localizations of the schema wrapper, if any var ls = new LocalizationsSerializer(); ls.Deserialize(wrapper, dummyTemplate); // Handle the Tenant-wide settings of the schema wrapper, if any var ts = new TenantSerializer(); ts.Deserialize(wrapper, dummyTemplate); // Handle the Sequences var ss = new SequenceSerializer(); ss.Deserialize(wrapper, dummyTemplate); // Handle the Provisioning Hierarchy properties var phs = new ProvisioningHierarchySerializer(); phs.Deserialize(wrapper, dummyTemplate); // Remove the dummy template from the hierarchy resultHierarchy.Templates.Remove(dummyTemplate); } return(resultHierarchy); }
public override CanProvisionResult CanProvision(Tenant tenant, ProvisioningHierarchy hierarchy, string sequenceId, ProvisioningTemplateApplyingInformation applyingInformation) { // Rely on the corresponding Site level CanProvision rule return(this.EvaluateSiteRule <CanProvisionTermStoreRuleSite>(tenant, hierarchy, sequenceId, applyingInformation)); }
public override ProvisioningHierarchy ExtractObjects(Tenant tenant, ProvisioningHierarchy hierarchy, ExtractConfiguration configuration) { ProvisioningHierarchy tenantTemplate = new ProvisioningHierarchy(); List <string> siteCollectionUrls = configuration.Tenant.Sequence.SiteUrls; List <string> connectedSiteUrls = new List <string>(); foreach (var siteCollectionUrl in siteCollectionUrls) { using (var siteContext = tenant.Context.Clone(siteCollectionUrl)) { if (configuration.Tenant.Sequence.IncludeJoinedSites && siteContext.Site.EnsureProperty(s => s.IsHubSite)) { foreach (var hubsiteChildUrl in tenant.GetHubSiteChildUrls(siteContext.Site.EnsureProperty(s => s.Id))) { if (!connectedSiteUrls.Contains(hubsiteChildUrl) && !siteCollectionUrl.Contains(hubsiteChildUrl)) { connectedSiteUrls.Add(hubsiteChildUrl); } } } } } siteCollectionUrls.AddRange(connectedSiteUrls); ProvisioningSequence provisioningSequence = new ProvisioningSequence(); provisioningSequence.ID = "TENANTSEQUENCE"; foreach (var siteCollectionUrl in siteCollectionUrls) { var siteProperties = tenant.GetSitePropertiesByUrl(siteCollectionUrl, true); tenant.Context.Load(siteProperties); tenant.Context.ExecuteQueryRetry(); Model.SiteCollection siteCollection = null; using (var siteContext = tenant.Context.Clone(siteCollectionUrl)) { siteContext.Site.EnsureProperties(s => s.Id, s => s.ShareByEmailEnabled); var templateGuid = siteContext.Site.Id.ToString("N"); switch (siteProperties.Template) { case "SITEPAGEPUBLISHING#0": { siteCollection = new CommunicationSiteCollection(); siteCollection.IsHubSite = siteProperties.IsHubSite; if (siteProperties.IsHubSite) { var hubsiteProperties = tenant.GetHubSitePropertiesByUrl(siteCollectionUrl); tenant.Context.Load(hubsiteProperties); tenant.Context.ExecuteQueryRetry(); siteCollection.HubSiteLogoUrl = hubsiteProperties.LogoUrl; siteCollection.HubSiteTitle = hubsiteProperties.Title; } siteCollection.Description = siteProperties.Description; ((CommunicationSiteCollection)siteCollection).Language = (int)siteProperties.Lcid; ((CommunicationSiteCollection)siteCollection).Owner = siteProperties.OwnerEmail; ((CommunicationSiteCollection)siteCollection).AllowFileSharingForGuestUsers = siteContext.Site.ShareByEmailEnabled; tenantTemplate.Parameters.Add($"SITECOLLECTION_{siteContext.Site.Id.ToString("N")}_URL", siteProperties.Url); ((CommunicationSiteCollection)siteCollection).Url = $"{{parameter:SITECOLLECTION_{siteContext.Site.Id.ToString("N")}_URL}}"; tenantTemplate.Parameters.Add($"SITECOLLECTION_{siteContext.Site.Id.ToString("N")}_TITLE", siteProperties.Title); siteCollection.Title = $"{{parameter:SITECOLLECTION_{siteContext.Site.Id.ToString("N")}_TITLE}}"; break; } case "GROUP#0": { siteCollection = new TeamSiteCollection(); siteCollection.IsHubSite = siteProperties.IsHubSite; if (siteProperties.IsHubSite) { var hubsiteProperties = tenant.GetHubSitePropertiesByUrl(siteCollectionUrl); tenant.Context.Load(hubsiteProperties); tenant.Context.ExecuteQueryRetry(); siteCollection.HubSiteLogoUrl = hubsiteProperties.LogoUrl; siteCollection.HubSiteTitle = hubsiteProperties.Title; } siteCollection.Description = siteProperties.Description; tenantTemplate.Parameters.Add($"SITECOLLECTION_{siteContext.Site.Id.ToString("N")}_ALIAS", siteProperties.Url.Substring(siteProperties.Url.LastIndexOf("/"))); ((TeamSiteCollection)siteCollection).Alias = $"{{parameter:SITECOLLECTION_{siteContext.Site.Id.ToString("N")}_ALIAS}}"; ((TeamSiteCollection)siteCollection).DisplayName = siteProperties.Title; ((TeamSiteCollection)siteCollection).HideTeamify = Core.Sites.SiteCollection.IsTeamifyPromptHiddenAsync(siteContext).GetAwaiter().GetResult(); tenantTemplate.Parameters.Add($"SITECOLLECTION_{siteContext.Site.Id.ToString("N")}_TITLE", siteProperties.Title); siteCollection.Title = $"{{parameter:SITECOLLECTION_{siteContext.Site.Id.ToString("N")}_TITLE}}"; break; } case "STS#3": { siteCollection = new TeamNoGroupSiteCollection(); siteCollection.IsHubSite = siteProperties.IsHubSite; if (siteProperties.IsHubSite) { var hubsiteProperties = tenant.GetHubSitePropertiesByUrl(siteCollectionUrl); tenant.Context.Load(hubsiteProperties); tenant.Context.ExecuteQueryRetry(); siteCollection.HubSiteLogoUrl = hubsiteProperties.LogoUrl; siteCollection.HubSiteTitle = hubsiteProperties.Title; } siteCollection.Description = siteProperties.Description; ((TeamNoGroupSiteCollection)siteCollection).Language = (int)siteProperties.Lcid; ((TeamNoGroupSiteCollection)siteCollection).Owner = siteProperties.OwnerEmail; ((TeamNoGroupSiteCollection)siteCollection).TimeZoneId = siteProperties.TimeZoneId; tenantTemplate.Parameters.Add($"SITECOLLECTION_{siteContext.Site.Id.ToString("N")}_URL", siteProperties.Url); ((TeamNoGroupSiteCollection)siteCollection).Url = $"{{parameter:SITECOLLECTION_{siteContext.Site.Id.ToString("N")}_URL}}"; tenantTemplate.Parameters.Add($"SITECOLLECTION_{siteContext.Site.Id.ToString("N")}_TITLE", siteProperties.Title); siteCollection.Title = $"{{parameter:SITECOLLECTION_{siteContext.Site.Id.ToString("N")}_TITLE}}"; break; } } var siteTemplateCreationInfo = new ProvisioningTemplateCreationInformation(siteContext.Web); // Retrieve the template for the site if (configuration != null) { siteTemplateCreationInfo = configuration.ToCreationInformation(siteContext.Web); } var siteTemplate = siteContext.Web.GetProvisioningTemplate(siteTemplateCreationInfo); siteTemplate.Id = $"TEMPLATE-{templateGuid}"; if (siteProperties.HubSiteId != null && siteProperties.HubSiteId != Guid.Empty && siteProperties.HubSiteId != siteContext.Site.Id && siteTemplate.WebSettings != null) { siteTemplate.WebSettings.HubSiteUrl = $"{{parameter:SITECOLLECTION_{siteProperties.HubSiteId.ToString("N")}_URL}}"; } tenantTemplate.Templates.Add(siteTemplate); siteCollection.Templates.Add(siteTemplate.Id); if (siteProperties.WebsCount > 1 && configuration.Tenant.Sequence.IncludeSubsites) { var webs = siteContext.Web.EnsureProperty(w => w.Webs); int currentDepth = 1; foreach (var subweb in webs) { siteCollection.Sites.Add(ParseSubsiteSequences(subweb, ref tenantTemplate, configuration, currentDepth, configuration.Tenant.Sequence.MaxSubsiteDepth)); } } provisioningSequence.SiteCollections.Add(siteCollection); } } tenantTemplate.Sequences.Add(provisioningSequence); PnPProvisioningContext.Current.ParsedSiteUrls.Clear(); PnPProvisioningContext.Current.ParsedSiteUrls.AddRange(siteCollectionUrls); return(tenantTemplate); }
public override void Save(ProvisioningHierarchy hierarchy) { throw new NotImplementedException(); }
public override void SaveAs(ProvisioningHierarchy hierarchy, string uri, ITemplateFormatter formatter = null) { throw new NotImplementedException(); }
public void ProvisionTenantTemplate() { if (TestCommon.AppOnlyTesting()) { Assert.Inconclusive("This test does not yet work with app-only due to group connected site creation"); } string tenantNameParamValue = new Uri(TestCommon.DevSiteUrl).DnsSafeHost.Split('.')[0]; string accountDomainParamValue = TestCommon.O365AccountDomain; if (string.IsNullOrEmpty(accountDomainParamValue)) { accountDomainParamValue = "contoso.com"; } var resourceFolder = string.Format(@"{0}\..\..\..\Resources\Templates", AppDomain.CurrentDomain.BaseDirectory); XMLTemplateProvider provider = new XMLFileSystemTemplateProvider(resourceFolder, ""); var existingTemplate = provider.GetTemplate("ProvisioningSchema-2018-07-FullSample-01.xml"); Guid siteGuid = Guid.NewGuid(); int siteId = siteGuid.GetHashCode(); var template = new ProvisioningTemplate { Id = "TestTemplate" }; template.Lists.Add(new ListInstance() { Title = "Testlist", TemplateType = 100, Url = "lists/testlist" }); template.TermGroups.AddRange(existingTemplate.TermGroups); ProvisioningHierarchy hierarchy = new ProvisioningHierarchy(); hierarchy.Templates.Add(template); hierarchy.Parameters.Add("CompanyName", "Contoso"); if (!string.IsNullOrEmpty(tenantNameParamValue)) { hierarchy.Parameters.Add("O365TenantName", tenantNameParamValue); } if (!string.IsNullOrEmpty(accountDomainParamValue)) { hierarchy.Parameters.Add("O365AccountDomain", accountDomainParamValue); } var sequence = new ProvisioningSequence { ID = Guid.NewGuid().ToString(), TermStore = new ProvisioningTermStore() }; var termGroup = new TermGroup() { Name = "Contoso TermGroup" }; var termSet = new TermSet() { Name = "Projects", Id = Guid.NewGuid(), IsAvailableForTagging = true, Language = 1033 }; var term = new Term() { Name = "Contoso Term" }; termSet.Terms.Add(term); // termGroup.TermSets.Add(termSet); var existingTermSet = existingTemplate.TermGroups[0].TermSets[0]; termGroup.TermSets.Add(existingTermSet); // sequence.TermStore.TermGroups.Add(termGroup); var teamSite1 = new TeamSiteCollection() { // Alias = $"prov-1-{siteId}", Alias = "prov-1", Description = "prov-1", DisplayName = "prov-1", IsHubSite = false, IsPublic = false, Title = "prov-1", }; teamSite1.Templates.Add("TestTemplate"); var subsite = new TeamNoGroupSubSite() { Description = "Test Sub", Url = "testsub1", Language = 1033, TimeZoneId = 4, Title = "Test Sub", UseSamePermissionsAsParentSite = true }; subsite.Templates.Add("TestTemplate"); teamSite1.Sites.Add(subsite); sequence.SiteCollections.Add(teamSite1); var teamSite2 = new TeamSiteCollection() { Alias = $"prov-2-{siteId}", Description = "prov-2", DisplayName = "prov-2", IsHubSite = false, IsPublic = false, Title = "prov-2" }; teamSite2.Templates.Add("TestTemplate"); sequence.SiteCollections.Add(teamSite2); hierarchy.Sequences.Add(sequence); using (var tenantContext = TestCommon.CreateTenantClientContext()) { var applyConfiguration = new ApplyConfiguration { ProgressDelegate = (message, step, total) => { if (message != null) { } } }; var tenant = new Tenant(tenantContext); tenant.ApplyTenantTemplate(hierarchy, sequence.ID, applyConfiguration); } }
public static async Task RunAsync([ServiceBusTrigger("actions", IsSessionsEnabled = false)] ProvisioningActionModel action, ILogger logger) { var startProvisioning = DateTime.Now; String provisioningEnvironment = ConfigurationManager.AppSettings["SPPA:ProvisioningEnvironment"]; logger.LogInformationWithPnPCorrelation("Processing queue trigger function for tenant {TenantId}", action.CorrelationId, action.TenantId); // Instantiate and use the telemetry model TelemetryUtility telemetry = new TelemetryUtility((s) => { logger.LogInformationWithPnPCorrelation(s, action.CorrelationId); }); Dictionary <string, string> telemetryProperties = new Dictionary <string, string>(); // Configure telemetry properties // telemetryProperties.Add("UserPrincipalName", action.UserPrincipalName); telemetryProperties.Add("TenantId", action.TenantId); telemetryProperties.Add("PnPCorrelationId", action.CorrelationId.ToString()); telemetryProperties.Add("TargetSiteAlreadyExists", action.TargetSiteAlreadyExists.ToString()); telemetryProperties.Add("TargetSiteBaseTemplateId", action.TargetSiteBaseTemplateId); // Get a reference to the data context ProvisioningAppDBContext dbContext = new ProvisioningAppDBContext(); try { // Log telemetry event telemetry?.LogEvent("ProvisioningFunction.Start"); if (CheckIfActionIsAlreadyRunning(action, dbContext)) { throw new ConcurrentProvisioningException("The requested package is currently provisioning in the selected target tenant and cannot be applied in parallel. Please wait for the previous provisioning action to complete."); } var tokenId = $"{action.TenantId}-{action.UserPrincipalName.ToLower().GetHashCode()}-{action.ActionType.ToString().ToLower()}-{provisioningEnvironment}"; // Retrieve the SPO target tenant via Microsoft Graph var graphAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync( tokenId, "https://graph.microsoft.com/", ConfigurationManager.AppSettings[$"{action.ActionType}:ClientId"], ConfigurationManager.AppSettings[$"{action.ActionType}:ClientSecret"], ConfigurationManager.AppSettings[$"{action.ActionType}:AppUrl"]); logger.LogInformationWithPnPCorrelation("Retrieved target Microsoft Graph Access Token.", action.CorrelationId); if (!String.IsNullOrEmpty(graphAccessToken)) { #region Get current context data (User, SPO Tenant, SPO Access Token) // Get the currently connected user name and email (UPN) var jwtAccessToken = new System.IdentityModel.Tokens.Jwt.JwtSecurityToken(graphAccessToken); String delegatedUPN = String.Empty; var upnClaim = jwtAccessToken.Claims.FirstOrDefault(c => c.Type == "upn"); if (upnClaim != null && !String.IsNullOrEmpty(upnClaim.Value)) { delegatedUPN = upnClaim.Value; } String delegatedUserName = String.Empty; var nameClaim = jwtAccessToken.Claims.FirstOrDefault(c => c.Type == "name"); if (nameClaim != null && !String.IsNullOrEmpty(nameClaim.Value)) { delegatedUserName = nameClaim.Value; } // Determine the URL of the root SPO site for the current tenant var rootSiteJson = HttpHelper.MakeGetRequestForString("https://graph.microsoft.com/v1.0/sites/root", graphAccessToken); SharePointSite rootSite = JsonConvert.DeserializeObject <SharePointSite>(rootSiteJson); String spoTenant = rootSite.WebUrl; logger.LogInformationWithPnPCorrelation("Target SharePoint Online Tenant: {SPOTenant}", action.CorrelationId, spoTenant); // Configure telemetry properties telemetryProperties.Add("SPOTenant", spoTenant); // Retrieve the SPO Access Token var spoAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync( tokenId, rootSite.WebUrl, ConfigurationManager.AppSettings[$"{action.ActionType}:ClientId"], ConfigurationManager.AppSettings[$"{action.ActionType}:ClientSecret"], ConfigurationManager.AppSettings[$"{action.ActionType}:AppUrl"]); logger.LogInformationWithPnPCorrelation("Retrieved target SharePoint Online Access Token.", action.CorrelationId); #endregion // Connect to SPO, create and provision site AuthenticationManager authManager = new AuthenticationManager(); using (ClientContext context = authManager.GetAzureADAccessTokenAuthenticatedContext(spoTenant, spoAccessToken)) { // Telemetry and startup var web = context.Web; context.ClientTag = $"SPDev:ProvisioningPortal-{provisioningEnvironment}"; context.Load(web, w => w.Title, w => w.Id); await context.ExecuteQueryAsync(); // Save the current SPO Correlation ID telemetryProperties.Add("SPOCorrelationId", context.TraceCorrelationId); logger.LogInformationWithPnPCorrelation("SharePoint Online Root Site Collection title: {WebTitle}", action.CorrelationId, web.Title); #region Store the main site URL in KeyVault // Store the main site URL in the Vault var vault = ProvisioningAppManager.SecurityTokensServiceProvider; // Read any existing properties for the current tenantId var properties = await vault.GetAsync(tokenId); if (properties == null) { // If there are no properties, create a new dictionary properties = new Dictionary <String, String>(); } // Set/Update the RefreshToken value properties["SPORootSite"] = spoTenant; // Add or Update the Key Vault accordingly await vault.AddOrUpdateAsync(tokenId, properties); #endregion #region Provision the package var package = dbContext.Packages.FirstOrDefault(p => p.Id == new Guid(action.PackageId)); if (package != null) { // Update the Popularity of the package package.TimesApplied++; dbContext.SaveChanges(); #region Get the Provisioning Hierarchy file // Determine reference path variables var blobConnectionString = ConfigurationManager.AppSettings["BlobTemplatesProvider:ConnectionString"]; var blobContainerName = ConfigurationManager.AppSettings["BlobTemplatesProvider:ContainerName"]; var packageFileName = package.PackageUrl.Substring(package.PackageUrl.LastIndexOf('/') + 1); var packageFileUri = new Uri(package.PackageUrl); var packageFileRelativePath = packageFileUri.AbsolutePath.Substring(2 + blobContainerName.Length); var packageFileRelativeFolder = packageFileRelativePath.Substring(0, packageFileRelativePath.LastIndexOf('/')); // Configure telemetry properties telemetryProperties.Add("PackageFileName", packageFileName); telemetryProperties.Add("PackageFileUri", packageFileUri.ToString()); // Read the main provisioning file from the Blob Storage CloudStorageAccount csa; if (!CloudStorageAccount.TryParse(blobConnectionString, out csa)) { throw new ArgumentException("Cannot create cloud storage account from given connection string."); } CloudBlobClient blobClient = csa.CreateCloudBlobClient(); CloudBlobContainer blobContainer = blobClient.GetContainerReference(blobContainerName); var blockBlob = blobContainer.GetBlockBlobReference(packageFileRelativePath); // Crate an in-memory copy of the source stream MemoryStream mem = new MemoryStream(); await blockBlob.DownloadToStreamAsync(mem); mem.Position = 0; // Prepare the output hierarchy ProvisioningHierarchy hierarchy = null; if (packageFileName.EndsWith(".xml", StringComparison.InvariantCultureIgnoreCase)) { // That's an XML Provisioning Template file XDocument xml = XDocument.Load(mem); mem.Position = 0; // Deserialize the stream into a provisioning hierarchy reading any // dependecy with the Azure Blob Storage connector var formatter = XMLPnPSchemaFormatter.GetSpecificFormatter(xml.Root.Name.NamespaceName); var templateLocalFolder = $"{blobContainerName}/{packageFileRelativeFolder}"; var provider = new XMLAzureStorageTemplateProvider( blobConnectionString, templateLocalFolder); formatter.Initialize(provider); // Get the full hierarchy hierarchy = ((IProvisioningHierarchyFormatter)formatter).ToProvisioningHierarchy(mem); hierarchy.Connector = provider.Connector; } else if (packageFileName.EndsWith(".pnp", StringComparison.InvariantCultureIgnoreCase)) { // That's a PnP Package file // Get a provider based on the in-memory .PNP Open XML file OpenXMLConnector openXmlConnector = new OpenXMLConnector(mem); XMLTemplateProvider provider = new XMLOpenXMLTemplateProvider( openXmlConnector); // Get the .xml provisioning template file name var xmlTemplateFileName = openXmlConnector.Info?.Properties?.TemplateFileName ?? packageFileName.Substring(packageFileName.LastIndexOf('/') + 1) .ToLower().Replace(".pnp", ".xml"); // Get the full hierarchy hierarchy = provider.GetHierarchy(xmlTemplateFileName); hierarchy.Connector = provider.Connector; } #endregion #region Apply the template // Prepare variable to collect provisioned sites var provisionedSites = new List <Tuple <String, String> >(); // If we have a hierarchy with at least one Sequence if (hierarchy != null) // && hierarchy.Sequences != null && hierarchy.Sequences.Count > 0) { Console.WriteLine($"Provisioning hierarchy \"{hierarchy.DisplayName}\""); var tenantUrl = UrlUtilities.GetTenantAdministrationUrl(context.Url); // Retrieve the SPO Access Token var spoAdminAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync( tokenId, tenantUrl, ConfigurationManager.AppSettings[$"{action.ActionType}:ClientId"], ConfigurationManager.AppSettings[$"{action.ActionType}:ClientSecret"], ConfigurationManager.AppSettings[$"{action.ActionType}:AppUrl"]); logger.LogInformationWithPnPCorrelation("Retrieved target SharePoint Online Admin Center Access Token.", action.CorrelationId); using (var tenantContext = authManager.GetAzureADAccessTokenAuthenticatedContext(tenantUrl, spoAdminAccessToken)) { using (var pnpTenantContext = PnPClientContext.ConvertFrom(tenantContext)) { var tenant = new Microsoft.Online.SharePoint.TenantAdministration.Tenant(pnpTenantContext); // Prepare a dictionary to hold the access tokens var accessTokens = new Dictionary <String, String>(); // Prepare logging for hierarchy application var ptai = new ProvisioningTemplateApplyingInformation(); ptai.MessagesDelegate += delegate(string message, ProvisioningMessageType messageType) { logger.LogInformationWithPnPCorrelation($"{messageType} - {message.Replace("{", "{{").Replace("}", "}}")}", action.CorrelationId); }; ptai.ProgressDelegate += delegate(string message, int step, int total) { logger.LogInformationWithPnPCorrelation($"{step:00}/{total:00} - {message.Replace("{", "{{").Replace("}", "}}")}", action.CorrelationId); }; ptai.SiteProvisionedDelegate += delegate(string title, string url) { logger.LogInformationWithPnPCorrelation("Fully provisioned site '{SiteTitle}' with URL: {SiteUrl}", action.CorrelationId, title, url); var provisionedSite = new Tuple <string, string>(title, url); if (!provisionedSites.Contains(provisionedSite)) { provisionedSites.Add(provisionedSite); } }; // Configure the OAuth Access Tokens for the client context accessTokens.Add(new Uri(tenantUrl).Authority, spoAdminAccessToken); accessTokens.Add(new Uri(spoTenant).Authority, spoAccessToken); // Configure the OAuth Access Tokens for the PnPClientContext, too pnpTenantContext.PropertyBag["AccessTokens"] = accessTokens; ptai.AccessTokens = accessTokens; #region Theme handling // Process the graphical Theme if (action.ApplyTheme) { // If we don't have any custom Theme if (!action.ApplyCustomTheme) { // Associate the selected already existing Theme to all the sites of the hierarchy foreach (var sc in hierarchy.Sequences[0].SiteCollections) { sc.Theme = action.SelectedTheme; foreach (var s in sc.Sites) { UpdateChildrenSitesTheme(s, action.SelectedTheme); } } } } #endregion // Configure provisioning parameters if (action.PackageProperties != null) { foreach (var key in action.PackageProperties.Keys) { if (hierarchy.Parameters.ContainsKey(key.ToString())) { hierarchy.Parameters[key.ToString()] = action.PackageProperties[key].ToString(); } else { hierarchy.Parameters.Add(key.ToString(), action.PackageProperties[key].ToString()); } // Configure telemetry properties telemetryProperties.Add($"PackageProperty.{key}", action.PackageProperties[key].ToString()); } } // Log telemetry event telemetry?.LogEvent("ProvisioningFunction.BeginProvisioning", telemetryProperties); // Define a PnPProvisioningContext scope to share the security context across calls using (var pnpProvisioningContext = new PnPProvisioningContext(async(r, s) => { if (accessTokens.ContainsKey(r)) { // In this scenario we just use the dictionary of access tokens // in fact the overall operation for sure will take less than 1 hour return(await Task.FromResult(accessTokens[r])); } else { // Try to get a fresh new Access Token var token = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync( tokenId, $"https://{r}", ConfigurationManager.AppSettings[$"{action.ActionType}:ClientId"], ConfigurationManager.AppSettings[$"{action.ActionType}:ClientSecret"], ConfigurationManager.AppSettings[$"{action.ActionType}:AppUrl"]); accessTokens.Add(r, token); return(token); } })) { // Configure the webhooks, if any if (action.Webhooks != null && action.Webhooks.Count > 0) { foreach (var t in hierarchy.Templates) { foreach (var wh in action.Webhooks) { AddProvisioningTemplateWebhook(t, wh, ProvisioningTemplateWebhookKind.ProvisioningTemplateStarted); AddProvisioningTemplateWebhook(t, wh, ProvisioningTemplateWebhookKind.ObjectHandlerProvisioningStarted); AddProvisioningTemplateWebhook(t, wh, ProvisioningTemplateWebhookKind.ObjectHandlerProvisioningCompleted); AddProvisioningTemplateWebhook(t, wh, ProvisioningTemplateWebhookKind.ProvisioningTemplateCompleted); AddProvisioningTemplateWebhook(t, wh, ProvisioningTemplateWebhookKind.ExceptionOccurred); } } foreach (var wh in action.Webhooks) { AddProvisioningWebhook(hierarchy, wh, ProvisioningTemplateWebhookKind.ProvisioningStarted); AddProvisioningWebhook(hierarchy, wh, ProvisioningTemplateWebhookKind.ProvisioningCompleted); AddProvisioningWebhook(hierarchy, wh, ProvisioningTemplateWebhookKind.ProvisioningExceptionOccurred); } } // Disable the WebSettings handler for non-admin users if (!TenantExtensions.IsCurrentUserTenantAdmin(tenantContext)) { ptai.HandlersToProcess &= ~Handlers.WebSettings; } // Apply the hierarchy logger.LogInformationWithPnPCorrelation("Hierarchy Provisioning Started: {ProvisioningStartDateTime}", action.CorrelationId, DateTime.Now.ToString("hh.mm.ss")); tenant.ApplyProvisionHierarchy(hierarchy, (hierarchy.Sequences != null && hierarchy.Sequences.Count > 0) ? hierarchy.Sequences[0].ID : null, ptai); logger.LogInformationWithPnPCorrelation("Hierarchy Provisioning Completed: {ProvisioningEndDateTime}", action.CorrelationId, DateTime.Now.ToString("hh.mm.ss")); } if (action.ApplyTheme && action.ApplyCustomTheme) { if (!String.IsNullOrEmpty(action.ThemePrimaryColor) && !String.IsNullOrEmpty(action.ThemeBodyTextColor) && !String.IsNullOrEmpty(action.ThemeBodyBackgroundColor)) { logger.LogInformationWithPnPCorrelation("Applying custom Theme to provisioned sites", action.CorrelationId); #region Palette generation for Theme var jsonPalette = ThemeUtility.GetThemeAsJSON( action.ThemePrimaryColor, action.ThemeBodyTextColor, action.ThemeBodyBackgroundColor); #endregion // Apply the custom theme to all of the provisioned sites foreach (var ps in provisionedSites) { using (var provisionedSiteContext = authManager.GetAzureADAccessTokenAuthenticatedContext(ps.Item2, spoAccessToken)) { if (provisionedSiteContext.Web.ApplyTheme(jsonPalette)) { logger.LogInformationWithPnPCorrelation($"Custom Theme applied on site '{ps.Item1}' with URL: {ps.Item2}", action.CorrelationId); } else { logger.LogInformationWithPnPCorrelation($"Failed to apply custom Theme on site '{ps.Item1}' with URL: {ps.Item2}", action.CorrelationId); } } } } } // Log telemetry event telemetry?.LogEvent("ProvisioningFunction.EndProvisioning", telemetryProperties); // Notify user about the provisioning outcome if (!String.IsNullOrEmpty(action.NotificationEmail)) { var appOnlyAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAppOnlyAccessTokenAsync( "https://graph.microsoft.com/", ConfigurationManager.AppSettings["OfficeDevPnP:TenantId"], ConfigurationManager.AppSettings["OfficeDevPnP:ClientId"], ConfigurationManager.AppSettings["OfficeDevPnP:ClientSecret"], ConfigurationManager.AppSettings["OfficeDevPnP:AppUrl"]); MailHandler.SendMailNotification( "ProvisioningCompleted", action.NotificationEmail, null, new { TemplateName = action.DisplayName, ProvisionedSites = provisionedSites, }, appOnlyAccessToken); } // Log reporting event (1 = Success) LogReporting(action, provisioningEnvironment, startProvisioning, package, 1); // Log source tracking for provisioned sites LogSourceTrackingProvisionedSites(action, spoAccessToken, authManager, provisionedSites); } } } else { throw new ApplicationException($"The requested package does not contain a valid PnP Hierarchy!"); } #endregion } else { throw new ApplicationException($"Cannot find the package with ID: {action.PackageId}"); } #endregion #region Process any children items // If there are children items if (action.ChildrenItems != null && action.ChildrenItems.Count > 0) { // Prepare any further child provisioning request action.PackageId = action.ChildrenItems[0].PackageId; action.PackageProperties = action.ChildrenItems[0].Parameters; action.ChildrenItems.RemoveAt(0); // Enqueue any further child provisioning request await ProvisioningAppManager.EnqueueProvisioningRequest(action); } #endregion logger.LogInformationWithPnPCorrelation("Function successfully executed!", action.CorrelationId); // Log telemetry event telemetry?.LogEvent("ProvisioningFunction.End", telemetryProperties); } } else { var noTokensErrorMessage = $"Cannot retrieve Refresh Token or Access Token for action {action.CorrelationId} in tenant {action.TenantId}!"; logger.LogInformationWithPnPCorrelation(noTokensErrorMessage, action.CorrelationId); throw new ApplicationException(noTokensErrorMessage); } } catch (Exception ex) { // Skip logging exception for Recycled Site if (ex is RecycledSiteException) { // Log reporting event (3 = RecycledSite) LogReporting(action, provisioningEnvironment, startProvisioning, null, 3, ex.ToDetailedString()); // rather log an event telemetry?.LogEvent("ProvisioningFunction.RecycledSite", telemetryProperties); } // Skip logging exception for Concurrent Provisioning else if (ex is ConcurrentProvisioningException) { // Log reporting event (4 = ConcurrentProvisioningException) LogReporting(action, provisioningEnvironment, startProvisioning, null, 4, ex.ToDetailedString()); // rather log an event telemetry?.LogEvent("ProvisioningFunction.ConcurrentProvisioning", telemetryProperties); } else { // Log reporting event (2 = Failed) LogReporting(action, provisioningEnvironment, startProvisioning, null, 2, ex.ToDetailedString()); // Log telemetry event telemetry?.LogException(ex, "ProvisioningFunction.RunAsync", telemetryProperties); } if (!String.IsNullOrEmpty(action.NotificationEmail)) { var appOnlyAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAppOnlyAccessTokenAsync( "https://graph.microsoft.com/", ConfigurationManager.AppSettings["OfficeDevPnP:TenantId"], ConfigurationManager.AppSettings["OfficeDevPnP:ClientId"], ConfigurationManager.AppSettings["OfficeDevPnP:ClientSecret"], ConfigurationManager.AppSettings["OfficeDevPnP:AppUrl"]); // Notify user about the provisioning outcome MailHandler.SendMailNotification( "ProvisioningFailed", action.NotificationEmail, null, new { TemplateName = action.DisplayName, ExceptionDetails = SimplifyException(ex), PnPCorrelationId = action.CorrelationId.ToString(), }, appOnlyAccessToken); } ProcessWebhooksExceptionNotification(action, ex); // Track the failure in the local action log MarkCurrentActionItemAsFailed(action, dbContext); throw ex; } finally { // Try to cleanup the pending action item, if any CleanupCurrentActionItem(action, dbContext); telemetry?.Flush(); } }
/// <summary> /// Constructor for ProvisioningSequenceCollection class /// </summary> /// <param name="parentProvisioning">Parent Provisioning object</param> public ProvisioningSequenceCollection(ProvisioningHierarchy parentProvisioning) : base(parentProvisioning) { }
private async Task FillPackageAsync(DomainModel.Package package, ITemplateFile packageFile) { using (Stream stream = await packageFile.DownloadAsync()) { // Crate a copy of the source stream MemoryStream mem = new MemoryStream(); await stream.CopyToAsync(mem); mem.Position = 0; // Prepare the output hierarchy ProvisioningHierarchy hierarchy = null; if (packageFile.Path.EndsWith(".xml", StringComparison.InvariantCultureIgnoreCase)) { // That's an XML Provisioning Template file XDocument xml = XDocument.Load(mem); mem.Position = 0; // Deserialize the stream into a provisioning hierarchy reading any // dependecy with the Azure Blob Storage connector var formatter = XMLPnPSchemaFormatter.GetSpecificFormatter(xml.Root.Name.NamespaceName); var templateLocalFolder = $"{ConfigurationManager.AppSettings["BlobTemplatesProvider:ContainerName"]}/{packageFile.Path.Substring(0, packageFile.Path.LastIndexOf('/'))}"; var provider = new XMLAzureStorageTemplateProvider( ConfigurationManager.AppSettings["BlobTemplatesProvider:ConnectionString"], templateLocalFolder); formatter.Initialize(provider); // Get the full hierarchy hierarchy = ((IProvisioningHierarchyFormatter)formatter).ToProvisioningHierarchy(mem); } else if (packageFile.Path.EndsWith(".pnp", StringComparison.InvariantCultureIgnoreCase)) { // That's a PnP Package file // Get a provider based on the in-memory .PNP Open XML file OpenXMLConnector openXmlConnector = new OpenXMLConnector(mem); XMLTemplateProvider provider = new XMLOpenXMLTemplateProvider( openXmlConnector); // Get the .xml provisioning template file name var xmlTemplateFileName = openXmlConnector.Info?.Properties?.TemplateFileName ?? packageFile.Path.Substring(packageFile.Path.LastIndexOf('/') + 1) .ToLower().Replace(".pnp", ".xml"); // Get the full hierarchy hierarchy = provider.GetHierarchy(xmlTemplateFileName); } if (hierarchy != null) { package.DisplayName = hierarchy.DisplayName; package.ImagePreviewUrl = ChangeUri(packageFile.DownloadUri, hierarchy?.ImagePreviewUrl ?? String.Empty); package.Description = await GetDescriptionAsync(packageFile.GetDirectoryPath()) ?? hierarchy?.Description ?? ""; package.Version = hierarchy?.Version.ToString(); package.PackageProperties = JsonConvert.SerializeObject(hierarchy.Parameters); } } }
public override ProvisioningHierarchy ExtractObjects(Tenant tenant, ProvisioningHierarchy hierarchy, ProvisioningTemplateCreationInformation creationInfo) { throw new NotImplementedException(); }
protected override void ExecuteCmdlet() { var sitesProvisioned = new List <ProvisionedSite>(); var configuration = new ApplyConfiguration(); if (ParameterSpecified(nameof(Configuration))) { configuration = Configuration.GetConfiguration(SessionState.Path.CurrentFileSystemLocation.Path); } configuration.SiteProvisionedDelegate = (title, url) => { if (sitesProvisioned.FirstOrDefault(s => s.Url == url) == null) { sitesProvisioned.Add(new ProvisionedSite() { Title = title, Url = url }); } }; if (ParameterSpecified(nameof(Handlers))) { if (!Handlers.Has(Handlers.All)) { foreach (var enumValue in (Handlers[])Enum.GetValues(typeof(Handlers))) { if (Handlers.Has(enumValue)) { if (enumValue == Handlers.TermGroups) { configuration.Handlers.Add(ConfigurationHandler.Taxonomy); } else if (enumValue == Handlers.PageContents) { configuration.Handlers.Add(ConfigurationHandler.Pages); } else if (Enum.TryParse <ConfigurationHandler>(enumValue.ToString(), out ConfigurationHandler configHandler)) { configuration.Handlers.Add(configHandler); } } } } } if (ParameterSpecified(nameof(ExcludeHandlers))) { foreach (var handler in (Handlers[])Enum.GetValues(typeof(Handlers))) { if (!ExcludeHandlers.Has(handler) && handler != Handlers.All) { if (handler == Handlers.TermGroups) { if (configuration.Handlers.Contains(ConfigurationHandler.Taxonomy)) { configuration.Handlers.Remove(ConfigurationHandler.Taxonomy); } else if (Enum.TryParse <ConfigurationHandler>(handler.ToString(), out ConfigurationHandler configHandler)) { if (configuration.Handlers.Contains(configHandler)) { configuration.Handlers.Remove(configHandler); } } } } } } if (ExtensibilityHandlers != null) { configuration.Extensibility.Handlers = ExtensibilityHandlers.ToList(); } configuration.ProgressDelegate = (message, step, total) => { if (message != null) { var percentage = Convert.ToInt32((100 / Convert.ToDouble(total)) * Convert.ToDouble(step)); progressRecord.Activity = $"Applying template to tenant"; progressRecord.StatusDescription = message; progressRecord.PercentComplete = percentage; progressRecord.RecordType = ProgressRecordType.Processing; WriteProgress(progressRecord); } }; var warningsShown = new List <string>(); configuration.MessagesDelegate = (message, type) => { switch (type) { case ProvisioningMessageType.Warning: { if (!warningsShown.Contains(message)) { WriteWarning(message); warningsShown.Add(message); } break; } case ProvisioningMessageType.Progress: { if (message != null) { var activity = message; if (message.IndexOf("|") > -1) { var messageSplitted = message.Split('|'); if (messageSplitted.Length == 4) { var current = double.Parse(messageSplitted[2]); var total = double.Parse(messageSplitted[3]); subProgressRecord.RecordType = ProgressRecordType.Processing; subProgressRecord.Activity = string.IsNullOrEmpty(messageSplitted[0]) ? "-" : messageSplitted[0]; subProgressRecord.StatusDescription = string.IsNullOrEmpty(messageSplitted[1]) ? "-" : messageSplitted[1]; subProgressRecord.PercentComplete = Convert.ToInt32((100 / total) * current); WriteProgress(subProgressRecord); } else { subProgressRecord.Activity = "Processing"; subProgressRecord.RecordType = ProgressRecordType.Processing; subProgressRecord.StatusDescription = activity; subProgressRecord.PercentComplete = 0; WriteProgress(subProgressRecord); } } else { subProgressRecord.Activity = "Processing"; subProgressRecord.RecordType = ProgressRecordType.Processing; subProgressRecord.StatusDescription = activity; subProgressRecord.PercentComplete = 0; WriteProgress(subProgressRecord); } } break; } case ProvisioningMessageType.Completed: { WriteProgress(new ProgressRecord(1, message, " ") { RecordType = ProgressRecordType.Completed }); break; } } }; configuration.PropertyBag.OverwriteSystemValues = OverwriteSystemPropertyBagValues; configuration.Lists.IgnoreDuplicateDataRowErrors = IgnoreDuplicateDataRowErrors; configuration.Navigation.ClearNavigation = ClearNavigation; configuration.ContentTypes.ProvisionContentTypesToSubWebs = ProvisionContentTypesToSubWebs; configuration.Fields.ProvisionFieldsToSubWebs = ProvisionFieldsToSubWebs; ProvisioningHierarchy hierarchyToApply = null; switch (ParameterSetName) { case ParameterSet_PATH: { hierarchyToApply = GetHierarchy(); break; } case ParameterSet_OBJECT: { hierarchyToApply = Template; if (ResourceFolder != null) { var fileSystemConnector = new FileSystemConnector(ResourceFolder, ""); hierarchyToApply.Connector = fileSystemConnector; } else { if (Path != null) { if (!System.IO.Path.IsPathRooted(Path)) { Path = System.IO.Path.Combine(SessionState.Path.CurrentFileSystemLocation.Path, Path); } } else { Path = SessionState.Path.CurrentFileSystemLocation.Path; } var fileInfo = new FileInfo(Path); var fileConnector = new FileSystemConnector(fileInfo.DirectoryName, ""); hierarchyToApply.Connector = fileConnector; } break; } } if (Parameters != null) { foreach (var parameter in Parameters.Keys) { if (hierarchyToApply.Parameters.ContainsKey(parameter.ToString())) { hierarchyToApply.Parameters[parameter.ToString()] = Parameters[parameter].ToString(); } else { hierarchyToApply.Parameters.Add(parameter.ToString(), Parameters[parameter].ToString()); } } } #if !ONPREMISES // check if consent is needed and in place var consentRequired = false; if (hierarchyToApply.Teams != null) { consentRequired = true; } if (hierarchyToApply.AzureActiveDirectory != null) { consentRequired = true; } if (consentRequired) { // try to retrieve an access token for the Microsoft Graph: var accessToken = PnPConnection.CurrentConnection.TryGetAccessToken(Enums.TokenAudience.MicrosoftGraph); if (accessToken == null) { if (PnPConnection.CurrentConnection.PSCredential != null) { // Using normal credentials accessToken = TokenHandler.AcquireToken("graph.microsoft.com", null); } if (accessToken == null) { throw new PSInvalidOperationException("Your template contains artifacts that require an access token. Please provide consent to the PnP Management Shell application first by executing: Register-PnPManagementShellAccess"); } } } using (var provisioningContext = new PnPProvisioningContext((resource, scope) => { if (resource.ToLower().StartsWith("https://")) { var uri = new Uri(resource); resource = uri.Authority; } // Get Azure AD Token if (PnPConnection.CurrentConnection != null) { if (resource.Equals("graph.microsoft.com", StringComparison.OrdinalIgnoreCase)) { var graphAccessToken = PnPConnection.CurrentConnection.TryGetAccessToken(Enums.TokenAudience.MicrosoftGraph); if (graphAccessToken != null) { // Authenticated using -Graph or using another way to retrieve the accesstoken with Connect-PnPOnline return(Task.FromResult(graphAccessToken)); } } } if (PnPConnection.CurrentConnection.PSCredential != null) { // Using normal credentials return(Task.FromResult(TokenHandler.AcquireToken(resource, null))); } else { // No token... throw new PSInvalidOperationException("Your template contains artifacts that require an access token. Please provide consent to the PnP Management Shell application first by executing: Register-PnPManagementShellAccess"); } })) { #endif if (!string.IsNullOrEmpty(SequenceId)) { Tenant.ApplyTenantTemplate(hierarchyToApply, SequenceId, configuration); } else { if (hierarchyToApply.Sequences.Count > 0) { foreach (var sequence in hierarchyToApply.Sequences) { Tenant.ApplyTenantTemplate(hierarchyToApply, sequence.ID, configuration); } } else { Tenant.ApplyTenantTemplate(hierarchyToApply, null, configuration); } } #if !ONPREMISES } #endif WriteObject(sitesProvisioned, true); }
public Stream ToFormattedHierarchy(ProvisioningHierarchy hierarchy) { if (hierarchy == null) { throw new ArgumentNullException(nameof(hierarchy)); } using (var scope = new PnPSerializationScope(typeof(TSchemaTemplate))) { // We prepare a dummy template to leverage the existing deserialization infrastructure var dummyTemplate = new ProvisioningTemplate(); dummyTemplate.Id = $"DUMMY-{Guid.NewGuid()}"; hierarchy.Templates.Add(dummyTemplate); // Prepare the output wrapper Type wrapperType; object wrapper, templatesItem; Array templates; ProcessOutputHierarchy(dummyTemplate, out wrapperType, out wrapper, out templates, out templatesItem); // Handle the Sequences, if any var ts = new SequenceSerializer(); ts.Serialize(dummyTemplate, wrapper); // Remove the dummy template hierarchy.Templates.Remove(dummyTemplate); // Add every single template to the output var provisioningTemplates = Array.CreateInstance(typeof(TSchemaTemplate), hierarchy.Templates.Count); for (int c = 0; c < hierarchy.Templates.Count; c++) { // Prepare variable to hold the output template var outputTemplate = new TSchemaTemplate(); // Serialize the real templates SerializeTemplate(hierarchy.Templates[c], outputTemplate); // Add the serialized template to the output provisioningTemplates.SetValue(outputTemplate, c); } templatesItem.GetType().GetProperty("ProvisioningTemplate", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.IgnoreCase).SetValue(templatesItem, provisioningTemplates); templates.SetValue(templatesItem, 0); wrapperType.GetProperty("Templates", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.IgnoreCase).SetValue(wrapper, templates); XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); ns.Add(((IXMLSchemaFormatter)this).NamespacePrefix, ((IXMLSchemaFormatter)this).NamespaceUri); MemoryStream output = new MemoryStream(); XmlSerializer xmlSerializer = new XmlSerializer(wrapperType); if (ns != null) { xmlSerializer.Serialize(output, wrapper, ns); } else { xmlSerializer.Serialize(output, wrapper); } output.Position = 0; return(output); } }
protected override void ExecuteCmdlet() { var applyingInformation = new ProvisioningTemplateApplyingInformation(); if (MyInvocation.BoundParameters.ContainsKey("Handlers")) { applyingInformation.HandlersToProcess = Handlers; } if (MyInvocation.BoundParameters.ContainsKey("ExcludeHandlers")) { foreach (var handler in (Handlers[])Enum.GetValues(typeof(Handlers))) { if (!ExcludeHandlers.Has(handler) && handler != Handlers.All) { Handlers = Handlers | handler; } } applyingInformation.HandlersToProcess = Handlers; } if (ExtensibilityHandlers != null) { applyingInformation.ExtensibilityHandlers = ExtensibilityHandlers.ToList(); } applyingInformation.ProgressDelegate = (message, step, total) => { if (message != null) { var percentage = Convert.ToInt32((100 / Convert.ToDouble(total)) * Convert.ToDouble(step)); progressRecord.Activity = $"Applying template to tenant"; progressRecord.StatusDescription = message; progressRecord.PercentComplete = percentage; progressRecord.RecordType = ProgressRecordType.Processing; WriteProgress(progressRecord); } }; var warningsShown = new List <string>(); applyingInformation.MessagesDelegate = (message, type) => { switch (type) { case ProvisioningMessageType.Warning: { if (!warningsShown.Contains(message)) { WriteWarning(message); warningsShown.Add(message); } break; } case ProvisioningMessageType.Progress: { if (message != null) { var activity = message; if (message.IndexOf("|") > -1) { var messageSplitted = message.Split('|'); if (messageSplitted.Length == 4) { var current = double.Parse(messageSplitted[2]); var total = double.Parse(messageSplitted[3]); subProgressRecord.RecordType = ProgressRecordType.Processing; subProgressRecord.Activity = string.IsNullOrEmpty(messageSplitted[0]) ? "-" : messageSplitted[0]; subProgressRecord.StatusDescription = string.IsNullOrEmpty(messageSplitted[1]) ? "-" : messageSplitted[1]; subProgressRecord.PercentComplete = Convert.ToInt32((100 / total) * current); WriteProgress(subProgressRecord); } else { subProgressRecord.Activity = "Processing"; subProgressRecord.RecordType = ProgressRecordType.Processing; subProgressRecord.StatusDescription = activity; subProgressRecord.PercentComplete = 0; WriteProgress(subProgressRecord); } } else { subProgressRecord.Activity = "Processing"; subProgressRecord.RecordType = ProgressRecordType.Processing; subProgressRecord.StatusDescription = activity; subProgressRecord.PercentComplete = 0; WriteProgress(subProgressRecord); } } break; } case ProvisioningMessageType.Completed: { WriteProgress(new ProgressRecord(1, message, " ") { RecordType = ProgressRecordType.Completed }); break; } } }; applyingInformation.OverwriteSystemPropertyBagValues = OverwriteSystemPropertyBagValues; applyingInformation.IgnoreDuplicateDataRowErrors = IgnoreDuplicateDataRowErrors; applyingInformation.ClearNavigation = ClearNavigation; applyingInformation.ProvisionContentTypesToSubWebs = ProvisionContentTypesToSubWebs; applyingInformation.ProvisionFieldsToSubWebs = ProvisionFieldsToSubWebs; ProvisioningHierarchy hierarchyToApply = null; switch (ParameterSetName) { case ParameterSet_PATH: { hierarchyToApply = GetHierarchy(); break; } case ParameterSet_OBJECT: { hierarchyToApply = Hierarchy; if (ResourceFolder != null) { var fileSystemConnector = new FileSystemConnector(ResourceFolder, ""); hierarchyToApply.Connector = fileSystemConnector; } else { if (Path != null) { if (!System.IO.Path.IsPathRooted(Path)) { Path = System.IO.Path.Combine(SessionState.Path.CurrentFileSystemLocation.Path, Path); } } else { Path = SessionState.Path.CurrentFileSystemLocation.Path; } var fileInfo = new FileInfo(Path); var fileConnector = new FileSystemConnector(fileInfo.DirectoryName, ""); hierarchyToApply.Connector = fileConnector; } break; } } if (Parameters != null) { foreach (var parameter in Parameters.Keys) { if (hierarchyToApply.Parameters.ContainsKey(parameter.ToString())) { hierarchyToApply.Parameters[parameter.ToString()] = Parameters[parameter].ToString(); } else { hierarchyToApply.Parameters.Add(parameter.ToString(), Parameters[parameter].ToString()); } } } if (!string.IsNullOrEmpty(SequenceId)) { Tenant.ApplyProvisionHierarchy(hierarchyToApply, SequenceId, applyingInformation); } else { foreach (var sequence in hierarchyToApply.Sequences) { Tenant.ApplyProvisionHierarchy(hierarchyToApply, sequence.ID, applyingInformation); } } }
/// <summary> /// Constructor for SiteCollectionCollection class /// </summary> /// <param name="parentHierarchy">Parent Provisioning object</param> public SiteCollectionCollection(ProvisioningHierarchy parentHierarchy) : base(parentHierarchy) { }