private static String GetX509CertificateInformation(SetupInformation info)
        {
            // var basePath = String.Format(@"{0}..\..\..\..\Scripts\", AppDomain.CurrentDomain.BaseDirectory);

            var certificate = info.AuthenticationCertificate;
            //var certificate = new X509Certificate2();
            //if (info.SslCertificateGenerate)
            //{
            //    certificate.Import($@"{basePath}{info.SslCertificateCommonName}.cer");
            //}
            //else
            //{
            //    certificate = new X509Certificate2(info.SslCertificateFile, info.SslCertificatePassword);
            //}

            var rawCert        = certificate.GetRawCertData();
            var base64Cert     = System.Convert.ToBase64String(rawCert);
            var rawCertHash    = certificate.GetCertHash();
            var base64CertHash = System.Convert.ToBase64String(rawCertHash);
            var KeyId          = System.Guid.NewGuid().ToString();

            var keyCredential =
                "{" +
                "\"customKeyIdentifier\": \"" + base64CertHash + "\"," +
                "\"keyId\": \"" + KeyId + "\"," +
                "\"type\": \"AsymmetricX509Cert\"," +
                "\"usage\": \"Verify\"," +
                "\"key\":  \"" + base64Cert + "\"" +
                "}";

            return(keyCredential);
        }
        private static async Task BuildAndDeployWebSite(SetupInformation info)
        {
            // Get the Project Path
            var basePath    = String.Format(@"{0}..\..\..\", AppDomain.CurrentDomain.BaseDirectory);
            var projectPath = (new System.IO.FileInfo(basePath + @"OfficeDevPnP.PartnerPack.SiteProvisioning")).FullName;

            // Save the PublishingSettings file
            var xmlPublishingSettings  = XElement.Parse(info.AzureAppPublishingSettings);
            var publishingSettingsPath = projectPath + @"\pnp-partner-pack.publishingSettings";

            xmlPublishingSettings.Save(publishingSettingsPath);

            // Run PowerShell script to build and deploy
            Hashtable packageBuildParameters = new Hashtable();

            packageBuildParameters.Add("ProjectPath", projectPath);
            packageBuildParameters.Add("PublishingSettingsPath", publishingSettingsPath);

            var    powerShellScriptPath = (new System.IO.FileInfo($@"{basePath}\OfficeDevPnP.PartnerPack.Setup\Scripts\MsBuildWebSite.ps1")).FullName;
            string packageBuildResult   = Run.RunScript(powerShellScriptPath, packageBuildParameters);

            if (packageBuildResult.Contains("Build FAILED"))
            {
                throw new ApplicationException("Failed to build the web project with MSBuild!");
            }
            else if (packageBuildResult.Contains("Missing MSBuild"))
            {
                throw new ApplicationException("Missing MSBuild v. 14.0.25420.1 or higher!");
            }
        }
        private static void LoadX509Certificate(SetupInformation info)
        {
            var certificate = new X509Certificate2(info.SslCertificateFile, info.SslCertificatePassword);

            info.AuthenticationCertificate = certificate;
            info.SslCertificateCommonName  = certificate.SubjectName.Name;
        }
        private static void CreateX509Certificate(SetupInformation info)
        {
            var certificate = CreateSelfSignedCertificate(info.SslCertificateCommonName.ToLower(),
                                                          info.SslCertificateStartDate, info.SslCertificateEndDate, info.SslCertificatePassword);

            SaveCertificateFiles(info, certificate);
        }
        private async static Task CreateAzureStorageAccount(SetupInformation info)
        {
            var key = await AzureManagementUtility.CreateStorageAccount(info.AzureAccessToken,
                                                                        info.AzureTargetSubscriptionId,
                                                                        info.AzureResourceGroupName,
                                                                        info.AzureServicePlanName,
                                                                        info.AzureBlobStorageName,
                                                                        info.AzureLocationDisplayName);

            info.AzureStorageKey = key;
        }
        private static void SaveCertificateFiles(SetupInformation info, X509Certificate2 certificate)
        {
            info.AuthenticationCertificate = certificate;
            //var basePath = String.Format(@"{0}..\..\..\..\Scripts\", AppDomain.CurrentDomain.BaseDirectory);

            //info.SslCertificateFile = $@"{basePath}{info.SslCertificateCommonName}.pfx";
            //var pfx = certificate.Export(X509ContentType.Pfx, info.SslCertificatePassword);
            //System.IO.File.WriteAllBytes(info.SslCertificateFile, pfx);

            //var cer = certificate.Export(X509ContentType.Cert);
            //System.IO.File.WriteAllBytes($@"{basePath}{info.SslCertificateCommonName}.cer", cer);
        }
        private static async Task CreateAzureADApplication(SetupInformation info, AzureAdApplication application, string office365AzureADAccessToken)
        {
            String jsonResponse = await HttpHelper.MakePostRequestForStringAsync(
                String.Format("{0}applications",
                              AzureManagementUtility.MicrosoftGraphBetaBaseUri),
                application,
                "application/json", office365AzureADAccessToken);

            var azureAdApplication = JsonConvert.DeserializeObject <AzureAdApplication>(jsonResponse);

            info.AzureAppClientId = azureAdApplication.AppId.HasValue ? azureAdApplication.AppId.Value : Guid.Empty;
        }
 private static async Task UpdateProgress(SetupInformation info, SetupStep currentStep, String stepDescription)
 {
     if (currentStep == SetupStep.Completed)
     {
         info.ViewModel.SetupProgress = 100;
     }
     else
     {
         info.ViewModel.SetupProgress = (100 / (Int32)SetupStep.Completed) * (Int32)currentStep;
     }
     info.ViewModel.SetupProgressDescription = stepDescription;
 }
        private async static Task BuildAndDeployJob(SetupInformation info, String jobName, String jobPath, String basePath, JobType jobType)
        {
            // Run PowerShell script to build and deploy
            Hashtable packageBuildParameters = new Hashtable();

            packageBuildParameters.Add("ProjectPath", jobPath);

            var    powerShellScriptPath = (new System.IO.FileInfo($@"{basePath}\OfficeDevPnP.PartnerPack.Setup\Scripts\MsBuildWebJob.ps1")).FullName;
            string packageBuildResult   = Run.RunScript(powerShellScriptPath, packageBuildParameters);

            if (packageBuildResult.Contains("Build FAILED"))
            {
                // TODO: Handle exception
            }

            // Create the WebJob ZIP file
            var binPath = Path.Combine(jobPath, @"bin\Release");
            var zipPath = $@"{basePath}\OfficeDevPnP.PartnerPack.Setup\{jobName}.zip";

            // Remove any already existing file
            if (System.IO.File.Exists(zipPath))
            {
                System.IO.File.Delete(zipPath);
            }

            System.IO.Compression.ZipFile.CreateFromDirectory(binPath, zipPath);

            // Get the Azure App Service Publishing Credentials
            var xmlPublishingSettings = XElement.Parse(info.AzureAppPublishingSettings);

            if (xmlPublishingSettings != null)
            {
                var xmlPublishProfile = xmlPublishingSettings.Element("publishProfile");

                if (xmlPublishProfile != null)
                {
                    var username = xmlPublishProfile.Attribute("userName").Value;
                    var password = xmlPublishProfile.Attribute("userPWD").Value;

                    // Upload the WebJobB
                    await AzureManagementUtility.UploadWebJob(info.AzureAppServiceName, username, password, jobName, zipPath, jobType);
                }
            }

            // Remove the ZIP file after having created the job
            if (System.IO.File.Exists(zipPath))
            {
                System.IO.File.Delete(zipPath);
            }
        }
        private async static Task BuildAndDeployJobs(SetupInformation info)
        {
            var basePath = String.Format(@"{0}..\..\..\", AppDomain.CurrentDomain.BaseDirectory);

            await BuildAndDeployJob(info, "CheckAdminsJob",
                                    (new System.IO.FileInfo(basePath + @"OfficeDevPnP.PartnerPack.CheckAdminsJob")).FullName,
                                    basePath, JobType.Triggered);
            await BuildAndDeployJob(info, "ExternalUsersJob",
                                    (new System.IO.FileInfo(basePath + @"OfficeDevPnP.PartnerPack.ExternalUsersJob")).FullName,
                                    basePath, JobType.Triggered);
            await BuildAndDeployJob(info, "ScheduledJob",
                                    (new System.IO.FileInfo(basePath + @"OfficeDevPnP.PartnerPack.ScheduledJob")).FullName,
                                    basePath, JobType.Triggered);
            await BuildAndDeployJob(info, "ContinousJob",
                                    (new System.IO.FileInfo(basePath + @"OfficeDevPnP.PartnerPack.ContinousJob")).FullName,
                                    basePath, JobType.Continuous);
        }
        private async static Task CreateAzureAppService(SetupInformation info)
        {
            var appSettings = new AzureAppServiceSetting[3];

            appSettings[0] = new AzureAppServiceSetting {
                Name = "WEBSITE_LOAD_CERTIFICATES", Value = "*"
            };
            appSettings[1] = new AzureAppServiceSetting {
                Name = "WEBJOBS_IDLE_TIMEOUT", Value = "10000"
            };
            appSettings[2] = new AzureAppServiceSetting {
                Name = "SCM_COMMAND_IDLE_TIMEOUT", Value = "10000"
            };

            await AzureManagementUtility.CreateAppServiceWebSite(info.AzureAccessToken,
                                                                 info.AzureTargetSubscriptionId,
                                                                 info.AzureResourceGroupName,
                                                                 info.AzureServicePlanName,
                                                                 info.AzureAppServiceName,
                                                                 info.AzureLocationDisplayName,
                                                                 appSettings);

            var certificate = info.AuthenticationCertificate;
            var pfxBlob     = certificate.Export(X509ContentType.Pfx, info.SslCertificatePassword);

            await AzureManagementUtility.UploadCertificateToAzureAppService(info.AzureAccessToken,
                                                                            info.AzureTargetSubscriptionId,
                                                                            info.AzureResourceGroupName,
                                                                            info.AzureAppServiceName,
                                                                            info.AzureLocationDisplayName,
                                                                            pfxBlob,
                                                                            info.SslCertificatePassword);

            info.AzureAppPublishingSettings = await AzureManagementUtility.GetAppServiceWebSitePublishingSettings(
                info.AzureAccessToken,
                info.AzureTargetSubscriptionId,
                info.AzureResourceGroupName,
                info.AzureAppServiceName);
        }
        private async static Task CreateAzureResourceGroup(SetupInformation info)
        {
            await AzureManagementUtility.RegisterAzureProvider(info.AzureAccessToken,
                                                               info.AzureTargetSubscriptionId,
                                                               "Microsoft.Storage");

            await AzureManagementUtility.RegisterAzureProvider(info.AzureAccessToken,
                                                               info.AzureTargetSubscriptionId,
                                                               "Microsoft.Web");

            info.AzureResourceGroupName = $"{info.AzureAppServiceName}-resource-group";
            info.AzureServicePlanName   = $"{info.AzureAppServiceName}-plan";

            await AzureManagementUtility.CreateResourceGroup(info.AzureAccessToken,
                                                             info.AzureTargetSubscriptionId,
                                                             info.AzureResourceGroupName,
                                                             info.AzureLocationId);

            await AzureManagementUtility.CreateServicePlan(info.AzureAccessToken,
                                                           info.AzureTargetSubscriptionId,
                                                           info.AzureResourceGroupName,
                                                           info.AzureServicePlanName,
                                                           info.AzureLocationDisplayName);
        }
        private async static Task RegisterAzureADApplication(SetupInformation info)
        {
            // Fix the App URL
            if (!info.AzureWebAppUrl.EndsWith("/"))
            {
                info.AzureWebAppUrl = info.AzureWebAppUrl + "/";
            }

            // Load the App Manifest template
            Stream stream = typeof(SetupManager)
                            .Assembly
                            .GetManifestResourceStream("OfficeDevPnP.PartnerPack.Setup.Resources.azure-ad-app-manifest.json");

            using (StreamReader sr = new StreamReader(stream))
            {
                // Get the JSON manifest
                var jsonApplication = sr.ReadToEnd();

                var application   = JsonConvert.DeserializeObject <AzureAdApplication>(jsonApplication);
                var keyCredential = JsonConvert.DeserializeObject <KeyCredential>(info.AzureAppKeyCredential);

                application.displayName    = info.ApplicationName;
                application.homepage       = info.AzureWebAppUrl;
                application.identifierUris = new List <String>();
                application.identifierUris.Add(info.ApplicationUniqueUri);
                application.keyCredentials = new List <KeyCredential>();
                application.keyCredentials.Add(keyCredential);
                application.replyUrls = new List <String>();
                application.replyUrls.Add(info.AzureWebAppUrl);

                // Generate the Application Shared Secret
                var    startDate = DateTime.Now;
                Byte[] bytes     = new Byte[32];
                using (var rand = System.Security.Cryptography.RandomNumberGenerator.Create())
                {
                    rand.GetBytes(bytes);
                }
                info.AzureAppSharedSecret       = System.Convert.ToBase64String(bytes);
                application.passwordCredentials = new List <object>();
                application.passwordCredentials.Add(new AzureAdApplicationPasswordCredential
                {
                    CustomKeyIdentifier = null,
                    StartDate           = startDate.ToString("o"),
                    EndDate             = startDate.AddYears(2).ToString("o"),
                    KeyId = Guid.NewGuid().ToString(),
                    Value = info.AzureAppSharedSecret,
                });

                // Get an Access Token to create the application via Microsoft Graph
                var office365AzureADAccessToken = await AzureManagementUtility.GetAccessTokenSilentAsync(
                    AzureManagementUtility.MicrosoftGraphResourceId,
                    ConfigurationManager.AppSettings["O365:ClientId"]);

                var azureAdApplicationCreated = false;

                // Create the Azure AD Application
                try
                {
                    await CreateAzureADApplication(info, application, office365AzureADAccessToken);

                    azureAdApplicationCreated = true;
                }
                catch (ApplicationException ex)
                {
                    var graphError = JsonConvert.DeserializeObject <GraphError>(((HttpException)ex.InnerException).Message);
                    if (graphError != null && graphError.error.code == "Request_BadRequest" &&
                        graphError.error.message.Contains("identifierUris already exists"))
                    {
                        // We need to remove the existing application

                        // Thus, retrieve it
                        String jsonApplications = await HttpHelper.MakeGetRequestForStringAsync(
                            String.Format("{0}applications?$filter=identifierUris/any(c:c+eq+'{1}')",
                                          AzureManagementUtility.MicrosoftGraphBetaBaseUri,
                                          HttpUtility.UrlEncode(info.ApplicationUniqueUri)),
                            office365AzureADAccessToken);

                        var applications        = JsonConvert.DeserializeObject <AzureAdApplications>(jsonApplications);
                        var applicationToUpdate = applications.Applications.FirstOrDefault();
                        if (applicationToUpdate != null)
                        {
                            // Remove it
                            await HttpHelper.MakeDeleteRequestAsync(
                                String.Format("{0}applications/{1}",
                                              AzureManagementUtility.MicrosoftGraphBetaBaseUri,
                                              applicationToUpdate.Id),
                                office365AzureADAccessToken);

                            // And add it again
                            await CreateAzureADApplication(info, application, office365AzureADAccessToken);

                            azureAdApplicationCreated = true;
                        }
                    }
                }

                if (azureAdApplicationCreated)
                {
                    // TODO: We should upload the logo
                    // property mainLogo: stream of the application via PATCH
                }
            }
        }
        /// <summary>
        /// This method handles the real setup process
        /// </summary>
        /// <returns></returns>
        public static async Task SetupPartnerPackAsync(SetupInformation info)
        {
            #region Create the Infrastructural Site Collection

            await UpdateProgress(info, SetupStep.CreateInfrastructuralSiteCollection, "Creating Infrastructural Site Collection");
            await CreateInfrastructuralSiteCollectionAsync(info);

            #endregion

            #region Create or manage the X.509 Certificate for the App-Only token

            await UpdateProgress(info, SetupStep.ConfigureX509Certificate, "Configuring X.509 Certificate");

            if (info.SslCertificateGenerate)
            {
                CreateX509Certificate(info);
            }
            else
            {
                LoadX509Certificate(info);
            }

            info.SslCertificateThumbprint = GetX509CertificateThumbprint(info);
            info.AzureAppKeyCredential    = GetX509CertificateInformation(info);

            #endregion

            #region Register the Azure AD Application

            await UpdateProgress(info, SetupStep.RegisterAzureADApplication, "Registering Azure AD Application");
            await RegisterAzureADApplication(info);

            #endregion

            #region Create the Azure Resource Group

            await UpdateProgress(info, SetupStep.CreateResourceGroup, "Creating Azure Resource Group");
            await CreateAzureResourceGroup(info);

            #endregion

            #region Create the Azure Blob Storage

            await UpdateProgress(info, SetupStep.CreateBlobStorageAccount, "Creating Azure Blob Storage Account");
            await CreateAzureStorageAccount(info);

            #endregion

            #region Create the Azure App Service

            await UpdateProgress(info, SetupStep.CreateAzureAppService, "Creating Azure App Service");
            await CreateAzureAppService(info);

            #endregion

            #region Configure the .config files of the App Service, before uploading files

            await UpdateProgress(info, SetupStep.ConfigureSettings, "Configuring Settings");
            await ConfigureSettings(info);

            #endregion

            #region Provision the Web Site

            await UpdateProgress(info, SetupStep.ProvisionWebSite, "Provisioning Azure Web Site");
            await BuildAndDeployWebSite(info);

            #endregion

            #region Provision the WebJobs

            await UpdateProgress(info, SetupStep.ProvisionWebJobs, "Provisioning Azure WebJobs");
            await BuildAndDeployJobs(info);

            #endregion

            await UpdateProgress(info, SetupStep.Completed, "Setup Completed");
        }
        private async static Task ConfigureSettings(SetupInformation info)
        {
            var basePath = String.Format(@"{0}..\..\..\", AppDomain.CurrentDomain.BaseDirectory);

            var configFiles = new String[5];

            configFiles[0] = (new System.IO.FileInfo(basePath + @"OfficeDevPnP.PartnerPack.CheckAdminsJob\App.config")).FullName;
            configFiles[1] = (new System.IO.FileInfo(basePath + @"OfficeDevPnP.PartnerPack.ExternalUsersJob\App.config")).FullName;
            configFiles[2] = (new System.IO.FileInfo(basePath + @"OfficeDevPnP.PartnerPack.ContinousJob\App.config")).FullName;
            configFiles[3] = (new System.IO.FileInfo(basePath + @"OfficeDevPnP.PartnerPack.ScheduledJob\App.config")).FullName;
            configFiles[4] = (new System.IO.FileInfo(basePath + @"OfficeDevPnP.PartnerPack.SiteProvisioning\Web.config")).FullName;

            var azureStorageConnection = $"DefaultEndpointsProtocol=https;AccountName={info.AzureBlobStorageName};AccountKey={info.AzureStorageKey}";

            foreach (var config in configFiles)
            {
                XElement xmlConfig = XElement.Load(config);

                // Configure Connection Strings
                var connectionStrings = xmlConfig.Element("connectionStrings");
                foreach (var cn in connectionStrings.Elements("add"))
                {
                    if (cn.Attribute("name").Value == "AzureWebJobsDashboard" ||
                        cn.Attribute("name").Value == "AzureWebJobsStorage")
                    {
                        cn.Attribute("connectionString").Value = azureStorageConnection;
                    }
                }

                // Configure AppSettings
                var appSettings = xmlConfig.Element("appSettings");
                foreach (var s in appSettings.Elements("add"))
                {
                    switch (s.Attribute("key").Value)
                    {
                    case "ida:ClientId":
                        s.Attribute("value").Value = info.AzureAppClientId.ToString();
                        break;

                    case "ida:ClientSecret":
                        s.Attribute("value").Value = info.AzureAppSharedSecret;
                        break;
                    }
                }

                // PnP Partner Pack custom settings
                var pnpNamespace = XNamespace.Get("http://schemas.dev.office.com/PnP/2016/08/PnPPartnerPackConfiguration");

                var pnpPartnerPackSettings = xmlConfig.Element(pnpNamespace + "PnPPartnerPackConfiguration");
                if (pnpPartnerPackSettings != null)
                {
                    var tenantSettings = pnpPartnerPackSettings.Element(pnpNamespace + "TenantSettings");

                    if (tenantSettings != null)
                    {
                        if (tenantSettings.Attribute("tenant") != null)
                        {
                            tenantSettings.Attribute("tenant").Value = $"{info.AzureADTenant}.onmicrosoft.com";
                        }
                        if (tenantSettings.Attribute("appOnlyCertificateThumbprint") != null)
                        {
                            tenantSettings.Attribute("appOnlyCertificateThumbprint").Value = info.SslCertificateThumbprint;
                        }
                        if (tenantSettings.Attribute("infrastructureSiteUrl") != null)
                        {
                            tenantSettings.Attribute("infrastructureSiteUrl").Value = info.InfrastructuralSiteUrl;
                        }
                    }
                }
                xmlConfig.Save(config);
            }
        }
        private static String GetX509CertificateThumbprint(SetupInformation info)
        {
            var certificate = info.AuthenticationCertificate;

            return(certificate.Thumbprint.ToUpper());
        }
        private async static Task CreateInfrastructuralSiteCollectionAsync(SetupInformation info)
        {
            Uri infrastructureSiteUri = new Uri(info.InfrastructuralSiteUrl);
            Uri tenantAdminUri        = new Uri(infrastructureSiteUri.Scheme + "://" +
                                                infrastructureSiteUri.Host.Replace(".sharepoint.com", "-admin.sharepoint.com"));
            Uri sharepointUri = new Uri(infrastructureSiteUri.Scheme + "://" +
                                        infrastructureSiteUri.Host + "/");
            var siteUrl           = info.InfrastructuralSiteUrl.Substring(info.InfrastructuralSiteUrl.IndexOf("sharepoint.com/") + 14);
            var siteCreated       = false;
            var siteAlreadyExists = false;

            var accessToken = await AzureManagementUtility.GetAccessTokenSilentAsync(
                tenantAdminUri.ToString(), ConfigurationManager.AppSettings["O365:ClientId"]);

            AuthenticationManager am = new AuthenticationManager();

            using (var adminContext = am.GetAzureADAccessTokenAuthenticatedContext(
                       tenantAdminUri.ToString(), accessToken))
            {
                adminContext.RequestTimeout = Timeout.Infinite;

                var tenant = new Tenant(adminContext);

                // Check if the site already exists, and eventually removes it from the Recycle Bin
                if (tenant.CheckIfSiteExists(info.InfrastructuralSiteUrl, "Recycled"))
                {
                    tenant.DeleteSiteCollectionFromRecycleBin(info.InfrastructuralSiteUrl);
                }

                siteAlreadyExists = tenant.SiteExists(info.InfrastructuralSiteUrl);
                if (!siteAlreadyExists)
                {
                    // Configure the Site Collection properties
                    SiteEntity newSite = new SiteEntity();
                    newSite.Description          = "PnP Partner Pack - Infrastructural Site Collection";
                    newSite.Lcid                 = (uint)info.InfrastructuralSiteLCID;
                    newSite.Title                = newSite.Description;
                    newSite.Url                  = info.InfrastructuralSiteUrl;
                    newSite.SiteOwnerLogin       = info.InfrastructuralSitePrimaryAdmin;
                    newSite.StorageMaximumLevel  = 1000;
                    newSite.StorageWarningLevel  = 900;
                    newSite.Template             = "STS#0";
                    newSite.TimeZoneId           = info.InfrastructuralSiteTimeZone;
                    newSite.UserCodeMaximumLevel = 0;
                    newSite.UserCodeWarningLevel = 0;

                    // Create the Site Collection and wait for its creation (we're asynchronous)
                    tenant.CreateSiteCollection(newSite, true, true, (top) =>
                    {
                        if (top == TenantOperationMessage.CreatingSiteCollection)
                        {
                            var maxProgress = (100 / (Int32)SetupStep.Completed);
                            info.ViewModel.SetupProgress += 1;
                            if (info.ViewModel.SetupProgress >= maxProgress)
                            {
                                info.ViewModel.SetupProgress = maxProgress;
                            }
                        }
                        return(false);
                    });
                }
            }

            await Task.Delay(5000);

            using (var adminContext = am.GetAzureADAccessTokenAuthenticatedContext(
                       tenantAdminUri.ToString(), accessToken))
            {
                adminContext.RequestTimeout = Timeout.Infinite;

                var  tenant = new Tenant(adminContext);
                Site site   = tenant.GetSiteByUrl(info.InfrastructuralSiteUrl);
                Web  web    = site.RootWeb;

                adminContext.Load(site, s => s.Url);
                adminContext.Load(web, w => w.Url);
                adminContext.ExecuteQueryRetry();

                // Enable Secondary Site Collection Administrator
                if (!String.IsNullOrEmpty(info.InfrastructuralSiteSecondaryAdmin))
                {
                    Microsoft.SharePoint.Client.User secondaryOwner = web.EnsureUser(info.InfrastructuralSiteSecondaryAdmin);
                    secondaryOwner.IsSiteAdmin = true;
                    secondaryOwner.Update();

                    web.SiteUsers.AddUser(secondaryOwner);
                    adminContext.ExecuteQueryRetry();
                }
                siteCreated = true;
            }

            if (siteAlreadyExists || siteCreated)
            {
                accessToken = await AzureManagementUtility.GetAccessTokenSilentAsync(
                    sharepointUri.ToString(), ConfigurationManager.AppSettings["O365:ClientId"]);

                using (ClientContext clientContext = am.GetAzureADAccessTokenAuthenticatedContext(
                           info.InfrastructuralSiteUrl, accessToken))
                {
                    clientContext.RequestTimeout = Timeout.Infinite;

                    Site site = clientContext.Site;
                    Web  web  = site.RootWeb;

                    clientContext.Load(site, s => s.Url);
                    clientContext.Load(web, w => w.Url);
                    clientContext.ExecuteQueryRetry();

                    // Override settings within templates, before uploading them
                    UpdateProvisioningTemplateParameter("Responsive", "SPO-Responsive.xml",
                                                        "AzureWebSiteUrl", info.AzureWebAppUrl);
                    UpdateProvisioningTemplateParameter("Overrides", "PnP-Partner-Pack-Overrides.xml",
                                                        "AzureWebSiteUrl", info.AzureWebAppUrl);

                    // Apply the templates to the target site
                    ApplyProvisioningTemplate(web, "Infrastructure", "PnP-Partner-Pack-Infrastructure-Jobs.xml");
                    ApplyProvisioningTemplate(web, "Infrastructure", "PnP-Partner-Pack-Infrastructure-Templates.xml");
                    ApplyProvisioningTemplate(web, "", "PnP-Partner-Pack-Infrastructure-Contents.xml");

                    // We to it twice to force the content types, due to a small bug in the provisioning engine
                    ApplyProvisioningTemplate(web, "", "PnP-Partner-Pack-Infrastructure-Contents.xml");
                }
            }
            else
            {
                // TODO: Handle some kind of exception ...
            }
        }