DSCResources/MSFT_ADGroup/MSFT_ADGroup.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 'ActiveDirectoryDsc.Common' Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'ActiveDirectoryDsc.Common.psm1') $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_ADGroup' <# .SYNOPSIS Returns the current state of the Active Directory group. .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. .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 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. #> 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 <# .SYNOPSIS Determines if the Active Directory group is in the desired state. .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. .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 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. #> 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 -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 <# .SYNOPSIS Creates, removes or modifies the Active Directory group. .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. .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 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. #> 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 |