From 5c49c2d5edf783b6345dcbd2d9ee8bb59cdebab7 Mon Sep 17 00:00:00 2001 From: Sandro Lanfranchi Date: Wed, 28 Aug 2024 13:58:54 +0200 Subject: [PATCH] fix #4994 --- .../MSFT_AADGroup/MSFT_AADGroup.psm1 | 73 ++++++++++++++++++- .../MSFT_AADGroup/MSFT_AADGroup.schema.mof | 1 + .../Examples/Resources/AADGroup/1-Create.ps1 | 2 + .../Examples/Resources/AADGroup/2-Update.ps1 | 2 + .../Microsoft365DSC.AADGroup.Tests.ps1 | 60 +++++++++++++++ 5 files changed, 137 insertions(+), 1 deletion(-) diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 index 02f13b01dc..0a74f43a58 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 @@ -24,6 +24,10 @@ function Get-TargetResource [System.String[]] $Members, + [Parameter()] + [System.String[]] + $GroupAsMembers, + [Parameter()] [System.String[]] $MemberOf, @@ -213,12 +217,17 @@ function Get-TargetResource # Members [Array]$members = Get-MgGroupMember -GroupId $Group.Id -All:$true $MembersValues = @() + $GroupAsMembersValues = @() foreach ($member in $members) { - if ($member.AdditionalProperties.userPrincipalName -ne $null) + if ($member.AdditionalProperties.'@odata.type' -eq "#microsoft.graph.user") { $MembersValues += $member.AdditionalProperties.userPrincipalName } + elseif($member.AdditionalProperties.'@odata.type' -eq "#microsoft.graph.group") + { + $GroupAsMembersValues += $member.AdditionalProperties.displayName + } } } @@ -265,6 +274,7 @@ function Get-TargetResource Id = $Group.Id Owners = $OwnersValues Members = $MembersValues + GroupAsMembers = $GroupAsMembersValues MemberOf = $MemberOfValues Description = $Group.Description GroupTypes = [System.String[]]$Group.GroupTypes @@ -327,6 +337,10 @@ function Set-TargetResource [System.String[]] $Members, + [Parameter()] + [System.String[]] + $GroupAsMembers, + [Parameter()] [System.String[]] $MemberOf, @@ -432,10 +446,12 @@ function Set-TargetResource $currentParameters.Remove('ManagedIdentity') | Out-Null $backCurrentOwners = $currentGroup.Owners $backCurrentMembers = $currentGroup.Members + $backCurrentGroupAsMembers = $currentGroup.GroupAsMembers $backCurrentMemberOf = $currentGroup.MemberOf $backCurrentAssignedToRole = $currentGroup.AssignedToRole $currentParameters.Remove('Owners') | Out-Null $currentParameters.Remove('Members') | Out-Null + $currentParameters.Remove('GroupAsMembers') | Out-Null $currentParameters.Remove('MemberOf') | Out-Null $currentParameters.Remove('AssignedToRole') | Out-Null @@ -728,6 +744,57 @@ function Set-TargetResource Write-Verbose -Message 'Ignoring membership since this is a dynamic group.' } + #GroupAsMembers + if ($MembershipRuleProcessingState -ne 'On' -and $PSBoundParameters.ContainsKey('GroupAsMembers')) + { + $currentGroupAsMembersValue = @() + if ($currentParameters.GroupAsMembers.Length -ne 0) + { + $currentGroupAsMembersValue = $backCurrentGroupAsMembers + } + $desiredGroupAsMembersValue = @() + if ($GroupAsMembers.Length -ne 0) + { + $desiredGroupAsMembersValue = $GroupAsMembers + } + if ($backCurrentGroupAsMembers -eq $null) + { + $backCurrentGroupAsMembers = @() + } + $groupAsMembersDiff = Compare-Object -ReferenceObject $backCurrentGroupAsMembers -DifferenceObject $desiredGroupAsMembersValue + foreach ($diff in $groupAsMembersDiff) + { + try + { + $groupAsMember = Get-MgGroup -Filter "DisplayName eq '$($diff.InputObject)'" -ErrorAction Stop + } + catch + { + $groupAsMember = $null + } + if ($null -eq $groupAsMember) + { + throw "Group '$($diff.InputObject)' does not exist" + } + else + { + if ($diff.SideIndicator -eq '=>') + { + Write-Verbose -Message "Adding AAD group {$($groupAsMember.DisplayName)} as member of AAD group {$($currentGroup.DisplayName)}" + $groupAsMemberObject = @{ + "@odata.id"= "https://graph.microsoft.com/v1.0/directoryObjects/$($groupAsMember.Id)" + } + New-MgGroupMemberByRef -GroupId ($currentGroup.Id) -Body $groupAsMemberObject | Out-Null + } + if ($diff.SideIndicator -eq '<=') + { + Write-Verbose -Message "Removing AAD Group {$($groupAsMember.DisplayName)} from AAD group {$($currentGroup.DisplayName)}" + Remove-MgGroupMemberDirectoryObjectByRef -GroupId ($currentGroup.Id) -DirectoryObjectId ($groupAsMember.Id) | Out-Null + } + } + } + } + #MemberOf if ($PSBoundParameters.ContainsKey('MemberOf')) { @@ -879,6 +946,10 @@ function Test-TargetResource [System.String[]] $Members, + [Parameter()] + [System.String[]] + $GroupAsMembers, + [Parameter()] [System.String[]] $MemberOf, diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.schema.mof index 8f9bc80293..5e3aaf41be 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.schema.mof +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.schema.mof @@ -14,6 +14,7 @@ class MSFT_AADGroup : OMI_BaseResource [Write, Description("Specifies an ID for the group.")] String Id; [Write, Description("User Service Principal values for the group's owners.")] String Owners[]; [Write, Description("User Service Principal values for the group's members.")] String Members[]; + [Write, Description("Displayname values for the groups member of the group.")] String GroupAsMembers[]; [Write, Description("DisplayName values for the groups that this group is a member of.")] String MemberOf[]; [Write, Description("Specifies that the group is a dynamic group. To create a dynamic group, specify a value of DynamicMembership.")] String GroupTypes[]; [Write, Description("Specifies the membership rule for a dynamic group.")] String MembershipRule; diff --git a/Modules/Microsoft365DSC/Examples/Resources/AADGroup/1-Create.ps1 b/Modules/Microsoft365DSC/Examples/Resources/AADGroup/1-Create.ps1 index 243f549348..fcd816c154 100644 --- a/Modules/Microsoft365DSC/Examples/Resources/AADGroup/1-Create.ps1 +++ b/Modules/Microsoft365DSC/Examples/Resources/AADGroup/1-Create.ps1 @@ -30,6 +30,8 @@ Configuration Example MailEnabled = $True GroupTypes = @("Unified") MailNickname = "M365DSC" + Members = @("admin@$TenantId", "AdeleV@$TenantId") + GroupAsMembers = @("Group1", "Group2") Visibility = "Private" Owners = @("admin@$TenantId", "AdeleV@$TenantId") Ensure = "Present" diff --git a/Modules/Microsoft365DSC/Examples/Resources/AADGroup/2-Update.ps1 b/Modules/Microsoft365DSC/Examples/Resources/AADGroup/2-Update.ps1 index 827370cdcc..07b9ba1c62 100644 --- a/Modules/Microsoft365DSC/Examples/Resources/AADGroup/2-Update.ps1 +++ b/Modules/Microsoft365DSC/Examples/Resources/AADGroup/2-Update.ps1 @@ -29,6 +29,8 @@ Configuration Example MailEnabled = $True GroupTypes = @("Unified") MailNickname = "M365DSC" + Members = @("AdeleV@$TenantId") + GroupAsMembers = @("Group1") Visibility = "Private" Owners = @("admin@$TenantId", "AdeleV@$TenantId") Ensure = "Present" diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADGroup.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADGroup.Tests.ps1 index 9bf9dfdaac..0215457741 100644 --- a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADGroup.Tests.ps1 +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADGroup.Tests.ps1 @@ -37,6 +37,9 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Mock -CommandName Get-MgGroupMember -MockWith { } + Mock -CommandName Get-MgGroup -MockWith { + } + Mock -CommandName Restore-MgBetaDirectoryDeletedItem -MockWith { } Mock -CommandName Get-MgBetaDirectoryDeletedItemAsGroup -MockWith { @@ -500,6 +503,63 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { } } + Context -Name 'The Group Exists but group is not assigned as member. Values are NOT in the desired state' -Fixture { + BeforeAll { + $testParams = @{ + DisplayName = 'DSCGroup' + ID = '12345-12345-12345-12345' + Description = 'Microsoft DSC Group' + SecurityEnabled = $True + MailEnabled = $true + GroupTypes = @() + MailNickname = 'M365DSC' + IsAssignableToRole = $true + GroupAsMembers = 'DSCGroupMember' + Ensure = 'Present' + Credential = $Credential + } + + + Mock -CommandName New-M365DSCConnection -MockWith { + return 'Credentials' + } + + Mock -CommandName Get-MgGroup -MockWith { + return @{ + DisplayName = 'DSCGroupMember' + ID = '12345-12345-12345-12345' + Description = 'Microsoft DSC Group' + SecurityEnabled = $True + MailEnabled = $true + GroupTypes = @() + MailNickname = 'M365DSC' + IsAssignableToRole = $true + AssignedToRole = @() + Ensure = 'Present' + } + } + + Mock -CommandName New-MgGroupMemberByRef -MockWith { + } + } + + It 'Should return Values from the Get method' { + Get-TargetResource @testParams + Should -Invoke -CommandName 'Get-MgGroup' -Exactly 1 + } + + It 'Should return false from the Test method' { + Test-TargetResource @testParams | Should -Be $false + } + + It 'Should call the Set method' { + Set-TargetResource @testParams + Should -Invoke -CommandName 'Get-MgGroup' -Exactly 2 + Should -Invoke -CommandName 'New-MgGroupMemberByRef' -Exactly 1 + #Should -Invoke -CommandName 'Remove-MgGroupMemberDirectoryObjectByRef' -Exactly 1 + } + } + Context -Name "The Group Exists and is assigned to a role but it shouldn't be. Values are NOT in the desired state" -Fixture { BeforeAll { $testParams = @{