Skip to content

Commit

Permalink
Added Exchange Online properties of Microsoft 365 Groups (#3958)
Browse files Browse the repository at this point in the history
* Added implementation

* Added PR reference

* Fixing documentation build issue

---------

Co-authored-by: Gautam Sheth <[email protected]>
  • Loading branch information
KoenZomers and gautamdsheth authored May 22, 2024
1 parent 7409316 commit d576b53
Show file tree
Hide file tree
Showing 10 changed files with 151 additions and 22 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
- Added `Get-PnPLibraryFileVersionBatchDeleteJobStatus` and `Get-PnPSiteFileVersionBatchDeleteJobStatus` to check on the status of applying file based version expiration based on age on a library and site level [#3828](https://github.com/pnp/powershell/pull/3828)
- Added support for `Get-PnPSiteCollectionAppCatalog` and `Get-PnPTenantSite` to be used with vanity domain tenants [#3895](https://github.com/pnp/powershell/pull/3895)
- Added support for using vanity domain tenants with `Grant-PnPTenantServicePrincipalPermission`, `Revoke-PnPTenantServicePrincipalPermission`, `Set-PnPWebTheme`, `Invoke-PnPListDesign`, `Set-PnPSite`, `Add-PnPSiteDesignTask`, `Get-PnPSiteDesignRun`, `Get-PnPSiteDesignTask` and `Invoke-PnPSiteDesign` cmdlets [#3898](https://github.com/pnp/powershell/pull/3898)
- Added `-Detailed` to `Get-PnPMicrosoft365Group` which allows retrieval of the AllowExternalSenders, IsSubscribedByMail and AutoSubscribeNewMembers properties of the group [#3958](https://github.com/pnp/powershell/pull/3958)
- Added `-RequireSenderAuthenticationEnabled` and `-AutoSubscribeNewMembers` to `Set-PnPMicrosoft365Group` which allows setting these properties on a group [#3958](https://github.com/pnp/powershell/pull/3958)

### Fixed

Expand Down
22 changes: 20 additions & 2 deletions documentation/Get-PnPMicrosoft365Group.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Gets one Microsoft 365 Group or a list of Microsoft 365 Groups
## SYNTAX

```powershell
Get-PnPMicrosoft365Group [-Identity <Microsoft365GroupPipeBind>] [-IncludeSiteUrl] [-IncludeOwners] [-Filter <string>]
Get-PnPMicrosoft365Group [-Identity <Microsoft365GroupPipeBind>] [-IncludeSiteUrl] [-IncludeOwners] [-Detailed] [-Filter <string>]
```

## DESCRIPTION
Expand Down Expand Up @@ -84,10 +84,28 @@ Retrieves all Microsoft 365 Groups in this tenant and retrieves the owners for e
$groups = Get-PnPMicrosoft365Group -Filter "startswith(description, 'contoso')"
```

Retrieves all Microsoft 365 Groups in this tenant with description starting with Contoso. This example demonstrates using Advanced Query capabilities (see: https://learn.microsoft.com/en-us/graph/aad-advanced-queries?tabs=http#group-properties).
Retrieves all Microsoft 365 Groups in this tenant with description starting with Contoso. This example demonstrates using Advanced Query capabilities (see: https://learn.microsoft.com/graph/aad-advanced-queries?tabs=http#group-properties).

## PARAMETERS

### -Detailed
When provided, the following properties originating from Exchange Online, will also be loaded into the returned group. Without providing this flag, they will not be populated. Providing this flag causes an extra call to be made to Microsoft Graph, so only add it when you need one of the properties below.

- AutoSubscribeNewMembers
- RequireSenderAuthenticationEnabled
- IsSubscribedByMail

```yaml
Type: SwitchParameter
Parameter Sets: (All)

Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### -IncludeSiteUrl
Include fetching the site URL for Microsoft 365 Groups. This slows down large listings.
Expand Down
55 changes: 50 additions & 5 deletions documentation/Set-PnPMicrosoft365Group.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ Sets Microsoft 365 Group properties
## SYNTAX

```powershell
Set-PnPMicrosoft365Group -Identity <Microsoft365GroupPipeBind> [-DisplayName <String>] [-Description <String>]
[-Owners <String[]>] [-Members <String[]>] [-IsPrivate] [-LogoPath <String>] [-CreateTeam]
[-HideFromAddressLists <Boolean>] [-HideFromOutlookClients <Boolean>] [-MailNickname <String>] [-SensitivityLabels <GUID[]>]
Set-PnPMicrosoft365Group -Identity <Microsoft365GroupPipeBind> [-DisplayName <String>] [-Description <String>] [-Owners <String[]>] [-Members <String[]>] [-IsPrivate] [-LogoPath <String>] [-CreateTeam] [-HideFromAddressLists <Boolean>] [-HideFromOutlookClients <Boolean>] [-RequireSenderAuthenticationEnabled <Boolean>] [-AutoSubscribeNewMembers <Boolean>] [-MailNickname <String>] [-SensitivityLabels <GUID[]>] [-Verbose]
```

## DESCRIPTION
Expand Down Expand Up @@ -76,6 +73,25 @@ Sets the sensitivity label of the group

## PARAMETERS

### -AutoSubscribeNewMembers
The AutoSubscribeNewMembers switch specifies whether to automatically subscribe new members that are added to the Microsoft 365 Group to conversations and calendar events. Only users that are added to the group after you enable this setting are automatically subscribed to the group.

To subscribe new members to conversations and calendar events, use this exact syntax: -AutoSubscribeNewMembers:$true.
If you don't want to subscribe new members to conversations and calendar events, use this exact syntax: -AutoSubscribeNewMembers:$false.

Note: This property is evaluated only when you add internal members from your organization. Guest user accounts are always subscribed when added as a member.

```yaml
Type: SwitchParameter
Parameter Sets: (All)

Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### -CreateTeam
Creates a Microsoft Teams team associated with created group
Expand Down Expand Up @@ -230,6 +246,21 @@ Accept pipeline input: False
Accept wildcard characters: False
```
### -RequireSenderAuthenticationEnabled
Allows configuring if the Microsoft 365 Group should accept e-mail from senders outside of the organisation (false) or if both internal as well as external senders can send e-mail to the e-mail address of the Microsoft 365 group (true).
Alias: AllowExternalSenders
```yaml
Type: Boolean
Parameter Sets: (All)
Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### -SensitivityLabels
The Sensitivity label to be set to the Microsoft 365 Group. To retrieve the sensitivity label Ids you can use [Get-PnPAvailableSensitivityLabel](Get-PnPAvailableSensitivityLabel.md).
Expand All @@ -243,7 +274,21 @@ Accept pipeline input: False
Accept wildcard characters: False
```
### -Verbose
When provided, additional debug statements will be shown while executing the cmdlet.
```yaml
Type: SwitchParameter
Parameter Sets: (All)

Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
## RELATED LINKS
[Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp)
[Microsoft Graph documentation](https://learn.microsoft.com/graph/api/group-update)
[Microsoft Graph documentation](https://learn.microsoft.com/graph/api/group-update)
10 changes: 5 additions & 5 deletions src/Commands/Base/PipeBinds/Microsoft365GroupPipeBind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,20 @@ public Microsoft365GroupPipeBind(Guid guid)

public Guid GroupId => _groupId;

public Microsoft365Group GetGroup(PnPConnection connection, string accessToken, bool includeSite, bool includeOwners)
public Microsoft365Group GetGroup(PnPConnection connection, string accessToken, bool includeSite, bool includeOwners, bool detailed)
{
Microsoft365Group group = null;
if (Group != null)
{
group = Microsoft365GroupsUtility.GetGroupAsync(connection, _group.Id.Value, accessToken, includeSite, includeOwners).GetAwaiter().GetResult();
group = Microsoft365GroupsUtility.GetGroupAsync(connection, _group.Id.Value, accessToken, includeSite, includeOwners, detailed).GetAwaiter().GetResult();
}
else if (_groupId != Guid.Empty)
{
group = Microsoft365GroupsUtility.GetGroupAsync(connection, _groupId, accessToken, includeSite, includeOwners).GetAwaiter().GetResult();
group = Microsoft365GroupsUtility.GetGroupAsync(connection, _groupId, accessToken, includeSite, includeOwners, detailed).GetAwaiter().GetResult();
}
else if (!string.IsNullOrEmpty(DisplayName))
{
group = Microsoft365GroupsUtility.GetGroupAsync(connection, DisplayName, accessToken, includeSite, includeOwners).GetAwaiter().GetResult();
group = Microsoft365GroupsUtility.GetGroupAsync(connection, DisplayName, accessToken, includeSite, includeOwners, detailed).GetAwaiter().GetResult();
}
return group;
}
Expand All @@ -74,7 +74,7 @@ public Guid GetGroupId(PnPConnection connection, string accessToken)
}
else if (!string.IsNullOrEmpty(DisplayName))
{
var group = Microsoft365GroupsUtility.GetGroupAsync(connection, DisplayName, accessToken, false, false).GetAwaiter().GetResult();
var group = Microsoft365GroupsUtility.GetGroupAsync(connection, DisplayName, accessToken, false, false, false).GetAwaiter().GetResult();
if (group != null)
{
return group.Id.Value;
Expand Down
5 changes: 4 additions & 1 deletion src/Commands/Microsoft365Groups/GetMicrosoft365Group.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ public class GetMicrosoft365Group : PnPGraphCmdlet
[Parameter(Mandatory = false)]
public SwitchParameter IncludeOwners;

[Parameter(Mandatory = false)]
public SwitchParameter Detailed;

[Parameter(Mandatory = false)]
public string Filter;

Expand All @@ -30,7 +33,7 @@ protected override void ExecuteCmdlet()

if (Identity != null)
{
var group = Identity.GetGroup(Connection, AccessToken, includeSiteUrl, IncludeOwners);
var group = Identity.GetGroup(Connection, AccessToken, includeSiteUrl, IncludeOwners, Detailed.ToBool());
WriteObject(group);
}
else
Expand Down
4 changes: 2 additions & 2 deletions src/Commands/Microsoft365Groups/NewMicrosoft365Group.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ protected override void ExecuteCmdlet()

if (!Force)
{
var candidate = Microsoft365GroupsUtility.GetGroupAsync(Connection, MailNickname, AccessToken, false, false).GetAwaiter().GetResult();
var candidate = Microsoft365GroupsUtility.GetGroupAsync(Connection, MailNickname, AccessToken, false, false, false).GetAwaiter().GetResult();
forceCreation = candidate == null || ShouldContinue($"The Microsoft 365 Group '{MailNickname} already exists. Do you want to create a new one?", Properties.Resources.Confirm);
}
else
Expand Down Expand Up @@ -180,7 +180,7 @@ protected override void ExecuteCmdlet()
Microsoft365GroupsUtility.SetVisibilityAsync(Connection, AccessToken, group.Id.Value, HideFromAddressLists, HideFromOutlookClients).GetAwaiter().GetResult();
}

var updatedGroup = Microsoft365GroupsUtility.GetGroupAsync(Connection, group.Id.Value, AccessToken, true, false).GetAwaiter().GetResult();
var updatedGroup = Microsoft365GroupsUtility.GetGroupAsync(Connection, group.Id.Value, AccessToken, true, false, false).GetAwaiter().GetResult();

WriteObject(updatedGroup);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class RemoveMicrosoft365GroupPicture : PnPGraphCmdlet

protected override void ExecuteCmdlet()
{
var group = Identity.GetGroup(Connection, AccessToken, false, false);
var group = Identity.GetGroup(Connection, AccessToken, false, false, false);
if (group != null)
{
var response = Microsoft365GroupsUtility.DeletePhotoAsync(Connection, AccessToken, group.Id.Value).GetAwaiter().GetResult();
Expand Down
33 changes: 30 additions & 3 deletions src/Commands/Microsoft365Groups/SetMicrosoft365Group.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,23 @@ public class SetMicrosoft365Group : PnPGraphCmdlet

[Parameter(Mandatory = false)]
public string MailNickname;


[Parameter(Mandatory = false)]
[Alias("AllowExternalSenders")] // This is the name used in Microsoft Graph while the name below is the one used within Exchange Online. They both are about the same feature.
public bool? RequireSenderAuthenticationEnabled;

[Parameter(Mandatory = false)]
public bool? AutoSubscribeNewMembers;

protected override void ExecuteCmdlet()
{
var group = Identity.GetGroup(Connection, AccessToken, false, false);

var group = Identity.GetGroup(Connection, AccessToken, false, false, false);

if (group != null)
{
bool changed = false;
bool exchangeOnlinePropertiesChanged = false;

if (ParameterSpecified(nameof(DisplayName)))
{
group.DisplayName = DisplayName;
Expand Down Expand Up @@ -91,9 +99,28 @@ protected override void ExecuteCmdlet()
}
if (changed)
{
WriteVerbose("Updating Microsoft 365 Group properties in Microsoft Graph");
group = Microsoft365GroupsUtility.UpdateAsync(Connection, AccessToken, group).GetAwaiter().GetResult();
}

if (ParameterSpecified(nameof(RequireSenderAuthenticationEnabled)) && RequireSenderAuthenticationEnabled.HasValue)
{
group.AllowExternalSenders = RequireSenderAuthenticationEnabled.Value;
exchangeOnlinePropertiesChanged = true;
}

if (ParameterSpecified(nameof(AutoSubscribeNewMembers)) && AutoSubscribeNewMembers.HasValue)
{
group.AutoSubscribeNewMembers = AutoSubscribeNewMembers.Value;
exchangeOnlinePropertiesChanged = true;
}

if (exchangeOnlinePropertiesChanged)
{
WriteVerbose("Updating Microsoft 365 Group Exchange Online properties through Microsoft Graph");
group = Microsoft365GroupsUtility.UpdateExchangeOnlineSettingAsync(Connection, group.Id.Value, AccessToken, group).GetAwaiter().GetResult();
}

if (ParameterSpecified(nameof(Owners)))
{
Microsoft365GroupsUtility.UpdateOwnersAsync(Connection, group.Id.Value, AccessToken, Owners).GetAwaiter().GetResult();
Expand Down
6 changes: 6 additions & 0 deletions src/Commands/Model/Microsoft365Group.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

namespace PnP.PowerShell.Commands.Model
{
/// <summary>
/// Properties of one Microsoft 365 Group
/// </summary>
public class Microsoft365Group
{
[JsonPropertyName("[email protected]")]
Expand Down Expand Up @@ -53,6 +56,9 @@ public string GroupId
public string SiteUrl { get; set; }
public string[] GroupTypes { get; set; }
public IEnumerable<Microsoft365User> Owners { get; set; }
public bool? AllowExternalSenders { get; set; }
public bool? IsSubscribedByMail { get; set; }
public bool? AutoSubscribeNewMembers { get; set; }

public List<AssignedLabels> AssignedLabels { get; set; }

Expand Down
34 changes: 31 additions & 3 deletions src/Commands/Utilities/Microsoft365GroupsUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ internal static async Task<IEnumerable<Microsoft365Group>> GetGroupsAsync(PnPCon
return items;
}

internal static async Task<Microsoft365Group> GetGroupAsync(PnPConnection connection, Guid groupId, string accessToken, bool includeSiteUrl, bool includeOwners)
internal static async Task<Microsoft365Group> GetGroupAsync(PnPConnection connection, Guid groupId, string accessToken, bool includeSiteUrl, bool includeOwners, bool detailed)
{
var results = await GraphHelper.GetAsync<RestResultCollection<Microsoft365Group>>(connection, $"v1.0/groups?$filter=groupTypes/any(c:c+eq+'Unified') and id eq '{groupId}'", accessToken);

Expand Down Expand Up @@ -100,7 +100,6 @@ internal static async Task<Microsoft365Group> GetGroupAsync(PnPConnection connec
{
if (iterations * 30 >= 300)
{
wait = false;
throw;
}
else
Expand All @@ -114,12 +113,19 @@ internal static async Task<Microsoft365Group> GetGroupAsync(PnPConnection connec
{
group.Owners = await GetGroupMembersAsync("owners", connection, group.Id.Value, accessToken);
}
if (detailed)
{
var exchangeOnlineProperties = await GetGroupExchangeOnlineSettingsAsync(connection, group.Id.Value, accessToken);
group.AllowExternalSenders = exchangeOnlineProperties.AllowExternalSenders;
group.AutoSubscribeNewMembers = exchangeOnlineProperties.AutoSubscribeNewMembers;
group.IsSubscribedByMail = exchangeOnlineProperties.IsSubscribedByMail;
}
return group;
}
return null;
}

internal static async Task<Microsoft365Group> GetGroupAsync(PnPConnection connection, string displayName, string accessToken, bool includeSiteUrl, bool includeOwners)
internal static async Task<Microsoft365Group> GetGroupAsync(PnPConnection connection, string displayName, string accessToken, bool includeSiteUrl, bool includeOwners, bool detailed)
{
var results = await GraphHelper.GetAsync<RestResultCollection<Microsoft365Group>>(connection, $"v1.0/groups?$filter=groupTypes/any(c:c+eq+'Unified') and (displayName eq '{displayName}' or mailNickName eq '{displayName}')", accessToken);
if (results != null && results.Items.Any())
Expand Down Expand Up @@ -335,6 +341,12 @@ private static async Task<IEnumerable<Microsoft365User>> GetGroupMembersAsync(st
return results;
}

private static async Task<Microsoft365Group> GetGroupExchangeOnlineSettingsAsync(PnPConnection connection, Guid groupId, string accessToken)
{
var results = await GraphHelper.GetAsync<Microsoft365Group>(connection, $"v1.0/groups/{groupId}?$select=allowExternalSenders,isSubscribedByMail,autoSubscribeNewMembers", accessToken);
return results;
}

internal static async Task ClearMembersAsync(PnPConnection connection, Guid groupId, string accessToken)
{
var members = await GetMembersAsync(connection, groupId, accessToken);
Expand Down Expand Up @@ -393,6 +405,22 @@ internal static async Task UpdateMembersAsync(PnPConnection connection, Guid gro
}
}

internal static async Task<Microsoft365Group> UpdateExchangeOnlineSettingAsync(PnPConnection connection, Guid groupId, string accessToken, Microsoft365Group group)
{
var patchData = new
{
group.AllowExternalSenders,
group.AutoSubscribeNewMembers
};

var result = await GraphHelper.PatchAsync(connection, accessToken, $"v1.0/groups/{groupId}", patchData);

group.AllowExternalSenders = result.AllowExternalSenders;
group.AutoSubscribeNewMembers = result.AutoSubscribeNewMembers;

return group;
}

internal static async Task<Dictionary<string, string>> GetSiteUrlBatchedAsync(PnPConnection connection, string accessToken, string[] groupIds)
{
Dictionary<string, string> returnValue = new Dictionary<string, string>();
Expand Down

0 comments on commit d576b53

Please sign in to comment.