using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.Online.SharePoint.TenantAdministration; using Microsoft.SharePoint.Client; using SharepointToolbox.Core.Models; namespace SharepointToolbox.Services; public class SiteListService : ISiteListService { private readonly SessionManager _sessionManager; public SiteListService(SessionManager sessionManager) { _sessionManager = sessionManager; } public async Task> GetSitesAsync( TenantProfile profile, IProgress progress, CancellationToken ct) { ct.ThrowIfCancellationRequested(); progress.Report(OperationProgress.Indeterminate("Loading sites...")); var adminUrl = DeriveAdminUrl(profile.TenantUrl); var adminProfile = new TenantProfile { Name = profile.Name, TenantUrl = adminUrl, ClientId = profile.ClientId }; ClientContext adminCtx; try { adminCtx = await _sessionManager.GetOrCreateContextAsync(adminProfile, ct); } catch (ServerException ex) when (ex.Message.Contains("Access denied", StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException( "Site listing requires SharePoint administrator permissions. Connect with an admin account.", ex); } var tenant = new Tenant(adminCtx); var siteProps = tenant.GetSitePropertiesFromSharePoint("", true); adminCtx.Load(siteProps); await adminCtx.ExecuteQueryAsync(); ct.ThrowIfCancellationRequested(); return siteProps .Where(s => s.Status == "Active" && !s.Url.Contains("-my.sharepoint.com", StringComparison.OrdinalIgnoreCase)) .Select(s => new SiteInfo(s.Url, s.Title)) .OrderBy(s => s.Url) .ToList(); } internal static string DeriveAdminUrl(string tenantUrl) => Regex.Replace(tenantUrl.TrimEnd('/'), @"(https://[^.]+)(\.sharepoint\.com)", "$1-admin$2", RegexOptions.IgnoreCase); }