From 77a4aff7321d546739863bef8723df6810e7dfbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Albeck?= Date: Thu, 22 Sep 2022 12:04:28 +0200 Subject: [PATCH 1/7] Adding MemberOf and AssignedToRole --- .../MSFT_AADGroup/MSFT_AADGroup.psm1 | 175 +++++++++ .../MSFT_AADGroup/MSFT_AADGroup.schema.mof | 2 + ...DMSGroups.ps1 => 1-ConfigureAADGroups.ps1} | 0 .../AADGroup/2-ConfigureAADGroups.ps1 | 31 ++ .../AADGroup/3-ConfigureAADGroups.ps1 | 39 ++ .../Microsoft365DSC.AADGroup.Tests.ps1 | 347 +++++++++++++++++- 6 files changed, 592 insertions(+), 2 deletions(-) rename Modules/Microsoft365DSC/Examples/Resources/AADGroup/{1-ConfigureAADMSGroups.ps1 => 1-ConfigureAADGroups.ps1} (100%) create mode 100644 Modules/Microsoft365DSC/Examples/Resources/AADGroup/2-ConfigureAADGroups.ps1 create mode 100644 Modules/Microsoft365DSC/Examples/Resources/AADGroup/3-ConfigureAADGroups.ps1 diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 index a1b27cf655..b7e3a48075 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[]] + $MemberOf, + [Parameter()] [System.String] $Description, @@ -53,6 +57,10 @@ function Get-TargetResource [System.Boolean] $IsAssignableToRole, + [Parameter()] + [System.String[]] + $AssignedToRole, + [Parameter()] [ValidateSet('Public', 'Private', 'HiddenMembership')] [System.String] @@ -175,7 +183,36 @@ function Get-TargetResource } } + # MemberOf + [Array]$memberOf = Get-MgGroupMemberOf -GroupId $Group.Id -All:$true # result also used for/by AssignedToRole + $MemberOfValues = @() + # Note: only process security-groups that this group is a member of and not directory roles (if any) + foreach ($member in ($memberOf | Where-Object -FilterScript {$_.AdditionalProperties.'@odata.type' -eq "#microsoft.graph.group"})) + { + if ($null -ne $member.AdditionalProperties.displayName) + { + $MemberOfValues += $member.AdditionalProperties.displayName + } + } + + # AssignedToRole + $AssignedToRoleValues = $null + if ($Group.IsAssignableToRole -eq $true) + { + $AssignedToRoleValues = @() + # Note: only process directory roles and not group membership (if any) + foreach ($role in $($memberOf | Where-Object -FilterScript {$_.AdditionalProperties.'@odata.type' -eq "#microsoft.graph.directoryRole"})) + { + if ($null -ne $role.AdditionalProperties.displayName) + { + $AssignedToRoleValues += $role.AdditionalProperties.displayName + } + } + } + + # Licenses + $assignedLicensesValues = $null $assignedLicensesRequest = Invoke-MgGraphRequest -Method 'GET' ` -Uri "https://graph.microsoft.com/v1.0/groups/$($Group.Id)/assignedLicenses" @@ -190,6 +227,7 @@ function Get-TargetResource Id = $Group.Id Owners = $OwnersValues Members = $MembersValues + MemberOf = $MemberOfValues Description = $Group.Description GroupTypes = [System.String[]]$Group.GroupTypes MembershipRule = $Group.MembershipRule @@ -197,6 +235,7 @@ function Get-TargetResource SecurityEnabled = $Group.SecurityEnabled MailEnabled = $Group.MailEnabled IsAssignableToRole = $Group.IsAssignableToRole + AssignedToRole = $AssignedToRoleValues MailNickname = $Group.MailNickname Visibility = $Group.Visibility AssignedLicenses = $assignedLicensesValues @@ -259,6 +298,10 @@ function Set-TargetResource [System.String[]] $Members, + [Parameter()] + [System.String[]] + $MemberOf, + [Parameter()] [System.String] $Description, @@ -288,6 +331,10 @@ function Set-TargetResource [System.Boolean] $IsAssignableToRole, + [Parameter()] + [System.string[]] + $AssignedToRole, + [Parameter()] [ValidateSet('Public', 'Private', 'HiddenMembership')] [System.String] @@ -352,8 +399,12 @@ function Set-TargetResource $currentParameters.Remove('ManagedIdentity') | Out-Null $backCurrentOwners = $currentGroup.Owners $backCurrentMembers = $currentGroup.Members + $backCurrentMemberOf = $currentGroup.MemberOf + $backCurrentAssignedToRole = $currentGroup.AssignedToRole $currentParameters.Remove('Owners') | Out-Null $currentParameters.Remove('Members') | Out-Null + $currentParameters.Remove('MemberOf') | Out-Null + $currentParameters.Remove('AssignedToRole') | Out-Null if ($Ensure -eq 'Present' -and ` ($null -ne $GroupTypes -and $GroupTypes.Contains('Unified')) -and ` @@ -607,6 +658,122 @@ function Set-TargetResource { Write-Verbose -Message 'Ignoring membership since this is a dynamic group.' } + + #MemberOf + $currentMemberOfValue = @() + if ($currentParameters.MemberOf.Length -ne 0) + { + $currentMemberOfValue = $backCurrentMemberOf + } + $desiredMemberOfValue = @() + if ($MemberOf.Length -ne 0) + { + $desiredMemberOfValue = $MemberOf + } + if ($null -eq $backCurrentMemberOf) + { + $backCurrentMemberOf = @() + } + $memberOfDiff = Compare-Object -ReferenceObject $backCurrentMemberOf -DifferenceObject $desiredMemberOfValue + foreach ($diff in $memberOfDiff) + { + try + { + $memberOfGroup = Get-MgGroup -Filter "DisplayName -eq '$($diff.InputObject)'" -ErrorAction Stop + } + catch + { + $memberOfGroup = $null + } + if ($null -eq $memberOfGroup) + { + throw "Security-group or directory role '$($diff.InputObject)' does not exist" + } + else + { + if ($diff.SideIndicator -eq '=>') + { + # see if memberOfGroup contains property SecurityEnabled (it can be true or false) + if ($memberOfgroup.psobject.Typenames -match 'Group') + { + Write-Verbose -Message "Adding AAD group {$($currentGroup.DisplayName)} as member of AAD group {$($memberOfGroup.DisplayName)}" + #$memberOfObject = @{ + # "@odata.id"= "https://graph.microsoft.com/v1.0/groups/{$($group.Id)}" + #} + New-MgGroupMember -GroupId ($memberOfGroup.Id) -DirectoryObject ($currentGroup.Id) | Out-Null + } + else + { + Throw "Cannot add AAD group {$($currentGroup.DisplayName)} to {$($memberOfGroup.DisplayName)} as it is not a security-group" + } + + } + elseif ($diff.SideIndicator -eq '<=') + { + if ($memberOfgroup.psobject.Typenames -match 'Group') + { + Write-Verbose -Message "Removing AAD Group {$($currentGroup.DisplayName)} from AAD group {$($memberOfGroup.DisplayName)}" + Remove-MgGroupMemberByRef -GroupId ($memberOfGroup.Id) -DirectoryObjectId ($currentGroup.Id) |Out-Null + } + else + { + Throw "Cannot remove AAD group {$($currentGroup.DisplayName)} from {$($memberOfGroup.DisplayName)} as it is not a security-group" + } + } + } + } + + if ($currentGroup.IsAssignableToRole -eq $true) + { + #AssignedToRole + $currentAssignedToRoleValue = @() + if ($currentParameters.AssignedToRole.Length -ne 0) + { + $currentAssignedToRoleValue = $backCurrentAssignedToRole + } + $desiredAssignedToRoleValue = @() + if ($AssignedToRole.Length -ne 0) + { + $desiredAssignedToRoleValue = $AssignedToRole + } + if ($null -eq $backCurrentAssignedToRole) + { + $backCurrentAssignedToRole = @() + } + $assignedToRoleDiff = Compare-Object -ReferenceObject $backCurrentAssignedToRole -DifferenceObject $desiredAssignedToRoleValue + foreach ($diff in $assignedToRoleDiff) + { + try + { + $role = Get-MgDirectoryRole -Filter "DisplayName -eq '$($diff.InputObject)'" -ErrorAction Stop + } + catch + { + $role = $null + } + if ($null -eq $role) + { + throw "Directory Role '$($diff.InputObject)' does not exist or is not enabled" + } + else + { + if ($diff.SideIndicator -eq '=>') + { + Write-Verbose -Message "Assigning AAD group {$($currentGroup.DisplayName)} to Directory Role {$($diff.InputObject)}" + $DirObject = @{ + "@odata.id" = "https://graph.microsoft.com/v1.0/directoryObjects/$($currentGroup.Id)" + } + New-MgDirectoryRoleMemberByRef -DirectoryRoleId ($role.Id) -BodyParameter $DirObject | Out-null + + } + elseif ($diff.SideIndicator -eq '<=') + { + Write-Verbose -Message "Removing AAD group {$($currentGroup.DisplayName)} from Directory Role {$($role.DisplayName)}" + Remove-MgDirectoryRoleMemberByRef -DirectoryRoleId ($role.Id) -DirectoryObjectId ($currentGroup.Id) | Out-null + } + } + } + } } } @@ -636,6 +803,10 @@ function Test-TargetResource [System.String[]] $Members, + [Parameter()] + [System.String[]] + $MemberOf, + [Parameter()] [System.String] $Description, @@ -665,6 +836,10 @@ function Test-TargetResource [System.Boolean] $IsAssignableToRole, + [Parameter()] + [System.String[]] + $AssignedToRole, + [Parameter()] [ValidateSet('Public', 'Private', 'HiddenMembership')] [System.String] diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.schema.mof index ccd692fb9e..054ff964cc 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.schema.mof +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.schema.mof @@ -14,12 +14,14 @@ 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 that htis 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; [Write, Description("Specifies the rule processing state. The acceptable values for this parameter are: On. Process the group rule or Paused. Stop processing the group rule."), ValueMap{"On","Paused"}, Values{"On","Paused"}] String MembershipRuleProcessingState; [Write, Description("Specifies whether the group is security enabled. For security groups, this value must be $True.")] Boolean SecurityEnabled; [Write, Description("Specifies whether this group is mail enabled. Currently, you cannot create mail enabled groups in Azure AD.")] Boolean MailEnabled; [Write, Description("Specifies whether this group can be assigned a role. Only available when creating a group and can't be modified after group is created.")] Boolean IsAssignableToRole; + [Write, Description("DisplayName values for the roles that the group is assigned to.")] String AssignedToRole[]; [Write, Description("This parameter determines the visibility of the group's content and members list."), ValueMap{"Public","Private","HiddenMembership"}, Values{"Public","Private","HiddenMembership"}] String Visibility; [Write, Description("List of Licenses assigned to the group."),EmbeddedInstance("MSFT_AADGroupLicense")] String AssignedLicenses[]; [Write, Description("Specify if the Azure AD Group should exist or not."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; diff --git a/Modules/Microsoft365DSC/Examples/Resources/AADGroup/1-ConfigureAADMSGroups.ps1 b/Modules/Microsoft365DSC/Examples/Resources/AADGroup/1-ConfigureAADGroups.ps1 similarity index 100% rename from Modules/Microsoft365DSC/Examples/Resources/AADGroup/1-ConfigureAADMSGroups.ps1 rename to Modules/Microsoft365DSC/Examples/Resources/AADGroup/1-ConfigureAADGroups.ps1 diff --git a/Modules/Microsoft365DSC/Examples/Resources/AADGroup/2-ConfigureAADGroups.ps1 b/Modules/Microsoft365DSC/Examples/Resources/AADGroup/2-ConfigureAADGroups.ps1 new file mode 100644 index 0000000000..8ff211e7be --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/AADGroup/2-ConfigureAADGroups.ps1 @@ -0,0 +1,31 @@ +<# +This example is used to test new resources and showcase the usage of new resources being worked on. +It is not meant to use as a production baseline. +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $credsGlobalAdmin + ) + Import-DscResource -ModuleName Microsoft365DSC + + node localhost + { + AADGroup 'MyGroups' + { + DisplayName = "DSCGroup" + Description = "Microsoft DSC Group" + SecurityEnabled = $True + MailEnabled = $False + GroupTypes = @() + MailNickname = "DSCGroup" + Ensure = "Present" + IsAssignableToRole = $True + AssignedToRole = "Identity Governance Administrator" + Credential = $credsGlobalAdmin + } + } +} diff --git a/Modules/Microsoft365DSC/Examples/Resources/AADGroup/3-ConfigureAADGroups.ps1 b/Modules/Microsoft365DSC/Examples/Resources/AADGroup/3-ConfigureAADGroups.ps1 new file mode 100644 index 0000000000..be2e268fd7 --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/AADGroup/3-ConfigureAADGroups.ps1 @@ -0,0 +1,39 @@ +<# +This example is used to test new resources and showcase the usage of new resources being worked on. +It is not meant to use as a production baseline. +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $credsGlobalAdmin + ) + Import-DscResource -ModuleName Microsoft365DSC + + node localhost + { + AADGroup 'MyGroups1' + { + DisplayName = "DSCGroup" + Description = "Microsoft DSC Group" + SecurityEnabled = $True + GroupTypes = @() + MailNickname = "M365DSCG" + Ensure = "Present" + Credential = $credsGlobalAdmin + } + AADGroup 'MyGroups2' + { + DisplayName = "DSCMemberGroup" + Description = "Microsoft DSC Editor" + SecurityEnabled = $True + GroupTypes = @() + MailNickname = "M365DSCMG" + Ensure = "Present" + MemberOf = @("DSCGroup") + Credential = $credsGlobalAdmin + } + } +} diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADGroup.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADGroup.Tests.ps1 index e7cfc14d88..e0506389d7 100644 --- a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADGroup.Tests.ps1 +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADGroup.Tests.ps1 @@ -41,6 +41,22 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { } + Mock -CommandName Get-MgGroupMember -MockWith { + + } + + Mock -CommandName Get-MgGroupMemberOf -MockWith { + + } + + Mock -CommandName Get-MgGroupOwner -MockWith { + + } + + Mock -CommandName Invoke-MgGraphRequest -MockWith { + + } + Mock -CommandName Update-MgGroup -MockWith { } @@ -52,6 +68,30 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Mock -CommandName New-MgGroup -MockWith { } + + Mock -CommandName New-MgGroupOwnerByRef -MockWith { + + } + + Mock -CommandName New-MgGroupMember -MockWith { + + } + + Mock -CommandName New-MgDirectoryRoleMemberByRef -MockWith { + + } + + Mock -CommandName Remove-MgGroupOwnerByRef -MockWith { + + } + + Mock -CommandName Remove-MgGroupMemberByRef -MockWith { + + } + + Mock -CommandName Remove-MgDirectoryRoleMemberByRef -MockWith { + + } } # Test contexts Context -Name "The Group should exist but it DOES NOT" -Fixture { @@ -129,6 +169,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Should -Invoke -CommandName "Remove-MgGroup" -Exactly 1 } } + Context -Name "The Group Exists and Values are already in the desired state" -Fixture { BeforeAll { $testParams = @{ @@ -159,7 +200,6 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { MailNickname = "M365DSC" GroupTypes = @("Unified") Visibility = "Private" - Ensure = "Present" } } } @@ -174,6 +214,123 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { } } + Context -Name "The Group Exists and is a member of another group. Values are already in the desired state" -Fixture { + BeforeAll { + $testParams = @{ + DisplayName = "DSCGroup" + ID = "12345-12345-12345-12345" + Description = "Microsoft DSC Group" + SecurityEnabled = $True + GroupTypes = @() + MailNickname = "M365DSC" + MemberOf = "DSCMemberOfGroup" + Ensure = "Present" + Credential = $Credential; + } + + + Mock -CommandName New-M365DSCConnection -MockWith { + return "Credential" + } + + Mock -CommandName Get-MgGroup -ParameterFilter {$Id -eq "12345-12345-12345-12345" -or $Filter -eq "DisplayName eq 'DSCGroup'"} -MockWith { + return @{ + DisplayName = "DSCGroup" + ID = "12345-12345-12345-12345" + Description = "Microsoft DSC Group" + SecurityEnabled = $True + MailEnabled = $False + MailNickname = "M365DSC" + GroupTypes = @() + } + } + Mock -CommandName Get-MgGroupMemberOf -MockWith { + return @{ + AdditionalProperties = @{ + '@odata.type' = "#microsoft.graph.group" + displayName = "DSCMemberOfGroup" + } + } + } + Mock -CommandName Get-MgGroup -ParameterFilter {$Id -eq '67890-67890-67890-67890' -or $Filter -eq "DisplayName -eq 'DSCMemberOfGroup'"} -MockWith { + $returnData = @{ + DisplayName = "DSCMemberOfGroup" + ID = "67890-67890-67890-67890" + Description = "Microsoft DSC MemberOf Group" + SecurityEnabled = $True + GroupTypes = @() + MailEnabled = $False + MailNickname = "M365DSCM" + } + # Set-TargetResource expects data-type of answer to contain 'group' + $returnData.psobject.TypeNames.insert(0, 'Group') + return $returnData + } + } + + It "Should return Values from the Get method" { + Get-TargetResource @testParams + Should -Invoke -CommandName "Get-MgGroup" -Exactly 1 + Should -Invoke -CommandName "Get-MgGroupMemberOf" -Exactly 1 + } + + It 'Should return true from the Test method' { + Test-TargetResource @testParams | Should -Be $true + } + } + + Context -Name "The Group Exists and is assigned to the correct role. Values are already in the desired state" -Fixture { + BeforeAll { + $testParams = @{ + DisplayName = "DSCGroup" + ID = "12345-12345-12345-12345" + Description = "Microsoft DSC Group" + SecurityEnabled = $True + GroupTypes = @() + MailNickname = "M365DSC" + IsAssignableToRole = $true + AssignedToRole = "AADRole" + Ensure = "Present" + Credential = $Credential; + } + + + Mock -CommandName New-M365DSCConnection -MockWith { + return "Credential" + } + + Mock -CommandName Get-MgGroup -MockWith { + return @{ + DisplayName = "DSCGroup" + ID = "12345-12345-12345-12345" + Description = "Microsoft DSC Group" + SecurityEnabled = $True + GroupTypes = @() + MailNickname = "M365DSC" + IsAssignableToRole = $true + } + } + Mock -CommandName Get-MgGroupMemberOf -MockWith { + return @{ + AdditionalProperties = @{ + '@odata.type' = "#microsoft.graph.directoryRole" + displayName = "AADRole" + } + } + } + } + + It "Should return Values from the Get method" { + Get-TargetResource @testParams + Should -Invoke -CommandName "Get-MgGroup" -Exactly 1 + Should -Invoke -CommandName "Get-MgGroupMemberOf" -Exactly 1 + } + + It 'Should return true from the Test method' { + Test-TargetResource @testParams | Should -Be $true + } + } + Context -Name "The Group exists and values are NOT in the desired state" -Fixture { BeforeAll { $testParams = @{ @@ -185,7 +342,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { GroupTypes = @("Unified") Visibility = "Private" Ensure = "Present" - Credential = $Credential; + Credential = $Credential; } Mock -CommandName New-M365DSCConnection -MockWith { @@ -221,6 +378,192 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { } } + Context -Name "The Group Exists but is not a member of another group. Values are NOT in the desired state" -Fixture { + BeforeAll { + $testParams = @{ + DisplayName = "DSCGroup" + ID = "12345-12345-12345-12345" + Description = "Microsoft DSC Group" + SecurityEnabled = $True + GroupTypes = @() + MailNickname = "M365DSC" + MemberOf = "DSCMemberOfGroup" + Ensure = "Present" + Credential = $Credential; + } + + + Mock -CommandName New-M365DSCConnection -MockWith { + return "Credential" + } + Mock -CommandName Get-MgGroup -ParameterFilter {$Id -eq "12345-12345-12345-12345" -or $Filter -eq "DisplayName eq 'DSCGroup'"} -MockWith { + return @{ + DisplayName = "DSCGroup" + ID = "12345-12345-12345-12345" + Description = "Microsoft DSC Group" + SecurityEnabled = $True + MailEnabled = $False + MailNickname = "M365DSC" + GroupTypes = @() + } + } + Mock -CommandName Get-MgGroup -ParameterFilter {$Id -eq '67890-67890-67890-67890' -or $Filter -eq "DisplayName -eq 'DSCMemberOfGroup'"} -MockWith { + $returnData = @{ + DisplayName = "DSCMemberOfGroup" + ID = "67890-67890-67890-67890" + Description = "Microsoft DSC MemberOf Group" + SecurityEnabled = $True + GroupTypes = @() + MailEnabled = $False + MailNickname = "M365DSCM" + } + # Set-TargetResource expects object-type of answer to contain 'group' + $returnData.psobject.TypeNames.insert(0, 'Group') + return $returnData + } + } + + It "Should return Values from the Get method" { + Get-TargetResource @testParams + Should -Invoke -CommandName "Get-MgGroup" -Exactly 1 + Should -Invoke -CommandName "Get-MgGroupMemberOf" -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-MgGroupMember' -Exactly 1 + } + } + + Context -Name "The Group Exists but is not assigned to a role. Values are NOT in the desired state" -Fixture { + BeforeAll { + $testParams = @{ + DisplayName = "DSCGroup" + ID = "12345-12345-12345-12345" + Description = "Microsoft DSC Group" + SecurityEnabled = $True + GroupTypes = @() + MailNickname = "M365DSC" + IsAssignableToRole = $true + AssignedToRole = "AADRole" + Ensure = "Present" + Credential = $Credential; + } + + + Mock -CommandName New-M365DSCConnection -MockWith { + return "Credential" + } + + Mock -CommandName Get-MgGroup -MockWith { + return @{ + DisplayName = "DSCGroup" + ID = "12345-12345-12345-12345" + Description = "Microsoft DSC Group" + SecurityEnabled = $True + GroupTypes = @() + MailNickname = "M365DSC" + IsAssignableToRole = $true + Ensure = "Present" + } + } + + Mock -CommandName Get-MgDirectoryRole -Mockwith { + return @{ + DisplayName = "AADRole" + ID = "12345-12345-12345-12345" + } + } + } + + 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 1 + Should -Invoke -CommandName 'Get-MgDirectoryRole' -Exactly 1 + Should -Invoke -CommandName 'New-MgDirectoryRoleMemberByRef' -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 = @{ + DisplayName = "DSCGroup" + ID = "12345-12345-12345-12345" + Description = "Microsoft DSC Group" + SecurityEnabled = $True + GroupTypes = @() + MailNickname = "M365DSC" + IsAssignableToRole = $true + AssignedToRole = @() + Ensure = "Present" + Credential = $Credential; + } + + + Mock -CommandName New-M365DSCConnection -MockWith { + return "Credential" + } + + Mock -CommandName Get-MgGroup -MockWith { + return @{ + DisplayName = "DSCGroup" + ID = "12345-12345-12345-12345" + Description = "Microsoft DSC Group" + SecurityEnabled = $True + GroupTypes = @() + MailNickname = "M365DSC" + IsAssignableToRole = $true + Ensure = "Present" + } + } + Mock -CommandName Get-MgGroupMemberOf -MockWith { + return @{ + AdditionalProperties = @{ + '@odata.type' = "#microsoft.graph.directoryRole" + displayName = "AADRole" + } + } + } + Mock -CommandName Get-MgDirectoryRole -Mockwith { + return @{ + DisplayName = "AADRole" + ID = "12345-12345-12345-12345" + } + } + } + + It "Should return Values from the Get method" { + Get-TargetResource @testParams + Should -Invoke -CommandName "Get-MgGroup" -Exactly 1 + Should -Invoke -CommandName "Get-MgGroupMemberOf" -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 1 + Should -Invoke -CommandName 'Get-MgDirectoryRole' -Exactly 1 + Should -Invoke -CommandName 'Remove-MgDirectoryRoleMemberByRef' -Exactly 1 + } + } + Context -Name "ReverseDSC Tests" -Fixture { BeforeAll { $Global:CurrentModeIsExport = $true From cc3589b7d0e823edf9017a7b84791594d7ca39aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Albeck?= Date: Wed, 28 Sep 2022 13:36:09 +0200 Subject: [PATCH 2/7] fixed missing param in AADGroup unit-tests --- .../Microsoft365DSC/Microsoft365DSC.AADGroup.Tests.ps1 | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADGroup.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADGroup.Tests.ps1 index e0506389d7..a384823fbc 100644 --- a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADGroup.Tests.ps1 +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADGroup.Tests.ps1 @@ -149,8 +149,8 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Mock -CommandName Get-MgGroup -MockWith { return @{ - DisplayName = "DSCGroup" - ID = "12345-12345-12345-12345-12345" + DisplayName = "DSCGroup" + ID = "12345-12345-12345-12345-12345" } } } @@ -160,7 +160,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Should -Invoke -CommandName "Get-MgGroup" -Exactly 1 } - It 'Should return true from the Test method' { + It 'Should return false from the Test method' { Test-TargetResource @testParams | Should -Be $false } @@ -239,7 +239,6 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { ID = "12345-12345-12345-12345" Description = "Microsoft DSC Group" SecurityEnabled = $True - MailEnabled = $False MailNickname = "M365DSC" GroupTypes = @() } @@ -402,7 +401,6 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { ID = "12345-12345-12345-12345" Description = "Microsoft DSC Group" SecurityEnabled = $True - MailEnabled = $False MailNickname = "M365DSC" GroupTypes = @() } @@ -414,7 +412,6 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Description = "Microsoft DSC MemberOf Group" SecurityEnabled = $True GroupTypes = @() - MailEnabled = $False MailNickname = "M365DSCM" } # Set-TargetResource expects object-type of answer to contain 'group' From 0e15e4d55587eb657c200df6079da6ebe843db6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Albeck?= Date: Wed, 28 Sep 2022 13:36:31 +0200 Subject: [PATCH 3/7] fixed typo in schema.mof --- .../DSCResources/MSFT_AADGroup/MSFT_AADGroup.schema.mof | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.schema.mof index 054ff964cc..c6f9b11fa3 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.schema.mof +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.schema.mof @@ -14,7 +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 that htis group is a member of.")] String MemberOf[]; + [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; [Write, Description("Specifies the rule processing state. The acceptable values for this parameter are: On. Process the group rule or Paused. Stop processing the group rule."), ValueMap{"On","Paused"}, Values{"On","Paused"}] String MembershipRuleProcessingState; From 31b553b431e1c2760df5cfbf5f313a2c8d60fd76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Albeck?= Date: Wed, 28 Sep 2022 13:37:15 +0200 Subject: [PATCH 4/7] removed param -All from calls to Get-MgGroup* --- .../DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 index b7e3a48075..96ba7c9902 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 @@ -158,7 +158,7 @@ function Get-TargetResource Write-Verbose -Message 'Found existing AzureAD Group' # Owners - [Array]$owners = Get-MgGroupOwner -GroupId $Group.Id -All:$true + [Array]$owners = Get-MgGroupOwner -GroupId $Group.Id $OwnersValues = @() foreach ($owner in $owners) { @@ -172,7 +172,7 @@ function Get-TargetResource if ($Group.MembershipRuleProcessingState -ne 'On') { # Members - [Array]$members = Get-MgGroupMember -GroupId $Group.Id -All:$true + [Array]$members = Get-MgGroupMember -GroupId $Group.Id $MembersValues = @() foreach ($member in $members) { @@ -184,7 +184,7 @@ function Get-TargetResource } # MemberOf - [Array]$memberOf = Get-MgGroupMemberOf -GroupId $Group.Id -All:$true # result also used for/by AssignedToRole + [Array]$memberOf = Get-MgGroupMemberOf -GroupId $Group.Id # result also used for/by AssignedToRole $MemberOfValues = @() # Note: only process security-groups that this group is a member of and not directory roles (if any) foreach ($member in ($memberOf | Where-Object -FilterScript {$_.AdditionalProperties.'@odata.type' -eq "#microsoft.graph.group"})) @@ -1017,7 +1017,7 @@ function Export-TargetResource try { - [array] $groups = Get-MgGroup -Filter $Filter -All:$true -ErrorAction Stop + [array] $groups = Get-MgGroup -Filter $Filter -ErrorAction Stop $i = 1 $dscContent = '' Write-Host "`r`n" -NoNewline From 8426a8946ec35aa44c00072edbf8457977dc92a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Albeck?= Date: Wed, 28 Sep 2022 13:37:26 +0200 Subject: [PATCH 5/7] updated changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c67e666e2..1ddfd1730a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ # UNRELEASED +* AADGroup + * Added properties MemberOf and AssignedToRole + Implements [#2301](https://github.com/microsoft/Microsoft365DSC/issues/2301) * EXOMailContact * Initial Release. * EXOMailTips From 814864f622c3dba5c73e797315d0f501acf014e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Albeck?= Date: Thu, 29 Sep 2022 10:12:14 +0200 Subject: [PATCH 6/7] re-add param -All in Get-MgGroup* cmdlet-calls --- .../DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 index 96ba7c9902..ecc98e5952 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 @@ -158,7 +158,7 @@ function Get-TargetResource Write-Verbose -Message 'Found existing AzureAD Group' # Owners - [Array]$owners = Get-MgGroupOwner -GroupId $Group.Id + [Array]$owners = Get-MgGroupOwner -GroupId $Group.Id -All $OwnersValues = @() foreach ($owner in $owners) { @@ -172,7 +172,7 @@ function Get-TargetResource if ($Group.MembershipRuleProcessingState -ne 'On') { # Members - [Array]$members = Get-MgGroupMember -GroupId $Group.Id + [Array]$members = Get-MgGroupMember -GroupId $Group.Id -All $MembersValues = @() foreach ($member in $members) { @@ -184,7 +184,7 @@ function Get-TargetResource } # MemberOf - [Array]$memberOf = Get-MgGroupMemberOf -GroupId $Group.Id # result also used for/by AssignedToRole + [Array]$memberOf = Get-MgGroupMemberOf -GroupId $Group.Id -All # result also used for/by AssignedToRole $MemberOfValues = @() # Note: only process security-groups that this group is a member of and not directory roles (if any) foreach ($member in ($memberOf | Where-Object -FilterScript {$_.AdditionalProperties.'@odata.type' -eq "#microsoft.graph.group"})) @@ -1017,7 +1017,7 @@ function Export-TargetResource try { - [array] $groups = Get-MgGroup -Filter $Filter -ErrorAction Stop + [array] $groups = Get-MgGroup -Filter $Filter -All -ErrorAction Stop $i = 1 $dscContent = '' Write-Host "`r`n" -NoNewline From c12c021f435a7efc642a04a34d050d2001070d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Albeck?= Date: Thu, 29 Sep 2022 10:18:11 +0200 Subject: [PATCH 7/7] change -All to -All:$true --- .../DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 index ecc98e5952..2085493bfd 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 @@ -158,7 +158,7 @@ function Get-TargetResource Write-Verbose -Message 'Found existing AzureAD Group' # Owners - [Array]$owners = Get-MgGroupOwner -GroupId $Group.Id -All + [Array]$owners = Get-MgGroupOwner -GroupId $Group.Id -All:$true $OwnersValues = @() foreach ($owner in $owners) { @@ -172,7 +172,7 @@ function Get-TargetResource if ($Group.MembershipRuleProcessingState -ne 'On') { # Members - [Array]$members = Get-MgGroupMember -GroupId $Group.Id -All + [Array]$members = Get-MgGroupMember -GroupId $Group.Id -All:$true $MembersValues = @() foreach ($member in $members) { @@ -1017,7 +1017,7 @@ function Export-TargetResource try { - [array] $groups = Get-MgGroup -Filter $Filter -All -ErrorAction Stop + [array] $groups = Get-MgGroup -Filter $Filter -All:$true -ErrorAction Stop $i = 1 $dscContent = '' Write-Host "`r`n" -NoNewline