diff --git a/CHANGELOG.md b/CHANGELOG.md index 62963bd4a..80a1d7cb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). ## [Current nightly] ### Added +- Added `Remove-PnPGeoAdministrator` cmdlet to remove SharePoint Online geo administrators. [#5380](https://github.com/pnp/powershell/pull/5380) - Added `Get-PnPGeoAdministrator` cmdlet to retrieve SharePoint Online geo administrators. [#5378](https://github.com/pnp/powershell/pull/5378) - Added `Get-PnPMultiGeoExperience` cmdlet to retrieve the SharePoint Online multi-geo experience mode. [#5372](https://github.com/pnp/powershell/pull/5372) - Added `Set-PnPMultiGeoExperience` cmdlet to upgrade the tenant multi-geo experience to include SharePoint Online Multi-Geo. [#5369](https://github.com/pnp/powershell/pull/5369) diff --git a/documentation/Remove-PnPGeoAdministrator.md b/documentation/Remove-PnPGeoAdministrator.md new file mode 100644 index 000000000..0a7885bc3 --- /dev/null +++ b/documentation/Remove-PnPGeoAdministrator.md @@ -0,0 +1,118 @@ +--- +Module Name: PnP.PowerShell +title: Remove-PnPGeoAdministrator +schema: 2.0.0 +applicable: SharePoint Online +external help file: PnP.PowerShell.dll-Help.xml +online version: https://pnp.github.io/powershell/cmdlets/Remove-PnPGeoAdministrator.html +--- + +# Remove-PnPGeoAdministrator + +## SYNOPSIS +Removes a SharePoint Online geo administrator. + +## SYNTAX + +```powershell +Remove-PnPGeoAdministrator [-UserPrincipalName] [-Connection ] + +Remove-PnPGeoAdministrator [-GroupAlias] [-Connection ] + +Remove-PnPGeoAdministrator [-ObjectId] [-Connection ] +``` + +## DESCRIPTION +Removes a user or group as a SharePoint Online geo administrator. + +## EXAMPLES + +### EXAMPLE 1 + +```powershell +Remove-PnPGeoAdministrator -UserPrincipalName user@contoso.onmicrosoft.com +``` + +Removes the specified user as a geo administrator. + +### EXAMPLE 2 + +```powershell +Remove-PnPGeoAdministrator -GroupAlias spo-geo-admins +``` + +Removes the specified group as a geo administrator. + +### EXAMPLE 3 + +```powershell +Remove-PnPGeoAdministrator -ObjectId 11111111-1111-1111-1111-111111111111 +``` + +Removes the geo administrator with the specified object ID. + +## PARAMETERS + +### -Connection +Optional connection to be used by the cmdlet. Retrieve the value for this parameter by specifying `-ReturnConnection` on `Connect-PnPOnline` or by executing `Get-PnPConnection`. + +```yaml +Type: PnPConnection +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -GroupAlias +Specifies the alias of the group to remove as a geo administrator. + +```yaml +Type: String +Parameter Sets: Group + +Required: True +Position: 0 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ObjectId +Specifies the object ID of the user or group to remove as a geo administrator. + +```yaml +Type: Guid +Parameter Sets: ObjectId + +Required: True +Position: 0 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -UserPrincipalName +Specifies the user principal name of the user to remove as a geo administrator. + +```yaml +Type: String +Parameter Sets: User + +Required: True +Position: 0 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +## OUTPUTS + +### None + +## RELATED LINKS + +[Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) diff --git a/src/Commands/Admin/RemoveGeoAdministrator.cs b/src/Commands/Admin/RemoveGeoAdministrator.cs new file mode 100644 index 000000000..cc46fea07 --- /dev/null +++ b/src/Commands/Admin/RemoveGeoAdministrator.cs @@ -0,0 +1,52 @@ +using PnP.PowerShell.Commands.Attributes; +using PnP.PowerShell.Commands.Base; +using PnP.PowerShell.Commands.Utilities.MultiGeo; +using System; +using System.Management.Automation; + +namespace PnP.PowerShell.Commands.Admin +{ + [Cmdlet(VerbsCommon.Remove, "PnPGeoAdministrator", DefaultParameterSetName = ParameterSetUser)] + [RequiredApiApplicationPermissions("sharepoint/Sites.FullControl.All")] + [RequiredApiDelegatedPermissions("sharepoint/AllSites.FullControl")] + public class RemoveGeoAdministrator : PnPSharePointOnlineAdminCmdlet + { + private const string ParameterSetUser = "User"; + private const string ParameterSetGroup = "Group"; + private const string ParameterSetObjectId = "ObjectId"; + + [Parameter(Mandatory = true, Position = 0, ParameterSetName = ParameterSetUser)] + [ValidateNotNullOrEmpty] + public string UserPrincipalName { get; set; } + + [Parameter(Mandatory = true, Position = 0, ParameterSetName = ParameterSetGroup)] + [ValidateNotNullOrEmpty] + public string GroupAlias { get; set; } + + [Parameter(Mandatory = true, Position = 0, ParameterSetName = ParameterSetObjectId)] + [ValidateNotNullOrEmpty] + public Guid ObjectId { get; set; } + + protected override void ExecuteCmdlet() + { + var multiGeoRestApiClient = new MultiGeoRestApiClient(AdminContext); + switch (ParameterSetName) + { + case ParameterSetGroup: + multiGeoRestApiClient.RemoveGeoAdministrator(GroupAlias, isGroup: true); + break; + + case ParameterSetUser: + multiGeoRestApiClient.RemoveGeoAdministrator(UserPrincipalName, isGroup: false); + break; + + case ParameterSetObjectId: + multiGeoRestApiClient.RemoveGeoAdministrator(ObjectId); + break; + + default: + throw new ArgumentException("Parameter set cannot be resolved using the specified named parameters."); + } + } + } +} diff --git a/src/Commands/Utilities/MultiGeo/MultiGeoRestApiClient.cs b/src/Commands/Utilities/MultiGeo/MultiGeoRestApiClient.cs index f696f33b5..de65c47e4 100644 --- a/src/Commands/Utilities/MultiGeo/MultiGeoRestApiClient.cs +++ b/src/Commands/Utilities/MultiGeo/MultiGeoRestApiClient.cs @@ -39,10 +39,16 @@ internal class MultiGeoRestApiClient private const string UpdateAllInstancesExperienceModePath = "GeoExperience/UpgradeAllInstancesToSPOMode"; private const string AllowedDataLocationsApiVersion = "1.3.11"; private const string AllowedDataLocationsPath = "AllowedDataLocations"; + private const string GeoAdministratorsByLoginNameMaximumApiVersion = "1.3.8"; + private const string GeoAdministratorsByPrincipalMinimumApiVersion = "1.3.9"; + private const string GeoAdministratorsByLoginNamePath = "GeoAdministrators(loginName='{0}')"; + private const string GeoAdministratorsByLoginNameAndTypePath = "GeoAdministrators/GetByLoginNameAndType(loginName='{0}', memberType={1:D})"; + private const string GeoAdministratorsByObjectIdPath = "GeoAdministrators/GetByObjectId(guid'{0:D}')"; private const string StorageQuotasMinimumApiVersion = "1.3.1"; private const string StorageQuotasPath = "StorageQuotas"; private const string StorageQuotaByLocationPath = "StorageQuotas(geoLocation='{0}')"; private const string MultiGeoApiVersionsPath = "MultiGeoApiVersions"; + private const string DeleteVerbString = "DELETE"; private const string PatchVerbString = "PATCH"; private const string UserMoveJobsMinimumApiVersion = "1.0"; private const string UserMoveJobsByMoveIdMinimumApiVersion = "1.2.2"; @@ -66,6 +72,8 @@ internal class MultiGeoRestApiClient private const int SiteMoveRunsWorkflow2013ErrorCode = -113; private const int SiteMoveRequiresForceErrorCode = -116; private const int SiteMoveContainsBcsErrorCode = -139; + private const int GeoAdministratorUserMemberType = 1; + private const int GeoAdministratorGroupMemberType = 2; private const int MaximumPagination = 10; private const int ApiVersionCacheValidTimeInHours = 1; private static readonly TimeSpan CreateTenantRenameJobTimeout = TimeSpan.FromSeconds(300); @@ -340,6 +348,23 @@ internal void PartialUpdateStorageQuota(StorageQuotaEntityData quota) PostWithMethodOverride(path, quota, PatchVerbString, apiVersion); } + internal void RemoveGeoAdministrator(string loginName, bool isGroup) + { + var apiVersion = GetGeoAdministratorsApiVersion(); + var path = IsSupportedApiVersionRange(apiVersion, GeoAdministratorsMinimumApiVersion, GeoAdministratorsByLoginNameMaximumApiVersion) + ? string.Format(CultureInfo.InvariantCulture, GeoAdministratorsByLoginNamePath, ProcessSpecialChars(loginName)) + : string.Format(CultureInfo.InvariantCulture, GeoAdministratorsByLoginNameAndTypePath, ProcessSpecialChars(loginName), isGroup ? GeoAdministratorGroupMemberType : GeoAdministratorUserMemberType); + + PostWithMethodOverrideEmptyBody(path, DeleteVerbString, apiVersion); + } + + internal void RemoveGeoAdministrator(Guid objectId) + { + var apiVersion = GetGeoAdministratorsByPrincipalApiVersion(); + var path = string.Format(CultureInfo.InvariantCulture, GeoAdministratorsByObjectIdPath, objectId); + PostWithMethodOverrideEmptyBody(path, DeleteVerbString, apiVersion); + } + internal void CancelUserMoveJob(string userPrincipalName) { var apiVersion = GetCurrentApiVersion(UserMoveJobsMinimumApiVersion); @@ -442,6 +467,21 @@ private void PostWithMethodOverride(string path, object payload, string methodOv }, timeout: null, allowRetries: false); } + private void PostWithMethodOverrideEmptyBody(string path, string methodOverride, string apiVersion) + { + Send(() => + { + var request = CreateRequest(HttpMethod.Post, path, apiVersion, string.Empty); + request.Headers.TryAddWithoutValidation("X-HTTP-Method", methodOverride); + if (request.Content != null) + { + request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json;charset=UTF-8"); + } + + return request; + }, timeout: null, allowRetries: false); + } + private HttpRequestMessage CreateRequest(HttpMethod method, string path, string apiVersion, string jsonPayload = null) { return CreateRequest(method, CreateApiUri(path, apiVersion), jsonPayload); @@ -535,6 +575,16 @@ private static bool IsSupportedApiVersion(string apiVersion, string minimumApiVe return apiVersionIndex >= 0 && minimumApiVersionIndex >= 0 && apiVersionIndex <= minimumApiVersionIndex; } + private static bool IsSupportedApiVersionRange(string apiVersion, string minimumApiVersion, string maximumApiVersion) + { + var apiVersionIndex = Array.IndexOf(ClientSupportedApiVersions, apiVersion); + var minimumApiVersionIndex = Array.IndexOf(ClientSupportedApiVersions, minimumApiVersion); + var maximumApiVersionIndex = Array.IndexOf(ClientSupportedApiVersions, maximumApiVersion); + return apiVersionIndex >= 0 && minimumApiVersionIndex >= 0 && maximumApiVersionIndex >= 0 + && apiVersionIndex <= minimumApiVersionIndex + && apiVersionIndex >= maximumApiVersionIndex; + } + private string GetStorageQuotasApiVersion() { var apiVersion = GetCurrentApiVersion(); @@ -557,6 +607,17 @@ private string GetGeoAdministratorsApiVersion() return apiVersion; } + private string GetGeoAdministratorsByPrincipalApiVersion() + { + var apiVersion = GetCurrentApiVersion(); + if (!IsSupportedApiVersion(apiVersion, GeoAdministratorsByPrincipalMinimumApiVersion)) + { + throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, CommandResources.CrossGeoInvalidVersion, GetApplicationVersion())); + } + + return apiVersion; + } + private string GetGeoExperienceApiVersion() { var apiVersion = GetCurrentApiVersion();