DSCResources/MSFT_xADGroup/MSFT_xADGroup.psm1
# Localized messages data LocalizedData { # culture="en-US" ConvertFrom-StringData @' RetrievingGroupMembers = Retrieving group membership based on '{0}' property. GroupMembershipInDesiredState = Group membership is in the desired state. GroupMembershipNotDesiredState = Group membership is NOT in the desired state. AddingGroupMembers = Adding '{0}' member(s) to AD group '{1}'. RemovingGroupMembers = Removing '{0}' member(s) from AD group '{1}'. AddingGroup = Adding AD Group '{0}' UpdatingGroup = Updating AD Group '{0}' RemovingGroup = Removing AD Group '{0}' MovingGroup = Moving AD Group '{0}' to '{1}' GroupNotFound = AD Group '{0}' was not found NotDesiredPropertyState = AD Group '{0}' is not correct. Expected '{1}', actual '{2}' UpdatingGroupProperty = Updating AD Group property '{0}' to '{1}' '@ } function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $GroupName, [ValidateSet('DomainLocal','Global','Universal')] [System.String] $GroupScope = 'Global', [ValidateSet('Security','Distribution')] [System.String] $Category = 'Security', [ValidateNotNullOrEmpty()] [System.String] $Path, [ValidateSet("Present", "Absent")] [System.String] $Ensure = "Present", [ValidateNotNullOrEmpty()] [System.String] $Description, [ValidateNotNullOrEmpty()] [System.String] $DisplayName, [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $Credential, [ValidateNotNullOrEmpty()] [System.String] $DomainController, [ValidateNotNullOrEmpty()] [System.String[]] $Members, [ValidateNotNullOrEmpty()] [System.String[]] $MembersToInclude, [ValidateNotNullOrEmpty()] [System.String[]] $MembersToExclude, [ValidateSet('SamAccountName','DistinguishedName','SID','ObjectGUID')] [System.String] $MembershipAttribute = 'SamAccountName', ## This must be the user's DN [ValidateNotNullOrEmpty()] [System.String] $ManagedBy, [ValidateNotNullOrEmpty()] [System.String] $Notes ) 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 ($LocalizedData.RetrievingGroupMembers -f $MembershipAttribute); ## Retrieve the current list of members, returning the specified membership attribute $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 ($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, [ValidateSet('DomainLocal','Global','Universal')] [System.String] $GroupScope = 'Global', [ValidateSet('Security','Distribution')] [System.String] $Category = 'Security', [ValidateNotNullOrEmpty()] [System.String] $Path, [ValidateSet("Present", "Absent")] [System.String] $Ensure = "Present", [ValidateNotNullOrEmpty()] [System.String] $Description, [ValidateNotNullOrEmpty()] [System.String] $DisplayName, [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $Credential, [ValidateNotNullOrEmpty()] [System.String] $DomainController, [ValidateNotNullOrEmpty()] [System.String[]] $Members, [ValidateNotNullOrEmpty()] [System.String[]] $MembersToInclude, [ValidateNotNullOrEmpty()] [System.String[]] $MembersToExclude, [ValidateSet('SamAccountName','DistinguishedName','SID','ObjectGUID')] [System.String] $MembershipAttribute = 'SamAccountName', ## This must be the user's DN [ValidateNotNullOrEmpty()] [System.String] $ManagedBy, [ValidateNotNullOrEmpty()] [System.String] $Notes ) ## Validate parameters before we even attempt to retrieve anything $assertMemberParameters = @{}; if ($PSBoundParameters.ContainsKey('Members')) { $assertMemberParameters['Members'] = $Members; } if ($PSBoundParameters.ContainsKey('MembersToInclude')) { $assertMemberParameters['MembersToInclude'] = $MembersToInclude; } if ($PSBoundParameters.ContainsKey('MembersToExclude')) { $assertMemberParameters['MembersToExclude'] = $MembersToExclude; } Assert-MemberParameters @assertMemberParameters -ModuleName 'xADDomain' -ErrorAction Stop; $targetResource = Get-TargetResource @PSBoundParameters; $targetResourceInCompliance = $true; if ($targetResource.GroupScope -ne $GroupScope) { Write-Verbose ($LocalizedData.NotDesiredPropertyState -f 'GroupScope', $GroupScope, $targetResource.GroupScope); $targetResourceInCompliance = $false; } if ($targetResource.Category -ne $Category) { Write-Verbose ($LocalizedData.NotDesiredPropertyState -f 'Category', $Category, $targetResource.Category); $targetResourceInCompliance = $false; } if ($Path -and ($targetResource.Path -ne $Path)) { Write-Verbose ($LocalizedData.NotDesiredPropertyState -f 'Path', $Path, $targetResource.Path); $targetResourceInCompliance = $false; } if ($Description -and ($targetResource.Description -ne $Description)) { Write-Verbose ($LocalizedData.NotDesiredPropertyState -f 'Description', $Description, $targetResource.Description); $targetResourceInCompliance = $false; } if ($DisplayName -and ($targetResource.DisplayName -ne $DisplayName)) { Write-Verbose ($LocalizedData.NotDesiredPropertyState -f 'DisplayName', $DisplayName, $targetResource.DisplayName); $targetResourceInCompliance = $false; } if ($ManagedBy -and ($targetResource.ManagedBy -ne $ManagedBy)) { Write-Verbose ($LocalizedData.NotDesiredPropertyState -f 'ManagedBy', $ManagedBy, $targetResource.ManagedBy); $targetResourceInCompliance = $false; } if ($Notes -and ($targetResource.Notes -ne $Notes)) { Write-Verbose ($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 $LocalizedData.GroupMembershipNotDesiredState; $targetResourceInCompliance = $false; } if ($targetResource.Ensure -ne $Ensure) { Write-Verbose ($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, [ValidateSet('DomainLocal','Global','Universal')] [System.String] $GroupScope = 'Global', [ValidateSet('Security','Distribution')] [System.String] $Category = 'Security', [ValidateNotNullOrEmpty()] [System.String] $Path, [ValidateSet("Present", "Absent")] [System.String] $Ensure = "Present", [ValidateNotNullOrEmpty()] [System.String] $Description, [ValidateNotNullOrEmpty()] [System.String] $DisplayName, [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $Credential, [ValidateNotNullOrEmpty()] [System.String] $DomainController, [ValidateNotNullOrEmpty()] [System.String[]] $Members, [ValidateNotNullOrEmpty()] [System.String[]] $MembersToInclude, [ValidateNotNullOrEmpty()] [System.String[]] $MembersToExclude, [ValidateSet('SamAccountName','DistinguishedName','SID','ObjectGUID')] [System.String] $MembershipAttribute = 'SamAccountName', ## This must be the user's DN [ValidateNotNullOrEmpty()] [System.String] $ManagedBy, [ValidateNotNullOrEmpty()] [System.String] $Notes ) Assert-Module -ModuleName 'ActiveDirectory'; $adGroupParams = Get-ADCommonParameters @PSBoundParameters; try { $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 ($Category -ne $adGroup.GroupCategory) { Write-Verbose ($LocalizedData.UpdatingGroupProperty -f 'Category', $Category); $setADGroupParams['GroupCategory'] = $Category; } if ($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 ($LocalizedData.UpdatingGroupProperty -f 'GroupScope', $GroupScope); $setADGroupParams['GroupScope'] = $GroupScope; } if ($Description -and ($Description -ne $adGroup.Description)) { Write-Verbose ($LocalizedData.UpdatingGroupProperty -f 'Description', $Description); $setADGroupParams['Description'] = $Description; } if ($DisplayName -and ($DisplayName -ne $adGroup.DisplayName)) { Write-Verbose ($LocalizedData.UpdatingGroupProperty -f 'DisplayName', $DisplayName); $setADGroupParams['DisplayName'] = $DisplayName; } if ($ManagedBy -and ($ManagedBy -ne $adGroup.ManagedBy)) { Write-Verbose ($LocalizedData.UpdatingGroupProperty -f 'ManagedBy', $ManagedBy); $setADGroupParams['ManagedBy'] = $ManagedBy; } if ($Notes -and ($Notes -ne $adGroup.Info)) { Write-Verbose ($LocalizedData.UpdatingGroupProperty -f 'Notes', $Notes); $setADGroupParams['Replace'] = @{ Info = $Notes }; } Write-Verbose ($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 ($LocalizedData.MovingGroup -f $GroupName, $Path); $moveADObjectParams = $adGroupParams.Clone(); $moveADObjectParams['Identity'] = $adGroup.DistinguishedName Move-ADObject @moveADObjectParams -TargetPath $Path; } Write-Verbose -Message ($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')) { # 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 ($LocalizedData.RemovingGroupMembers -f $adGroupMembers.Count, $GroupName); Remove-ADGroupMember @adGroupParams -Members $adGroupMembers -Confirm:$false; } Write-Verbose -Message ($LocalizedData.AddingGroupMembers -f $Members.Count, $GroupName); Add-ADGroupMember @adGroupParams -Members $Members; } if ($PSBoundParameters.ContainsKey('MembersToInclude')) { $MembersToInclude = Remove-DuplicateMembers -Members $MembersToInclude; Write-Verbose -Message ($LocalizedData.AddingGroupMembers -f $MembersToInclude.Count, $GroupName); Add-ADGroupMember @adGroupParams -Members $MembersToInclude; } if ($PSBoundParameters.ContainsKey('MembersToExclude')) { $MembersToExclude = Remove-DuplicateMembers -Members $MembersToExclude; Write-Verbose -Message ($LocalizedData.RemovingGroupMembers -f $MembersToExclude.Count, $GroupName); Remove-ADGroupMember @adGroupParams -Members $MembersToExclude -Confirm:$false; } } } elseif ($Ensure -eq 'Absent') { # Remove existing group Write-Verbose ($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 ($LocalizedData.GroupNotFound -f $GroupName); Write-Verbose ($LocalizedData.AddingGroup -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 $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 ($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')) { $Members = Remove-DuplicateMembers -Members $Members; Write-Verbose -Message ($LocalizedData.AddingGroupMembers -f $Members.Count, $GroupName); Add-ADGroupMember @adGroupParams -Members $Members; } elseif ($PSBoundParameters.ContainsKey('MembersToInclude')) { $MembersToInclude = Remove-DuplicateMembers -Members $MembersToInclude; Write-Verbose -Message ($LocalizedData.AddingGroupMembers -f $MembersToInclude.Count, $GroupName); Add-ADGroupMember @adGroupParams -Members $MembersToInclude; } } } #end catch } #end function Set-TargetResource ## Import the common AD functions $adCommonFunctions = Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.ps1'; . $adCommonFunctions; Export-ModuleMember -Function *-TargetResource; |