private static async Task <SiteCreationOptions> EnsureSiteCreationOptionsAsync(PnPContext context, SiteCreationOptions creationOptions)
        {
            if (creationOptions == null)
            {
                creationOptions = new SiteCreationOptions();
            }

            await ProcessCreationOptionsAsync(context, creationOptions).ConfigureAwait(false);

            // Configure the defaults for the wait on async provisioning complete
            if (!creationOptions.MaxAsyncProvisioningStatusChecks.HasValue)
            {
                creationOptions.MaxAsyncProvisioningStatusChecks = 80;
            }
            if (!creationOptions.WaitAfterAsyncProvisioningStatusCheck.HasValue)
            {
                creationOptions.WaitAfterAsyncProvisioningStatusCheck = 15;
            }

            return(creationOptions);
        }
        internal static async Task <PnPContext> ConnectGroupToSiteAsync(PnPContext contextIn, ConnectSiteToGroupOptions siteToGroupify, CreationOptions creationOptions)
        {
            using (var context = await GetContextForGroupConnectAsync(contextIn, siteToGroupify.Url).ConfigureAwait(false))
            {
                // Provide default creation options as input
                creationOptions = await EnsureCreationOptionsAsync(context, creationOptions).ConfigureAwait(false);

                // If we're using application permissions we use Microsoft Graph to create the site
                if (creationOptions.UsingApplicationPermissions.Value)
                {
                    throw new NotSupportedException("Group connecting sites using application permissions is not supported");
                }
                else
                {
                    var creationOptionsValues           = new List <string>();
                    Dictionary <string, object> payload = new Dictionary <string, object>
                    {
                        { "displayName", siteToGroupify.DisplayName },
                        { "alias", siteToGroupify.Alias },
                        { "isPublic", siteToGroupify.IsPublic }
                    };

                    var optionalParams = new Dictionary <string, object>
                    {
                        { "Description", siteToGroupify.Description ?? "" }
                    };

                    // Sensitivity labels have replaced classification (see https://docs.microsoft.com/en-us/microsoft-365/compliance/sensitivity-labels-teams-groups-sites?view=o365-worldwide#classic-azure-ad-group-classification)
                    // once enabled. Therefore we prefer setting a sensitivity label id over classification when specified. Also note that for setting sensitivity labels on
                    // group connected sites one needs to have at least one Azure AD P1 license. See https://docs.microsoft.com/en-us/azure/active-directory/enterprise-users/groups-assign-sensitivity-labels
                    if (siteToGroupify.SensitivityLabelId != Guid.Empty)
                    {
                        creationOptionsValues.Add($"SensitivityLabel:{siteToGroupify.SensitivityLabelId}");
                    }
                    else
                    {
                        optionalParams.Add("Classification", siteToGroupify.Classification ?? "");
                    }

                    if (siteToGroupify.Language != Language.Default)
                    {
                        creationOptionsValues.Add($"SPSiteLanguage:{(int)siteToGroupify.Language}");
                    }
                    if (!string.IsNullOrEmpty(siteToGroupify.SiteAlias))
                    {
                        creationOptionsValues.Add($"SiteAlias:{siteToGroupify.SiteAlias}");
                    }
                    creationOptionsValues.Add($"HubSiteId:{siteToGroupify.HubSiteId}");

                    if (siteToGroupify.Owners != null && siteToGroupify.Owners.Length > 0)
                    {
                        optionalParams.Add("Owners", siteToGroupify.Owners);
                    }
                    if (siteToGroupify.PreferredDataLocation.HasValue)
                    {
                        optionalParams.Add("PreferredDataLocation", siteToGroupify.PreferredDataLocation.Value.ToString());
                    }

                    if (creationOptionsValues.Any())
                    {
                        var creationOptionsValuesBody = new
                        {
                            __metadata = new { type = "Collection(Edm.String)" },
                            results    = creationOptionsValues
                        }.AsExpando();
                        optionalParams.Add("CreationOptions", creationOptionsValuesBody);
                    }

                    payload.Add("optionalParams", optionalParams);

                    SiteCreationOptions siteCreationOptions = new SiteCreationOptions()
                    {
                        UsingApplicationPermissions = creationOptions.UsingApplicationPermissions,
                        MaxStatusChecks             = creationOptions.MaxStatusChecks,
                        WaitAfterStatusCheck        = creationOptions.WaitAfterStatusCheck,
                    };

                    // Delegated permissions can use the SharePoint endpoints for site collection creation
                    return(await CreateSiteUsingSpoRestImplementationAsync(context, SiteCreationModel.GroupSiteManagerCreateGroupForSite, payload, siteCreationOptions).ConfigureAwait(false));
                }
            }
        }
        private static async Task WaitForProvisioningToCompleteAsync(PnPContext context, SiteCreationOptions creationOptions)
        {
            context.Logger.LogInformation($"Started waiting for the async provisioning of site {context.Uri} to be complete");

            var stopwatch = new Stopwatch();

            stopwatch.Start();

            bool isProvisioningComplete = false;
            bool validatePendingWebTemplateExtensionCalled = false;
            var  retryAttempt = 1;

            do
            {
                context.Logger.LogDebug($"Elapsed: {stopwatch.Elapsed:mm\\:ss\\.fff} | Attempt {retryAttempt}/{creationOptions.MaxAsyncProvisioningStatusChecks}");

                if (retryAttempt > 1)
                {
                    context.Logger.LogDebug($"Elapsed: {stopwatch.Elapsed:mm\\:ss\\.fff} | Waiting {creationOptions.WaitAfterAsyncProvisioningStatusCheck.Value} seconds");

                    await context.WaitAsync(TimeSpan.FromSeconds(creationOptions.WaitAfterAsyncProvisioningStatusCheck.Value)).ConfigureAwait(false);
                }

                var web = await context.Web.GetAsync(p => p.IsProvisioningComplete).ConfigureAwait(false);

                if (web.IsProvisioningComplete)
                {
                    isProvisioningComplete = true;
                }

                // We waited for more than 90 seconds
                if (!isProvisioningComplete && !validatePendingWebTemplateExtensionCalled && retryAttempt * creationOptions.WaitAfterAsyncProvisioningStatusCheck.Value > 90)
                {
                    context.Logger.LogDebug($"Calling ValidatePendingWebTemplateExtension for site {context.Uri}");

                    // Try "push" the process
                    await(context.Web as Web).RawRequestAsync(
                        new ApiCall("_api/Microsoft.Sharepoint.Utilities.WebTemplateExtensions.SiteScriptUtility.ValidatePendingWebTemplateExtension", ApiType.SPORest),
                        HttpMethod.Post).ConfigureAwait(false);
                    validatePendingWebTemplateExtensionCalled = true;

                    context.Logger.LogDebug($"Calling ValidatePendingWebTemplateExtension for site {context.Uri} done");
                }

                retryAttempt++;
            }while (!isProvisioningComplete && retryAttempt <= creationOptions.MaxAsyncProvisioningStatusChecks);

            stopwatch.Stop();

            context.Logger.LogDebug($"Elapsed: {stopwatch.Elapsed:mm\\:ss\\.fff} | Finished");

            if (!isProvisioningComplete)
            {
                // Bummer, sites seems to be still not ready...log a warning but let's not fail
                context.Logger.LogWarning($"Async provisioning of site {context.Uri} did not complete in {stopwatch.Elapsed:mm\\:ss\\.fff}");
            }
            else
            {
                context.Logger.LogInformation($"Async provisioning of site {context.Uri} is complete");
            }
        }
        private static async Task <PnPContext> CreateSiteUsingSpoRestImplementationAsync(PnPContext context, SiteCreationModel siteCreationModel, Dictionary <string, object> payload, SiteCreationOptions creationOptions)
        {
            string apiCall;
            string body;

            if (siteCreationModel == SiteCreationModel.SPSiteManagerCreate)
            {
                apiCall = $"_api/SPSiteManager/Create";
                var json = new { request = payload }.AsExpando();
                body = JsonSerializer.Serialize(json, typeof(ExpandoObject));
            }
            else if (siteCreationModel == SiteCreationModel.GroupSiteManagerCreateGroupEx)
            {
                apiCall = $"_api/GroupSiteManager/CreateGroupEx";
                body    = JsonSerializer.Serialize(payload, PnPConstants.JsonSerializer_IgnoreNullValues_CamelCase);
            }
            else if (siteCreationModel == SiteCreationModel.GroupSiteManagerCreateGroupForSite)
            {
                apiCall = $"_api/GroupSiteManager/CreateGroupForSite";
                body    = JsonSerializer.Serialize(payload, PnPConstants.JsonSerializer_IgnoreNullValues_CamelCase);
            }
            else
            {
                throw new ClientException(ErrorType.Unsupported, "The requested site creation model does not exist");
            }

            var result = await(context.Web as Web).RawRequestAsync(new ApiCall(apiCall, ApiType.SPORest, body), HttpMethod.Post).ConfigureAwait(false);

            var responseJson = JsonSerializer.Deserialize <JsonElement>(result.Json);

            #region Json Response

            /*
             * Success => SiteStatus = 2
             * Provisioning => SiteStatus = 1
             * Error => all other values
             *
             * {
             *  "d": {
             *      "Create": {
             *          "__metadata": {
             *              "type": "Microsoft.SharePoint.Portal.SPSiteCreationResponse"
             *          },
             *          "SiteId": "12befc2b-4c17-46a4-985e-530e6745cf35",
             *          "SiteStatus": 2,
             *          "SiteUrl": "https://bertonline.sharepoint.com/sites/pnpcoresdktestcommsite1"
             *      }
             *  }
             * }
             *
             */
            #endregion

            int siteStatus = responseJson.GetProperty("SiteStatus").GetInt32();

            PnPContext responseContext;
            if (siteStatus == 2)
            {
                // Site creation succeeded
                responseContext = await context.CloneAsync(new Uri(responseJson.GetProperty("SiteUrl").ToString())).ConfigureAwait(false);
            }
            else if (siteStatus == 1)
            {
                Guid groupId = Guid.Empty;
                if (siteCreationModel != SiteCreationModel.SPSiteManagerCreate)
                {
                    groupId = responseJson.GetProperty("GroupId").GetGuid();
                }

                // Site creation in progress, let's wait for it to finish
                responseContext = await VerifySiteStatusAsync(context, siteCreationModel != SiteCreationModel.SPSiteManagerCreate?groupId.ToString() : payload["Url"].ToString(),
                                                                  siteCreationModel, creationOptions.MaxStatusChecks.Value, creationOptions.WaitAfterStatusCheck.Value).ConfigureAwait(false);
            }
            else
            {
                // Something went wrong
                throw new ClientException(ErrorType.SharePointRestServiceError, string.Format(PnPCoreAdminResources.Exception_SiteCreation, payload["Url"].ToString(), siteStatus));
            }

            // Apply our "wait" strategy
            if (creationOptions.WaitAfterCreation.HasValue && creationOptions.WaitAfterCreation.Value > 0)
            {
                await responseContext.WaitAsync(TimeSpan.FromSeconds(creationOptions.WaitAfterCreation.Value)).ConfigureAwait(false);
            }
            else
            {
                if (creationOptions.WaitForAsyncProvisioning.HasValue && creationOptions.WaitForAsyncProvisioning.Value)
                {
                    // Let's wait for the async provisioning of features, site scripts and content types to be done before we allow API's to further update the created site
                    await WaitForProvisioningToCompleteAsync(responseContext, creationOptions).ConfigureAwait(false);
                }
            }

            return(responseContext);
        }
        internal static async Task <PnPContext> CreateSiteCollectionAsync(PnPContext context, CommonSiteOptions siteToCreate, SiteCreationOptions creationOptions)
        {
            // Provide default creation options as input
            creationOptions = await EnsureSiteCreationOptionsAsync(context, creationOptions).ConfigureAwait(false);

            if (siteToCreate is ClassicSiteOptions classicSite)
            {
                return(await CreateClassicSiteAsync(context, classicSite, creationOptions).ConfigureAwait(false));
            }
            else if (siteToCreate is TeamSiteOptions teamSite)
            {
                return(await CreateTeamSiteAsync(context, teamSite, creationOptions).ConfigureAwait(false));
            }
            else if (siteToCreate is CommonNoGroupSiteOptions noGroupSite)
            {
                return(await CreateCommonNoGroupSiteAsync(context, noGroupSite, creationOptions).ConfigureAwait(false));
            }

            throw new ArgumentException("Provided site options are not implemented");
        }
        private static async Task <PnPContext> CreateClassicSiteAsync(PnPContext context, ClassicSiteOptions siteToCreate, SiteCreationOptions creationOptions)
        {
            using (var tenantAdminContext = await context.GetSharePointAdmin().GetTenantAdminCenterContextAsync().ConfigureAwait(false))
            {
                string owner      = siteToCreate.Owner;
                var    splitOwner = owner.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
                if (splitOwner.Length == 3)
                {
                    owner = splitOwner[2];
                }

                List <IRequest <object> > csomRequests = new List <IRequest <object> >
                {
                    new CreateSiteRequest(siteToCreate.Url, siteToCreate.Title, owner, siteToCreate.WebTemplate, (int)siteToCreate.Language, (int)siteToCreate.TimeZone)
                };

                var result = await(tenantAdminContext.Web as Web).RawRequestAsync(new ApiCall(csomRequests), HttpMethod.Post).ConfigureAwait(false);

                SpoOperation op = result.ApiCall.CSOMRequests[0].Result as SpoOperation;

                if (!op.IsComplete)
                {
                    await SiteCollectionManagement.WaitForSpoOperationCompleteAsync(tenantAdminContext, op).ConfigureAwait(false);
                }

                return(await context.CloneAsync(siteToCreate.Url).ConfigureAwait(false));
            }
        }
        private static async Task <PnPContext> CreateTeamSiteAsync(PnPContext context, TeamSiteOptions siteToCreate, SiteCreationOptions creationOptions)
        {
            // If we're using application permissions we use Microsoft Graph to create the site
            if (creationOptions.UsingApplicationPermissions.Value)
            {
                //TODO: implement group creation functionality under Microsoft365 and use it here
                throw new NotSupportedException("Creating Team sites using application permissions is not yet supported in this preview version");
            }
            else
            {
                var creationOptionsValues           = new List <string>();
                Dictionary <string, object> payload = new Dictionary <string, object>
                {
                    { "displayName", siteToCreate.DisplayName },
                    { "alias", siteToCreate.Alias },
                    { "isPublic", siteToCreate.IsPublic }
                };

                var optionalParams = new Dictionary <string, object>
                {
                    { "Description", siteToCreate.Description ?? "" }
                };

                // Sensitivity labels have replaced classification (see https://docs.microsoft.com/en-us/microsoft-365/compliance/sensitivity-labels-teams-groups-sites?view=o365-worldwide#classic-azure-ad-group-classification)
                // once enabled. Therefore we prefer setting a sensitivity label id over classification when specified. Also note that for setting sensitivity labels on
                // group connected sites one needs to have at least one Azure AD P1 license. See https://docs.microsoft.com/en-us/azure/active-directory/enterprise-users/groups-assign-sensitivity-labels
                if (siteToCreate.SensitivityLabelId != Guid.Empty)
                {
                    creationOptionsValues.Add($"SensitivityLabel:{siteToCreate.SensitivityLabelId}");
                }
                else
                {
                    optionalParams.Add("Classification", siteToCreate.Classification ?? "");
                }

                if (siteToCreate.SiteDesignId.HasValue)
                {
                    creationOptionsValues.Add($"implicit_formula_292aa8a00786498a87a5ca52d9f4214a_{siteToCreate.SiteDesignId.Value.ToString("D").ToLower()}");
                }
                if (siteToCreate.Language != Language.Default)
                {
                    creationOptionsValues.Add($"SPSiteLanguage:{(int)siteToCreate.Language}");
                }
                if (!string.IsNullOrEmpty(siteToCreate.SiteAlias))
                {
                    creationOptionsValues.Add($"SiteAlias:{siteToCreate.SiteAlias}");
                }
                creationOptionsValues.Add($"HubSiteId:{siteToCreate.HubSiteId}");

                if (siteToCreate.Owners != null && siteToCreate.Owners.Length > 0)
                {
                    optionalParams.Add("Owners", siteToCreate.Owners);
                }
                if (siteToCreate.PreferredDataLocation.HasValue)
                {
                    optionalParams.Add("PreferredDataLocation", siteToCreate.PreferredDataLocation.Value.ToString());
                }

                if (creationOptionsValues.Any())
                {
                    var creationOptionsValuesBody = new
                    {
                        __metadata = new { type = "Collection(Edm.String)" },
                        results    = creationOptionsValues
                    }.AsExpando();
                    optionalParams.Add("CreationOptions", creationOptionsValuesBody);
                }

                payload.Add("optionalParams", optionalParams);

                // Delegated permissions can use the SharePoint endpoints for site collection creation
                return(await CreateSiteUsingSpoRestImplementationAsync(context, SiteCreationModel.GroupSiteManagerCreateGroupEx, payload, creationOptions).ConfigureAwait(false));
            }
        }
        private static async Task <PnPContext> CreateCommonNoGroupSiteAsync(PnPContext context, CommonNoGroupSiteOptions siteToCreate, SiteCreationOptions creationOptions)
        {
            if (string.IsNullOrEmpty(siteToCreate.Owner) && creationOptions.UsingApplicationPermissions.Value)
            {
                throw new ClientException(ErrorType.Unsupported, "You need to set an owner when using Application permissions to create a communicaiton site");
            }

            var payload = BuildBaseCommonNoGroupSiteRequestPayload(siteToCreate);

            var siteDesignId = GetSiteDesignId(siteToCreate);

            if (siteDesignId != Guid.Empty)
            {
                payload.Add("SiteDesignId", siteDesignId);

                // As per https://github.com/SharePoint/sp-dev-docs/issues/4810 the WebTemplateExtensionId property
                // is what currently drives the application of a custom site design during the creation of a modern site.
                payload["WebTemplateExtensionId"] = siteDesignId;
            }

            payload.Add("HubSiteId", siteToCreate.HubSiteId);

            // Sensitivity labels have replaced classification (see https://docs.microsoft.com/en-us/microsoft-365/compliance/sensitivity-labels-teams-groups-sites?view=o365-worldwide#classic-azure-ad-group-classification)
            // once enabled. Therefore we prefer setting a sensitivity label id over classification when specified.
            if (siteToCreate.SensitivityLabelId != Guid.Empty)
            {
                payload.Add("SensitivityLabel", siteToCreate.SensitivityLabelId);
            }
            else
            {
                payload["Classification"] = siteToCreate.Classification ?? "";
            }

            return(await CreateSiteUsingSpoRestImplementationAsync(context, SiteCreationModel.SPSiteManagerCreate, payload, creationOptions).ConfigureAwait(false));
        }
 public PnPContext CreateSiteCollection(CommonSiteOptions siteToCreate, SiteCreationOptions creationOptions = null)
 {
     return(CreateSiteCollectionAsync(siteToCreate, creationOptions).GetAwaiter().GetResult());
 }
        public async Task <PnPContext> CreateSiteCollectionAsync(CommonSiteOptions siteToCreate, SiteCreationOptions creationOptions = null)
        {
            if (siteToCreate == null)
            {
                throw new ArgumentNullException(nameof(siteToCreate));
            }

            return(await SiteCollectionCreator.CreateSiteCollectionAsync(context, siteToCreate, creationOptions).ConfigureAwait(false));
        }