diff --git a/src/lib/PnP.Framework.Test/Extensions/TenantExtensionsTests.cs b/src/lib/PnP.Framework.Test/Extensions/TenantExtensionsTests.cs
index 258cd8077..baed4289b 100644
--- a/src/lib/PnP.Framework.Test/Extensions/TenantExtensionsTests.cs
+++ b/src/lib/PnP.Framework.Test/Extensions/TenantExtensionsTests.cs
@@ -210,6 +210,33 @@ public void SubSiteExistsTest()
Assert.IsFalse(subSiteExists4);
}
}
+
+ ///
+ /// Validate if site collection exists anywhere
+ ///
+ [TestMethod()]
+ [Timeout(15 * 60 * 1000)]
+ public void SiteExistsAnywhereTest()
+ {
+ using (var tenantContext = TestCommon.CreateTenantClientContext())
+ {
+ Tenant tenant = new(tenantContext);
+ IList siteCollections = tenant.GetSiteCollections(includeDetail: false);
+
+ // Grab a random site collection from the list of returned site collections
+ int siteNumberToCheck = new Random().Next(0, siteCollections.Count - 1);
+
+ SiteEntity site = siteCollections[siteNumberToCheck];
+
+ SiteExistence siteExists1 = tenant.SiteExistsAnywhere(site.Url);
+ Assert.IsTrue(siteExists1 == SiteExistence.Yes);
+
+ string devSiteUrl = TestCommon.AppSetting("SPODevSiteUrl");
+ string siteToCreateUrl = GetTestSiteCollectionName(devSiteUrl, "aaabbbccc");
+ SiteExistence siteExists2 = tenant.SiteExistsAnywhere(siteToCreateUrl);
+ Assert.IsFalse(siteExists2 == SiteExistence.No, "Invalid site returned as valid.");
+ }
+ }
#endregion
#region Site collection creation and deletion tests
diff --git a/src/lib/PnP.Framework/Extensions/TenantExtensions.cs b/src/lib/PnP.Framework/Extensions/TenantExtensions.cs
index d64e1e530..f9271481f 100644
--- a/src/lib/PnP.Framework/Extensions/TenantExtensions.cs
+++ b/src/lib/PnP.Framework/Extensions/TenantExtensions.cs
@@ -5,6 +5,7 @@
using PnP.Framework.Entities;
using PnP.Framework.Graph;
using PnP.Framework.Graph.Model;
+using PnP.Framework.Http;
using PnP.Framework.Provisioning.Model;
using PnP.Framework.Provisioning.Model.Configuration;
using PnP.Framework.Provisioning.ObjectHandlers;
@@ -15,6 +16,7 @@
using System.Globalization;
using System.Linq;
using System.Net;
+using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -1229,71 +1231,60 @@ public static bool CheckIfSiteExists(this Tenant tenant, string siteFullUrl, str
/// An enumerated type that can be: No, Yes, Recycled
public static SiteExistence SiteExistsAnywhere(this Tenant tenant, string siteFullUrl)
{
- var userIsTenantAdmin = TenantExtensions.IsCurrentUserTenantAdmin((ClientContext)tenant.Context);
-
try
{
- // CHANGED: Modified in order to support non privilege users
- if (userIsTenantAdmin)
+ using (ClientContext siteContext = tenant.Context.Clone(siteFullUrl))
{
- // Get the site name
- var properties = tenant.GetSitePropertiesByUrl(siteFullUrl, false);
- tenant.Context.Load(properties);
- tenant.Context.ExecuteQueryRetry();
- }
- else
- {
- // Get the site context for the current user
- using (var siteContext = tenant.Context.Clone(siteFullUrl))
- {
- var site = siteContext.Site;
- siteContext.Load(site);
- siteContext.ExecuteQueryRetry();
- }
+ Site site = siteContext.Site;
+ siteContext.Load(site);
+ siteContext.ExecuteQueryRetry();
}
- // Will cause an exception if site URL is not there. Not optimal, but the way it works.
return SiteExistence.Yes;
}
catch (Exception ex)
{
- if (userIsTenantAdmin && (IsCannotGetSiteException(ex) || IsUnableToAccessSiteException(ex)))
+ //If is unauthorized to access site collection, it means the site collection exists
+ if (IsUnauthorizedToAccessSiteException(ex))
{
- if (IsUnableToAccessSiteException(ex))
+ return SiteExistence.Yes;
+ }
+
+ //Validate if when connect to the site the response contains 'connection: close', if it returns that, the site is recycled
+ try
+ {
+ // Use an HTTP client to send a request to the site URL.
+ using (HttpClient httpClient = PnPHttpClient.Instance.GetHttpClient(tenant.Context.Clone(siteFullUrl)))
+ using (HttpRequestMessage request = new(HttpMethod.Get, siteFullUrl))
{
- //Let's retry to see if this site collection was recycled
- try
+ HttpResponseMessage response = httpClient.SendAsync(request, new CancellationToken()).GetAwaiter().GetResult();
+
+ // Check if the response indicates a failure (not successful).
+ if (response.StatusCode == HttpStatusCode.NotFound)
{
- var deletedProperties = tenant.GetDeletedSitePropertiesByUrl(siteFullUrl);
- tenant.Context.Load(deletedProperties);
- tenant.Context.ExecuteQueryRetry();
- if (deletedProperties.Status.Equals("Recycled", StringComparison.OrdinalIgnoreCase))
+ string responseBody = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
+
+ // Check if the response indicates that the site was not found (404 error).
+ if (responseBody.ToUpper().Contains("404 FILE NOT FOUND"))
{
- return SiteExistence.Recycled;
+ return SiteExistence.No;
}
- else
+ // Check if the response indicates that the site is recycled.
+ else if (responseBody.ToUpper().Contains("CONNECTION: CLOSE"))
{
- return SiteExistence.No;
+ return SiteExistence.Recycled;
}
}
- catch
- {
- return SiteExistence.No;
- }
- }
- else
- {
- return SiteExistence.No;
}
}
- else if (IsNotFoundException(ex))
+ catch { }
+
+ if (IsNotFoundException(ex))
{
return SiteExistence.No;
}
- else
- {
- return SiteExistence.Yes;
- }
+
+ return SiteExistence.Yes;
}
}
@@ -1438,6 +1429,25 @@ private static bool IsCannotRemoveSiteException(Exception ex)
return false;
}
}
+
+ ///
+ /// Check if exception Is "unauthorized" to access site collection
+ ///
+ /// Exception
+ /// True if yes, false if not
+ private static bool IsUnauthorizedToAccessSiteException(Exception ex)
+ {
+ if (ex is ServerException serverException && serverException.ServerErrorCode == -2147024891 && serverException.ServerErrorTypeName.Equals("System.UnauthorizedAccessException", StringComparison.InvariantCultureIgnoreCase))
+ {
+ return true;
+ }
+
+ if (ex is System.Net.WebException { Response: System.Net.HttpWebResponse { StatusCode: HttpStatusCode.Unauthorized } })
+ {
+ return true;
+ }
+ return false;
+ }
#endregion
#region ClientSide Package Deployment