Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).

## [Current nightly]

### Added
- Added `Get-PnPMultiGeoCompanyAllowedDataLocation` cmdlet to retrieve SharePoint Online multi-geo allowed data locations. [#5336](https://github.com/pnp/powershell/pull/5336)

### Changed
- Added properties `CoreOrganizationSharingLinkRecommendedExpirationInDays`, `CoreOrganizationSharingLinkMaxExpirationInDays`,`RestrictResourceAccountAccess`, `RestrictExternalSharingForAgents` to Set-pnptenant and Get-pnptenant cmdlet. [#5330](https://github.com/pnp/powershell/pull/5330)
- Added properties OrganizationSharingLinkRecommendedExpirationInDays, OrganizationSharingLinkMaxExpirationInDays, OverrideTenantOrganizationSharingLinkExpirationPolicy to set-pnpsite, set-pnptenantsite cmdlets. [#5333](https://github.com/pnp/powershell/pull/5333)
- Added properties `CoreOrganizationSharingLinkRecommendedExpirationInDays`, `CoreOrganizationSharingLinkMaxExpirationInDays`,`RestrictResourceAccountAccess`, `RestrictExternalSharingForAgents` to `Set-PnPTenant` and `Get-PnPTenant` cmdlet. [#5330](https://github.com/pnp/powershell/pull/5330)
- Added properties `OrganizationSharingLinkRecommendedExpirationInDays`, `OrganizationSharingLinkMaxExpirationInDays`, `OverrideTenantOrganizationSharingLinkExpirationPolicy` to `Set-PnPSite`, `Set-PnPTenantsite` cmdlets. [#5333](https://github.com/pnp/powershell/pull/5333)
- Added `WhoCanShareAllowListInTenantByPrincipalIdentity` property to `Set-PnPTenant` cmdlet. [#5322](https://github.com/pnp/powershell/pull/5322)

### Contributors

- Reshmee Auckloo [reshmee011]
- [Tetronic]
- Vasco Azevedo [vascoazevedo08]

## [3.2.0]

Expand Down
57 changes: 57 additions & 0 deletions documentation/Get-PnPMultiGeoCompanyAllowedDataLocation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
Module Name: PnP.PowerShell
title: Get-PnPMultiGeoCompanyAllowedDataLocation
schema: 2.0.0
applicable: SharePoint Online
external help file: PnP.PowerShell.dll-Help.xml
online version: https://pnp.github.io/powershell/cmdlets/Get-PnPMultiGeoCompanyAllowedDataLocation.html
---

# Get-PnPMultiGeoCompanyAllowedDataLocation

## SYNOPSIS
Returns the multi-geo data locations allowed for the SharePoint Online tenant.

## SYNTAX

```powershell
Get-PnPMultiGeoCompanyAllowedDataLocation [-Connection <PnPConnection>]
```

## DESCRIPTION
Returns the SharePoint Online multi-geo data locations configured for the tenant, including each location code, the associated domain, and whether the location is the default location.

## EXAMPLES

### EXAMPLE 1

```powershell
Get-PnPMultiGeoCompanyAllowedDataLocation
```

Returns all allowed multi-geo data locations for the current tenant.

## 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
```

## OUTPUTS

### PnP.PowerShell.Commands.Model.MultiGeoCompanyAllowedDataLocation
Returns objects with `Location`, `Domain`, and `IsDefault` properties.

## RELATED LINKS

[Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp)
21 changes: 21 additions & 0 deletions src/Commands/Admin/GetMultiGeoCompanyAllowedDataLocation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using PnP.PowerShell.Commands.Attributes;
using PnP.PowerShell.Commands.Base;
using PnP.PowerShell.Commands.Model;
using PnP.PowerShell.Commands.Utilities.MultiGeo;
using System.Management.Automation;

namespace PnP.PowerShell.Commands.Admin
{
[Cmdlet(VerbsCommon.Get, "PnPMultiGeoCompanyAllowedDataLocation")]
[RequiredApiApplicationPermissions("sharepoint/Sites.FullControl.All")]
[RequiredApiDelegatedPermissions("sharepoint/AllSites.FullControl")]
[OutputType(typeof(MultiGeoCompanyAllowedDataLocation))]
public class GetMultiGeoCompanyAllowedDataLocation : PnPSharePointOnlineAdminCmdlet
{
protected override void ExecuteCmdlet()
{
var multiGeoRestApiClient = new MultiGeoRestApiClient(AdminContext);
WriteObject(multiGeoRestApiClient.GetAllowedDataLocations(), true);
}
}
}
23 changes: 23 additions & 0 deletions src/Commands/Model/MultiGeoCompanyAllowedDataLocation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace PnP.PowerShell.Commands.Model
{
/// <summary>
/// Contains an allowed multi-geo data location configured for the SharePoint Online tenant.
/// </summary>
public class MultiGeoCompanyAllowedDataLocation
{
/// <summary>
/// The geo location code, such as NAM or EUR.
/// </summary>
public string Location { get; set; }

/// <summary>
/// The SharePoint Online domain associated with the geo location.
/// </summary>
public string Domain { get; set; }

/// <summary>
/// Indicates whether this is the tenant default data location.
/// </summary>
public bool IsDefault { get; set; }
}
}
117 changes: 114 additions & 3 deletions src/Commands/Utilities/MultiGeo/MultiGeoRestApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ namespace PnP.PowerShell.Commands.Utilities.MultiGeo
internal class MultiGeoRestApiClient
{
private const string TenantRenameApiVersion = "1.5.3";
private const string TenantRenameCancelApiVersion = "1.5.5";
private const string TenantRenameStatusV2ApiVersion = "1.5.18";
private const string TenantRenameJobsPath = "TenantRenameJobs";
private const string TenantRenameJobsPathToGetWarningMessages = "TenantRenameJobs/GetWarningMessages";
private const string TenantRenameJobsPathToGetStatus = "TenantRenameJobs/Get";
private const string TenantRenameJobsPathToGetStatusV2 = "TenantRenameJobs/GetV2";
private const string TenantRenameJobsPathToCancelAJob = "TenantRenameJobs/Cancel";
private const string AllowedDataLocationsApiVersion = "1.3.11";
private const string AllowedDataLocationsPath = "AllowedDataLocations";
private const int MaximumPagination = 10;
private static readonly TimeSpan CreateTenantRenameJobTimeout = TimeSpan.FromSeconds(300);
private static readonly JsonSerializerOptions SerializerOptions = new()
{
Expand Down Expand Up @@ -54,12 +58,17 @@ internal TenantRenameJob GetTenantRenameJobV2()

internal IEnumerable<string> GetTenantRenameWarningMessages()
{
return Get<List<string>>(TenantRenameJobsPathToGetWarningMessages);
return GetFeed<string>(TenantRenameJobsPathToGetWarningMessages, TenantRenameApiVersion);
}

internal IEnumerable<MultiGeoCompanyAllowedDataLocation> GetAllowedDataLocations()
{
return GetFeed<MultiGeoCompanyAllowedDataLocation>(AllowedDataLocationsPath, AllowedDataLocationsApiVersion);
}

internal void CancelTenantRenameJob()
{
Post<string>(TenantRenameJobsPathToCancelAJob, payload: null);
Post<string>(TenantRenameJobsPathToCancelAJob, payload: null, apiVersion: TenantRenameCancelApiVersion);
}

private T Get<T>(string path, string apiVersion = TenantRenameApiVersion)
Expand All @@ -68,6 +77,43 @@ private T Get<T>(string path, string apiVersion = TenantRenameApiVersion)
return DeserializeResponse<T>(responseText);
}

private IEnumerable<T> GetFeed<T>(string path, string apiVersion)
{
var results = new List<T>();
var requestUri = CreateApiUri(path, apiVersion);
var pages = 0;

while (requestUri != null && pages < MaximumPagination)
{
var responseText = Send(() => CreateRequest(HttpMethod.Get, requestUri), timeout: null, allowRetries: true);
var collection = DeserializeFeed<T>(responseText);
if (collection.Value != null)
{
results.AddRange(collection.Value);
}

if (!string.IsNullOrWhiteSpace(collection.NextLink))
{
requestUri = new Uri(requestUri, collection.NextLink);
checked
{
pages++;
}
}
else
{
requestUri = null;
}
}

if (requestUri != null)
{
throw new InvalidOperationException("SharePoint Online REST request returned too many pages.");
}

return results;
}

private T Post<T>(string path, object payload, TimeSpan? timeout = null, string apiVersion = TenantRenameApiVersion)
{
var jsonPayload = payload == null ? null : JsonSerializer.Serialize(payload, SerializerOptions);
Expand All @@ -77,7 +123,12 @@ private T Post<T>(string path, object payload, TimeSpan? timeout = null, string

private HttpRequestMessage CreateRequest(HttpMethod method, string path, string apiVersion, string jsonPayload = null)
{
var request = new HttpRequestMessage(method, CreateApiUri(path, apiVersion))
return CreateRequest(method, CreateApiUri(path, apiVersion), jsonPayload);
}

private HttpRequestMessage CreateRequest(HttpMethod method, Uri requestUri, string jsonPayload = null)
{
var request = new HttpRequestMessage(method, requestUri)
{
Version = new Version(2, 0)
};
Expand Down Expand Up @@ -170,6 +221,59 @@ private static T DeserializeResponse<T>(string responseText)
return JsonSerializer.Deserialize<T>(responseElement.GetRawText(), SerializerOptions);
}

private static ODataFeed<T> DeserializeFeed<T>(string responseText)
{
if (string.IsNullOrWhiteSpace(responseText))
{
return new ODataFeed<T>();
}

using var jsonDocument = JsonDocument.Parse(responseText);
var responseElement = jsonDocument.RootElement;
if (responseElement.ValueKind == JsonValueKind.Object && responseElement.TryGetProperty("d", out var dElement))
{
responseElement = dElement;
}

var feed = new ODataFeed<T>();
if (responseElement.ValueKind == JsonValueKind.Object)
{
if (responseElement.TryGetProperty("value", out var valueElement) || responseElement.TryGetProperty("results", out valueElement))
{
feed.Value = DeserializeFeedValue<T>(valueElement);
}

feed.NextLink = GetStringProperty(responseElement, "@odata.nextLink", "odata.nextLink", "nextLink", "__next");
return feed;
}

feed.Value = DeserializeFeedValue<T>(responseElement);
return feed;
}

private static T[] DeserializeFeedValue<T>(JsonElement valueElement)
{
if (valueElement.ValueKind != JsonValueKind.Array)
{
return Array.Empty<T>();
}

return JsonSerializer.Deserialize<T[]>(valueElement.GetRawText(), SerializerOptions) ?? Array.Empty<T>();
}

private static string GetStringProperty(JsonElement element, params string[] propertyNames)
{
foreach (var propertyName in propertyNames)
{
if (element.TryGetProperty(propertyName, out var propertyElement) && propertyElement.ValueKind == JsonValueKind.String)
{
return propertyElement.GetString();
}
}

return null;
}

private static JsonElement UnwrapODataResponse(JsonElement responseElement)
{
if (responseElement.ValueKind != JsonValueKind.Object)
Expand Down Expand Up @@ -240,5 +344,12 @@ private static bool TryGetODataErrorMessage(JsonElement rootElement, out string

return false;
}

private sealed class ODataFeed<T>
{
public T[] Value { get; set; }

public string NextLink { get; set; }
}
}
}
Loading