diff --git a/CHANGELOG.md b/CHANGELOG.md index e3a375430..b0b2571b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ For older change log history see the [historic changelog](HISTORIC_CHANGELOG.md) ## [Unreleased] +### Changed + +- ADGroup + - Refactored Module. + - Refactored Unit and Integration Tests. + ### Added - ADGroup diff --git a/source/DSCResources/MSFT_ADGroup/MSFT_ADGroup.psm1 b/source/DSCResources/MSFT_ADGroup/MSFT_ADGroup.psm1 index 66d42a955..cdc225335 100644 --- a/source/DSCResources/MSFT_ADGroup/MSFT_ADGroup.psm1 +++ b/source/DSCResources/MSFT_ADGroup/MSFT_ADGroup.psm1 @@ -16,52 +16,26 @@ $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' .PARAMETER GroupName Name of the Active Directory group. - .PARAMETER GroupScope - Active Directory group scope. Default value is 'Global'. - - .PARAMETER Category - Active Directory group category. Default value is 'Security'. - - .PARAMETER Path - Location of the group within Active Directory expressed as a Distinguished Name. - - .PARAMETER Ensure - Specifies if this Active Directory group should be present or absent. - Default value is 'Present'. - - .PARAMETER Description - Description of the Active Directory group. - - .PARAMETER DisplayName - Display name of the Active Directory group. - .PARAMETER Credential - Credentials used to enact the change upon. + The credential to be used to perform the operation on Active Directory. .PARAMETER DomainController Active Directory domain controller to enact the change upon. - .PARAMETER Members - Active Directory group membership should match membership exactly. - - .PARAMETER MembersToInclude - Active Directory group should include these members. - - .PARAMETER MembersToExclude - Active Directory group should NOT include these members. - - .PARAMETER MembershipAttribute + .PARAMETER MembershipAttribute Active Directory attribute used to perform membership operations. Default value is 'SamAccountName'. - .PARAMETER ManagedBy - Active Directory managed by attribute specified as a DistinguishedName. - - .PARAMETER Notes - Active Directory group notes field. - - .PARAMETER RestoreFromRecycleBin - Try to restore the group from the recycle bin before creating a new one. + .NOTES + Used Functions: + Name | Module + ------------------------------|-------------------------- + Get-ADGroup | ActiveDirectory + Get-ADGroupMember | ActiveDirectory + Assert-Module | ActiveDirectoryDsc.Common + Get-ADCommonParameters | ActiveDirectoryDsc.Common + Get-ADObjectParentDN | ActiveDirectoryDsc.Common + New-InvalidOperationException | ActiveDirectoryDsc.Common #> function Get-TargetResource { @@ -74,36 +48,6 @@ function Get-TargetResource [System.String] $GroupName, - [Parameter()] - [ValidateSet('DomainLocal', 'Global', 'Universal')] - [System.String] - $GroupScope = 'Global', - - [Parameter()] - [ValidateSet('Security', 'Distribution')] - [System.String] - $Category = 'Security', - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $Path, - - [Parameter()] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure = 'Present', - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $Description, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $DisplayName, - [Parameter()] [ValidateNotNull()] [System.Management.Automation.PSCredential] @@ -115,160 +59,142 @@ function Get-TargetResource [System.String] $DomainController, - [Parameter()] - [System.String[]] - $Members, - - [Parameter()] - [System.String[]] - $MembersToInclude, - - [Parameter()] - [System.String[]] - $MembersToExclude, - [Parameter()] [ValidateSet('SamAccountName', 'DistinguishedName', 'SID', 'ObjectGUID')] [System.String] - $MembershipAttribute = 'SamAccountName', - - # This must be the user's DN - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $ManagedBy, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $Notes, - - [Parameter()] - [ValidateNotNull()] - [System.Boolean] - $RestoreFromRecycleBin + $MembershipAttribute = 'SamAccountName' ) Assert-Module -ModuleName 'ActiveDirectory' - $getTargetResourceReturnValue = @{ - Ensure = 'Absent' - GroupName = $GroupName - GroupScope = $null - Category = $null - Path = $null - Description = $null - DisplayName = $null - Members = @() - MembersToInclude = $MembersToInclude - MembersToExclude = $MembersToExclude - MembershipAttribute = $MembershipAttribute - ManagedBy = $null - Notes = $null - DistinguishedName = $null - } - $commonParameters = Get-ADCommonParameters @PSBoundParameters + Write-Verbose -Message ($script:localizedData.RetrievingGroup -f $GroupName) + + $getADGroupProperties = ('Name', 'GroupScope', 'GroupCategory', 'DistinguishedName', 'Description', 'DisplayName', + 'ManagedBy', 'Members', 'Info') + try { - $adGroup = Get-ADGroup @commonParameters -Properties @( - 'Name', - 'GroupScope', - 'GroupCategory', - 'DistinguishedName', - 'Description', - 'DisplayName', - 'ManagedBy', - 'Members', - 'Info' - ) + $adGroup = Get-ADGroup @commonParameters -Properties $getADGroupProperties + } + catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] + { + $adGroup = $null + } + catch + { + $errorMessage = $script:localizedData.RetrievingGroupError -f $GroupName + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + if ($adGroup) + { + Write-Verbose -Message ($script:localizedData.GroupIsPresent -f $GroupName) Write-Verbose -Message ($script:localizedData.RetrievingGroupMembers -f $MembershipAttribute) - if ($adGroup) + try { - try - { - [System.Array] $adGroupMembers = (Get-ADGroupMember @commonParameters).$MembershipAttribute - } - catch - { - # This FullyQualifiedErrorId is indicative of a failure to retrieve members with Get-ADGroupMember - # for a one-way trust - $oneWayTrustFullyQualifiedErrorId = ` - 'ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.GetADGroupMember' + [System.Array] $adGroupMembers = (Get-ADGroupMember @commonParameters).$MembershipAttribute + } + catch + { + # This FullyQualifiedErrorId is indicative of a failure to retrieve members with Get-ADGroupMember + # for a one-way trust + $oneWayTrustFullyQualifiedErrorId = ` + 'ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.GetADGroupMember' - if ($_.FullyQualifiedErrorId -eq $oneWayTrustFullyQualifiedErrorId) + if ($_.FullyQualifiedErrorId -eq $oneWayTrustFullyQualifiedErrorId) + { + # Get-ADGroupMember returns property name 'SID' while Get-ADObject returns property name 'ObjectSID' + if ($MembershipAttribute -eq 'SID') { - # Get-ADGroupMember returns property name 'SID' while Get-ADObject returns property name 'ObjectSID' - if ($MembershipAttribute -eq 'SID') - { - $selectProperty = 'ObjectSID' - } - else - { - $selectProperty = $MembershipAttribute - } + $selectProperty = 'ObjectSID' + } + else + { + $selectProperty = $MembershipAttribute + } - # Use the same results from Get-ADCommonParameters but remove the Identity - # for usage with Get-ADObject - $getADObjectParameters = $commonParameters.Clone() - $getADObjectParameters.Remove('Identity') + # Use the same results from Get-ADCommonParameters but remove the Identity + # for usage with Get-ADObject + $getADObjectParameters = $commonParameters.Clone() + $getADObjectParameters.Remove('Identity') - # Retrieve the current list of members, returning the specified membership attribute - [System.Array] $adGroupMembers = $adGroup.Members | ForEach-Object -Process { - # Adding a Filter and additional Properties for the AD object retrieval - $getADObjectParameters['Filter'] = "DistinguishedName -eq '$($_)'" - $getADObjectParameters['Properties'] = @( - 'SamAccountName', - 'ObjectSID' - ) + # Retrieve the current list of members, returning the specified membership attribute + [System.Array] $adGroupMembers = $adGroup.Members | ForEach-Object -Process { + # Adding a Filter and additional Properties for the AD object retrieval + $getADObjectParameters['Filter'] = "DistinguishedName -eq '$($_)'" + $getADObjectParameters['Properties'] = @( + 'SamAccountName', + 'ObjectSID' + ) - $adObject = Get-ADObject @getADObjectParameters + $adObject = Get-ADObject @getADObjectParameters - # Perform SID translation to a readable name as the SamAccountName if the member is - # of objectClass "foreignSecurityPrincipal" - $classMatchForResolve = $adObject.objectClass -eq 'foreignSecurityPrincipal' - $attributeMatchForResolve = $MembershipAttribute -eq 'SamAccountName' + # Perform SID translation to a readable name as the SamAccountName if the member is + # of objectClass "foreignSecurityPrincipal" + $classMatchForResolve = $adObject.objectClass -eq 'foreignSecurityPrincipal' + $attributeMatchForResolve = $MembershipAttribute -eq 'SamAccountName' - if ($classMatchForResolve -and $attributeMatchForResolve) - { - Resolve-SamAccountName -ObjectSid $adObject.objectSid - } - else - { - $adObject.$selectProperty - } + if ($classMatchForResolve -and $attributeMatchForResolve) + { + Resolve-SamAccountName -ObjectSid $adObject.objectSid + } + else + { + $adObject.$selectProperty } - } - else - { - $errorMessage = $script:localizedData.RetrievingGroupMembersError -f $GroupName - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ } } + else + { + $errorMessage = $script:localizedData.RetrievingGroupMembersError -f $GroupName + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } - $getTargetResourceReturnValue['Ensure'] = 'Present' - $getTargetResourceReturnValue['GroupName'] = $adGroup.Name - $getTargetResourceReturnValue['GroupScope'] = $adGroup.GroupScope - $getTargetResourceReturnValue['Category'] = $adGroup.GroupCategory - $getTargetResourceReturnValue['DistinguishedName'] = $adGroup.DistinguishedName - $getTargetResourceReturnValue['Path'] = Get-ADObjectParentDN -DN $adGroup.DistinguishedName - $getTargetResourceReturnValue['Description'] = $adGroup.Description - $getTargetResourceReturnValue['DisplayName'] = $adGroup.DisplayName - $getTargetResourceReturnValue['Members'] = $adGroupMembers - $getTargetResourceReturnValue['ManagedBy'] = $adGroup.ManagedBy - $getTargetResourceReturnValue['Notes'] = $adGroup.Info + $targetResource = @{ + Ensure = 'Present' + GroupName = $adGroup.Name + GroupScope = $adGroup.GroupScope + Category = $adGroup.GroupCategory + DistinguishedName = $adGroup.DistinguishedName + Path = Get-ADObjectParentDN -DN $adGroup.DistinguishedName + Description = $adGroup.Description + DisplayName = $adGroup.DisplayName + Members = $adGroupMembers + MembersToInclude = $null + MembersToExclude = $null + MembershipAttribute = $MembershipAttribute + ManagedBy = $adGroup.ManagedBy + Notes = $adGroup.Info } } - catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] + else { - Write-Verbose -Message ($script:localizedData.GroupNotFound -f $GroupName) + Write-Verbose -Message ($script:localizedData.GroupIsAbsent -f $GroupName) + + $targetResource = @{ + Ensure = 'Absent' + GroupName = $GroupName + GroupScope = $null + Category = $null + DistinguishedName = $null + Path = $null + Description = $null + DisplayName = $null + Members = @() + MembersToInclude = $null + MembersToExclude = $null + MembershipAttribute = $MembershipAttribute + ManagedBy = $null + Notes = $null + } } - return $getTargetResourceReturnValue -} #end function Get-TargetResource + return $targetResource +} <# .SYNOPSIS @@ -297,7 +223,7 @@ function Get-TargetResource Display name of the Active Directory group. .PARAMETER Credential - Credentials used to enact the change upon. + The credential to be used to perform the operation on Active Directory. .PARAMETER DomainController Active Directory domain controller to enact the change upon. @@ -323,6 +249,14 @@ function Get-TargetResource .PARAMETER RestoreFromRecycleBin Try to restore the group from the recycle bin before creating a new one. + + .NOTES + Used Functions: + Name | Module + ------------------------------------------|-------------------------- + Assert-MemberParameters | ActiveDirectoryDsc.Common + Test-Members | ActiveDirectoryDsc.Common + Compare-ResourcePropertyState | ActiveDirectoryDsc.Common #> function Test-TargetResource { @@ -410,10 +344,8 @@ function Test-TargetResource $RestoreFromRecycleBin ) - # Validate parameters before we even attempt to retrieve anything $assertMemberParameters = @{} - # Members parameter should always be tested to enforce an empty group (issue #189) if ($PSBoundParameters.ContainsKey('Members')) { $assertMemberParameters['Members'] = $Members @@ -431,71 +363,94 @@ function Test-TargetResource Assert-MemberParameters @assertMemberParameters - $targetResource = Get-TargetResource @PSBoundParameters - - $targetResourceInCompliance = $true + [HashTable] $parameters = $PSBoundParameters + $parameters['MembershipAttribute'] = $MembershipAttribute - if ($PSBoundParameters.ContainsKey('GroupScope') -and $targetResource.GroupScope -ne $GroupScope) - { - Write-Verbose -Message ($script:localizedData.NotDesiredPropertyState -f 'GroupScope', $GroupScope, $targetResource.GroupScope) - $targetResourceInCompliance = $false + $getTargetResourceParameters = @{ + GroupName = $GroupName + DomainController = $DomainController + Credential = $Credential + MembershipAttribute = $MembershipAttribute } - if ($PSBoundParameters.ContainsKey('Category') -and $targetResource.Category -ne $Category) - { - Write-Verbose -Message ($script:localizedData.NotDesiredPropertyState -f 'Category', $Category, $targetResource.Category) - $targetResourceInCompliance = $false - } + # Remove parameters that have not been specified + @($getTargetResourceParameters.Keys) | + ForEach-Object { + if (-not $parameters.ContainsKey($_)) + { + $getTargetResourceParameters.Remove($_) + } + } - if ($Path -and ($targetResource.Path -ne $Path)) - { - Write-Verbose -Message ($script:localizedData.NotDesiredPropertyState -f 'Path', $Path, $targetResource.Path) - $targetResourceInCompliance = $false - } + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters - if ($Description -and ($targetResource.Description -ne $Description)) + if ($getTargetResourceResult.Ensure -eq 'Present') { - Write-Verbose -Message ($script:localizedData.NotDesiredPropertyState -f 'Description', $Description, $targetResource.Description) - $targetResourceInCompliance = $false - } + # Resource exists + if ($Ensure -eq 'Present') + { + # Resource should exist - if ($DisplayName -and ($targetResource.DisplayName -ne $DisplayName)) - { - Write-Verbose -Message ($script:localizedData.NotDesiredPropertyState -f 'DisplayName', $DisplayName, $targetResource.DisplayName) - $targetResourceInCompliance = $false - } + # Test group members match passed membership parameters + if (-not (Test-Members @assertMemberParameters -ExistingMembers $getTargetResourceResult.Members ` + -Verbose:$VerbosePreference)) + { + Write-Verbose -Message $script:localizedData.GroupMembershipNotDesiredState + $membersInDesiredState = $false + } + else + { + $membersInDesiredState = $true + } - if ($ManagedBy -and ($targetResource.ManagedBy -ne $ManagedBy)) - { - Write-Verbose -Message ($script:localizedData.NotDesiredPropertyState -f 'ManagedBy', $ManagedBy, $targetResource.ManagedBy) - $targetResourceInCompliance = $false - } + $ignoreProperties = @('DomainController', 'Credential', 'MembershipAttribute', 'Members', + 'MembersToInclude', 'MembersToExclude') - if ($Notes -and ($targetResource.Notes -ne $Notes)) - { - Write-Verbose -Message ($script:localizedData.NotDesiredPropertyState -f 'Notes', $Notes, $targetResource.Notes) - $targetResourceInCompliance = $false - } + $propertiesNotInDesiredState = (Compare-ResourcePropertyState -CurrentValues $getTargetResourceResult ` + -DesiredValues $parameters -IgnoreProperties $ignoreProperties -Verbose:$VerbosePreference | + Where-Object -Property InDesiredState -eq $false) - # Test group members match passed membership parameters - if (-not (Test-Members @assertMemberParameters -ExistingMembers $targetResource.Members)) - { - Write-Verbose -Message $script:localizedData.GroupMembershipNotDesiredState - $targetResourceInCompliance = $false + if ($propertiesNotInDesiredState -or $membersInDesiredState -eq $false) + { + $inDesiredState = $false + } + else + { + # Resource is in desired state + Write-Verbose -Message ($script:localizedData.ResourceInDesiredStateMessage -f $GroupName) + $inDesiredState = $true + } + } + else + { + # Resource should not exist + Write-Verbose -Message ($script:localizedData.ResourceExistsButShouldNotMessage -f $GroupName) + $inDesiredState = $false + } } - - if ($targetResource.Ensure -ne $Ensure) + else { - Write-Verbose -Message ($script:localizedData.NotDesiredPropertyState -f 'Ensure', $Ensure, $targetResource.Ensure) - $targetResourceInCompliance = $false + # Resource does not exist + if ($Ensure -eq 'Present') + { + # Resource should exist + Write-Verbose -Message ($script:localizedData.ResourceDoesNotExistButShouldMessage -f $GroupName) + $inDesiredState = $false + } + else + { + # Resource should not exist + Write-Verbose -Message ($script:localizedData.ResourceInDesiredStateMessage -f $GroupName) + $inDesiredState = $true + } } - return $targetResourceInCompliance -} #end function Test-TargetResource + return $inDesiredState +} <# .SYNOPSIS - Creates, removes or modifies the Active Directory group. + Sets the state of an Active Directory group. .PARAMETER GroupName Name of the Active Directory group. @@ -520,7 +475,7 @@ function Test-TargetResource Display name of the Active Directory group. .PARAMETER Credential - Credentials used to enact the change upon. + The credential to be used to perform the operation on Active Directory. .PARAMETER DomainController Active Directory domain controller to enact the change upon. @@ -546,6 +501,22 @@ function Test-TargetResource .PARAMETER RestoreFromRecycleBin Try to restore the group from the recycle bin before creating a new one. + + .NOTES + Used Functions: + Name | Module + ------------------------------------------|-------------------------- + Assert-MemberParameters | ActiveDirectoryDsc.Common + Get-ADCommonParameters | ActiveDirectoryDsc.Common + Compare-ResourcePropertyState | ActiveDirectoryDsc.Common + New-InvalidOperationException | ActiveDirectoryDsc.Common + Remove-DuplicateMembers | ActiveDirectoryDsc.Common + Set-ADCommonGroupMember | ActiveDirectoryDsc.Common + Restore-ADCommonObject | ActiveDirectoryDsc.Common + Set-ADGroup | ActiveDirectory + Move-ADObject | ActiveDirectory + New-ADGroup | ActiveDirectory + Remove-ADGroup | ActiveDirectory #> function Set-TargetResource { @@ -633,8 +604,6 @@ function Set-TargetResource ) - Assert-Module -ModuleName 'ActiveDirectory' - $assertMemberParameters = @{} # Members parameter should always be added to enforce an empty group (issue #189) @@ -655,258 +624,275 @@ function Set-TargetResource Assert-MemberParameters @assertMemberParameters - if ($MembershipAttribute -eq 'DistinguishedName') - { - $allMembers = $Members + $MembersToInclude + $MembersToExclude + [HashTable] $parameters = $PSBoundParameters + $parameters['MembershipAttribute'] = $MembershipAttribute - $groupMemberDomains = @() + $getTargetResourceParameters = @{ + GroupName = $GroupName + DomainController = $DomainController + Credential = $Credential + MembershipAttribute = $MembershipAttribute + } - foreach ($member in $allMembers) - { - $groupMemberDomains += Get-ADDomainNameFromDistinguishedName -DistinguishedName $member + # Remove parameters that have not been specified + @($getTargetResourceParameters.Keys) | + ForEach-Object { + if (-not $parameters.ContainsKey($_)) + { + $getTargetResourceParameters.Remove($_) + } } - $uniqueGroupMemberDomainCount = $groupMemberDomains | - Select-Object -Unique - - $GroupMemberDomainCount = $uniqueGroupMemberDomainCount.count - - if ($GroupMemberDomainCount -gt 1 -or ($groupMemberDomains -ine (Get-DomainName)).Count -gt 0) - { - Write-Verbose -Message ($script:localizedData.GroupMembershipMultipleDomains -f $GroupMemberDomainCount) - } - } + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters $commonParameters = Get-ADCommonParameters @PSBoundParameters - $getTargetResourceResult = Get-TargetResource @PSBoundParameters - - if ($getTargetResourceResult.Ensure -eq 'Present') + if ($Ensure -eq 'Present') { - if ($Ensure -eq 'Present') + # Resource should be present + if ($getTargetResourceResult.Ensure -eq 'Present') { - $setADGroupParams = $commonParameters.Clone() - $setADGroupParams['Identity'] = $getTargetResourceResult.DistinguishedName - - # Update existing group properties - if ($PSBoundParameters.ContainsKey('Category') -and $Category -ne $getTargetResourceResult.Category) - { - Write-Verbose -Message ($script:localizedData.UpdatingGroupProperty -f 'Category', $Category) - - $setADGroupParams['GroupCategory'] = $Category - } + # Resource is present + $moveAdGroupRequired = $false - if ($PSBoundParameters.ContainsKey('GroupScope') -and $GroupScope -ne $getTargetResourceResult.GroupScope) - { - # Cannot change DomainLocal to Global or vice versa directly. Need to change them to a Universal group first! - Set-ADGroup -Identity $getTargetResourceResult.DistinguishedName -GroupScope 'Universal' -ErrorAction 'Stop' - - Write-Verbose -Message ($script:localizedData.UpdatingGroupProperty -f 'GroupScope', $GroupScope) - - $setADGroupParams['GroupScope'] = $GroupScope - } - - if ($Description -and ($Description -ne $getTargetResourceResult.Description)) - { - Write-Verbose -Message ($script:localizedData.UpdatingGroupProperty -f 'Description', $Description) + $ignoreProperties = @('DomainController', 'Credential', 'MembershipAttribute', 'MembersToInclude', + 'MembersToExclude') + $propertiesNotInDesiredState = Compare-ResourcePropertyState -CurrentValues $getTargetResourceResult ` + -DesiredValues $parameters -IgnoreProperties $ignoreProperties -Verbose:$VerbosePreference | + Where-Object -Property InDesiredState -eq $false - $setADGroupParams['Description'] = $Description - } - - if ($DisplayName -and ($DisplayName -ne $getTargetResourceResult.DisplayName)) + if ($propertiesNotInDesiredState) { - Write-Verbose -Message ($script:localizedData.UpdatingGroupProperty -f 'DisplayName', $DisplayName) - - $setADGroupParams['DisplayName'] = $DisplayName - } + $setADGroupParameters = $commonParameters.Clone() + $setADGroupParameters['Identity'] = $getTargetResourceResult.DistinguishedName - if ($ManagedBy -and ($ManagedBy -ne $getTargetResourceResult.ManagedBy)) - { - Write-Verbose -Message ($script:localizedData.UpdatingGroupProperty -f 'ManagedBy', $ManagedBy) + $SetAdGroupRequired = $false - $setADGroupParams['ManagedBy'] = $ManagedBy - } + foreach ($property in $propertiesNotInDesiredState) + { + if ($property.ParameterName -eq 'Path') + { + # The path has changed, so the account needs moving, but not until after any other changes + $moveAdGroupRequired = $true + } + elseif ($property.ParameterName -eq 'Category') + { + $setAdGroupRequired = $true - if ($Notes -and ($Notes -ne $getTargetResourceResult.Notes)) - { - Write-Verbose -Message ($script:localizedData.UpdatingGroupProperty -f 'Notes', $Notes) + Write-Verbose -Message ($script:localizedData.UpdatingResourceProperty -f + $GroupName, $property.ParameterName, ($property.Expected -join ', ')) - $setADGroupParams['Replace'] = @{ - Info = $Notes - } - } + $setADGroupParameters['GroupCategory'] = $property.Expected + } + elseif ($property.ParameterName -eq 'GroupScope') + { + if ($GroupScope -ne 'Universal' -and $getTargetResourceResult.GroupScope -ne 'Universal') + { + # Cannot change DomainLocal <-> Global directly, so need to change to a Universal group first + Write-Verbose -Message ($script:localizedData.UpdatingResourceProperty -f + $GroupName, $property.ParameterName, 'Universal') - Write-Verbose -Message ($script:localizedData.UpdatingGroup -f $GroupName) + $setADGroupUniversalGroupScopeParameters = $commonParameters.Clone() + $setADGroupUniversalGroupScopeParameters['Identity'] = $getTargetResourceResult.DistinguishedName + $setADGroupUniversalGroupScopeParameters['GroupScope'] = 'Universal' - Set-ADGroup @setADGroupParams -ErrorAction 'Stop' + try + { + Set-ADGroup @setADGroupUniversalGroupScopeParameters + } + catch + { + $errorMessage = ($script:localizedData.SettingGroupError -f $GroupName) + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } - $groupParentDistinguishedName = Get-ADObjectParentDN -DN $getTargetResourceResult.DistinguishedName + $setAdGroupRequired = $true - # Move group if the path is not correct - if ($Path -and $Path -ne $groupParentDistinguishedName) - { - Write-Verbose -Message ($script:localizedData.MovingGroup -f $GroupName, $Path) + Write-Verbose -Message ($script:localizedData.UpdatingResourceProperty -f + $GroupName, $property.ParameterName, $property.Expected) - $moveADObjectParams = $commonParameters.Clone() - $moveADObjectParams['Identity'] = $getTargetResourceResult.DistinguishedName - $moveADObjectParams['TargetPath'] = $Path - $moveADObjectParams['ErrorAction'] = 'Stop' + $SetAdGroupParameters[$property.ParameterName] = $property.Expected + } + elseif ($property.ParameterName -eq 'Notes') + { + $setAdGroupRequired = $true - Move-ADObject @moveADObjectParams - } + Write-Verbose -Message ($script:localizedData.UpdatingResourceProperty -f + $GroupName, $property.ParameterName, ($property.Expected -join ', ')) - if ($assertMemberParameters.Count -gt 0) - { - Write-Verbose -Message ($script:localizedData.RetrievingGroupMembers -f $MembershipAttribute) + $setADGroupParameters['Replace'] = @{ + Info = $property.Expected + } + } + elseif ($property.ParameterName -eq 'Members') + { + $Members = Remove-DuplicateMembers -Members $Members - try - { - $adGroupMembers = (Get-ADGroupMember @commonParameters).$MembershipAttribute - } - catch - { - # This FullyQualifiedErrorId is indicative of a failure to retrieve members with Get-ADGroupMember - # for a one-way trust - $oneWayTrustFullyQualifiedErrorId = ` - 'ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.GetADGroupMember' + if (-not [System.String]::IsNullOrEmpty($property.Actual) -and + -not [System.String]::IsNullOrEmpty($property.Expected)) + { + $compareResult = Compare-Object -ReferenceObject $property.Actual ` + -DifferenceObject $property.Expected - if ($_.FullyQualifiedErrorId -eq $oneWayTrustFullyQualifiedErrorId) - { - # Get-ADGroupMember returns property name 'SID' while Get-ADObject - # returns property name 'ObjectSID' - if ($MembershipAttribute -eq 'SID') + $membersToAdd = ($compareResult | + Where-Object -Property SideIndicator -eq '=>').InputObject + $membersToRemove = ($compareResult | + Where-Object -Property SideIndicator -eq '<=').InputObject + } + elseif ([System.String]::IsNullOrEmpty($property.Expected)) { - $selectProperty = 'ObjectSID' + $membersToRemove = $property.Actual + $membersToAdd = $null } else { - $selectProperty = $MembershipAttribute + $membersToAdd = $property.Expected + $membersToRemove = $null } - # Use the same results from Get-ADCommonParameters but remove the Identity - # for usage with Get-ADObject - $getADObjectParameters = $commonParameters.Clone() - $getADObjectParameters.Remove('Identity') - - $adGroupMemberDNs = (Get-ADGroup @commonParameters -Properties Members).Members - - # Retrieve the current list of members, returning the specified membership attribute - $adGroupMembers = $adGroupMemberDNs | ForEach-Object -Process { - # Adding a Filter and additional Properties for the AD object retrieval - $getADObjectParameters['Filter'] = "DistinguishedName -eq '$($_)'" - $getADObjectParameters['Properties'] = @( - 'SamAccountName', - 'ObjectSID' - ) - - $adObject = Get-ADObject @getADObjectParameters - - # Perform SID translation to a readable name as the SamAccountName if the member is - # of objectClass "foreignSecurityPrincipal" - $classMatchForResolve = $adObject.objectClass -eq 'foreignSecurityPrincipal' - $attributeMatchForResolve = $MembershipAttribute -eq 'SamAccountName' + if (-not [System.String]::IsNullOrEmpty($membersToAdd)) + { + Write-Verbose -Message ($script:localizedData.AddingGroupMembers -f + ($MembersToAdd -join ', '), $GroupName) - if ($classMatchForResolve -and $attributeMatchForResolve) - { - Resolve-SamAccountName -ObjectSid $adObject.objectSid - } - else - { - $adObject.$selectProperty + $setADCommonGroupMemberParms = @{ + Members = $MembersToAdd + MembershipAttribute = $MembershipAttribute + Parameters = $commonParameters + Action = 'Add' } + Set-ADCommonGroupMember @setADCommonGroupMemberParms } - } - else - { - $errorMessage = $script:localizedData.RetrievingGroupMembersError -f $GroupName - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ - } - } - - $assertMemberParameters['ExistingMembers'] = $adGroupMembers - - # Return $false if the members mismatch. - if (-not (Test-Members @assertMemberParameters)) - { - # Members parameter should always be enforce if it is bound (issue #189) - if ($PSBoundParameters.ContainsKey('Members')) - { - # Remove all existing first and add explicit members - $Members = Remove-DuplicateMembers -Members $Members - # We can only remove members if there are members already in the group! - if ($adGroupMembers.Count -gt 0) + if (-not [System.String]::IsNullOrEmpty($membersToRemove)) { - Write-Verbose -Message ($script:localizedData.RemovingGroupMembers -f $adGroupMembers.Count, $GroupName) + Write-Verbose -Message ($script:localizedData.RemovingGroupMembers -f + ($MembersToRemove -join ', '), $GroupName) $setADCommonGroupMemberParms = @{ - Members = $adGroupMembers + Members = $MembersToRemove MembershipAttribute = $MembershipAttribute Parameters = $commonParameters Action = 'Remove' } Set-ADCommonGroupMember @setADCommonGroupMemberParms } + } + else + { + $setAdGroupRequired = $true - Write-Verbose -Message ($script:localizedData.AddingGroupMembers -f $Members.Count, $GroupName) + Write-Verbose -Message ($script:localizedData.UpdatingResourceProperty -f + $GroupName, $property.ParameterName, ($property.Expected -join ', ')) - $setADCommonGroupMemberParms = @{ - Members = $Members - MembershipAttribute = $MembershipAttribute - Parameters = $commonParameters - Action = 'Add' - } - Set-ADCommonGroupMember @setADCommonGroupMemberParms + $SetAdGroupParameters[$property.ParameterName] = $property.Expected } + } - if ($PSBoundParameters.ContainsKey('MembersToInclude') -and -not [System.String]::IsNullOrEmpty($MembersToInclude)) + if ($setAdGroupRequired) + { + try { - $MembersToInclude = Remove-DuplicateMembers -Members $MembersToInclude + Set-ADGroup @setADGroupParameters + } + catch + { + $errorMessage = ($script:localizedData.SettingGroupError -f $GroupName) + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } + } - Write-Verbose -Message ($script:localizedData.AddingGroupMembers -f $MembersToInclude.Count, $GroupName) + if ($PSBoundParameters.ContainsKey('MembersToInclude') -and + -not [System.String]::IsNullOrEmpty($MembersToInclude)) + { + $MembersToInclude = Remove-DuplicateMembers -Members $MembersToInclude - $setADCommonGroupMemberParms = @{ - Members = $MembersToInclude - MembershipAttribute = $MembershipAttribute - Parameters = $commonParameters - Action = 'Add' - } - Set-ADCommonGroupMember @setADCommonGroupMemberParms + if (-not [System.String]::IsNullOrEmpty($getTargetResourceResult.Members)) + { + $compareResult = Compare-Object -ReferenceObject $getTargetResourceResult.Members ` + -DifferenceObject $MembersToInclude + + $membersToAdd = ($compareResult | + Where-Object -Property SideIndicator -eq '=>').InputObject + } + else + { + $membersToAdd = $MembersToInclude + } + + if (-not [System.String]::IsNullOrEmpty($membersToAdd)) + { + Write-Verbose -Message ($script:localizedData.AddingGroupMembers -f + ($MembersToAdd -join ', '), $GroupName) + + $setADCommonGroupMemberParms = @{ + Members = $MembersToAdd + MembershipAttribute = $MembershipAttribute + Parameters = $commonParameters + Action = 'Add' } + Set-ADCommonGroupMember @setADCommonGroupMemberParms + } + } - if ($PSBoundParameters.ContainsKey('MembersToExclude') -and -not [System.String]::IsNullOrEmpty($MembersToExclude)) - { - $MembersToExclude = Remove-DuplicateMembers -Members $MembersToExclude + if ($PSBoundParameters.ContainsKey('MembersToExclude') -and + -not [System.String]::IsNullOrEmpty($MembersToExclude)) + { + $MembersToExclude = Remove-DuplicateMembers -Members $MembersToExclude - Write-Verbose -Message ($script:localizedData.RemovingGroupMembers -f $MembersToExclude.Count, $GroupName) + if (-not [System.String]::IsNullOrEmpty($getTargetResourceResult.Members)) + { + $compareResult = Compare-Object -ReferenceObject $getTargetResourceResult.Members ` + -DifferenceObject $MembersToExclude -IncludeEqual - $setADCommonGroupMemberParms = @{ - Members = $MembersToExclude - MembershipAttribute = $MembershipAttribute - Parameters = $commonParameters - Action = 'Remove' - } - Set-ADCommonGroupMember @setADCommonGroupMemberParms + $membersToRemove = ($compareResult | + Where-Object -Property SideIndicator -eq '==').InputObject + } + else + { + $membersToRemove = $null + } + + if (-not [System.String]::IsNullOrEmpty($membersToRemove)) + { + Write-Verbose -Message ($script:localizedData.RemovingGroupMembers -f + ($MembersToRemove -join ', '), $GroupName) + + $setADCommonGroupMemberParms = @{ + Members = $MembersToRemove + MembershipAttribute = $MembershipAttribute + Parameters = $commonParameters + Action = 'Remove' } + Set-ADCommonGroupMember @setADCommonGroupMemberParms } } - } - elseif ($Ensure -eq 'Absent') - { - # Remove existing group - Write-Verbose -Message ($script:localizedData.RemovingGroup -f $GroupName) - Remove-ADGroup @commonParameters -Confirm:$false -ErrorAction 'Stop' + if ($moveAdGroupRequired) + { + Write-Verbose -Message ($script:localizedData.MovingGroup -f $GroupName, $Path) + + $moveADObjectParameters = $commonParameters.Clone() + $moveADObjectParameters['Identity'] = $getTargetResourceResult.DistinguishedName + + try + { + Move-ADObject @moveADObjectParameters -TargetPath $Path + } + catch + { + $errorMessage = ($script:localizedData.MovingGroupError -f + $GroupName, $getTargetResourceResult.Path, $Path) + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } } - } - else - { - # The Active Directory group does not exist, check if it should. - if ($Ensure -eq 'Present') + else { - $commonParametersUsingName = Get-ADCommonParameters @PSBoundParameters -UseNameParameter - - $newAdGroupParameters = $commonParametersUsingName.Clone() + # Resource is absent + $newAdGroupParameters = Get-ADCommonParameters @PSBoundParameters -UseNameParameter $newAdGroupParameters['GroupCategory'] = $Category $newAdGroupParameters['GroupScope'] = $GroupScope @@ -930,6 +916,13 @@ function Set-TargetResource $newAdGroupParameters['Path'] = $Path } + if ($PSBoundParameters.ContainsKey('Notes')) + { + $newAdGroupParameters['OtherAttributes'] = @{ + Info = $Notes + } + } + $adGroup = $null # Create group. Try to restore account first if it exists. @@ -937,41 +930,23 @@ function Set-TargetResource { Write-Verbose -Message ($script:localizedData.RestoringGroup -f $GroupName) - $restoreParams = Get-ADCommonParameters @PSBoundParameters - - $adGroup = Restore-ADCommonObject @restoreParams -ObjectClass 'Group' + $adGroup = Restore-ADCommonObject @commonParameters -ObjectClass 'Group' } - <# - Check if the Active Directory group was restored, if not create - the group. - #> + # Check if the Active Directory group was restored, if not create the group. if (-not $adGroup) { Write-Verbose -Message ($script:localizedData.AddingGroup -f $GroupName) - $adGroup = New-ADGroup @newAdGroupParameters -PassThru -ErrorAction 'Stop' - } - - <# - Only the New-ADGroup cmdlet takes a -Name parameter. Refresh - the parameters with the -Identity parameter rather than -Name. - #> - $commonParameters = Get-ADCommonParameters @PSBoundParameters - - if ($PSBoundParameters.ContainsKey('Notes')) - { - # Can't set the Notes field when creating the group - Write-Verbose -Message ($script:localizedData.UpdatingGroupProperty -f 'Notes', $Notes) - - $setADGroupParams = $commonParameters.Clone() - $setADGroupParams['Identity'] = $adGroup.DistinguishedName - $setADGroupParams['ErrorAction'] = 'Stop' - $setADGroupParams['Add'] = @{ - Info = $Notes + try + { + $adGroup = New-ADGroup @newAdGroupParameters -PassThru + } + catch + { + $errorMessage = ($script:localizedData.AddingGroupError -f $GroupName) + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ } - - Set-ADGroup @setADGroupParams } # Add the required members @@ -979,7 +954,7 @@ function Set-TargetResource { $Members = Remove-DuplicateMembers -Members $Members - Write-Verbose -Message ($script:localizedData.AddingGroupMembers -f $Members.Count, $GroupName) + Write-Verbose -Message ($script:localizedData.AddingGroupMembers -f ($Members -join ', '), $GroupName) $setADCommonGroupMemberParms = @{ Members = $Members @@ -989,11 +964,13 @@ function Set-TargetResource } Set-ADCommonGroupMember @setADCommonGroupMemberParms } - elseif ($PSBoundParameters.ContainsKey('MembersToInclude') -and -not [System.String]::IsNullOrEmpty($MembersToInclude)) + elseif ($PSBoundParameters.ContainsKey('MembersToInclude') -and + -not [System.String]::IsNullOrEmpty($MembersToInclude)) { $MembersToInclude = Remove-DuplicateMembers -Members $MembersToInclude - Write-Verbose -Message ($script:localizedData.AddingGroupMembers -f $MembersToInclude.Count, $GroupName) + Write-Verbose -Message ($script:localizedData.AddingGroupMembers -f + ($MembersToInclude -join ', '), $GroupName) $setADCommonGroupMemberParms = @{ Members = $MembersToInclude @@ -1004,7 +981,31 @@ function Set-TargetResource Set-ADCommonGroupMember @setADCommonGroupMemberParms } } - } #end catch -} #end function Set-TargetResource + } + else + { + # Resource should be absent + if ($getTargetResourceResult.Ensure -eq 'Present') + { + # Resource is present + Write-Verbose -Message ($script:localizedData.RemovingGroup -f $GroupName) + + try + { + Remove-ADGroup @commonParameters -Confirm:$false + } + catch + { + $errorMessage = ($script:localizedData.RemovingGroupError -f $GroupName) + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } + else + { + # Resource is absent + Write-Verbose -Message ($script:localizedData.ResourceInDesiredStateMessage -f $GroupName) + } + } +} Export-ModuleMember -Function *-TargetResource diff --git a/source/DSCResources/MSFT_ADGroup/MSFT_ADGroup.schema.mof b/source/DSCResources/MSFT_ADGroup/MSFT_ADGroup.schema.mof index 7021d37c2..ac48c53f0 100644 --- a/source/DSCResources/MSFT_ADGroup/MSFT_ADGroup.schema.mof +++ b/source/DSCResources/MSFT_ADGroup/MSFT_ADGroup.schema.mof @@ -8,7 +8,7 @@ class MSFT_ADGroup : OMI_BaseResource [Write, Description("Specifies if this Active Directory group should be present or absent. Default value is 'Present'."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; [Write, Description("Description of the Active Directory group.")] String Description; [Write, Description("Display name of the Active Directory group.")] String DisplayName; - [Write, Description("Credentials used to enact the change upon."), EmbeddedInstance("MSFT_Credential")] String Credential; + [Write, Description("The credential to be used to perform the operation on Active Directory."), EmbeddedInstance("MSFT_Credential")] String Credential; [Write, Description("Active Directory domain controller to enact the change upon.")] String DomainController; [Write, Description("Active Directory group membership should match membership exactly.")] String Members[]; [Write, Description("Active Directory group should include these members.")] String MembersToInclude[]; diff --git a/source/DSCResources/MSFT_ADGroup/en-US/MSFT_ADGroup.strings.psd1 b/source/DSCResources/MSFT_ADGroup/en-US/MSFT_ADGroup.strings.psd1 index ce1b4cc1b..252dafee0 100644 --- a/source/DSCResources/MSFT_ADGroup/en-US/MSFT_ADGroup.strings.psd1 +++ b/source/DSCResources/MSFT_ADGroup/en-US/MSFT_ADGroup.strings.psd1 @@ -1,17 +1,24 @@ # culture="en-US" ConvertFrom-StringData @' - RetrievingGroupMembers = Retrieving group membership based on '{0}' property. (ADG0001) - GroupMembershipNotDesiredState = Group membership is NOT in the desired state. (ADG0002) - AddingGroupMembers = Adding '{0}' member(s) to AD group '{1}'. (ADG0003) - RemovingGroupMembers = Removing '{0}' member(s) from AD group '{1}'. (ADG0004) - AddingGroup = Creating AD Group '{0}'. (ADG0005) - UpdatingGroup = Updating AD Group '{0}'. (ADG0006) - RemovingGroup = Removing AD Group '{0}'. (ADG0007) - MovingGroup = Moving AD Group '{0}' to '{1}'. (ADG0008) - RestoringGroup = Attempting to restore the group {0} from recycle bin. (ADG0009) - GroupNotFound = AD Group '{0}' was not found. (ADG00010) - NotDesiredPropertyState = AD Group '{0}' is not correct. Expected '{1}', actual '{2}'. (ADG0011) - UpdatingGroupProperty = Updating AD Group property '{0}' to '{1}'. (ADG0012) - GroupMembershipMultipleDomains = Group membership objects are in '{0}' different AD Domains. (ADG0013) - RetrievingGroupMembersError = Error retrieving membership for AD Group '{0}'. (ADG0014) + RetrievingGroupMembers = Retrieving group membership based on '{0}' property. (ADG0001) + GroupMembershipNotDesiredState = Group membership is NOT in the desired state. (ADG0002) + AddingGroupMembers = Adding '{0}' member(s) to AD group '{1}'. (ADG0003) + RemovingGroupMembers = Removing '{0}' member(s) from AD group '{1}'. (ADG0004) + AddingGroup = Creating AD Group '{0}'. (ADG0005) + RemovingGroup = Removing AD Group '{0}'. (ADG0007) + MovingGroup = Moving AD Group '{0}' to '{1}'. (ADG0008) + RestoringGroup = Attempting to restore the group {0} from recycle bin. (ADG0009) + UpdatingResourceProperty = Updating AD Group '{0}' property '{1}' to '{2}'. (ADG0012) + RetrievingGroupMembersError = Error retrieving membership for AD Group '{0}'. (ADG0014) + ResourceExistsButShouldNotMessage = AD Group '{0}' exists but should not. (ADG0015) + ResourceDoesNotExistButShouldMessage = AD Group '{0}' does not exist but should. (ADG0016) + AddingGroupError = Error adding AD Group '{0}'. (ADG0017) + RemovingGroupError = Error removing AD Group '{0}'. (ADG0018) + MovingGroupError = Error moving AD Group '{0}' from '{1}' to '{2}'. (ADG0019) + SettingGroupError = Error setting AD Group '{0}'. (ADG0020) + RetrievingGroup = Retrieving AD Group '{0}'. (ADG0021) + RetrievingGroupError = Error Retrieving AD Group '{0}'. (ADG0022) + GroupIsPresent = The AD Group '{0}' is present. (ADG0023) + GroupIsAbsent = The AD Group '{0}' is absent. (ADG0024) + ResourceInDesiredStateMessage = AD Group '{0}' is in the desired state. (ADG0025) '@ diff --git a/source/DSCResources/MSFT_ADGroup/en-US/about_ADGroup.help.txt b/source/DSCResources/MSFT_ADGroup/en-US/about_ADGroup.help.txt index ee212c32a..1d5da0827 100644 --- a/source/DSCResources/MSFT_ADGroup/en-US/about_ADGroup.help.txt +++ b/source/DSCResources/MSFT_ADGroup/en-US/about_ADGroup.help.txt @@ -45,7 +45,7 @@ .PARAMETER Credential Write - PSCredential - Credentials used to enact the change upon. + The credential to be used to perform the operation on Active Directory. .PARAMETER DomainController Write - String diff --git a/source/Modules/ActiveDirectoryDsc.Common/ActiveDirectoryDsc.Common.psm1 b/source/Modules/ActiveDirectoryDsc.Common/ActiveDirectoryDsc.Common.psm1 index a0e86c619..43cd30a2f 100644 --- a/source/Modules/ActiveDirectoryDsc.Common/ActiveDirectoryDsc.Common.psm1 +++ b/source/Modules/ActiveDirectoryDsc.Common/ActiveDirectoryDsc.Common.psm1 @@ -218,33 +218,17 @@ function Assert-MemberParameters } } - if ($PSBoundParameters.ContainsKey('MembersToInclude')) - { - $MembersToInclude = Remove-DuplicateMembers -Members $MembersToInclude - } - - if ($PSBoundParameters.ContainsKey('MembersToExclude')) - { - $MembersToExclude = Remove-DuplicateMembers -Members $MembersToExclude - } + $MembersToInclude = Remove-DuplicateMembers -Members $MembersToInclude + $MembersToExclude = Remove-DuplicateMembers -Members $MembersToExclude - if (($PSBoundParameters.ContainsKey('MembersToInclude')) -and ($PSBoundParameters.ContainsKey('MembersToExclude'))) + # Check if MembersToInclude and MembersToExclude have common principals. + foreach ($member in $MembersToInclude) { - if (($MembersToInclude.Length -eq 0) -and ($MembersToExclude.Length -eq 0)) + if ($member -in $MembersToExclude) { - $errorMessage = $script:localizedData.IncludeAndExcludeAreEmptyError -f 'MembersToInclude', 'MembersToExclude' + $errorMessage = $script:localizedData.IncludeAndExcludeConflictError -f $member, 'MembersToInclude', 'MembersToExclude' New-InvalidArgumentException -ArgumentName 'MembersToInclude, MembersToExclude' -Message $errorMessage } - - # Both MembersToInclude and MembersToExclude were provided. Check if they have common principals. - foreach ($member in $MembersToInclude) - { - if ($member -in $MembersToExclude) - { - $errorMessage = $script:localizedData.IncludeAndExcludeConflictError -f $member, 'MembersToInclude', 'MembersToExclude' - New-InvalidArgumentException -ArgumentName 'MembersToInclude, MembersToExclude' -Message $errorMessage - } - } } } @@ -282,16 +266,16 @@ function Remove-DuplicateMembers if ($null -eq $Members -or $Members.Count -eq 0) { - $uniqueMembers = [System.String[]] @() + $uniqueMembers = @() } else { - $uniqueMembers = [System.String[]] ($members | Sort-Object -Unique) + $uniqueMembers = @($members | Sort-Object -Unique) } <# - Comma make sure we return the string array as the correct type, - and also make sure one entry is returned as a string array. + Comma makes sure we return the string array as the correct type, + and also makes sure one entry is returned as a string array. #> return , $uniqueMembers } @@ -1135,6 +1119,8 @@ function Set-ADCommonGroupMember Assert-Module -ModuleName ActiveDirectory + $setADGroupParameters = $Parameters.Clone() + $resolveMembersSecurityIdentifierParms = @{ MembershipAttribute = $MembershipAttribute Parameters = $Parameters @@ -1142,13 +1128,13 @@ function Set-ADCommonGroupMember ErrorAction = 'Stop' } - $Parameters[$Action] = @{ + $setADGroupParameters[$Action] = @{ member = $Members | Resolve-MembersSecurityIdentifier @resolveMembersSecurityIdentifierParms } try { - Set-ADGroup @Parameters -ErrorAction 'Stop' + Set-ADGroup @setADGroupParameters -ErrorAction 'Stop' } catch { diff --git a/source/Modules/ActiveDirectoryDsc.Common/en-US/ActiveDirectoryDsc.Common.strings.psd1 b/source/Modules/ActiveDirectoryDsc.Common/en-US/ActiveDirectoryDsc.Common.strings.psd1 index 56a70ad2a..0022f3362 100644 --- a/source/Modules/ActiveDirectoryDsc.Common/en-US/ActiveDirectoryDsc.Common.strings.psd1 +++ b/source/Modules/ActiveDirectoryDsc.Common/en-US/ActiveDirectoryDsc.Common.strings.psd1 @@ -12,7 +12,6 @@ ConvertFrom-StringData @' UnableToCompareType = Unable to compare the type {0} as it is not handled by the Test-DscPropertyState cmdlet. (ADCOMMON0009) MembersAndIncludeExcludeError = The '{0}' and '{1}' and/or '{2}' parameters conflict. The '{0}' parameter should not be used in any combination with the '{1}' and '{2}' parameters. (ADCOMMON0011) IncludeAndExcludeConflictError = The member '{0}' is included in both '{1}' and '{2}' parameter values. The same member must not be included in both '{1}' and '{2}' parameter values. (ADCOMMON0014) - IncludeAndExcludeAreEmptyError = The '{0}' and '{1}' parameters are either both null or empty. At least one member must be specified in one of these parameters. (ADCOMMON0015) RecycleBinRestoreFailed = Failed restoring {0} ({1}) from the recycle bin. (ADCOMMON0017) CheckingMembers = Checking for '{0}' members. (ADCOMMON0019) MembershipCountMismatch = Membership count is not correct. Expected '{0}' members, actual '{1}' members. (ADCOMMON0020) diff --git a/tests/Integration/MSFT_ADGroup.Integration.Tests.ps1 b/tests/Integration/MSFT_ADGroup.Integration.Tests.ps1 index 457a3e5ff..8cfe1597e 100644 --- a/tests/Integration/MSFT_ADGroup.Integration.Tests.ps1 +++ b/tests/Integration/MSFT_ADGroup.Integration.Tests.ps1 @@ -1,10 +1,25 @@ +<# + .SYNOPSIS + Pester integration test for the ADUGroup Resource of the ActiveDirectoryDsc Module + + .DESCRIPTION + Verbose/Debug output can be set by running: + + Invoke-pester -Script @{Path='.\MSFT_ADGroup.Integration.Tests.ps1';Parameters=@{Verbose=$true;Debug=$true}} +#> + +[CmdletBinding()] +param () + +Set-StrictMode -Version 1.0 + $script:dscModuleName = 'ActiveDirectoryDsc' $script:dscResourceFriendlyName = 'ADGroup' $script:dscResourceName = "MSFT_$($script:dscResourceFriendlyName)" try { - Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' + Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' -Verbose:$false } catch [System.IO.FileNotFoundException] { @@ -26,846 +41,72 @@ try BeforeAll { $resourceId = "[$($script:dscResourceFriendlyName)]Integration_Test" } + foreach ($testName in $ConfigurationData.AllNodes.Tests.Keys) + { + $configurationName = "$($script:dscResourceName)_$($testName)_Config" - $configurationName = "$($script:dscResourceName)_CreateGroup1_Config" - - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { - { + Context ('When using configuration {0}' -f $configurationName) { + BeforeAll { $configurationParameters = @{ OutputPath = $TestDrive # The variable $ConfigurationData was dot-sourced above. ConfigurationData = $ConfigurationData } - & $configurationName @configurationParameters + & $configurationName @configurationParameters -Debug:$false $startDscConfigurationParameters = @{ Path = $TestDrive ComputerName = 'localhost' Wait = $true - Verbose = $true Force = $true ErrorAction = 'Stop' } - - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } - - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } - - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId } - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.GroupName | Should -Be $ConfigurationData.AllNodes.Group1_Name - $resourceCurrentState.GroupScope | Should -Be 'Global' - $resourceCurrentState.Category | Should -Be 'Security' - $resourceCurrentState.Path | Should -Be ('CN=Users,{0}' -f $ConfigurationData.AllNodes.DomainDistinguishedName) - $resourceCurrentState.Description | Should -BeNullOrEmpty - $resourceCurrentState.DisplayName | Should -BeNullOrEmpty - $resourceCurrentState.Credential | Should -BeNullOrEmpty - $resourceCurrentState.DomainController | Should -BeNullOrEmpty - $resourceCurrentState.Members | Should -BeNullOrEmpty - $resourceCurrentState.MembersToInclude | Should -BeNullOrEmpty - $resourceCurrentState.MembersToExclude | Should -BeNullOrEmpty - $resourceCurrentState.MembershipAttribute | Should -Be 'SamAccountName' - $resourceCurrentState.ManagedBy | Should -BeNullOrEmpty - $resourceCurrentState.Notes | Should -BeNullOrEmpty - $resourceCurrentState.RestoreFromRecycleBin | Should -BeNullOrEmpty - $resourceCurrentState.DistinguishedName | Should -Be ('CN={0},CN=Users,{1}' -f $ConfigurationData.AllNodes.Group1_Name, $ConfigurationData.AllNodes.DomainDistinguishedName) - } - - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } - } - - $configurationName = "$($script:dscResourceName)_CreateGroup2_Config" - - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } - - & $configurationName @configurationParameters - - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } - - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } - - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } - - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId + It 'Should compile and apply the MOF without throwing' { + { Start-DscConfiguration @startDscConfigurationParameters } | + Should -Not -Throw } - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.GroupName | Should -Be $ConfigurationData.AllNodes.Group2_Name - $resourceCurrentState.GroupScope | Should -Be $ConfigurationData.AllNodes.Group2_Scope - $resourceCurrentState.Category | Should -Be 'Security' - $resourceCurrentState.Path | Should -Be ('CN=Users,{0}' -f $ConfigurationData.AllNodes.DomainDistinguishedName) - $resourceCurrentState.Description | Should -BeNullOrEmpty - $resourceCurrentState.DisplayName | Should -BeNullOrEmpty - $resourceCurrentState.Credential | Should -BeNullOrEmpty - $resourceCurrentState.DomainController | Should -BeNullOrEmpty - $resourceCurrentState.Members | Should -BeNullOrEmpty - $resourceCurrentState.MembersToInclude | Should -BeNullOrEmpty - $resourceCurrentState.MembersToExclude | Should -BeNullOrEmpty - $resourceCurrentState.MembershipAttribute | Should -Be 'SamAccountName' - $resourceCurrentState.ManagedBy | Should -BeNullOrEmpty - $resourceCurrentState.Notes | Should -BeNullOrEmpty - $resourceCurrentState.RestoreFromRecycleBin | Should -BeNullOrEmpty - $resourceCurrentState.DistinguishedName | Should -Be ('CN={0},CN=Users,{1}' -f $ConfigurationData.AllNodes.Group2_Name, $ConfigurationData.AllNodes.DomainDistinguishedName) - } - - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } - } - - $configurationName = "$($script:dscResourceName)_CreateGroup3_Config" - - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } - - & $configurationName @configurationParameters - - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } - - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } - - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } - - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId + It 'Should be able to call Get-DscConfiguration without throwing' { + { $script:currentConfiguration = Get-DscConfiguration -ErrorAction Stop } | + Should -Not -Throw } - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.GroupName | Should -Be $ConfigurationData.AllNodes.Group3_Name - $resourceCurrentState.GroupScope | Should -Be $ConfigurationData.AllNodes.Group3_Scope - $resourceCurrentState.Category | Should -Be 'Security' - $resourceCurrentState.Path | Should -Be ('CN=Users,{0}' -f $ConfigurationData.AllNodes.DomainDistinguishedName) - $resourceCurrentState.Description | Should -BeNullOrEmpty - $resourceCurrentState.DisplayName | Should -BeNullOrEmpty - $resourceCurrentState.Credential | Should -BeNullOrEmpty - $resourceCurrentState.DomainController | Should -BeNullOrEmpty - $resourceCurrentState.Members | Should -BeNullOrEmpty - $resourceCurrentState.MembersToInclude | Should -BeNullOrEmpty - $resourceCurrentState.MembersToExclude | Should -BeNullOrEmpty - $resourceCurrentState.MembershipAttribute | Should -Be 'SamAccountName' - $resourceCurrentState.ManagedBy | Should -BeNullOrEmpty - $resourceCurrentState.Notes | Should -BeNullOrEmpty - $resourceCurrentState.RestoreFromRecycleBin | Should -BeNullOrEmpty - $resourceCurrentState.DistinguishedName | Should -Be ('CN={0},CN=Users,{1}' -f $ConfigurationData.AllNodes.Group3_Name, $ConfigurationData.AllNodes.DomainDistinguishedName) - } - - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } - } - - $configurationName = "$($script:dscResourceName)_ChangeCategoryGroup3_Config" - - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } - - & $configurationName @configurationParameters - - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } - - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } - - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } - - It 'Should have set the resource and all the parameters should match' { $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { $_.ConfigurationName -eq $configurationName ` -and $_.ResourceId -eq $resourceId } - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.GroupName | Should -Be $ConfigurationData.AllNodes.Group3_Name - $resourceCurrentState.GroupScope | Should -Be $ConfigurationData.AllNodes.Group3_Scope - $resourceCurrentState.Category | Should -Be 'Distribution' - $resourceCurrentState.Path | Should -Be ('CN=Users,{0}' -f $ConfigurationData.AllNodes.DomainDistinguishedName) - $resourceCurrentState.Description | Should -BeNullOrEmpty - $resourceCurrentState.DisplayName | Should -BeNullOrEmpty - $resourceCurrentState.Credential | Should -BeNullOrEmpty - $resourceCurrentState.DomainController | Should -BeNullOrEmpty - $resourceCurrentState.Members | Should -BeNullOrEmpty - $resourceCurrentState.MembersToInclude | Should -BeNullOrEmpty - $resourceCurrentState.MembersToExclude | Should -BeNullOrEmpty - $resourceCurrentState.MembershipAttribute | Should -Be 'SamAccountName' - $resourceCurrentState.ManagedBy | Should -BeNullOrEmpty - $resourceCurrentState.Notes | Should -BeNullOrEmpty - $resourceCurrentState.RestoreFromRecycleBin | Should -BeNullOrEmpty - $resourceCurrentState.DistinguishedName | Should -Be ('CN={0},CN=Users,{1}' -f $ConfigurationData.AllNodes.Group3_Name, $ConfigurationData.AllNodes.DomainDistinguishedName) - } - - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } - } - - $configurationName = "$($script:dscResourceName)_CreateGroup4_Config" - - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { + if ($testName -eq 'MembersToInclude') { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } - - & $configurationName @configurationParameters - - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' + It "Should have set the correct 'Members' property" { + $resourceCurrentState.Members | Sort-Object | + Should -Be ('Administrator', 'Guest' | Sort-Object) } - - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } - - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } - - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId } - - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.GroupName | Should -Be $ConfigurationData.AllNodes.Group4_Name - $resourceCurrentState.GroupScope | Should -Be $ConfigurationData.AllNodes.Group4_Scope - $resourceCurrentState.Category | Should -Be 'Security' - $resourceCurrentState.Path | Should -Be ('CN=Users,{0}' -f $ConfigurationData.AllNodes.DomainDistinguishedName) - $resourceCurrentState.Description | Should -BeNullOrEmpty - $resourceCurrentState.DisplayName | Should -BeNullOrEmpty - $resourceCurrentState.Credential | Should -BeNullOrEmpty - $resourceCurrentState.DomainController | Should -BeNullOrEmpty - $resourceCurrentState.Members | Should -BeNullOrEmpty - $resourceCurrentState.MembersToInclude | Should -BeNullOrEmpty - $resourceCurrentState.MembersToExclude | Should -BeNullOrEmpty - $resourceCurrentState.MembershipAttribute | Should -Be 'SamAccountName' - $resourceCurrentState.ManagedBy | Should -BeNullOrEmpty - $resourceCurrentState.Notes | Should -BeNullOrEmpty - $resourceCurrentState.RestoreFromRecycleBin | Should -BeNullOrEmpty - $resourceCurrentState.DistinguishedName | Should -Be ('CN={0},CN=Users,{1}' -f $ConfigurationData.AllNodes.Group4_Name, $ConfigurationData.AllNodes.DomainDistinguishedName) - } - - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } - } - - $configurationName = "$($script:dscResourceName)_RemoveGroup4_Config" - - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { + elseif ($testName -eq 'MembersToExclude') { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } - - & $configurationName @configurationParameters - - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' + It "Should have set the correct 'Members' property" { + $resourceCurrentState.Members | Sort-Object | + Should -Be ('Administrator' | Sort-Object) } - - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } - - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } - - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId } - - $resourceCurrentState.Ensure | Should -Be 'Absent' - $resourceCurrentState.GroupName | Should -Be $ConfigurationData.AllNodes.Group4_Name - $resourceCurrentState.GroupScope | Should -BeNullOrEmpty - $resourceCurrentState.Category | Should -BeNullOrEmpty - $resourceCurrentState.Path | Should -BeNullOrEmpty - $resourceCurrentState.Description | Should -BeNullOrEmpty - $resourceCurrentState.DisplayName | Should -BeNullOrEmpty - $resourceCurrentState.Credential | Should -BeNullOrEmpty - $resourceCurrentState.DomainController | Should -BeNullOrEmpty - $resourceCurrentState.Members | Should -BeNullOrEmpty - $resourceCurrentState.MembersToInclude | Should -BeNullOrEmpty - $resourceCurrentState.MembersToExclude | Should -BeNullOrEmpty - $resourceCurrentState.MembershipAttribute | Should -Be 'SamAccountName' - $resourceCurrentState.ManagedBy | Should -BeNullOrEmpty - $resourceCurrentState.Notes | Should -BeNullOrEmpty - $resourceCurrentState.RestoreFromRecycleBin | Should -BeNullOrEmpty - $resourceCurrentState.DistinguishedName | Should -BeNullOrEmpty - - } - - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } - } - - $configurationName = "$($script:dscResourceName)_RestoreGroup4_Config" - - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { + else { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } - - & $configurationName @configurationParameters - - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' + foreach ($property in $ConfigurationData.AllNodes.Tests.$testName.Keys) + { + It "Should have set the correct '$property' property" { + $resourceCurrentState.$property | Sort-Object | + Should -Be ($ConfigurationData.AllNodes.Tests.$testName.$property | Sort-Object) + } } - - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } - - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } - - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId } - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.GroupName | Should -Be $ConfigurationData.AllNodes.Group4_Name - $resourceCurrentState.GroupScope | Should -Be $ConfigurationData.AllNodes.Group4_Scope - $resourceCurrentState.Category | Should -Be 'Security' - $resourceCurrentState.Path | Should -Be ('CN=Users,{0}' -f $ConfigurationData.AllNodes.DomainDistinguishedName) - $resourceCurrentState.Description | Should -BeNullOrEmpty - $resourceCurrentState.DisplayName | Should -BeNullOrEmpty - $resourceCurrentState.Credential | Should -BeNullOrEmpty - $resourceCurrentState.DomainController | Should -BeNullOrEmpty - $resourceCurrentState.Members | Should -BeNullOrEmpty - $resourceCurrentState.MembersToInclude | Should -BeNullOrEmpty - $resourceCurrentState.MembersToExclude | Should -BeNullOrEmpty - $resourceCurrentState.MembershipAttribute | Should -Be 'SamAccountName' - $resourceCurrentState.ManagedBy | Should -BeNullOrEmpty - $resourceCurrentState.Notes | Should -BeNullOrEmpty - $resourceCurrentState.RestoreFromRecycleBin | Should -BeNullOrEmpty - $resourceCurrentState.DistinguishedName | Should -Be ('CN={0},CN=Users,{1}' -f $ConfigurationData.AllNodes.Group4_Name, $ConfigurationData.AllNodes.DomainDistinguishedName) - } - - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } - } - - $configurationName = "$($script:dscResourceName)_ChangeScopeGroup4_Config" - - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } - - & $configurationName @configurationParameters - - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } - - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } - - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } - - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration | Should -BeTrue } - - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.GroupName | Should -Be $ConfigurationData.AllNodes.Group4_Name - $resourceCurrentState.GroupScope | Should -Be 'Global' - $resourceCurrentState.Category | Should -Be 'Security' - $resourceCurrentState.Path | Should -Be ('CN=Users,{0}' -f $ConfigurationData.AllNodes.DomainDistinguishedName) - $resourceCurrentState.Description | Should -BeNullOrEmpty - $resourceCurrentState.DisplayName | Should -BeNullOrEmpty - $resourceCurrentState.Credential | Should -BeNullOrEmpty - $resourceCurrentState.DomainController | Should -BeNullOrEmpty - $resourceCurrentState.Members | Should -BeNullOrEmpty - $resourceCurrentState.MembersToInclude | Should -BeNullOrEmpty - $resourceCurrentState.MembersToExclude | Should -BeNullOrEmpty - $resourceCurrentState.MembershipAttribute | Should -Be 'SamAccountName' - $resourceCurrentState.ManagedBy | Should -BeNullOrEmpty - $resourceCurrentState.Notes | Should -BeNullOrEmpty - $resourceCurrentState.RestoreFromRecycleBin | Should -BeNullOrEmpty - $resourceCurrentState.DistinguishedName | Should -Be ('CN={0},CN=Users,{1}' -f $ConfigurationData.AllNodes.Group4_Name, $ConfigurationData.AllNodes.DomainDistinguishedName) - } - - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } - } - - $configurationName = "$($script:dscResourceName)_UpdateGroup1_Config" - - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } - - & $configurationName @configurationParameters - - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } - - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } - - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } - - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId - } - - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.GroupName | Should -Be $ConfigurationData.AllNodes.Group1_Name - $resourceCurrentState.GroupScope | Should -Be 'Global' - $resourceCurrentState.Category | Should -Be 'Security' - $resourceCurrentState.Path | Should -Be ('CN=Computers,{0}' -f $ConfigurationData.AllNodes.DomainDistinguishedName) - $resourceCurrentState.Description | Should -Be 'A DSC description' - $resourceCurrentState.DisplayName | Should -Be 'DSC Group 1' - $resourceCurrentState.Credential | Should -BeNullOrEmpty - $resourceCurrentState.DomainController | Should -BeNullOrEmpty - $resourceCurrentState.Members | Should -HaveCount 2 - $resourceCurrentState.Members | Should -Contain 'Administrator' - $resourceCurrentState.Members | Should -Contain 'Guest' - $resourceCurrentState.MembersToInclude | Should -BeNullOrEmpty - $resourceCurrentState.MembersToExclude | Should -BeNullOrEmpty - $resourceCurrentState.MembershipAttribute | Should -Be 'SamAccountName' - $resourceCurrentState.ManagedBy | Should -Be ('CN=Administrator,CN=Users,{0}' -f $ConfigurationData.AllNodes.DomainDistinguishedName) - $resourceCurrentState.Notes | Should -Be 'Notes for this group' - $resourceCurrentState.RestoreFromRecycleBin | Should -BeNullOrEmpty - $resourceCurrentState.DistinguishedName | Should -Be ('CN={0},CN=Computers,{1}' -f $ConfigurationData.AllNodes.Group1_Name, $ConfigurationData.AllNodes.DomainDistinguishedName) - } - - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } - } - - $configurationName = "$($script:dscResourceName)_CreateGroup5_Config" - - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } - - & $configurationName @configurationParameters - - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } - - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } - - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } - - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId - } - - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.GroupName | Should -Be $ConfigurationData.AllNodes.Group5_Name - $resourceCurrentState.GroupScope | Should -Be $ConfigurationData.AllNodes.Group5_Scope - $resourceCurrentState.Category | Should -Be $ConfigurationData.AllNodes.Group5_Category - $resourceCurrentState.Path | Should -Be ('CN=Users,{0}' -f $ConfigurationData.AllNodes.DomainDistinguishedName) - $resourceCurrentState.Description | Should -BeNullOrEmpty - $resourceCurrentState.DisplayName | Should -BeNullOrEmpty - $resourceCurrentState.Credential | Should -BeNullOrEmpty - $resourceCurrentState.DomainController | Should -BeNullOrEmpty - $resourceCurrentState.Members | Should -HaveCount 1 - $resourceCurrentState.Members | Should -Contain 'Administrator' - $resourceCurrentState.MembersToInclude | Should -BeNullOrEmpty - $resourceCurrentState.MembersToExclude | Should -BeNullOrEmpty - $resourceCurrentState.MembershipAttribute | Should -Be 'SamAccountName' - $resourceCurrentState.ManagedBy | Should -BeNullOrEmpty - $resourceCurrentState.Notes | Should -BeNullOrEmpty - $resourceCurrentState.RestoreFromRecycleBin | Should -BeNullOrEmpty - $resourceCurrentState.DistinguishedName | Should -Be ('CN={0},CN=Users,{1}' -f $ConfigurationData.AllNodes.Group5_Name, $ConfigurationData.AllNodes.DomainDistinguishedName) - } - - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } - } - - $configurationName = "$($script:dscResourceName)_ModifyMembersGroup5_Config" - - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } - - & $configurationName @configurationParameters - - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } - - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } - - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } - - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId - } - - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.GroupName | Should -Be $ConfigurationData.AllNodes.Group5_Name - $resourceCurrentState.GroupScope | Should -Be $ConfigurationData.AllNodes.Group5_Scope - $resourceCurrentState.Category | Should -Be $ConfigurationData.AllNodes.Group5_Category - $resourceCurrentState.Path | Should -Be ('CN=Users,{0}' -f $ConfigurationData.AllNodes.DomainDistinguishedName) - $resourceCurrentState.Description | Should -BeNullOrEmpty - $resourceCurrentState.DisplayName | Should -BeNullOrEmpty - $resourceCurrentState.Credential | Should -BeNullOrEmpty - $resourceCurrentState.DomainController | Should -BeNullOrEmpty - $resourceCurrentState.Members | Should -HaveCount 1 - $resourceCurrentState.Members | Should -Contain 'Guest' - $resourceCurrentState.MembersToInclude | Should -HaveCount 1 - $resourceCurrentState.MembersToInclude | Should -Contain 'Guest' - $resourceCurrentState.MembersToExclude | Should -HaveCount 1 - $resourceCurrentState.MembersToExclude | Should -Contain 'Administrator' - $resourceCurrentState.MembershipAttribute | Should -Be 'SamAccountName' - $resourceCurrentState.ManagedBy | Should -BeNullOrEmpty - $resourceCurrentState.Notes | Should -BeNullOrEmpty - $resourceCurrentState.RestoreFromRecycleBin | Should -BeNullOrEmpty - $resourceCurrentState.DistinguishedName | Should -Be ('CN={0},CN=Users,{1}' -f $ConfigurationData.AllNodes.Group5_Name, $ConfigurationData.AllNodes.DomainDistinguishedName) - } - - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } - } - - $configurationName = "$($script:dscResourceName)_EnforceMembersGroup5_Config" - - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } - - & $configurationName @configurationParameters - - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } - - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } - - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } - - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId - } - - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.GroupName | Should -Be $ConfigurationData.AllNodes.Group5_Name - $resourceCurrentState.GroupScope | Should -Be $ConfigurationData.AllNodes.Group5_Scope - $resourceCurrentState.Category | Should -Be $ConfigurationData.AllNodes.Group5_Category - $resourceCurrentState.Path | Should -Be ('CN=Users,{0}' -f $ConfigurationData.AllNodes.DomainDistinguishedName) - $resourceCurrentState.Description | Should -BeNullOrEmpty - $resourceCurrentState.DisplayName | Should -BeNullOrEmpty - $resourceCurrentState.Credential | Should -BeNullOrEmpty - $resourceCurrentState.DomainController | Should -BeNullOrEmpty - $resourceCurrentState.Members | Should -HaveCount 2 - $resourceCurrentState.Members | Should -Contain 'Administrator' - $resourceCurrentState.Members | Should -Contain 'Guest' - $resourceCurrentState.MembersToInclude | Should -BeNullOrEmpty - $resourceCurrentState.MembersToExclude | Should -BeNullOrEmpty - $resourceCurrentState.MembershipAttribute | Should -Be 'SamAccountName' - $resourceCurrentState.ManagedBy | Should -BeNullOrEmpty - $resourceCurrentState.Notes | Should -BeNullOrEmpty - $resourceCurrentState.RestoreFromRecycleBin | Should -BeNullOrEmpty - $resourceCurrentState.DistinguishedName | Should -Be ('CN={0},CN=Users,{1}' -f $ConfigurationData.AllNodes.Group5_Name, $ConfigurationData.AllNodes.DomainDistinguishedName) - } - - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } - } - - $configurationName = "$($script:dscResourceName)_ClearMembersGroup5_Config" - - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } - - & $configurationName @configurationParameters - - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } - - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } - - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } - - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId - } - - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.GroupName | Should -Be $ConfigurationData.AllNodes.Group5_Name - $resourceCurrentState.GroupScope | Should -Be $ConfigurationData.AllNodes.Group5_Scope - $resourceCurrentState.Category | Should -Be $ConfigurationData.AllNodes.Group5_Category - $resourceCurrentState.Path | Should -Be ('CN=Users,{0}' -f $ConfigurationData.AllNodes.DomainDistinguishedName) - $resourceCurrentState.Description | Should -BeNullOrEmpty - $resourceCurrentState.DisplayName | Should -BeNullOrEmpty - $resourceCurrentState.Credential | Should -BeNullOrEmpty - $resourceCurrentState.DomainController | Should -BeNullOrEmpty - $resourceCurrentState.Members | Should -BeNullOrEmpty - $resourceCurrentState.MembersToInclude | Should -BeNullOrEmpty - $resourceCurrentState.MembersToExclude | Should -BeNullOrEmpty - $resourceCurrentState.MembershipAttribute | Should -Be 'SamAccountName' - $resourceCurrentState.ManagedBy | Should -BeNullOrEmpty - $resourceCurrentState.Notes | Should -BeNullOrEmpty - $resourceCurrentState.RestoreFromRecycleBin | Should -BeNullOrEmpty - $resourceCurrentState.DistinguishedName | Should -Be ('CN={0},CN=Users,{1}' -f $ConfigurationData.AllNodes.Group5_Name, $ConfigurationData.AllNodes.DomainDistinguishedName) - } - - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } - } - - $configurationName = "$($script:dscResourceName)_Cleanup_Config" - - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } - - & $configurationName @configurationParameters - - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } - - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw } } } diff --git a/tests/Integration/MSFT_ADGroup.config.ps1 b/tests/Integration/MSFT_ADGroup.config.ps1 index 460c86ff0..c53a256b6 100644 --- a/tests/Integration/MSFT_ADGroup.config.ps1 +++ b/tests/Integration/MSFT_ADGroup.config.ps1 @@ -16,32 +16,63 @@ else $currentDomain = Get-ADDomain $netBiosDomainName = $currentDomain.NetBIOSName $domainDistinguishedName = $currentDomain.DistinguishedName + $AdminUserName = "$netBiosDomainName\Administrator" + $AdminPassword = 'Coffee33!' + $AdminCredential = [System.Management.Automation.PSCredential]::new($AdminUserName, + (ConvertTo-SecureString -String $AdminPassword -AsPlainText -Force)) + + $groupName = 'DscIntegrationTestGroup' $ConfigurationData = @{ AllNodes = @( @{ NodeName = 'localhost' CertificateFile = $env:DscPublicCertificatePath + PsDscAllowDomainUser = $true DomainDistinguishedName = $domainDistinguishedName - Group1_Name = 'DscGroup1' - - Group2_Name = 'DscGroup2' - Group2_Scope = 'Global' - - Group3_Name = 'DscGroup3' - Group3_Scope = 'Universal' - - Group4_Name = 'DscGroup4' - Group4_Scope = 'DomainLocal' - - Group5_Name = 'DscDistributionGroup1' - Group5_Scope = 'Universal' - Group5_Category = 'Distribution' - - AdministratorUserName = ('{0}\Administrator' -f $netBiosDomainName) - AdministratorPassword = 'P@ssw0rd1' + Tests = [Ordered]@{ + CreateGroup = @{ + GroupName = $groupName + GroupScope = 'Global' + Category = 'Security' + Path = "CN=Users,$domainDistinguishedName" + Description = 'Original Description' + DisplayName = 'Display Name' + Members = 'Administrator', 'Guest' + ManagedBy = "CN=Administrator,CN=Users,$domainDistinguishedName" + Notes = 'Notes' + Ensure = 'Present' + } + ModifyGroup = @{ + GroupName = $groupName + GroupScope = 'DomainLocal' + Category = 'Distribution' + Path = "CN=Computers,$domainDistinguishedName" + Description = 'Modified Description' + DisplayName = 'Modified Display Name' + Members = 'Administrator' + ManagedBy = "CN=Guest,CN=Users,$domainDistinguishedName" + Notes = 'Modified Notes' + } + MembersToInclude = @{ + GroupName = $groupName + MembersToInclude = 'Guest' + } + MembersToExclude = @{ + GroupName = $groupName + MembersToExclude = 'Guest' + } + RemoveAllMembersFromGroup = @{ + GroupName = $groupName + Members = @() + } + RemoveGroup = @{ + GroupName = $groupName + Ensure = 'Absent' + } + } } ) } @@ -49,440 +80,141 @@ else <# .SYNOPSIS - Add a group using default values. + Create an AD Group. #> -Configuration MSFT_ADGroup_CreateGroup1_Config +Configuration MSFT_ADGroup_CreateGroup_Config { Import-DscResource -ModuleName 'ActiveDirectoryDsc' - node $AllNodes.NodeName - { - ADGroup 'Integration_Test' - { - GroupName = $Node.Group1_Name - - Credential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @( - $Node.AdministratorUserName, - (ConvertTo-SecureString -String $Node.AdministratorPassword -AsPlainText -Force) - ) - } - } -} - -<# - .SYNOPSIS - Add a global group using default values. -#> -Configuration MSFT_ADGroup_CreateGroup2_Config -{ - Import-DscResource -ModuleName 'ActiveDirectoryDsc' + $testName = 'CreateGroup' node $AllNodes.NodeName { ADGroup 'Integration_Test' { - GroupName = $Node.Group2_Name - GroupScope = $Node.Group2_Scope - - Credential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @( - $Node.AdministratorUserName, - (ConvertTo-SecureString -String $Node.AdministratorPassword -AsPlainText -Force) - ) + GroupName = $Node.Tests.$testName.GroupName + GroupScope = $Node.Tests.$testName.GroupScope + Category = $Node.Tests.$testName.Category + Path = $Node.Tests.$testName.Path + Description = $Node.Tests.$testName.Description + DisplayName = $Node.Tests.$testName.DisplayName + Members = $Node.Tests.$testName.Members + ManagedBy = $Node.Tests.$testName.ManagedBy + Notes = $Node.Tests.$testName.Notes + Ensure = $Node.Tests.$testName.Ensure + PsDscRunAsCredential = $adminCredential } } } <# .SYNOPSIS - Add a universal group using default values. + Modify an AD Group. #> -Configuration MSFT_ADGroup_CreateGroup3_Config +Configuration MSFT_ADGroup_ModifyGroup_Config { Import-DscResource -ModuleName 'ActiveDirectoryDsc' - node $AllNodes.NodeName - { - ADGroup 'Integration_Test' - { - GroupName = $Node.Group3_Name - GroupScope = $Node.Group3_Scope - - Credential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @( - $Node.AdministratorUserName, - (ConvertTo-SecureString -String $Node.AdministratorPassword -AsPlainText -Force) - ) - } - } -} - -<# - .SYNOPSIS - Changes the category for an existing universal group. -#> -Configuration MSFT_ADGroup_ChangeCategoryGroup3_Config -{ - Import-DscResource -ModuleName 'ActiveDirectoryDsc' + $testName = 'ModifyGroup' node $AllNodes.NodeName { ADGroup 'Integration_Test' { - GroupName = $Node.Group3_Name - Category = 'Distribution' - - Credential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @( - $Node.AdministratorUserName, - (ConvertTo-SecureString -String $Node.AdministratorPassword -AsPlainText -Force) - ) + GroupName = $Node.Tests.$testName.GroupName + GroupScope = $Node.Tests.$testName.GroupScope + Category = $Node.Tests.$testName.Category + Path = $Node.Tests.$testName.Path + Description = $Node.Tests.$testName.Description + DisplayName = $Node.Tests.$testName.DisplayName + Members = $Node.Tests.$testName.Members + ManagedBy = $Node.Tests.$testName.ManagedBy + Notes = $Node.Tests.$testName.Notes + PsDscRunAsCredential = $adminCredential } } } <# .SYNOPSIS - Add a domain local group using default values. + Include members in an AD Group. #> -Configuration MSFT_ADGroup_CreateGroup4_Config +Configuration MSFT_ADGroup_MembersToInclude_Config { Import-DscResource -ModuleName 'ActiveDirectoryDsc' - node $AllNodes.NodeName - { - ADGroup 'Integration_Test' - { - GroupName = $Node.Group4_Name - GroupScope = $Node.Group4_Scope - - Credential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @( - $Node.AdministratorUserName, - (ConvertTo-SecureString -String $Node.AdministratorPassword -AsPlainText -Force) - ) - } - } -} - -<# - .SYNOPSIS - Remove a group. -#> -Configuration MSFT_ADGroup_RemoveGroup4_Config -{ - Import-DscResource -ModuleName 'ActiveDirectoryDsc' + $testName = 'MembersToInclude' node $AllNodes.NodeName { ADGroup 'Integration_Test' { - Ensure = 'Absent' - GroupName = $Node.Group4_Name - - Credential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @( - $Node.AdministratorUserName, - (ConvertTo-SecureString -String $Node.AdministratorPassword -AsPlainText -Force) - ) + GroupName = $Node.Tests.$testName.GroupName + MembersToInclude = $Node.Tests.$testName.MembersToInclude + PsDscRunAsCredential = $adminCredential } } } <# .SYNOPSIS - Restore a group with scope domain local from recycle bin. - - .NOTES - This restores a group with the scope domain local so that the test - will generate an error if the restore does not work instead a new group - is created. If a new group is created it will be created using default - value of scope with is Global, and the test will fail on the group - having the wrong scope. - - For this to work the Recycle Bin must be enabled prior to - running this test. + Exclude members in an AD Group. #> -Configuration MSFT_ADGroup_RestoreGroup4_Config +Configuration MSFT_ADGroup_MembersToExclude_Config { Import-DscResource -ModuleName 'ActiveDirectoryDsc' - node $AllNodes.NodeName - { - ADGroup 'Integration_Test' - { - Ensure = 'Present' - GroupName = $Node.Group4_Name - RestoreFromRecycleBin = $true - - Credential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @( - $Node.AdministratorUserName, - (ConvertTo-SecureString -String $Node.AdministratorPassword -AsPlainText -Force) - ) - } - } -} - -<# - .SYNOPSIS - Change existing domain local group to global group. -#> -Configuration MSFT_ADGroup_ChangeScopeGroup4_Config -{ - Import-DscResource -ModuleName 'ActiveDirectoryDsc' + $testName = 'MembersToExclude' node $AllNodes.NodeName { ADGroup 'Integration_Test' { - Ensure = 'Present' - GroupName = $Node.Group4_Name - GroupScope = 'Global' - - Credential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @( - $Node.AdministratorUserName, - (ConvertTo-SecureString -String $Node.AdministratorPassword -AsPlainText -Force) - ) + GroupName = $Node.Tests.$testName.GroupName + MembersToExclude = $Node.Tests.$testName.MembersToExclude + PsDscRunAsCredential = $adminCredential } } } <# .SYNOPSIS - Update an existing group. + Remove all members from an AD Group. #> -Configuration MSFT_ADGroup_UpdateGroup1_Config +Configuration MSFT_ADGroup_RemoveAllMembersFromGroup_Config { Import-DscResource -ModuleName 'ActiveDirectoryDsc' - node $AllNodes.NodeName - { - ADGroup 'Integration_Test' - { - Ensure = 'Present' - GroupName = $Node.Group1_Name - Path = 'CN=Computers,{0}' -f $Node.DomainDistinguishedName - DisplayName = 'DSC Group 1' - Description = 'A DSC description' - Notes = 'Notes for this group' - ManagedBy = 'CN=Administrator,CN=Users,{0}' -f $Node.DomainDistinguishedName - Members = @( - 'Administrator', - 'Guest' - ) - - Credential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @( - $Node.AdministratorUserName, - (ConvertTo-SecureString -String $Node.AdministratorPassword -AsPlainText -Force) - ) - } - } -} - -<# - .SYNOPSIS - Add a universal distribution group with one member. -#> -Configuration MSFT_ADGroup_CreateGroup5_Config -{ - Import-DscResource -ModuleName 'ActiveDirectoryDsc' + $testName = 'RemoveAllMembersFromGroup' node $AllNodes.NodeName { ADGroup 'Integration_Test' { - GroupName = $Node.Group5_Name - GroupScope = $Node.Group5_Scope - Category = $Node.Group5_Category - - Members = @( - 'Administrator' - ) - - Credential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @( - $Node.AdministratorUserName, - (ConvertTo-SecureString -String $Node.AdministratorPassword -AsPlainText -Force) - ) + GroupName = $Node.Tests.$testName.GroupName + Members = $Node.Tests.$testName.Members + PsDscRunAsCredential = $adminCredential } } } <# .SYNOPSIS - Add and remove members from a group. + Remove an AD Group. #> -Configuration MSFT_ADGroup_ModifyMembersGroup5_Config +Configuration MSFT_ADGroup_RemoveGroup_Config { Import-DscResource -ModuleName 'ActiveDirectoryDsc' - node $AllNodes.NodeName - { - ADGroup 'Integration_Test' - { - GroupName = $Node.Group5_Name - - MembersToInclude = @( - 'Guest' - ) - - MembersToExclude = @( - 'Administrator' - ) - - Credential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @( - $Node.AdministratorUserName, - (ConvertTo-SecureString -String $Node.AdministratorPassword -AsPlainText -Force) - ) - } - } -} - -<# - .SYNOPSIS - Enforce members in a group. -#> -Configuration MSFT_ADGroup_EnforceMembersGroup5_Config -{ - Import-DscResource -ModuleName 'ActiveDirectoryDsc' + $testName = 'RemoveGroup' node $AllNodes.NodeName { ADGroup 'Integration_Test' { - GroupName = $Node.Group5_Name - Members = @( - 'Administrator' - 'Guest' - ) - - Credential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @( - $Node.AdministratorUserName, - (ConvertTo-SecureString -String $Node.AdministratorPassword -AsPlainText -Force) - ) - } - } -} - -<# - .SYNOPSIS - Enforce no members in a group. - - .NOTES - Regression test for issue #189. -#> -Configuration MSFT_ADGroup_ClearMembersGroup5_Config -{ - Import-DscResource -ModuleName 'ActiveDirectoryDsc' - - node $AllNodes.NodeName - { - ADGroup 'Integration_Test' - { - GroupName = $Node.Group5_Name - Members = @() - - Credential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @( - $Node.AdministratorUserName, - (ConvertTo-SecureString -String $Node.AdministratorPassword -AsPlainText -Force) - ) - } - } -} - -<# - .SYNOPSIS - Cleanup everything -#> -Configuration MSFT_ADGroup_Cleanup_Config -{ - Import-DscResource -ModuleName 'ActiveDirectoryDsc' - - node $AllNodes.NodeName - { - ADGroup 'RemoveGroup1' - { - Ensure = 'Absent' - GroupName = $Node.Group1_Name - - Credential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @( - $Node.AdministratorUserName, - (ConvertTo-SecureString -String $Node.AdministratorPassword -AsPlainText -Force) - ) - } - - ADGroup 'RemoveGroup2' - { - Ensure = 'Absent' - GroupName = $Node.Group2_Name - - Credential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @( - $Node.AdministratorUserName, - (ConvertTo-SecureString -String $Node.AdministratorPassword -AsPlainText -Force) - ) - } - - ADGroup 'RemoveGroup3' - { - Ensure = 'Absent' - GroupName = $Node.Group3_Name - - Credential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @( - $Node.AdministratorUserName, - (ConvertTo-SecureString -String $Node.AdministratorPassword -AsPlainText -Force) - ) - } - - ADGroup 'RemoveGroup4' - { - Ensure = 'Absent' - GroupName = $Node.Group4_Name - - Credential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @( - $Node.AdministratorUserName, - (ConvertTo-SecureString -String $Node.AdministratorPassword -AsPlainText -Force) - ) - } - - ADGroup 'RemoveGroup5' - { - Ensure = 'Absent' - GroupName = $Node.Group5_Name - - Credential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @( - $Node.AdministratorUserName, - (ConvertTo-SecureString -String $Node.AdministratorPassword -AsPlainText -Force) - ) + GroupName = $Node.Tests.$testName.GroupName + Ensure = $Node.Tests.$testName.Ensure + PsDscRunAsCredential = $adminCredential } } } diff --git a/tests/Unit/ActiveDirectoryDsc.Common.Tests.ps1 b/tests/Unit/ActiveDirectoryDsc.Common.Tests.ps1 index f5c2cb7b8..e414f6340 100644 --- a/tests/Unit/ActiveDirectoryDsc.Common.Tests.ps1 +++ b/tests/Unit/ActiveDirectoryDsc.Common.Tests.ps1 @@ -106,53 +106,53 @@ InModuleScope 'ActiveDirectoryDsc.Common' { } Describe 'ActiveDirectoryDsc.Common\Remove-DuplicateMembers' { - It 'Should removes one duplicate' { + It 'Should remove one duplicate' { $members = Remove-DuplicateMembers -Members 'User1', 'User2', 'USER1' - $members -is [System.String[]] | Should -BeTrue $members.Count | Should -Be 2 $members -contains 'User1' | Should -BeTrue $members -contains 'User2' | Should -BeTrue + $members -is [System.Array] | Should -BeTrue } - It 'Should removes two duplicates' { + It 'Should remove two duplicates' { $members = Remove-DuplicateMembers -Members 'User1', 'User2', 'USER1', 'USER2' - $members -is [System.String[]] | Should -BeTrue $members.Count | Should -Be 2 $members -contains 'User1' | Should -BeTrue $members -contains 'User2' | Should -BeTrue + $members -is [System.Array] | Should -BeTrue } - It 'Should removes double duplicates' { + It 'Should remove double duplicates' { $members = Remove-DuplicateMembers -Members 'User1', 'User2', 'USER1', 'user1' - $members -is [System.String[]] | Should -BeTrue $members.Count | Should -Be 2 $members -contains 'User1' | Should -BeTrue $members -contains 'User2' | Should -BeTrue + $members -is [System.Array] | Should -BeTrue } It 'Should return a string array with one one entry' { $members = Remove-DuplicateMembers -Members 'User1', 'USER1', 'user1' - $members -is [System.String[]] | Should -BeTrue $members.Count | Should -Be 1 $members -contains 'User1' | Should -BeTrue + $members -is [System.Array] | Should -BeTrue } - It 'Should return empty collection when passed a $null value' { + It 'Should return an empty collection when passed a $null value' { $members = Remove-DuplicateMembers -Members $null - $members -is [System.String[]] | Should -BeTrue $members.Count | Should -Be 0 + $members -is [System.Array] | Should -BeTrue } - It 'Should return empty collection when passed an empty collection' { + It 'Should return an empty collection when passed an empty collection' { $members = Remove-DuplicateMembers -Members @() - $members -is [System.String[]] | Should -BeTrue $members.Count | Should -Be 0 + $members -is [System.Array] | Should -BeTrue } } @@ -309,28 +309,78 @@ InModuleScope 'ActiveDirectoryDsc.Common' { } Describe 'ActiveDirectoryDsc.Common\Assert-MemberParameters' { - It 'Should throws if both parameters Members and MembersToInclude are specified' { - { - Assert-MemberParameters -Members @('User1') -MembersToInclude @('User2') - } | Should -Throw ($script:localizedData.MembersAndIncludeExcludeError -f 'Members', 'MembersToInclude', 'MembersToExclude') + Context 'When only the Members parameter is specified' { + BeforeAll { + $assertMemberParameters = @{ + Members = 'User1' + } + } + It 'Should not throw' { + { Assert-MemberParameters @AssertMemberParameters } | Should -Not -Throw + } } - It 'Should throw if both parameters Members and MembersToExclude are specified' { - { - Assert-MemberParameters -Members @('User1') -MembersToExclude @('User2') - } | Should -Throw ($script:localizedData.MembersAndIncludeExcludeError -f 'Members', 'MembersToInclude', 'MembersToExclude') + Context 'When both the Members and MembersToInclude parameters are specified' { + BeforeAll { + $assertMemberParameters = @{ + Members = 'User1', 'User2' + MembersToInclude = 'User2', 'User3' + } + $expectedError = ($script:localizedData.MembersAndIncludeExcludeError -f + 'Members', 'MembersToInclude', 'MembersToExclude') + } + + It 'Should throw the expected error' { + { Assert-MemberParameters @AssertMemberParameters } | + Should -Throw $expectedError + } } - It 'Should throws if the both parameters MembersToInclude and MembersToExclude contain the same member' { - { - Assert-MemberParameters -MembersToExclude @('user1') -MembersToInclude @('USER1') - } | Should -Throw ($errorMessage = $script:localizedData.IncludeAndExcludeConflictError -f 'user1', 'MembersToInclude', 'MembersToExclude') + Context 'When both the Members and MembersToExclude parameters are specified' { + BeforeAll { + $assertMemberParameters = @{ + Members = 'User1', 'User2' + MembersToExclude = 'User3', 'User4' + } + $expectedError = ($script:localizedData.MembersAndIncludeExcludeError -f + 'Members', 'MembersToInclude', 'MembersToExclude') + } + + It 'Should throw the expected error' { + { Assert-MemberParameters @AssertMemberParameters } | + Should -Throw $expectedError + } } - It 'Should throw if both parameters MembersToInclude and MembersToExclude contains no members (are empty)' { - { - Assert-MemberParameters -MembersToExclude @() -MembersToInclude @() - } | Should -Throw ($script:localizedData.IncludeAndExcludeAreEmptyError -f 'MembersToInclude', 'MembersToExclude') + Context 'When both the MembersToInclude and MembersToExclude parameters contain different members' { + BeforeAll { + $assertMemberParameters = @{ + MembersToInclude = 'User1', 'User2' + MembersToExclude = 'User3', 'User4' + } + } + + It 'Should not throw' { + { Assert-MemberParameters @AssertMemberParameters } | + Should -Not -Throw + } + } + + Context 'When both the MembersToInclude and MembersToExclude parameters contain the same member' { + BeforeAll { + $testMember = 'user1' + $assertMemberParameters = @{ + MembersToInclude = $testMember + MembersToExclude = $testMember + } + $expectedError = ($script:localizedData.IncludeAndExcludeConflictError -f + $testMember, 'MembersToInclude', 'MembersToExclude') + } + + It 'Should throw the expected error' { + { Assert-MemberParameters @AssertMemberParameters } | + Should -Throw $expectedError + } } } diff --git a/tests/Unit/MSFT_ADGroup.Tests.ps1 b/tests/Unit/MSFT_ADGroup.Tests.ps1 index 5f77cefd9..0ffe81926 100644 --- a/tests/Unit/MSFT_ADGroup.Tests.ps1 +++ b/tests/Unit/MSFT_ADGroup.Tests.ps1 @@ -40,7 +40,7 @@ try GroupName = 'TestGroup' GroupScope = 'Global' Category = 'Security' - Path = 'OU=Fake,DC=contoso,DC=com' + Path = 'OU=OU,DC=contoso,DC=com' Description = 'Test AD group description' DisplayName = 'Test display name' Ensure = 'Present' @@ -48,64 +48,26 @@ try ManagedBy = 'CN=User 1,CN=Users,DC=contoso,DC=com' } - $testAbsentParams = $testPresentParams.Clone() - $testAbsentParams['Ensure'] = 'Absent' - $testPresentParamsMultiDomain = $testPresentParams.Clone() - $testPresentParamsMultiDomain.MembershipAttribute = 'DistinguishedName' - - $fakeADGroup = @{ - Name = $testPresentParams.GroupName - Identity = $testPresentParams.GroupName - GroupScope = $testPresentParams.GroupScope - GroupCategory = $testPresentParams.Category - DistinguishedName = "CN=$($testPresentParams.GroupName),$($testPresentParams.Path)" - Description = $testPresentParams.Description - DisplayName = $testPresentParams.DisplayName - ManagedBy = $testPresentParams.ManagedBy - Info = $testPresentParams.Notes - } - - $fakeADUser1 = [PSCustomObject] @{ - DistinguishedName = 'CN=User 1,CN=Users,DC=contoso,DC=com' - ObjectGUID = 'a97cc867-0c9e-4928-8387-0dba0c883b8e' - SamAccountName = 'USER1' - SID = 'S-1-5-21-1131554080-2861379300-292325817-1106' - } - $fakeADUser2 = [PSCustomObject] @{ - DistinguishedName = 'CN=User 2,CN=Users,DC=contoso,DC=com' - ObjectGUID = 'a97cc867-0c9e-4928-8387-0dba0c883b8f' - SamAccountName = 'USER2' - SID = 'S-1-5-21-1131554080-2861379300-292325817-1107' - } - $fakeADUser3 = [PSCustomObject] @{ - DistinguishedName = 'CN=User 3,CN=Users,DC=contoso,DC=com' - ObjectGUID = 'a97cc867-0c9e-4928-8387-0dba0c883b90' - SamAccountName = 'USER3' - SID = 'S-1-5-21-1131554080-2861379300-292325817-1108' - } - $fakeADUser4 = [PSCustomObject] @{ - DistinguishedName = 'CN=User 4,CN=Users,DC=sub,DC=contoso,DC=com' - ObjectGUID = 'ebafa34e-b020-40cd-8652-ee7286419869' - SamAccountName = 'USER4' - SID = 'S-1-5-21-1131554080-2861379300-292325817-1109' - } + $mockGroupName = 'TestGroup' + $mockGroupPath = 'OU=Test,DC=contoso,DC=com' + $mockGroupDN = "CN=$mockGroupName,$mockGroupPath" - $fakeADGroupMembersAsADObjects = @( - [PSCustomObject] @{ + $mockADGroupMembersAsADObjects = @( + @{ DistinguishedName = 'CN=User 1,CN=Users,DC=contoso,DC=com' ObjectGUID = 'a97cc867-0c9e-4928-8387-0dba0c883b8e' SamAccountName = 'USER1' ObjectSID = 'S-1-5-21-1131554080-2861379300-292325817-1106' ObjectClass = 'user' } - [PSCustomObject] @{ + @{ DistinguishedName = 'CN=Group 1,CN=Users,DC=contoso,DC=com' ObjectGUID = 'e2328767-2673-40b2-b3b7-ce9e6511df06' SamAccountName = 'GROUP1' ObjectSID = 'S-1-5-21-1131554080-2861379300-292325817-1206' ObjectClass = 'group' } - [PSCustomObject] @{ + @{ DistinguishedName = 'CN=Computer 1,CN=Users,DC=contoso,DC=com' ObjectGUID = '42f9d607-0934-4afc-bb91-bdf93e07cbfc' SamAccountName = 'COMPUTER1' @@ -113,7 +75,7 @@ try ObjectClass = 'computer' } # This entry is used to represent a group member from a one-way trusted domain - [PSCustomObject] @{ + @{ DistinguishedName = 'CN=S-1-5-21-8562719340-2451078396-046517832-2106,CN=ForeignSecurityPrincipals,DC=contoso,DC=com' ObjectGUID = '6df78e9e-c795-4e67-a626-e17f1b4a0d8b' SamAccountName = 'ADATUM\USER1' @@ -122,1184 +84,1454 @@ try } ) - $testDomainController = 'TESTDC' - - $testCredentials = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @( - 'DummyUser', - (ConvertTo-SecureString -String 'DummyPassword' -AsPlainText -Force) - ) - - #region Function Get-TargetResource - Describe 'ADGroup\Get-TargetResource' { - Mock -CommandName Assert-Module -ParameterFilter { - $ModuleName -eq 'ActiveDirectory' - } - - It 'Calls "Assert-Module" to check AD module is installed' { - Mock -CommandName Get-ADGroup -MockWith { - return $fakeADGroup - } - - Mock -CommandName Get-ADGroupMember -MockWith { - return @( - $fakeADUser1, - $fakeADUser2 - ) - } - - $null = Get-TargetResource @testPresentParams - - Assert-MockCalled -CommandName Assert-Module -ParameterFilter { - $ModuleName -eq 'ActiveDirectory' - } -Scope It - } - - It 'Returns the correct values when group exists' { - Mock -CommandName Get-ADGroup -MockWith { - return $fakeADGroup - } - - Mock -CommandName Get-ADGroupMember -MockWith { - return @( - $fakeADUser1, - $fakeADUser2 - ) - } - - $result = Get-TargetResource @testPresentParams - $result.Ensure | Should -Be 'Present' - $result.GroupName | Should -Be $fakeADGroup.Name - $result.GroupScope | Should -Be $fakeADGroup.GroupScope - $result.Category | Should -Be $fakeADGroup.GroupCategory - $result.Path | Should -Be 'OU=Fake,DC=contoso,DC=com' - $result.Description | Should -Be $fakeADGroup.Description - $result.DisplayName | Should -Be $fakeADGroup.DisplayName - $result.Members | Should -HaveCount 2 - $result.Members | Should -Contain $fakeADUser1.SamAccountName - $result.Members | Should -Contain $fakeADUser2.SamAccountName - $result.MembersToInclude | Should -BeNullOrEmpty - $result.MembersToExclude | Should -BeNullOrEmpty - $result.MembershipAttribute | Should -Be 'SamAccountName' - $result.ManagedBy | Should -Be $fakeADGroup.ManagedBy - $result.Notes | Should -Be $fakeADGroup.Info - $result.DistinguishedName | Should -Be $fakeADGroup.DistinguishedName - } - - It "Returns the correct 'Members' when 'MembershipAttribute' is 'SID' and 'Get-ADGroupMember' fails due to one-way trust" { - Mock -CommandName Get-ADGroup -MockWith { - $fakeADGroup['Members'] = $fakeADGroupMembersAsADObjects.DistinguishedName - return [PSCustomObject] $fakeADGroup - } - - Mock -CommandName Get-ADGroupMember -MockWith { - $errorMessage = 'Get-ADGroupMember' - $errorId = 'ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.GetADGroupMember' - Write-Error -Message $errorMessage -ErrorId $errorId -ErrorAction Stop - } - - $script:getADObjectCallCount = 0 - Mock -CommandName Get-ADObject -MockWith { - $memberADObject = $fakeADGroupMembersAsADObjects[$script:getADObjectCallCount] - $script:getADObjectCallCount++ - return $memberADObject - } - - $membersParamSplat = @{ - Members = $fakeADGroupMembersAsADObjects.ObjectSID - MembershipAttribute = 'SID' - } - - $result = Get-TargetResource @testPresentParams @membersParamSplat - $result.Members | Should -HaveCount $fakeADGroupMembersAsADObjects.Count - $fakeADGroupMembersAsADObjects.ObjectSID | ForEach-Object -Process { - $result.Members | Should -Contain $_ - } - } - - It 'Returns the correct values when group does not exist' { - Mock -CommandName Get-ADGroup -MockWith { - throw New-Object Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException - } - - $result = Get-TargetResource @testPresentParams - $result.Ensure | Should -Be 'Absent' - $result.GroupName | Should -Be $testPresentParams.GroupName - $result.GroupScope | Should -BeNullOrEmpty - $result.Category | Should -BeNullOrEmpty - $result.Path | Should -BeNullOrEmpty - $result.Description | Should -BeNullOrEmpty - $result.DisplayName | Should -BeNullOrEmpty - $result.Members | Should -BeNullOrEmpty - $result.MembersToInclude | Should -BeNullOrEmpty - $result.MembersToExclude | Should -BeNullOrEmpty - $result.MembershipAttribute | Should -Be 'SamAccountName' - $result.ManagedBy | Should -BeNullOrEmpty - $result.Notes | Should -BeNullOrEmpty - $result.DistinguishedName | Should -BeNullOrEmpty - } - - - It "Calls 'Get-ADGroup' with 'Server' parameter when 'DomainController' specified" { - Mock -CommandName Get-ADGroup -ParameterFilter { - $Server -eq $testDomainController - } -MockWith { - return $fakeADGroup - } - - Mock -CommandName Get-ADGroupMember -MockWith { - return @( - $fakeADUser1, - $fakeADUser2 - ) - } - - Get-TargetResource @testPresentParams -DomainController $testDomainController - - Assert-MockCalled -CommandName Get-ADGroup -ParameterFilter { - $Server -eq $testDomainController - } -Scope It - } - - It "Calls 'Get-ADGroup' with 'Credential' parameter when specified" { - Mock -CommandName Get-ADGroup -ParameterFilter { - $Credential -eq $testCredentials - } -MockWith { - return $fakeADGroup - } - - Mock -CommandName Get-ADGroupMember -MockWith { - return @( - $fakeADUser1, - $fakeADUser2 - ) - } - - Get-TargetResource @testPresentParams -Credential $testCredentials - - Assert-MockCalled -CommandName Get-ADGroup -ParameterFilter { - $Credential -eq $testCredentials - } -Scope It - } - - It "Calls 'Get-ADGroupMember' with 'Server' parameter when 'DomainController' specified" { - Mock -CommandName Get-ADGroup -MockWith { - return $fakeADGroup - } - - Mock -CommandName Get-ADGroupMember -ParameterFilter { - $Server -eq $testDomainController - } -MockWith { - return @( - $fakeADUser1, - $fakeADUser2 - ) - } - - Get-TargetResource @testPresentParams -DomainController $testDomainController - - Assert-MockCalled -CommandName Get-ADGroupMember -ParameterFilter { - $Server -eq $testDomainController - } -Scope It - } - - It "Calls 'Get-ADGroupMember' with 'Credential' parameter when specified" { - Mock -CommandName Get-ADGroup -MockWith { - return $fakeADGroup - } - - Mock -CommandName Get-ADGroupMember -ParameterFilter { - $Credential -eq $testCredentials - } -MockWith { - return @( - $fakeADUser1, - $fakeADUser2 - ) - } - - Get-TargetResource @testPresentParams -Credential $testCredentials - - Assert-MockCalled -CommandName Get-ADGroupMember -ParameterFilter { - $Credential -eq $testCredentials - } -Scope It - } - - It "Calls 'Get-ADObject' when 'Get-ADGroupMember' fails due to one-way trust" { - Mock -CommandName Get-ADGroup -MockWith { - $fakeADGroup['Members'] = $fakeADGroupMembersAsADObjects.DistinguishedName - return [PSCustomObject] $fakeADGroup - } - - Mock -CommandName Get-ADGroupMember -MockWith { - $errorMessage = 'Get-ADGroupMember' - $errorId = 'ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.GetADGroupMember' - Write-Error -Message $errorMessage -ErrorId $errorId -ErrorAction Stop - } - - $script:getADObjectCallCount = 0 - Mock -CommandName Get-ADObject -MockWith { - $memberADObject = $fakeADGroupMembersAsADObjects[$script:getADObjectCallCount] - $script:getADObjectCallCount++ - return $memberADObject - } + $mockADGroup = @{ + GroupName = $mockGroupName + GroupScope = 'Global' + GroupCategory = 'Security' + Path = $mockGroupPath + Description = 'Test AD group description' + DisplayName = 'Test display name' + Info = 'This is a test AD group' + ManagedBy = 'CN=User 1,CN=Users,DC=contoso,DC=com' + DistinguishedName = "CN=$mockGroupName,$mockGroupPath" + Members = $mockADGroupMembersAsADObjects.SamAccountName + } - Get-TargetResource @testPresentParams -MembershipAttribute DistinguishedName + $mockADGroupChanged = @{ + GroupScope = 'Universal' + Description = 'Test AD group description changed' + DisplayName = 'Test display name changed' + ManagedBy = 'CN=User 2,CN=Users,DC=contoso,DC=com' + } - Assert-MockCalled -CommandName Get-ADObject -Scope It - } + $mockGetTargetResourceResults = @{ + GroupName = $mockADGroup.GroupName + GroupScope = $mockADGroup.GroupScope + Category = $mockADGroup.GroupCategory + Path = $mockADGroup.Path + Description = $mockADGroup.Description + DisplayName = $mockADGroup.DisplayName + Notes = $mockADGroup.Info + ManagedBy = $mockADGroup.ManagedBy + DistinguishedName = $mockADGroup.DistinguishedName + Members = $mockADGroup.Members + Ensure = 'Present' + } - $itCallsResolveSamAccountNameString = "Calls 'Resolve-SamAccountName' when " + (@( - "'MembershipAttribute' is 'SamAccountName'" - "at least 1 group member has an 'ObjectClass' of 'foreignSecurityPrincipal'" - "and 'Get-ADGroupMember' fails due to one-way trust" - ) -join ', ') + $mockGetTargetResourceResultsAbsent = @{ + GroupName = $mockADGroup.GroupName + GroupScope = $null + GroupCategory = $null + Path = $null + Description = $null + DisplayName = $null + Notes = $null + ManagedBy = $null + DistinguishedName = $null + Members = @() + Ensure = 'Absent' + } - It $itCallsResolveSamAccountNameString { - Mock -CommandName Get-ADGroup -MockWith { - $fakeADGroup['Members'] = $fakeADGroupMembersAsADObjects.DistinguishedName - return [PSCustomObject] $fakeADGroup - } + $testDomainController = 'TESTDC' - Mock -CommandName Get-ADGroupMember -MockWith { - $errorMessage = 'Get-ADGroupMember' - $errorId = 'ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.GetADGroupMember' - Write-Error -Message $errorMessage -ErrorId $errorId -ErrorAction Stop - } + $testCredential = New-Object -TypeName 'System.Management.Automation.PSCredential' ` + -ArgumentList 'DummyUser', (ConvertTo-SecureString -String 'DummyPassword' -AsPlainText -Force) - $script:getADObjectCallCount = 0 - Mock -CommandName Get-ADObject -MockWith { - $memberADObject = $fakeADGroupMembersAsADObjects[$script:getADObjectCallCount] - $script:getADObjectCallCount++ - return $memberADObject - } + Describe 'ADGroup\Get-TargetResource' -Tag 'Get' { + BeforeAll { + Mock -CommandName Assert-Module - Mock -CommandName Resolve-SamAccountName -MockWith { - return $fakeADGroupMembersAsADObjects[($script:getADObjectCallCount - 1)].SamAccountName + $getTargetResourceParameters = @{ + GroupName = $mockADGroup.GroupName } - - Get-TargetResource @testPresentParams -MembershipAttribute SamAccountName - - Assert-MockCalled -CommandName Resolve-SamAccountName -Scope It } - It "Throws the correct error when 'Get-ADGroupMember' fails due to an unrecognized 'FullyQualifiedErrorId'" { - Mock -CommandName Get-ADGroup -MockWith { - return $fakeADGroup + Context 'When the resource is Present' { + BeforeAll { + Mock -CommandName Get-ADGroup -MockWith { $mockADGroup } + Mock -CommandName Get-ADGroupMember -MockWith { $mockADGroupMembersAsADObjects } + } + + It 'Should return the correct result' { + $result = Get-TargetResource @getTargetResourceParameters + + $result.Ensure | Should -Be 'Present' + $result.GroupName | Should -Be $mockADGroup.Name + $result.GroupScope | Should -Be $mockADGroup.GroupScope + $result.Category | Should -Be $mockADGroup.GroupCategory + $result.Path | Should -Be $mockADGroup.Path + $result.Description | Should -Be $mockADGroup.Description + $result.DisplayName | Should -Be $mockADGroup.DisplayName + $result.MembersToInclude | Should -BeNullOrEmpty + $result.MembersToExclude | Should -BeNullOrEmpty + $result.MembershipAttribute | Should -Be 'SamAccountName' + $result.ManagedBy | Should -Be $mockADGroup.ManagedBy + $result.Notes | Should -Be $mockADGroup.Info + $result.DistinguishedName | Should -Be $mockADGroup.DistinguishedName + $result.Members | Should -HaveCount $mockADGroupMembersAsADObjects.count + foreach ($member in $mockADGroupMembersAsADObjects) + { + $result.Members | Should -Contain $member.SamAccountName + } } - Mock -CommandName Get-ADGroupMember -MockWith { - $errorMessage = 'Get-ADGroupMember' - $errorId = (New-Guid).Guid - Write-Error -Message $errorMessage -ErrorId $errorId -ErrorAction Stop + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Assert-Module ` + -ParameterFilter { $ModuleName -eq 'ActiveDirectory' } + Assert-MockCalled -CommandName Get-ADGroup ` + -ParameterFilter { $Identity -eq $getTargetResourceParameters.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Get-ADGroupMember ` + -ParameterFilter { $Identity -eq $getTargetResourceParameters.GroupName } ` + -Exactly -Times 1 } - { Get-TargetResource @testPresentParams } | - Should -Throw ($script:localizedData.RetrievingGroupMembersError -f - $fakeADGroup.Name) - } - - } - #end region - - #region Function Test-TargetResource - Describe 'ADGroup\Test-TargetResource' { - Mock -CommandName Assert-Module -ParameterFilter { - $ModuleName -eq 'ActiveDirectory' - } - - foreach ($attribute in @('SamAccountName', 'DistinguishedName', 'ObjectGUID', 'SID')) - { - It "Passes when group 'Members' match using '$attribute'" { - Mock -CommandName Get-ADGroup -MockWith { - return $fakeADGroup + Context 'When the "Credential" parameter is specified' { + It 'Should not throw' { + { Get-TargetResource @getTargetResourceParameters -Credential $testCredential } | + Should -Not -Throw } - Mock -CommandName Get-ADGroupMember -MockWith { - return @( - $fakeADUser1, - $fakeADUser2 - ) + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-ADGroup ` + -ParameterFilter { ` + $Identity -eq $getTargetResourceParameters.GroupName -and ` + $Credential -eq $testCredential } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Get-ADGroupMember ` + -ParameterFilter { ` + $Identity -eq $getTargetResourceParameters.GroupName -and ` + $Credential -eq $testCredential } ` + -Exactly -Times 1 } - - $targetResource = Test-TargetResource @testPresentParams -Members $fakeADUser1.$attribute, $fakeADUser2.$attribute -MembershipAttribute $attribute - - $targetResource | Should -BeTrue } - It "Fails when group membership counts do not match using '$attribute'" { - Mock -CommandName Get-ADGroup -MockWith { - return $fakeADGroup + Context 'When the "DomainController" parameter is specified' { + It 'Should not throw' { + { Get-TargetResource @getTargetResourceParameters -DomainController $testDomainController } | + Should -Not -Throw } - Mock -CommandName Get-ADGroupMember -MockWith { - return @( - $fakeADUser1 - ) + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-ADGroup ` + -ParameterFilter { ` + $Identity -eq $getTargetResourceParameters.GroupName -and ` + $Server -eq $testDomainController } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Get-ADGroupMember ` + -ParameterFilter { ` + $Identity -eq $getTargetResourceParameters.GroupName -and ` + $Server -eq $testDomainController } ` + -Exactly -Times 1 } - - $targetResource = Test-TargetResource @testPresentParams -Members $fakeADUser2.$attribute, $fakeADUser3.$attribute -MembershipAttribute $attribute - - $targetResource | Should -BeFalse } - It "Fails when group 'Members' do not match using '$attribute'" { - Mock -CommandName Get-ADGroup -MockWith { - return $fakeADGroup - } + Context "When 'Get-ADGroupMember' fails due to one-way trust" { + BeforeAll { + $oneWayTrustFullyQualifiedErrorId = ` + 'ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.GetADGroupMember' - Mock -CommandName Get-ADGroupMember -MockWith { - return @( - $fakeADUser1, - $fakeADUser2 - ) + Mock -CommandName Get-ADGroupMember -MockWith { throw $oneWayTrustFullyQualifiedErrorId } + Mock -CommandName Get-ADObject -MockWith { + $memberADObject = $mockADGroupMembersAsADObjects[$script:getADObjectCallCount] + $script:getADObjectCallCount++ + return $memberADObject + } + Mock -CommandName Resolve-SamAccountName ` + -MockWith { $mockADGroupMembersAsADObjects[$script:getADObjectCallCount - 1].SamAccountName } } - $targetResource = Test-TargetResource @testPresentParams -Members $fakeADUser2.$attribute, $fakeADUser3.$attribute -MembershipAttribute $attribute + Context "When 'MembershipAttribute' is 'SamAccountName'" { + BeforeAll { + $script:getADObjectCallCount = 0 + } - $targetResource | Should -BeFalse - } + It 'Should return the correct result' { + $result = Get-TargetResource @getTargetResourceParameters -MembershipAttribute SamAccountName - It "Passes when specified 'MembersToInclude' match using '$attribute'" { - Mock -CommandName Get-ADGroup -MockWith { - return $fakeADGroup - } + $result.Members | Should -HaveCount $mockADGroupMembersAsADObjects.Count + foreach ($member in $result.Members) + { + $mockADGroupMembersAsADObjects.SamAccountName | Should -Contain $member + } + } - Mock -CommandName Get-ADGroupMember -MockWith { - return @( - $fakeADUser1, - $fakeADUser2 - ) + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Assert-Module ` + -ParameterFilter { $ModuleName -eq 'ActiveDirectory' } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Get-ADGroup ` + -ParameterFilter { $Identity -eq $getTargetResourceParameters.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Get-ADGroupMember ` + -ParameterFilter { $Identity -eq $getTargetResourceParameters.GroupName } ` + -Exactly -Times 1 + } } - $targetResource = Test-TargetResource @testPresentParams -MembersToInclude $fakeADUser2.$attribute -MembershipAttribute $attribute + Context "When 'MembershipAttribute' is 'SID'" { + BeforeAll { + $script:getADObjectCallCount = 0 + } - $targetResource | Should -BeTrue - } + It 'Should return the correct result' { + $result = Get-TargetResource @getTargetResourceParameters -MembershipAttribute SID - It "Fails when specified 'MembersToInclude' are missing using '$attribute'" { - Mock -CommandName Get-ADGroup -MockWith { - return $fakeADGroup - } + $result.Members | Should -HaveCount $mockADGroupMembersAsADObjects.Count + foreach ($member in $result.Members) + { + $mockADGroupMembersAsADObjects.ObjectSID | Should -Contain $member + } + } - Mock -CommandName Get-ADGroupMember -MockWith { - return @( - $fakeADUser1, - $fakeADUser2 - ) + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Assert-Module ` + -ParameterFilter { $ModuleName -eq 'ActiveDirectory' } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Get-ADGroup ` + -ParameterFilter { $Identity -eq $getTargetResourceParameters.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Get-ADGroupMember ` + -ParameterFilter { $Identity -eq $getTargetResourceParameters.GroupName } ` + -Exactly -Times 1 + } } - - $targetResource = Test-TargetResource @testPresentParams -MembersToInclude $fakeADUser3.$attribute -MembershipAttribute $attribute - - $targetResource | Should -BeFalse } - It "Passes when specified 'MembersToExclude' are missing using '$attribute'" { - Mock -CommandName Get-ADGroup -MockWith { - return $fakeADGroup + Context "When 'Get-ADGroup' throws an exception" { + BeforeAll { + Mock -CommandName Get-ADGroup -MockWith { Throw 'Error' } } - Mock -CommandName Get-ADGroupMember -MockWith { - return @( - $fakeADUser1, - $fakeADUser2 - ) + It 'Should throw the correct error' { + { Get-TargetResource @getTargetResourceParameters } | + Should -Throw ($script:localizedData.RetrievingGroupADGroupError -f $mockADGroup.GroupName) } - - $targetResource = Test-TargetResource @testPresentParams -MembersToExclude $fakeADUser3.$attribute -MembershipAttribute $attribute - - $targetResource | Should -BeTrue } - It "Fails when when specified 'MembersToExclude' match using '$attribute'" { - Mock -CommandName Get-ADGroup -MockWith { - return $fakeADGroup + Context "When 'Get-ADGroupMember' throws an exception" { + BeforeAll { + Mock -CommandName Get-ADGroupMember -MockWith { Throw 'Error' } } - Mock -CommandName Get-ADGroupMember -MockWith { - return @( - $fakeADUser1, - $fakeADUser2 - ) + It 'Should throw the correct error' { + { Get-TargetResource @getTargetResourceParameters } | + Should -Throw ($script:localizedData.RetrievingGroupMembersError -f $mockADGroup.GroupName) } - - $targetResource = Test-TargetResource @testPresentParams -MembersToExclude $fakeADUser2.$attribute -MembershipAttribute $attribute - - $targetResource | Should -BeFalse } - - } #end foreach attribute - - It "Fails when group does not exist and 'Ensure' is 'Present'" { - Mock -CommandName Get-TargetResource -MockWith { - return $testAbsentParams - } - - Test-TargetResource @testPresentParams | Should -BeFalse } - It "Fails when group exists, 'Ensure' is 'Present' but 'Scope' is wrong" { - Mock -CommandName Get-TargetResource -MockWith { - $duffADGroup = $testPresentParams.Clone() - $duffADGroup['GroupScope'] = 'Universal' - - return $duffADGroup - } - - Test-TargetResource @testPresentParams | Should -BeFalse - } - - It "Fails when group exists, 'Ensure' is 'Present' but 'Category' is wrong" { - Mock -CommandName Get-TargetResource -MockWith { - $duffADGroup = $testPresentParams.Clone() - $duffADGroup['Category'] = 'Distribution' - - return $duffADGroup + Context 'When the resource is Absent' { + BeforeAll { + Mock -CommandName Get-ADGroup -MockWith { + throw New-Object Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException + } + Mock -CommandName Get-ADGroupMember } - Test-TargetResource @testPresentParams | Should -BeFalse - } - - It "Fails when group exists, 'Ensure' is 'Present' but 'Path' is wrong" { - Mock -CommandName Get-TargetResource -MockWith { - $duffADGroup = $testPresentParams.Clone() - $duffADGroup['Path'] = 'OU=WrongPath,DC=contoso,DC=com' - - return $duffADGroup + It 'Should return the correct result' { + $result = Get-TargetResource @getTargetResourceParameters + $result.Ensure | Should -Be 'Absent' + $result.GroupName | Should -Be $getTargetResourceParameters.GroupName + $result.GroupScope | Should -BeNullOrEmpty + $result.Category | Should -BeNullOrEmpty + $result.Path | Should -BeNullOrEmpty + $result.Description | Should -BeNullOrEmpty + $result.DisplayName | Should -BeNullOrEmpty + $result.Members | Should -BeNullOrEmpty + $result.MembersToInclude | Should -BeNullOrEmpty + $result.MembersToExclude | Should -BeNullOrEmpty + $result.MembershipAttribute | Should -Be 'SamAccountName' + $result.ManagedBy | Should -BeNullOrEmpty + $result.Notes | Should -BeNullOrEmpty + $result.DistinguishedName | Should -BeNullOrEmpty + } + + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Assert-Module ` + -ParameterFilter { $ModuleName -eq 'ActiveDirectory' } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Get-ADGroup ` + -ParameterFilter { $Identity -eq $getTargetResourceParameters.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Get-ADGroupMember ` + -Exactly -Times 0 } - - Test-TargetResource @testPresentParams | Should -BeFalse } + } - It "Fails when group exists, 'Ensure' is 'Present' but 'Description' is wrong" { - Mock -CommandName Get-TargetResource -MockWith { - $duffADGroup = $testPresentParams.Clone() - $duffADGroup['Description'] = 'Test AD group description is wrong' - - return $duffADGroup - } - - Test-TargetResource @testPresentParams | Should -BeFalse + Describe 'ADGroup\Test-TargetResource' -Tag 'Test' { + BeforeAll { + $testTargetResourceParameters = @{ + GroupName = $mockADGroup.GroupName + GroupScope = $mockADGroup.GroupScope + Category = $mockADGroup.GroupCategory + Path = $mockADGroup.Path + Description = $mockADGroup.Description + DisplayName = $mockADGroup.DisplayName + ManagedBy = $mockADGroup.ManagedBy + Notes = $mockADGroup.Info + Members = $mockADGroup.Members + Ensure = 'Present' + } + + $testTargetResourceParametersAbsent = $testTargetResourceParameters.Clone() + $testTargetResourceParametersAbsent.Ensure = 'Absent' } - It "Fails when group exists, 'Ensure' is 'Present' but 'DisplayName' is wrong" { - Mock -CommandName Get-TargetResource -MockWith { - $duffADGroup = $testPresentParams.Clone() - $duffADGroup['DisplayName'] = 'Wrong display name' - - return $duffADGroup + Context 'When the Resource is Present' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { $mockGetTargetResourceResults } } - Test-TargetResource @testPresentParams | Should -BeFalse - } + Context 'When the Resource should be Present' { + It 'Should not throw' { + { Test-TargetResource @testTargetResourceParameters } | Should -Not -Throw + } - It "Fails when group exists, 'Ensure' is 'Present' but 'ManagedBy' is wrong" { - Mock -CommandName Get-TargetResource -MockWith { - $duffADGroup = $testPresentParams.Clone() - $duffADGroup['ManagedBy'] = $fakeADUser3.DistinguishedName + Context 'When the "Credential" parameter is specified' { + It 'Should not throw' { + { Test-TargetResource @testTargetResourceParameters -Credential $testCredential } | + Should -Not -Throw + } - return $duffADGroup - } + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { ` + $GroupName -eq $testTargetResourceParameters.GroupName -and ` + $Credential -eq $testCredential } ` + -Exactly -Times 1 + } + } - Test-TargetResource @testPresentParams | Should -BeFalse - } + Context 'When the "DomainController" parameter is specified' { + It 'Should not throw' { + { Test-TargetResource @testTargetResourceParameters ` + -DomainController $testDomainController } | Should -Not -Throw + } - It "Fails when group exists, 'Ensure' is 'Present' but 'Notes' is wrong" { - Mock -CommandName Get-TargetResource -MockWith { - $duffADGroup = $testPresentParams.Clone() - $duffADGroup['Notes'] = 'These notes are clearly wrong' + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { ` + $GroupName -eq $testTargetResourceParameters.GroupName -and ` + $DomainController -eq $testDomainController } ` + -Exactly -Times 1 + } + } - return $duffADGroup - } + Context 'When all the resource properties are in the desired state' { + It 'Should return $true' { + Test-TargetResource @testTargetResourceParameters | Should -Be $true + } + } - Test-TargetResource @testPresentParams | Should -BeFalse - } + Context "When the 'MembersToInclude' property is in the desired state" { + It 'Should return $true' { + $testTargetResourceParametersMembersToInclude = $testTargetResourceParameters.Clone() + $testTargetResourceParametersMembersToInclude.Remove('Members') + $testTargetResourceParametersMembersToInclude['MembersToInclude'] = $mockAdGroup.Members[0] - It "Fails when group exists and 'Ensure' is 'Absent'" { - Mock -CommandName Get-TargetResource -MockWith { - return $testPresentParams - } + Test-TargetResource @testTargetResourceParametersMembersToInclude | Should -Be $true + } + } - Test-TargetResource @testAbsentParams | Should -BeFalse - } + Context "When the 'MembersToExclude' property is in the desired state" { + It 'Should return $true' { + $testTargetResourceParametersMembersToExclude = $testTargetResourceParameters.Clone() + $testTargetResourceParametersMembersToExclude.Remove('Members') + $testTargetResourceParametersMembersToExclude['MembersToExclude'] = 'ExcludedUser' - It "Passes when group exists, target matches and 'Ensure' is 'Present'" { - Mock -CommandName Get-TargetResource -MockWith { - return $testPresentParams - } + Test-TargetResource @testTargetResourceParametersMembersToExclude | Should -Be $true + } + } - Test-TargetResource @testPresentParams | Should -BeTrue - } + foreach ($property in $mockAdGroupChanged.Keys) + { + Context "When the '$property' resource property is not in the desired state" { + It 'Should return $false' { + $testTargetResourceParametersChanged = $testTargetResourceParameters.Clone() + $testTargetResourceParametersChanged[$property] = $mockAdGroupChanged.$property - It "Passes when group does not exist and 'Ensure' is 'Absent'" { - Mock -CommandName Get-TargetResource -MockWith { - return $testAbsentParams - } + Test-TargetResource @testTargetResourceParametersChanged | Should -Be $false + } + } + } - Test-TargetResource @testAbsentParams | Should -BeTrue - } + Context "When the 'Members' resource property is not in the desired state" { + It 'Should return $false' { + $testTargetResourceParametersChanged = $testTargetResourceParameters.Clone() + $testTargetResourceParametersChanged['Members'] = 'ChangedUser' - } - #end region + Test-TargetResource @testTargetResourceParametersChanged | Should -Be $false + } + } - #region Function Set-TargetResource - Describe 'ADGroup\Set-TargetResource' { - Mock -CommandName Assert-Module -ParameterFilter { - $ModuleName -eq 'ActiveDirectory' - } + Context "When the 'MemberstoInclude' resource property is not in the desired state" { + It 'Should return $false' { + $testTargetResourceParametersChanged = $testTargetResourceParameters.Clone() + $testTargetResourceParametersChanged.Remove('Members') + $testTargetResourceParametersChanged['MembersToInclude'] = 'NotIncludedUser' - Mock -CommandName Assert-MemberParameters + Test-TargetResource @testTargetResourceParametersChanged | Should -Be $false + } + } - It "Calls 'New-ADGroup' when 'Ensure' is 'Present' and the group does not exist" { - Mock -CommandName Get-ADGroup -MockWith { - throw New-Object Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException - } + Context "When the 'MemberstoExclude' resource property is not in the desired state" { + It 'Should return $false' { + $testTargetResourceParametersChanged = $testTargetResourceParameters.Clone() + $testTargetResourceParametersChanged.Remove('Members') + $testTargetResourceParametersChanged['MembersToExclude'] = $mockADGroup.Members[0] - Mock -CommandName Set-ADGroup - Mock -CommandName New-ADGroup -MockWith { - return [PSCustomObject] $fakeADGroup + Test-TargetResource @testTargetResourceParametersChanged | Should -Be $false + } + } } - Set-TargetResource @testPresentParams - - Assert-MockCalled -CommandName New-ADGroup -Scope It - } - - $testProperties = @{ - Description = 'Test AD Group description is wrong' - ManagedBy = $fakeADUser3.DistinguishedName - DisplayName = 'Test DisplayName' - } - - foreach ($property in $testProperties.Keys) - { - It "Calls 'Set-ADGroup' when 'Ensure' is 'Present' and '$property' is specified" { - Mock -CommandName Set-ADGroup - Mock -CommandName Get-ADGroupMember - Mock -CommandName Get-ADGroup -MockWith { - $duffADGroup = $fakeADGroup.Clone() - $duffADGroup[$property] = $testProperties.$property - - return $duffADGroup + Context 'When the Resource should be Absent' { + It 'Should not throw' { + { Test-TargetResource @testTargetResourceParametersAbsent } | Should -Not -Throw } - Set-TargetResource @testPresentParams + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $GroupName -eq $testTargetResourceParametersAbsent.GroupName } ` + -Exactly -Times 1 + } - Assert-MockCalled -CommandName Set-ADGroup -Scope It -Exactly 1 + It 'Should return $false' { + Test-TargetResource @testTargetResourceParametersAbsent | Should -Be $false + } } } - It "Calls 'Set-ADGroup' when 'Ensure' is 'Present' and 'Category' is specified" { - Mock -CommandName Set-ADGroup -ParameterFilter { - $GroupCategory -eq $testPresentParams.Category + Context 'When the Resource is Absent' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { $mockGetTargetResourceResultsAbsent } } - Mock -CommandName Get-ADGroupMember - Mock -CommandName Get-ADGroup -MockWith { - $duffADGroup = $fakeADGroup.Clone() - $duffADGroup['GroupCategory'] = 'Distribution' - - return $duffADGroup - } - - Set-TargetResource @testPresentParams - - Assert-MockCalled -CommandName Set-ADGroup -ParameterFilter { $GroupCategory -eq $testPresentParams.Category } -Scope It -Exactly 1 - } - - It "Calls 'Set-ADGroup' when 'Ensure' is 'Present' and 'Notes' is specified" { - Mock -CommandName Set-ADGroup -ParameterFilter { - $Replace -ne $null - } + Context 'When the Resource should be Present' { + It 'Should not throw' { + { Test-TargetResource @testTargetResourceParameters } | Should -Not -Throw + } - Mock -CommandName Get-ADGroupMember - Mock -CommandName Get-ADGroup { - $duffADGroup = $fakeADGroup.Clone() - $duffADGroup['Info'] = 'My test note..' + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $GroupName -eq $testTargetResourceParameters.GroupName } ` + -Exactly -Times 1 + } - return $duffADGroup + It 'Should return $false' { + Test-TargetResource @testTargetResourceParameters | Should -Be $false + } } - Set-TargetResource @testPresentParams - - Assert-MockCalled -CommandName Set-ADGroup -ParameterFilter { $Replace -ne $null } -Scope It -Exactly 1 - } + Context 'When the Resource should be Absent' { + It 'Should not throw' { + { Test-TargetResource @testTargetResourceParametersAbsent } | Should -Not -Throw + } - It "Calls 'Set-ADGroup' twice when 'Ensure' is 'Present', the group exists but the 'Scope' has changed" { - Mock -CommandName Set-ADGroup - Mock -CommandName Get-ADGroupMember - Mock -CommandName Get-ADGroup -MockWith { - $duffADGroup = $fakeADGroup.Clone() - $duffADGroup['GroupScope'] = 'DomainLocal' + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $GroupName -eq $testTargetResourceParametersAbsent.GroupName } ` + -Exactly -Times 1 + } - return $duffADGroup + It 'Should return $true' { + Test-TargetResource @testTargetResourceParametersAbsent | Should -Be $true + } } - - Set-TargetResource @testPresentParams - - Assert-MockCalled -CommandName Set-ADGroup -Scope It -Exactly 2 } + } - It "Adds group members when 'Ensure' is 'Present', the group exists and 'Members' are specified" { - Mock -CommandName Get-ADGroup -MockWith { - throw New-Object Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException - } - + Describe 'ADGroup\Set-TargetResource' -Tag 'Set' { + BeforeAll { + $setTargetResourceParameters = @{ + GroupName = $mockADGroup.GroupName + GroupScope = $mockADGroup.GroupScope + Category = $mockADGroup.GroupCategory + Path = $mockADGroup.Path + Description = $mockADGroup.Description + DisplayName = $mockADGroup.DisplayName + ManagedBy = $mockADGroup.ManagedBy + Notes = $mockADGroup.Info + Members = $mockADGroup.Members + Ensure = 'Present' + } + + $setTargetResourceParametersAbsent = $setTargetResourceParameters.Clone() + $setTargetResourceParametersAbsent.Ensure = 'Absent' + + Mock -CommandName New-ADGroup Mock -CommandName Set-ADGroup + Mock -CommandName Remove-ADGroup + Mock -CommandName Move-ADObject Mock -CommandName Set-ADCommonGroupMember - Mock -CommandName New-ADGroup -MockWith { - return [PSCustomObject] $fakeADGroup - } - - Set-TargetResource @testPresentParams -Members @($fakeADUser1.SamAccountName, $fakeADUser2.SamAccountName) - - Assert-MockCalled -CommandName Set-ADCommonGroupMember ` - -ParameterFilter { $Action -eq 'Add' } ` - -Scope It -Exactly -Times 1 + Mock -CommandName Restore-ADCommonObject } - It "Tries to resolve the domain names for all groups in the same domain when the 'MembershipAttribute' property is set to distinguishedName" { - Mock -CommandName Get-ADGroup -MockWith { - throw New-Object Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException - } - - Mock -CommandName Set-ADGroup - Mock -CommandName Set-ADCommonGroupMember - Mock -CommandName New-ADGroup -MockWith { - return [PSCustomObject] $fakeADGroup - } + Context 'When the Resource should be Present' { - Mock -CommandName Get-DomainName -MockWith { - return 'contoso.com' - } - - Mock -CommandName Get-ADDomainNameFromDistinguishedName -MockWith { - return 'contoso.com' - } + Context 'When the Resource is Present' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { $mockGetTargetResourceResults } + } - Mock -CommandName Write-Verbose -ParameterFilter { - $Message -and $Message -match 'Group membership objects are in .* different AD Domains.' - } + foreach ($propertyName in $mockAdGroupChanged.Keys) + { + Context "When the '$propertyName' property has changed" { + BeforeAll { + $setTargetResourceParametersChangedProperty = $setTargetResourceParameters.Clone() + $setTargetResourceParametersChangedProperty.$propertyName = ` + $mockAdGroupChanged.$propertyName + } - Set-TargetResource @testPresentParamsMultiDomain -Members @($fakeADUser1.distinguishedName, $fakeADUser2.distinguishedName) + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | Should -Not -Throw + } - Assert-MockCalled -CommandName Get-ADDomainNameFromDistinguishedName - Assert-MockCalled -CommandName Set-ADCommonGroupMember ` - -ParameterFilter { $Action -eq 'Add' } ` - -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Write-Verbose -ParameterFilter { - $Message -and $Message -match 'Group membership objects are in .* different AD Domains.' - } -Exactly -Times 0 - } + It "Should call the expected mocks" { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { ` + $GroupName -eq $setTargetResourceParametersChangedProperty.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -ParameterFilter { (Get-Variable -Name $propertyName -ValueOnly) -eq ` + $setTargetResourceParametersChangedProperty.$propertyName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Remove-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName New-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Move-ADObject ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -Exactly -Times 0 + } + } + } - It "Tries to resolve the domain names for all groups in different domains when the 'MembershipAttribute' property is set to distinguishedName" { - Mock -CommandName Get-ADGroup -MockWith { - throw New-Object Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException - } + Context "When the `Path' property has changed" { + BeforeAll { + $setTargetResourceParametersChangedProperty = $setTargetResourceParameters.Clone() + $setTargetResourceParametersChangedProperty.Path = 'OU=Changed,DC=contoso,DC=com' + } - Mock -CommandName Set-ADGroup - Mock -CommandName Set-ADCommonGroupMember - Mock -CommandName New-ADGroup -MockWith { - return [PSCustomObject] $fakeADGroup - } + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | Should -Not -Throw + } - Mock -CommandName Get-DomainName -MockWith { - return 'contoso.com' - } + It "Should call the expected mocks" { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { ` + $GroupName -eq $setTargetResourceParametersChangedProperty.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Move-ADObject ` + -ParameterFilter { $TargetPath -eq $setTargetResourceParametersChangedProperty.Path } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Remove-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName New-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -Exactly -Times 0 + } - Mock -CommandName Get-ADDomainNameFromDistinguishedName -MockWith { - param - ( - [Parameter()] - [System.String] - $DistinguishedName - ) + Context "When 'Move-ADObject' throws an unexpected exception" { + BeforeAll { + Mock -CommandName Move-ADObject -MockWith { throw 'UnexpectedError' } + } - if ($DistinguishedName -match 'DC=sub') - { - return 'sub.contoso.com' - } - else - { - return 'contoso.com' + It 'Should throw the correct exception' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | + Should -Throw ($script:localizedData.MovingGroupError -f + $setTargetResourceParametersChangedProperty.GroupName, + $mockGetTargetResourceResults.Path, + $setTargetResourceParametersChangedProperty.Path) + } + } } - } - - Mock -CommandName Write-Verbose -ParameterFilter { - $Message -and $Message -match 'Group membership objects are in .* different AD Domains.' - } - Set-TargetResource @testPresentParamsMultiDomain -Members @($fakeADUser1.distinguishedName, $fakeADUser4.distinguishedName) + Context "When the 'Category' property has changed" { + BeforeAll { + $changedCategory = 'Distribution' - Assert-MockCalled -CommandName Get-ADDomainNameFromDistinguishedName - Assert-MockCalled -CommandName Set-ADCommonGroupMember ` - -ParameterFilter { $Action -eq 'Add' } ` - -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Write-Verbose -ParameterFilter { - $Message -and $Message -match 'Group membership objects are in .* different AD Domains.' - } - } - - It "Adds group members when 'Ensure' is 'Present', the group exists and 'MembersToInclude' are specified" { - Mock -CommandName Get-ADGroup -MockWith { - throw New-Object Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException - } + $setTargetResourceParametersChangedProperty = $setTargetResourceParameters.Clone() + $setTargetResourceParametersChangedProperty.Category = $changedCategory + } - Mock -CommandName Set-ADGroup - Mock -CommandName Set-ADCommonGroupMember - Mock -CommandName New-ADGroup -MockWith { - return [PSCustomObject] $fakeADGroup - } + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | Should -Not -Throw + } - Set-TargetResource @testPresentParams -MembersToInclude @($fakeADUser1.SamAccountName, $fakeADUser2.SamAccountName) + It "Should call the expected mocks" { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { ` + $GroupName -eq $setTargetResourceParametersChangedProperty.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -ParameterFilter { ` + $Identity -eq $mockGroupDN -and $GroupCategory -eq $changedCategory } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Remove-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName New-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -Exactly -Times 0 + } + } - Assert-MockCalled -CommandName Set-ADCommonGroupMember ` - -ParameterFilter { $Action -eq 'Add' } ` - -Scope It -Exactly -Times 1 - } + Context "When the 'GroupScope' property has changed" { + Context "When the `GroupScope' property was 'Global' and has changed to 'Domain Local'" { + BeforeAll { + $setTargetResourceParametersChangedProperty = $setTargetResourceParameters.Clone() + $setTargetResourceParametersChangedProperty.GroupScope = 'DomainLocal' + } - It "Moves group when 'Ensure' is 'Present', the group exists but the 'Path' has changed" { - Mock -CommandName Set-ADGroup - Mock -CommandName Get-ADGroupMember - Mock -CommandName Move-ADObject - Mock -CommandName Get-ADGroup -MockWith { - $duffADGroup = $fakeADGroup.Clone() - $duffADGroup['DistinguishedName'] = "CN=$($testPresentParams.GroupName),OU=WrongPath,DC=contoso,DC=com" + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | Should -Not -Throw + } - return $duffADGroup - } + It "Should call the expected mocks" { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { ` + $GroupName -eq $setTargetResourceParametersChangedProperty.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -ParameterFilter { $Identity -eq $mockGroupDN -and $GroupScope -eq 'Universal' } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -ParameterFilter { $Identity -eq $mockGroupDN -and $GroupScope -eq 'DomainLocal' } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Remove-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName New-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -Exactly -Times 0 + } + } - Set-TargetResource @testPresentParams + Context "When the `GroupScope' property was 'DomainLocal' and has changed to 'Global'" { + BeforeAll { + $mockGetTargetResourceDomainLocalResults = $mockGetTargetResourceResults.Clone() + $mockGetTargetResourceDomainLocalResults.GroupScope = 'DomainLocal' - Assert-MockCalled -CommandName Move-ADObject -Scope It - } + Mock -CommandName Get-TargetResource ` + -MockWith { $mockGetTargetResourceDomainLocalResults } - It "Resets group membership when 'Ensure' is 'Present' and 'Members' is incorrect" { - Mock -CommandName Get-ADGroup -MockWith { - return [PSCustomObject] $fakeADGroup - } + $setTargetResourceParametersChangedProperty = $setTargetResourceParameters.Clone() + $setTargetResourceParametersChangedProperty.GroupScope = 'Global' + } - Mock -CommandName Set-ADGroup - Mock -CommandName Get-ADGroupMember -MockWith { - return @( - $fakeADUser1, - $fakeADUser2 - ) - } + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | Should -Not -Throw + } - Mock -CommandName Set-ADCommonGroupMember ` - -ParameterFilter { $Action -eq 'Add' } - Mock -CommandName Set-ADCommonGroupMember ` - -ParameterFilter { $Action -eq 'Remove' } + It "Should call the expected mocks" { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { ` + $GroupName -eq $setTargetResourceParametersChangedProperty.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -ParameterFilter { $Identity -eq $mockGroupDN -and $GroupScope -eq 'Universal' } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -ParameterFilter { $Identity -eq $mockGroupDN -and $GroupScope -eq 'Global' } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Remove-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName New-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -Exactly -Times 0 + } - Set-TargetResource @testPresentParams -Members $fakeADUser1.SamAccountName + Context "When the 'Credential' parameter is specified" { + BeforeAll { + $setTargetResourceParametersChangedProperty = $setTargetResourceParameters.Clone() + $setTargetResourceParametersChangedProperty['Description'] = ` + $mockAdGroupChanged.Description + $setTargetResourceParametersChangedProperty['Credential'] = ` + $testCredential + } + + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | + Should -Not -Throw + } + + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $Credential -eq $testCredential } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -ParameterFilter { + $Identity -eq $mockGroupDN -and $GroupScope -eq 'Universal' -and ` + $Credential -eq $testCredential } ` + -Exactly -Times 1 + } + } - Assert-MockCalled -CommandName Set-ADCommonGroupMember ` - -ParameterFilter { $Action -eq 'Remove' } ` - -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Set-ADCommonGroupMember ` - -ParameterFilter { $Action -eq 'Add' } ` - -Scope It -Exactly -Times 1 - } + Context "When the 'DomainController' parameter is specified" { + BeforeAll { + $setTargetResourceParametersChangedProperty = $setTargetResourceParameters.Clone() + $setTargetResourceParametersChangedProperty['Description'] = ` + $mockAdGroupChanged.Description + $setTargetResourceParametersChangedProperty['DomainController'] = ` + $testDomainController + } + + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | + Should -Not -Throw + } + + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $DomainController -eq $testDomainController } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -ParameterFilter { ` + $Identity -eq $mockGroupDN -and $GroupScope -eq 'Universal' -and ` + $Server -eq $testDomainController } ` + -Exactly -Times 1 + } + } - It "Does not reset group membership when 'Ensure' is 'Present' and existing group is empty" { - Mock -CommandName Get-ADGroup -MockWith { - return [PSCustomObject] $fakeADGroup - } + Context "When 'Set-ADGroup' throws an unexpected exception" { + BeforeAll { + Mock -CommandName Set-ADGroup ` + -ParameterFilter { $GroupScope -eq 'Universal' } ` + -MockWith { throw 'UnexpectedError' } + } + + It 'Should throw the correct exception' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | + Should -Throw ($script:localizedData.SettingGroupError -f + $setTargetResourceParametersChangedProperty.GroupName) + } + } + } + } - Mock -CommandName Set-ADGroup - Mock -CommandName Get-ADGroupMember - Mock -CommandName Set-ADCommonGroupMember + Context "When the 'Notes' property has changed" { + BeforeAll { + $setTargetResourceParametersChangedProperty = $setTargetResourceParameters.Clone() + $setTargetResourceParametersChangedProperty.Notes = 'Changed Notes' + } - Set-TargetResource @testPresentParams -MembersToExclude $fakeADUser1.SamAccountName + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | Should -Not -Throw + } - Assert-MockCalled -CommandName Set-ADCommonGroupMember ` - -ParameterFilter { $Action -eq 'Remove' } ` - -Scope It -Exactly -Times 0 - } + It "Should call the expected mocks" { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $GroupName -eq $setTargetResourceParametersChangedProperty.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -ParameterFilter { ` + $Identity -eq $mockGroupDN -and (Get-Variable -Name Replace -ValueOnly).Info -eq ` + $setTargetResourceParametersChangedProperty.Notes } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Remove-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName New-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -Exactly -Times 0 + } + } - It "Removes members when 'Ensure' is 'Present' and 'MembersToExclude' is incorrect" { - Mock -CommandName Get-ADGroup -MockWith { - return [PSCustomObject] $fakeADGroup - } + Context "When the 'Members' property has changed" { + Context "When the 'Members property has members to add" { + BeforeAll { + $changedMembers = 'ChangedMember1', 'ChangedMember2' + $setTargetResourceParametersChangedProperty = $setTargetResourceParameters.Clone() + $setTargetResourceParametersChangedProperty.Members += $changedMembers + } - Mock -CommandName Set-ADGroup - Mock -CommandName Get-ADGroupMember -MockWith { - return @( - $fakeADUser1, - $fakeADUser2 - ) - } + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | Should -Not -Throw + } - Mock -CommandName Set-ADCommonGroupMember + It "Should call the expected mocks" { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $GroupName -eq $setTargetResourceParametersChangedProperty.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -ParameterFilter { ` + $Members.Count -eq $changedMembers.Count -and ` + $Members -contains $changedMembers[0] -and ` + $Members -contains $changedMembers[1] -and ` + $Action -eq 'Add' } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Remove-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName New-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -Exactly -Times 0 + } + } - Set-TargetResource @testPresentParams -MembersToExclude $fakeADUser1.SamAccountName + Context "When the 'Members' property has members to remove" { + BeforeAll { + $setTargetResourceParametersChangedProperty = $setTargetResourceParameters.Clone() + $setTargetResourceParametersChangedProperty.Members = ` + $mockADGroup.Members[0..($mockADGroup.Members.count - 2)] + } - Assert-MockCalled -CommandName Set-ADCommonGroupMember ` - -ParameterFilter { $Action -eq 'Remove' } ` - -Scope It -Exactly -Times 1 - } + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | Should -Not -Throw + } - It "Adds members when 'Ensure' is 'Present' and 'MembersToInclude' is incorrect" { - Mock -CommandName Get-ADGroup -MockWith { - return [PSCustomObject] $fakeADGroup - } + It "Should call the expected mocks" { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $GroupName -eq $setTargetResourceParametersChangedProperty.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -ParameterFilter { ` + $Members.Count -eq 1 -and ` + $Members -contains $mockADGroup.Members[-1] -and ` + $Action -eq 'Remove' } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Remove-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName New-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -Exactly -Times 0 + } + } - Mock -CommandName Set-ADGroup - Mock -CommandName Get-ADGroupMember -MockWith { - return @( - $fakeADUser1, - $fakeADUser2 - ) - } + Context "When the 'Members' property is specified as empty" { + BeforeAll { + $setTargetResourceParametersChangedProperty = $setTargetResourceParameters.Clone() + $setTargetResourceParametersChangedProperty.Members = @() + } - Mock -CommandName Set-ADCommonGroupMember + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | Should -Not -Throw + } - Set-TargetResource @testPresentParams -MembersToInclude $fakeADUser3.SamAccountName + It "Should call the expected mocks" { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $GroupName -eq $setTargetResourceParametersChangedProperty.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -ParameterFilter { ` + $Members.Count -eq $setTargetResourceParameters.Members.Count -and ` + $Members -contains $setTargetResourceParameters.Members[0] -and ` + $Members -contains $setTargetResourceParameters.Members[1] -and ` + $Members -contains $setTargetResourceParameters.Members[2] -and ` + $Members -contains $setTargetResourceParameters.Members[3] -and ` + $Action -eq 'Remove' } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Remove-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName New-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -Exactly -Times 0 + } + } - Assert-MockCalled -CommandName Set-ADCommonGroupMember ` - -ParameterFilter { $Action -eq 'Add' } ` - -Scope It -Exactly -Times 1 - } + Context "When the resource 'Members' value is empty" { + BeforeAll { + $mockGetTargetResourceEmptyMembersResults = $mockGetTargetResourceResults.Clone() + $mockGetTargetResourceEmptyMembersResults.Members = @() - It "Removes group when 'Ensure' is 'Absent' and group exists" { - Mock -CommandName Get-ADGroup -MockWith { - return $fakeADGroup - } + Mock -CommandName Get-TargetResource -MockWith { $mockGetTargetResourceEmptyMembersResults } - Mock -CommandName Remove-ADGroup + $setTargetResourceParametersChangedProperty = $setTargetResourceParameters.Clone() + $setTargetResourceParametersChangedProperty.Members = $mockADGroup.Members + } - Set-TargetResource @testAbsentParams + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | Should -Not -Throw + } - Assert-MockCalled -CommandName Remove-ADGroup -Scope It - } + It "Should call the expected mocks" { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $GroupName -eq $setTargetResourceParametersChangedProperty.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -ParameterFilter { ` + $Members.Count -eq $mockADGroup.Members.Count -and ` + $Members -contains $mockADGroup.Members[0] -and ` + $Members -contains $mockADGroup.Members[1] -and ` + $Members -contains $mockADGroup.Members[2] -and ` + $Members -contains $mockADGroup.Members[3] -and ` + $Action -eq 'Add' } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Remove-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName New-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -Exactly -Times 0 + } + } + } - It "Calls 'Set-ADGroup' with credentials when 'Ensure' is 'Present' and the group exists (#106)" { - Mock -CommandName Get-ADGroup -MockWith { - return $fakeADGroup - } + Context "When the 'MembersToInclude' property is not in the desired state" { + BeforeAll { + $membersToInclude = 'IncludeUser' + $setTargetResourceParametersChangedProperty = $setTargetResourceParameters.Clone() + $setTargetResourceParametersChangedProperty.Remove('Members') + $setTargetResourceParametersChangedProperty['MembersToInclude'] = $membersToInclude + } - Mock -CommandName New-ADGroup -MockWith { - return [PSCustomObject] $fakeADGroup - } + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | + Should -Not -Throw + } - Mock -CommandName Get-ADGroupMember - Mock -CommandName Set-ADGroup -ParameterFilter { - $Credential -eq $testCredentials - } + It "Should call the expected mocks" { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $GroupName -eq $setTargetResourceParametersChangedProperty.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -ParameterFilter { $Members -contains $membersToInclude -and $Action -eq 'Add' } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Remove-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName New-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -Exactly -Times 0 + } - Set-TargetResource @testPresentParams -Credential $testCredentials + Context "When the resource 'Members' value is empty" { + BeforeAll { + $mockGetTargetResourceEmptyMembersResults = $mockGetTargetResourceResults.Clone() + $mockGetTargetResourceEmptyMembersResults.Members = @() - Assert-MockCalled -CommandName Set-ADGroup -ParameterFilter { - $Credential -eq $testCredentials - } -Scope It - } + Mock -CommandName Get-TargetResource -MockWith { $mockGetTargetResourceEmptyMembersResults } + } - It "Calls 'Set-ADGroup' with credentials when 'Ensure' is 'Present' and the group does not exist (#106)" { - Mock -CommandName Get-ADGroup -MockWith { - throw New-Object Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException - } + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | + Should -Not -Throw + } - Mock -CommandName Set-ADGroup -ParameterFilter { - $Credential -eq $testCredentials - } + It "Should call the expected mocks" { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $GroupName -eq $setTargetResourceParametersChangedProperty.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -ParameterFilter { $Members -contains $membersToInclude -and $Action -eq 'Add' } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Remove-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName New-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -Exactly -Times 0 + } + } + } - Mock -CommandName New-ADGroup -MockWith { - return [PSCustomObject] $fakeADGroup - } + Context "When the 'MembersToExclude' property is not in the desired state" { + BeforeAll { + $membersToExclude = $mockADGroup.Members[0] + $setTargetResourceParametersChangedProperty = $setTargetResourceParameters.Clone() + $setTargetResourceParametersChangedProperty.Remove('Members') + $setTargetResourceParametersChangedProperty['MembersToExclude'] = $membersToExclude + } - Set-TargetResource @testPresentParams -Credential $testCredentials + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | + Should -Not -Throw + } - Assert-MockCalled -CommandName Set-ADGroup -ParameterFilter { - $Credential -eq $testCredentials - } -Scope It - } + It "Should call the expected mocks" { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $GroupName -eq $setTargetResourceParametersChangedProperty.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -ParameterFilter { $Members -contains $membersToExclude -and $Action -eq 'Remove' } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Remove-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName New-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -Exactly -Times 0 + } - It "Calls 'Move-ADObject' with credentials when specified (#106)" { - Mock -CommandName Set-ADGroup - Mock -CommandName Get-ADGroupMember - Mock -CommandName Move-ADObject -ParameterFilter { - $Credential -eq $testCredentials - } + Context "When the resource 'Members' value is empty" { + BeforeAll { + $mockGetTargetResourceEmptyMembersResults = $mockGetTargetResourceResults.Clone() + $mockGetTargetResourceEmptyMembersResults.Members = @() - Mock -CommandName Get-ADGroup -MockWith { - $duffADGroup = $fakeADGroup.Clone() - $duffADGroup['DistinguishedName'] = "CN=$($testPresentParams.GroupName),OU=WrongPath,DC=contoso,DC=com" + Mock -CommandName Get-TargetResource -MockWith { $mockGetTargetResourceEmptyMembersResults } + } - return $duffADGroup - } + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | + Should -Not -Throw + } - Set-TargetResource @testPresentParams -Credential $testCredentials + It "Should call the expected mocks" { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $GroupName -eq $setTargetResourceParametersChangedProperty.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Set-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Remove-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName New-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -Exactly -Times 0 + } + } + } - Assert-MockCalled -CommandName Move-ADObject -ParameterFilter { $Credential -eq $testCredentials } -Scope It - } + Context "When the 'Credential' parameter is specified" { + BeforeAll { + $setTargetResourceParametersChangedProperty = $setTargetResourceParameters.Clone() + $setTargetResourceParametersChangedProperty['Description'] = $mockAdGroupChanged.Description + $setTargetResourceParametersChangedProperty['Credential'] = $testCredential + } + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | + Should -Not -Throw + } - # tests for issue 183 - It "Doesn't reset to 'Global' when not specifying a 'Scope' and updating group membership" { - $testUniversalPresentParams = $testPresentParams.Clone() - $testUniversalPresentParams['GroupScope'] = 'Universal' - $fakeADUniversalGroup = $fakeADGroup.Clone() - $fakeADUniversalGroup['GroupScope'] = 'Universal' + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $Credential -eq $testCredential } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -ParameterFilter { $Credential -eq $testCredential } ` + -Exactly -Times 1 + } + } - Mock -CommandName Get-ADGroup -MockWith { - return [PSCustomObject] $fakeADUniversalGroup - } + Context "When the 'DomainController' parameter is specified" { + BeforeAll { + $setTargetResourceParametersChangedProperty = $setTargetResourceParameters.Clone() + $setTargetResourceParametersChangedProperty['Description'] = $mockAdGroupChanged.Description + $setTargetResourceParametersChangedProperty['DomainController'] = $testDomainController + } - Mock -CommandName Set-ADGroup -ParameterFilter { - $Identity -eq $fakeADUniversalGroup.Identity -and -not $PSBoundParameters.ContainsKey('GroupScope') - } + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | + Should -Not -Throw + } - Mock -CommandName Set-ADCommonGroupMember + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $DomainController -eq $testDomainController } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -ParameterFilter { $Server -eq $testDomainController } ` + -Exactly -Times 1 + } + } - Set-TargetResource -GroupName $testUniversalPresentParams.GroupName -Members @( - $fakeADUser1.SamAccountName, - $fakeADUser2.SamAccountName - ) + Context "When 'Set-ADGroup' throws an unexpected error" { + BeforeAll { + $setTargetResourceParametersChangedProperty = $setTargetResourceParameters.Clone() + $setTargetResourceParametersChangedProperty.Description = $mockADGroupChanged.Description - Assert-MockCalled -CommandName Set-ADGroup -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Set-ADCommonGroupMember ` - -ParameterFilter { $Action -eq 'Add' } ` - -Scope It -Exactly -Times 1 - } + Mock -CommandName Set-ADGroup -MockWith { throw 'UnexpectedError' } + } - # tests for issue 183 - It "Doesn't reset to 'Security' when not specifying a 'Category' and updating group membership" { - $testUniversalPresentParams = $testPresentParams.Clone() - $testUniversalPresentParams['Category'] = 'Distribution' - $fakeADUniversalGroup = $fakeADGroup.Clone() - $fakeADUniversalGroup['GroupCategory'] = 'Distribution' + It 'Should throw the correct exception' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | + Should -Throw ($script:localizedData.SettingGroupError -f + $setTargetResourceParametersChangedProperty.GroupName) + } + } - Mock -CommandName Get-ADGroup -MockWith { - return [PSCustomObject] $fakeADUniversalGroup - } + Context 'When the Resource is in the desired state' { + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + } - Mock -CommandName Set-ADGroup -ParameterFilter { - $Identity -eq $fakeADUniversalGroup.Identity -and -not $PSBoundParameters.ContainsKey('GroupCategory') + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $GroupName -eq $setTargetResourceParameters.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName New-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Remove-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Set-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -Exactly -Times 0 + } + } } - Mock -CommandName Set-ADCommonGroupMember - - Set-TargetResource -GroupName $testUniversalPresentParams.GroupName -Members @( - $fakeADUser1.SamAccountName, - $fakeADUser2.SamAccountName - ) - - Assert-MockCalled -CommandName Set-ADGroup -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Set-ADCommonGroupMember ` - -ParameterFilter { $Action -eq 'Add' } ` - -Scope It -Exactly -Times 1 - } + Context 'When the Resource is Absent' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { $mockGetTargetResourceResultsAbsent } + } - # tests for issue 183 - It "Doesn't reset to 'Global' when not specifying a 'Scope' and testing display name" { - $testUniversalPresentParams = $testPresentParams.Clone() - $testUniversalPresentParams['GroupScope'] = 'Universal' - $fakeADUniversalGroup = $fakeADGroup.Clone() - $fakeADUniversalGroup['GroupScope'] = 'Universal' + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + } - Mock -CommandName Get-ADGroup -MockWith { - return [PSCustomObject] $fakeADUniversalGroup - } + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $GroupName -eq $mockGetTargetResourceResultsAbsent.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName New-ADGroup ` + -ParameterFilter { $Name -eq $setTargetResourceParameters.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Remove-ADgroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -Exactly -Times 0 + } - Mock -CommandName Set-ADGroup -ParameterFilter { - $Identity -eq $fakeADUniversalGroup.Identity -and -not $PSBoundParameters.ContainsKey('GroupScope') - } + Context 'When the "RestoreFromRecycleBin" parameter is specified' { + BeforeAll { + $setTargetResourceRecycleBinParameters = $setTargetResourceParameters.Clone() + $setTargetResourceRecycleBinParameters['RestoreFromRecycleBin'] = $true - $universalGroupInCompliance = Test-TargetResource -GroupName $testUniversalPresentParams.GroupName -DisplayName $testUniversalPresentParams.DisplayName - $universalGroupInCompliance | Should -BeTrue - } + Mock -CommandName Restore-ADCommonObject -MockWith { $true } + } - # tests for issue 183 - It "Doesn't reset to 'Security' when not specifying a 'Category' and testing display name" { - $testUniversalPresentParams = $testPresentParams.Clone() - $testUniversalPresentParams['Category'] = 'Distribution' - $fakeADUniversalGroup = $fakeADGroup.Clone() - $fakeADUniversalGroup['GroupCategory'] = 'Distribution' + It 'Should not throw' { + { Set-TargetResource @setTargetResourceRecycleBinParameters } | Should -Not -Throw + } - Mock -CommandName Get-ADGroup -MockWith { - return [PSCustomObject] $fakeADUniversalGroup - } + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $GroupName -eq $setTargetResourceRecycleBinParameters.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -ParameterFilter { $Identity -eq $setTargetResourceRecycleBinParameters.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Remove-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName New-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Set-ADGroup ` + -Exactly -Times 0 + } - Mock -CommandName Set-ADGroup -ParameterFilter { - $Identity -eq $fakeADUniversalGroup.Identity -and -not $PSBoundParameters.ContainsKey('GroupScope') - } + Context "When 'Restore-ADCommonObject' does not return an object" { + BeforeAll { + Mock -CommandName Restore-ADCommonObject + } - $universalGroupInCompliance = Test-TargetResource -GroupName $testUniversalPresentParams.GroupName -DisplayName $testUniversalPresentParams.DisplayName - $universalGroupInCompliance | Should -BeTrue - } + It 'Should not throw' { + { Set-TargetResource @setTargetResourceRecycleBinParameters } | Should -Not -Throw + } - It "Calls Restore-AdCommonObject when RestoreFromRecycleBin is used" { - $restoreParam = $testPresentParams.Clone() - $restoreParam.RestoreFromRecycleBin = $true - Mock -CommandName Get-ADGroup -MockWith { - throw New-Object Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException - } + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $GroupName -eq $setTargetResourceRecycleBinParameters.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -ParameterFilter { $Identity -eq $setTargetResourceRecycleBinParameters.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName New-ADGroup ` + -ParameterFilter { $Name -eq $setTargetResourceParameters.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Remove-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -Exactly -Times 0 + } + } - Mock -CommandName Set-ADGroup - Mock -CommandName New-ADGroup -MockWith { - return [PSCustomObject] $fakeADGroup - } + Context "When the 'Credential' parameter is specified" { + BeforeAll { + $setTargetResourceRecycleBinCredentialParameters = $setTargetResourceRecycleBinParameters.Clone() + $setTargetResourceRecycleBinCredentialParameters['Credential'] = $testCredential + } - Mock -CommandName Restore-ADCommonObject -MockWith { - return [PSCustomObject] $fakeADGroup - } + It 'Should not throw' { + { Set-TargetResource @setTargetResourceRecycleBinCredentialParameters } | + Should -Not -Throw + } - Set-TargetResource @restoreParam + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $Credential -eq $testCredential } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -ParameterFilter { $Credential -eq $testCredential } ` + -Exactly -Times 1 + } + } - Assert-MockCalled -CommandName Restore-AdCommonObject -Scope It - Assert-MockCalled -CommandName New-ADGroup -Scope It -Exactly -Times 0 - Assert-MockCalled -CommandName Set-ADGroup -Scope It - } + Context "When the 'DomainController' parameter is specified" { + BeforeAll { + $setTargetResourceRecycleBinDomainControllerParameters = $setTargetResourceRecycleBinParameters.Clone() + $setTargetResourceRecycleBinDomainControllerParameters['DomainController'] = $testDomainController + } - It "Calls New-ADGroup when RestoreFromRecycleBin is used and if no object was found in the recycle bin" { - $restoreParam = $testPresentParams.Clone() - $restoreParam.RestoreFromRecycleBin = $true - Mock -CommandName Get-ADGroup -MockWith { - throw New-Object Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException - } + It 'Should not throw' { + { Set-TargetResource @setTargetResourceRecycleBinDomainControllerParameters } | + Should -Not -Throw + } - Mock -CommandName Set-ADGroup - Mock -CommandName New-ADGroup -MockWith { - return [PSCustomObject] $fakeADGroup - } + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $DomainController -eq $testDomainController } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -ParameterFilter { $Server -eq $testDomainController } ` + -Exactly -Times 1 + } + } + } - Mock -CommandName Restore-ADCommonObject + Context "When the 'Credential' parameter is specified" { + BeforeAll { + $setTargetResourceCredentialParameters = $setTargetResourceParameters.Clone() + $setTargetResourceCredentialParameters['Credential'] = $testCredential + } - Set-TargetResource @restoreParam + It 'Should not throw' { + { Set-TargetResource @setTargetResourceCredentialParameters } | + Should -Not -Throw + } - Assert-MockCalled -CommandName Restore-AdCommonObject -Scope It - Assert-MockCalled -CommandName New-ADGroup -Scope It - } + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $Credential -eq $testCredential } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName New-ADGroup ` + -ParameterFilter { $Credential -eq $testCredential } ` + -Exactly -Times 1 + } + } - It "Throws if the object cannot be restored" { - $restoreParam = $testPresentParams.Clone() - $restoreParam.RestoreFromRecycleBin = $true - Mock -CommandName Get-ADGroup -MockWith { - throw New-Object Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException - } + Context "When the 'DomainController' parameter is specified" { + BeforeAll { + $setTargetResourceDomainControllerParameters = $setTargetResourceParameters.Clone() + $setTargetResourceDomainControllerParameters['DomainController'] = $testDomainController + } - Mock -CommandName Set-ADGroup - Mock -CommandName New-ADGroup -MockWith { - return [PSCustomObject] $fakeADGroup - } + It 'Should not throw' { + { Set-TargetResource @setTargetResourceDomainControllerParameters } | + Should -Not -Throw + } - Mock -CommandName Restore-ADCommonObject -MockWith { - throw (New-Object -TypeName System.InvalidOperationException) - } + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $DomainController -eq $testDomainController } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName New-ADGroup ` + -ParameterFilter { $Server -eq $testDomainController } ` + -Exactly -Times 1 + } + } - { Set-TargetResource @restoreParam } | Should -Throw + Context "When 'New-ADGroup' throws an unexpected error" { + BeforeAll { + Mock -CommandName New-ADGroup -MockWith { throw 'UnexpectedError' } + } - Assert-MockCalled -CommandName Restore-AdCommonObject -Scope It - Assert-MockCalled -CommandName New-ADGroup -Scope It -Exactly -Times 0 - Assert-MockCalled -CommandName Set-ADGroup -Scope It -Exactly -Times 0 - } + It 'Should throw the correct exception' { + { Set-TargetResource @setTargetResourceParameters } | + Should -Throw ($script:localizedData.AddingResourceError -f + $setTargetResourceParameters.GroupName) + } + } - It "Throws the correct error when 'Get-ADGroupMember' fails due to an unrecognized 'FullyQualifiedErrorId'" { - Mock -CommandName Get-TargetResource -MockWith { - $fakeTargetResource = $testPresentParams.Clone() - $fakeTargetResource['DistinguishedName'] = $fakeADGroup.DistinguishedName - $fakeTargetResource['Members'] = $fakeADGroupMembersAsADObjects.SamAccountName - return $fakeTargetResource - } + Context "When the 'MembersToInclude' property is specified" { + BeforeAll { + $membersToInclude = $mockADGroup.Members + $setTargetResourceParametersChangedProperty = $setTargetResourceParameters.Clone() + $setTargetResourceParametersChangedProperty.Remove('Members') + $setTargetResourceParametersChangedProperty['MembersToInclude'] = $membersToInclude + } - Mock -CommandName Set-ADGroup + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersChangedProperty } | + Should -Not -Throw + } - Mock -CommandName Get-ADGroupMember -MockWith { - $errorMessage = 'Get-ADGroupMember' - $errorId = (New-Guid).Guid - Write-Error -Message $errorMessage -ErrorId $errorId -ErrorAction Stop + It "Should call the expected mocks" { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $GroupName -eq $setTargetResourceParametersChangedProperty.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName New-ADGroup ` + -ParameterFilter { $Name -eq $setTargetResourceParameters.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -ParameterFilter { + $Members.Count -eq $membersToInclude.Count -and ` + $Members -contains $membersToInclude[0] -and ` + $Members -contains $membersToInclude[1] -and ` + $Members -contains $membersToInclude[2] -and ` + $Members -contains $membersToInclude[3] -and ` + $Action -eq 'Add' } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Remove-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -Exactly -Times 0 + } + } } - - { Set-TargetResource @testPresentParams -Members $fakeADGroupMembersAsADObjects.SamAccountName -ErrorAction Stop } | - Should -Throw ($script:localizedData.RetrievingGroupMembersError -f - $fakeADGroup.Name) } - Context "When 'Get-TargetResource' is mocked and 'Get-ADGroupMember' fails due to one-way trust" { - Mock -CommandName Get-ADGroup -MockWith { - $fakeADGroup['Members'] = $fakeADGroupMembersAsADObjects.DistinguishedName - return [PSCustomObject] $fakeADGroup - } - - Mock -CommandName Get-ADGroupMember -MockWith { - $errorMessage = 'Get-ADGroupMember' - $errorId = 'ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.GetADGroupMember' - Write-Error -Message $errorMessage -ErrorId $errorId -ErrorAction Stop - } - - foreach ($attribute in @('SamAccountName', 'DistinguishedName', 'ObjectGUID', 'SID')) - { - if ($attribute -eq 'SID') - { - $memberProperty = 'ObjectSID' - } - else - { - $memberProperty = $attribute + Context 'When the Resource should be Absent' { + Context 'When the Resource is Present' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { $mockGetTargetResourceResults } } - $membersParamSplat = @{ - Members = $fakeADGroupMembersAsADObjects.$memberProperty - MembershipAttribute = $attribute + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersAbsent } | Should -Not -Throw } - Mock -CommandName Get-TargetResource -ParameterFilter { - $MembershipAttribute -eq $attribute - } -MockWith { - $fakeTargetResource = $testPresentParams.Clone() - $fakeTargetResource['DistinguishedName'] = $fakeADGroup.DistinguishedName - $fakeTargetResource['Members'] = $fakeADGroupMembersAsADObjects.$memberProperty - return $fakeTargetResource + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $GroupName -eq $setTargetResourceParametersAbsent.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Remove-ADGroup ` + -ParameterFilter { $Identity -eq $setTargetResourceParametersAbsent.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -Exactly -Times 0 + Assert-MockCalled -CommandName New-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Set-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -Exactly -Times 0 } - It "Calls 'Get-ADGroup' and 'Get-ADObject' when 'MembershipAttribute' is '$attribute'" { - $script:getADObjectCallCount1 = 0 - Mock -CommandName Get-ADObject -MockWith { - $memberADObject = $fakeADGroupMembersAsADObjects[$script:getADObjectCallCount1] - $script:getADObjectCallCount1++ - return $memberADObject + Context "When the 'Credential' parameter is specified" { + BeforeAll { + $setTargetResourceCredentialParametersAbsent = $setTargetResourceParametersAbsent.Clone() + $setTargetResourceCredentialParametersAbsent['Credential'] = $testCredential } - Mock -CommandName Resolve-SamAccountName -MockWith { - return $fakeADGroupMembersAsADObjects[($script:getADObjectCallCount1 - 1)].SamAccountName + It 'Should not throw' { + { Set-TargetResource @setTargetResourceCredentialParametersAbsent } | + Should -Not -Throw } - Set-TargetResource @testPresentParams @membersParamSplat - - Assert-MockCalled -CommandName Get-ADGroup - Assert-MockCalled -CommandName Get-ADObject -Scope It + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $Credential -eq $testCredential } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Remove-ADGroup ` + -ParameterFilter { $Credential -eq $testCredential } ` + -Exactly -Times 1 + } } - if ($attribute -eq 'SamAccountName') - { - $itCallsResolveSamAccountNameString = "Calls 'Resolve-SamAccountName' when " + (@( - "'MembershipAttribute' is 'SamAccountName'" - "at least 1 group member has an 'ObjectClass' of 'foreignSecurityPrincipal'" - ) -join ' and ') - - It $itCallsResolveSamAccountNameString { - $script:getADObjectCallCount2 = 0 - Mock -CommandName Get-ADObject -MockWith { - $memberADObject = $fakeADGroupMembersAsADObjects[$script:getADObjectCallCount2] - $script:getADObjectCallCount2++ - return $memberADObject - } + Context "When the 'DomainController' parameter is specified" { + BeforeAll { + $setTargetResourceDomainControllerParametersAbsent = $setTargetResourceParametersAbsent.Clone() + $setTargetResourceDomainControllerParametersAbsent['DomainController'] = $testDomainController + } - Mock -CommandName Resolve-SamAccountName -MockWith { - return $fakeADGroupMembersAsADObjects[($script:getADObjectCallCount2 - 1)].SamAccountName - } + It 'Should not throw' { + { Set-TargetResource @setTargetResourceDomainControllerParametersAbsent } | + Should -Not -Throw + } - Set-TargetResource @testPresentParams @membersParamSplat + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $DomainController -eq $testDomainController } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Remove-ADGroup ` + -ParameterFilter { $Server -eq $testDomainController } ` + -Exactly -Times 1 + } + } - Assert-MockCalled -CommandName Resolve-SamAccountName -Scope It + Context "When 'Remove-ADGroup' throws an unexpected error" { + BeforeAll { + Mock -CommandName Remove-ADGroup -MockWith { throw 'UnexpectedError' } } + + It 'Should throw the correct exception' { + { Set-TargetResource @setTargetResourceParametersAbsent } | + Should -Throw ($script:localizedData.RemovingResourceError -f + $setTargetResourceParametersAbsent.GroupName) + } + } + } + + Context 'When the Resource is Absent' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { $mockGetTargetResourceResultsAbsent } + } + + It 'Should not throw' { + { Set-TargetResource @setTargetResourceParametersAbsent } | Should -Not -Throw + } + + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-TargetResource ` + -ParameterFilter { $GroupName -eq $setTargetResourceParametersAbsent.GroupName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Set-ADCommonGroupMember ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Remove-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName New-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Set-ADGroup ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Restore-ADCommonObject ` + -Exactly -Times 0 } } } } - #end region - } - #end region } finally {