DSCResources/MSFT_xADGroup/MSFT_xADGroup.psm1
$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent $script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' $script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'xActiveDirectory.Common' Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'xActiveDirectory.Common.psm1') $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xADGroup' function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [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] [System.Management.Automation.CredentialAttribute()] $Credential, [Parameter()] [ValidateNotNullOrEmpty()] [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 ) Assert-Module -ModuleName 'ActiveDirectory' $adGroupParams = Get-ADCommonParameters @PSBoundParameters try { $adGroup = Get-ADGroup @adGroupParams -Property Name, GroupScope, GroupCategory, DistinguishedName, Description, DisplayName, ManagedBy, Info Write-Verbose -Message ($script:localizedData.RetrievingGroupMembers -f $MembershipAttribute) # Retrieve the current list of members, returning the specified membership attribute [System.Array]$adGroupMembers = (Get-ADGroupMember @adGroupParams).$MembershipAttribute $targetResource = @{ GroupName = $adGroup.Name GroupScope = $adGroup.GroupScope Category = $adGroup.GroupCategory Path = Get-ADObjectParentDN -DN $adGroup.DistinguishedName Description = $adGroup.Description DisplayName = $adGroup.DisplayName Members = $adGroupMembers MembersToInclude = $MembersToInclude MembersToExclude = $MembersToExclude MembershipAttribute = $MembershipAttribute ManagedBy = $adGroup.ManagedBy Notes = $adGroup.Info Ensure = 'Absent' } if ($adGroup) { $targetResource['Ensure'] = 'Present' } } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { Write-Verbose -Message ($script:localizedData.GroupNotFound -f $GroupName) $targetResource = @{ GroupName = $GroupName GroupScope = $GroupScope Category = $Category Path = $Path Description = $Description DisplayName = $DisplayName Members = @() MembersToInclude = $MembersToInclude MembersToExclude = $MembersToExclude MembershipAttribute = $MembershipAttribute ManagedBy = $ManagedBy Notes = $Notes Ensure = 'Absent' } } return $targetResource } #end function Get-TargetResource function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [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] [System.Management.Automation.CredentialAttribute()] $Credential, [Parameter()] [ValidateNotNullOrEmpty()] [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 ) # Validate parameters before we even attempt to retrieve anything $assertMemberParameters = @{ } if ($PSBoundParameters.ContainsKey('Members') -and -not [system.string]::IsNullOrEmpty($Members)) { $assertMemberParameters['Members'] = $Members } if ($PSBoundParameters.ContainsKey('MembersToInclude') -and -not [system.string]::IsNullOrEmpty($MembersToInclude)) { $assertMemberParameters['MembersToInclude'] = $MembersToInclude } if ($PSBoundParameters.ContainsKey('MembersToExclude') -and -not [system.string]::IsNullOrEmpty($MembersToExclude)) { $assertMemberParameters['MembersToExclude'] = $MembersToExclude } Assert-MemberParameters @assertMemberParameters -ModuleName 'xADDomain' -ErrorAction Stop $targetResource = Get-TargetResource @PSBoundParameters $targetResourceInCompliance = $true if ($PSBoundParameters.ContainsKey('GroupScope') -and $targetResource.GroupScope -ne $GroupScope) { Write-Verbose -Message ($script:localizedData.NotDesiredPropertyState -f 'GroupScope', $GroupScope, $targetResource.GroupScope) $targetResourceInCompliance = $false } if ($PSBoundParameters.ContainsKey('Category') -and $targetResource.Category -ne $Category) { Write-Verbose -Message ($script:localizedData.NotDesiredPropertyState -f 'Category', $Category, $targetResource.Category) $targetResourceInCompliance = $false } if ($Path -and ($targetResource.Path -ne $Path)) { Write-Verbose -Message ($script:localizedData.NotDesiredPropertyState -f 'Path', $Path, $targetResource.Path) $targetResourceInCompliance = $false } if ($Description -and ($targetResource.Description -ne $Description)) { Write-Verbose -Message ($script:localizedData.NotDesiredPropertyState -f 'Description', $Description, $targetResource.Description) $targetResourceInCompliance = $false } if ($DisplayName -and ($targetResource.DisplayName -ne $DisplayName)) { Write-Verbose -Message ($script:localizedData.NotDesiredPropertyState -f 'DisplayName', $DisplayName, $targetResource.DisplayName) $targetResourceInCompliance = $false } if ($ManagedBy -and ($targetResource.ManagedBy -ne $ManagedBy)) { Write-Verbose -Message ($script:localizedData.NotDesiredPropertyState -f 'ManagedBy', $ManagedBy, $targetResource.ManagedBy) $targetResourceInCompliance = $false } if ($Notes -and ($targetResource.Notes -ne $Notes)) { Write-Verbose -Message ($script:localizedData.NotDesiredPropertyState -f 'Notes', $Notes, $targetResource.Notes) $targetResourceInCompliance = $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 ($targetResource.Ensure -ne $Ensure) { Write-Verbose -Message ($script:localizedData.NotDesiredPropertyState -f 'Ensure', $Ensure, $targetResource.Ensure) $targetResourceInCompliance = $false } return $targetResourceInCompliance } #end function Test-TargetResource function Set-TargetResource { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [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] [System.Management.Automation.CredentialAttribute()] $Credential, [Parameter()] [ValidateNotNullOrEmpty()] [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 ) Assert-Module -ModuleName 'ActiveDirectory' $adGroupParams = Get-ADCommonParameters @PSBoundParameters try { if ($MembershipAttribute -eq 'DistinguishedName') { $allMembers = $Members + $MembersToInclude + $MembersToExclude $groupMemberDomains = @() foreach ($member in $allMembers) { $groupMemberDomains += Get-ADDomainNameFromDistinguishedName -DistinguishedName $member } $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) $MembersInMultipleDomains = $true } } $adGroup = Get-ADGroup @adGroupParams -Property Name, GroupScope, GroupCategory, DistinguishedName, Description, DisplayName, ManagedBy, Info if ($Ensure -eq 'Present') { $setADGroupParams = $adGroupParams.Clone() $setADGroupParams['Identity'] = $adGroup.DistinguishedName # Update existing group properties if ($PSBoundParameters.ContainsKey('Category') -and $Category -ne $adGroup.GroupCategory) { Write-Verbose -Message ($script:localizedData.UpdatingGroupProperty -f 'Category', $Category) $setADGroupParams['GroupCategory'] = $Category } if ($PSBoundParameters.ContainsKey('GroupScope') -and $GroupScope -ne $adGroup.GroupScope) { # Cannot change DomainLocal to Global or vice versa directly. Need to change them to a Universal group first! Set-ADGroup -Identity $adGroup.DistinguishedName -GroupScope Universal Write-Verbose -Message ($script:localizedData.UpdatingGroupProperty -f 'GroupScope', $GroupScope) $setADGroupParams['GroupScope'] = $GroupScope } if ($Description -and ($Description -ne $adGroup.Description)) { Write-Verbose -Message ($script:localizedData.UpdatingGroupProperty -f 'Description', $Description) $setADGroupParams['Description'] = $Description } if ($DisplayName -and ($DisplayName -ne $adGroup.DisplayName)) { Write-Verbose -Message ($script:localizedData.UpdatingGroupProperty -f 'DisplayName', $DisplayName) $setADGroupParams['DisplayName'] = $DisplayName } if ($ManagedBy -and ($ManagedBy -ne $adGroup.ManagedBy)) { Write-Verbose -Message ($script:localizedData.UpdatingGroupProperty -f 'ManagedBy', $ManagedBy) $setADGroupParams['ManagedBy'] = $ManagedBy } if ($Notes -and ($Notes -ne $adGroup.Info)) { Write-Verbose -Message ($script:localizedData.UpdatingGroupProperty -f 'Notes', $Notes) $setADGroupParams['Replace'] = @{ Info = $Notes } } Write-Verbose -Message ($script:localizedData.UpdatingGroup -f $GroupName) Set-ADGroup @setADGroupParams # Move group if the path is not correct if ($Path -and ($Path -ne (Get-ADObjectParentDN -DN $adGroup.DistinguishedName))) { Write-Verbose -Message ($script:localizedData.MovingGroup -f $GroupName, $Path) $moveADObjectParams = $adGroupParams.Clone() $moveADObjectParams['Identity'] = $adGroup.DistinguishedName Move-ADObject @moveADObjectParams -TargetPath $Path } Write-Verbose -Message ($script:localizedData.RetrievingGroupMembers -f $MembershipAttribute) $adGroupMembers = (Get-ADGroupMember @adGroupParams).$MembershipAttribute if (-not (Test-Members -ExistingMembers $adGroupMembers -Members $Members -MembersToInclude $MembersToInclude -MembersToExclude $MembersToExclude)) { <# The fact that we're in the Set method, there is no need to validate the parameter combination as this was performed in the Test method. #> if ($PSBoundParameters.ContainsKey('Members') -and -not [system.string]::IsNullOrEmpty($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) { Write-Verbose -Message ($script:localizedData.RemovingGroupMembers -f $adGroupMembers.Count, $GroupName) Remove-ADGroupMember @adGroupParams -Members $adGroupMembers -Confirm:$false } Write-Verbose -Message ($script:localizedData.AddingGroupMembers -f $Members.Count, $GroupName) Add-ADCommonGroupMember -Parameter $adGroupParams -Members $Members -MembersInMultipleDomains:$MembersInMultipleDomains } if ($PSBoundParameters.ContainsKey('MembersToInclude') -and -not [system.string]::IsNullOrEmpty($MembersToInclude)) { $MembersToInclude = Remove-DuplicateMembers -Members $MembersToInclude Write-Verbose -Message ($script:localizedData.AddingGroupMembers -f $MembersToInclude.Count, $GroupName) Add-ADCommonGroupMember -Parameter $adGroupParams -Members $MembersToInclude -MembersInMultipleDomains:$MembersInMultipleDomains } 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) Remove-ADGroupMember @adGroupParams -Members $MembersToExclude -Confirm:$false } } } elseif ($Ensure -eq 'Absent') { # Remove existing group Write-Verbose -Message ($script:localizedData.RemovingGroup -f $GroupName) Remove-ADGroup @adGroupParams -Confirm:$false } } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { # The AD group doesn't exist if ($Ensure -eq 'Present') { Write-Verbose -Message ($script:localizedData.GroupNotFound -f $GroupName) $adGroupParams = Get-ADCommonParameters @PSBoundParameters -UseNameParameter if ($Description) { $adGroupParams['Description'] = $Description } if ($DisplayName) { $adGroupParams['DisplayName'] = $DisplayName } if ($ManagedBy) { $adGroupParams['ManagedBy'] = $ManagedBy } if ($Path) { $adGroupParams['Path'] = $Path } # Create group. Try to restore account first if it exists. if ($RestoreFromRecycleBin) { Write-Verbose -Message ($script:localizedData.RestoringGroup -f $GroupName) $restoreParams = Get-ADCommonParameters @PSBoundParameters $adGroup = Restore-ADCommonObject @restoreParams -ObjectClass Group -ErrorAction Stop } if (-not $adGroup) { Write-Verbose -Message ($script:localizedData.AddingGroup -f $GroupName) $adGroup = New-ADGroup @adGroupParams -GroupCategory $Category -GroupScope $GroupScope -PassThru } <# Only the New-ADGroup cmdlet takes a -Name parameter. Refresh the parameters with the -Identity parameter rather than -Name. #> $adGroupParams = Get-ADCommonParameters @PSBoundParameters if ($Notes) { # Can't set the Notes field when creating the group Write-Verbose -Message ($script:localizedData.UpdatingGroupProperty -f 'Notes', $Notes) $setADGroupParams = $adGroupParams.Clone() $setADGroupParams['Identity'] = $adGroup.DistinguishedName Set-ADGroup @setADGroupParams -Add @{ Info = $Notes } } # Add the required members if ($PSBoundParameters.ContainsKey('Members') -and -not [system.string]::IsNullOrEmpty($Members)) { $Members = Remove-DuplicateMembers -Members $Members Write-Verbose -Message ($script:localizedData.AddingGroupMembers -f $Members.Count, $GroupName) Add-ADCommonGroupMember -Parameter $adGroupParams -Members $Members -MembersInMultipleDomains:$MembersInMultipleDomains } 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) Add-ADCommonGroupMember -Parameter $adGroupParams -Members $MembersToInclude -MembersInMultipleDomains:$MembersInMultipleDomains } } } #end catch } #end function Set-TargetResource Export-ModuleMember -Function *-TargetResource |