diff --git a/CHANGELOG.md b/CHANGELOG.md index 380f48bf9..abbf15145 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/documentation/Get-PnPMicrosoft365Group.md b/documentation/Get-PnPMicrosoft365Group.md index dfe3506c8..c172f5e64 100644 --- a/documentation/Get-PnPMicrosoft365Group.md +++ b/documentation/Get-PnPMicrosoft365Group.md @@ -20,7 +20,7 @@ Gets one Microsoft 365 Group or a list of Microsoft 365 Groups ## SYNTAX ```powershell -Get-PnPMicrosoft365Group [-Identity ] [-IncludeSiteUrl] [-IncludeOwners] [-Filter ] +Get-PnPMicrosoft365Group [-Identity ] [-IncludeSiteUrl] [-IncludeOwners] [-Detailed] [-Filter ] ``` ## DESCRIPTION @@ -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. diff --git a/documentation/Set-PnPMicrosoft365Group.md b/documentation/Set-PnPMicrosoft365Group.md index 5d442fd76..ac215e160 100644 --- a/documentation/Set-PnPMicrosoft365Group.md +++ b/documentation/Set-PnPMicrosoft365Group.md @@ -20,10 +20,7 @@ Sets Microsoft 365 Group properties ## SYNTAX ```powershell -Set-PnPMicrosoft365Group -Identity [-DisplayName ] [-Description ] - [-Owners ] [-Members ] [-IsPrivate] [-LogoPath ] [-CreateTeam] - [-HideFromAddressLists ] [-HideFromOutlookClients ] [-MailNickname ] [-SensitivityLabels ] - +Set-PnPMicrosoft365Group -Identity [-DisplayName ] [-Description ] [-Owners ] [-Members ] [-IsPrivate] [-LogoPath ] [-CreateTeam] [-HideFromAddressLists ] [-HideFromOutlookClients ] [-RequireSenderAuthenticationEnabled ] [-AutoSubscribeNewMembers ] [-MailNickname ] [-SensitivityLabels ] [-Verbose] ``` ## DESCRIPTION @@ -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 @@ -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). @@ -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) \ No newline at end of file diff --git a/src/Commands/Base/PipeBinds/Microsoft365GroupPipeBind.cs b/src/Commands/Base/PipeBinds/Microsoft365GroupPipeBind.cs index 164269ec4..eceed1af6 100644 --- a/src/Commands/Base/PipeBinds/Microsoft365GroupPipeBind.cs +++ b/src/Commands/Base/PipeBinds/Microsoft365GroupPipeBind.cs @@ -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; } @@ -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; diff --git a/src/Commands/Microsoft365Groups/GetMicrosoft365Group.cs b/src/Commands/Microsoft365Groups/GetMicrosoft365Group.cs index a42d954ca..48586482c 100644 --- a/src/Commands/Microsoft365Groups/GetMicrosoft365Group.cs +++ b/src/Commands/Microsoft365Groups/GetMicrosoft365Group.cs @@ -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; @@ -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 diff --git a/src/Commands/Microsoft365Groups/NewMicrosoft365Group.cs b/src/Commands/Microsoft365Groups/NewMicrosoft365Group.cs index fa328a13c..cc45d7b96 100644 --- a/src/Commands/Microsoft365Groups/NewMicrosoft365Group.cs +++ b/src/Commands/Microsoft365Groups/NewMicrosoft365Group.cs @@ -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 @@ -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); } diff --git a/src/Commands/Microsoft365Groups/RemoveMicrosoft365GroupPhoto.cs b/src/Commands/Microsoft365Groups/RemoveMicrosoft365GroupPhoto.cs index 35331d5f2..b2850dea9 100644 --- a/src/Commands/Microsoft365Groups/RemoveMicrosoft365GroupPhoto.cs +++ b/src/Commands/Microsoft365Groups/RemoveMicrosoft365GroupPhoto.cs @@ -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(); diff --git a/src/Commands/Microsoft365Groups/SetMicrosoft365Group.cs b/src/Commands/Microsoft365Groups/SetMicrosoft365Group.cs index 64419ee2a..eed620ff2 100644 --- a/src/Commands/Microsoft365Groups/SetMicrosoft365Group.cs +++ b/src/Commands/Microsoft365Groups/SetMicrosoft365Group.cs @@ -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; @@ -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(); diff --git a/src/Commands/Model/Microsoft365Group.cs b/src/Commands/Model/Microsoft365Group.cs index 189e69396..92e7b9409 100644 --- a/src/Commands/Model/Microsoft365Group.cs +++ b/src/Commands/Model/Microsoft365Group.cs @@ -5,6 +5,9 @@ namespace PnP.PowerShell.Commands.Model { + /// + /// Properties of one Microsoft 365 Group + /// public class Microsoft365Group { [JsonPropertyName("owners@odata.bind")] @@ -53,6 +56,9 @@ public string GroupId public string SiteUrl { get; set; } public string[] GroupTypes { get; set; } public IEnumerable Owners { get; set; } + public bool? AllowExternalSenders { get; set; } + public bool? IsSubscribedByMail { get; set; } + public bool? AutoSubscribeNewMembers { get; set; } public List AssignedLabels { get; set; } diff --git a/src/Commands/Utilities/Microsoft365GroupsUtility.cs b/src/Commands/Utilities/Microsoft365GroupsUtility.cs index 723486d55..0d952a99e 100644 --- a/src/Commands/Utilities/Microsoft365GroupsUtility.cs +++ b/src/Commands/Utilities/Microsoft365GroupsUtility.cs @@ -67,7 +67,7 @@ internal static async Task> GetGroupsAsync(PnPCon return items; } - internal static async Task GetGroupAsync(PnPConnection connection, Guid groupId, string accessToken, bool includeSiteUrl, bool includeOwners) + internal static async Task GetGroupAsync(PnPConnection connection, Guid groupId, string accessToken, bool includeSiteUrl, bool includeOwners, bool detailed) { var results = await GraphHelper.GetAsync>(connection, $"v1.0/groups?$filter=groupTypes/any(c:c+eq+'Unified') and id eq '{groupId}'", accessToken); @@ -100,7 +100,6 @@ internal static async Task GetGroupAsync(PnPConnection connec { if (iterations * 30 >= 300) { - wait = false; throw; } else @@ -114,12 +113,19 @@ internal static async Task 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 GetGroupAsync(PnPConnection connection, string displayName, string accessToken, bool includeSiteUrl, bool includeOwners) + internal static async Task GetGroupAsync(PnPConnection connection, string displayName, string accessToken, bool includeSiteUrl, bool includeOwners, bool detailed) { var results = await GraphHelper.GetAsync>(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()) @@ -335,6 +341,12 @@ private static async Task> GetGroupMembersAsync(st return results; } + private static async Task GetGroupExchangeOnlineSettingsAsync(PnPConnection connection, Guid groupId, string accessToken) + { + var results = await GraphHelper.GetAsync(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); @@ -393,6 +405,22 @@ internal static async Task UpdateMembersAsync(PnPConnection connection, Guid gro } } + internal static async Task 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> GetSiteUrlBatchedAsync(PnPConnection connection, string accessToken, string[] groupIds) { Dictionary returnValue = new Dictionary();