functions/groupmemberships/Test-DMGroupMembership.ps1
function Test-DMGroupMembership { <# .SYNOPSIS Tests, whether the target domain is compliant with the desired group membership assignments. .DESCRIPTION Tests, whether the target domain is compliant with the desired group membership assignments. .PARAMETER Server The server / domain to work with. .PARAMETER Credential The credentials to use for this operation. .PARAMETER EnableException This parameters disables user-friendly warnings and enables the throwing of exceptions. This is less user friendly, but allows catching exceptions in calling scripts. .EXAMPLE PS C:\> Test-DMGroupMembership -Server contoso.com Tests, whether the "contoso.com" domain is in compliance with the desired group membership assignments. #> [CmdletBinding()] param ( [PSFComputer] $Server, [PSCredential] $Credential, [switch] $EnableException ) begin { $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include Server, Credential $parameters['Debug'] = $false Assert-ADConnection @parameters -Cmdlet $PSCmdlet Invoke-Callback @parameters -Cmdlet $PSCmdlet Assert-Configuration -Type GroupMemberShips -Cmdlet $PSCmdlet Set-DMDomainContext @parameters $resultDefaults = @{ Server = $Server ObjectType = 'GroupMembership' } #region Functions function Get-GroupMember { [CmdletBinding()] param ( $ADObject, [hashtable] $Parameters ) $ADObject.Members | ForEach-Object { $distinguishedName = $_ try { Get-ADObject @parameters -Identity $_ -ErrorAction Stop -Properties SamAccountName, objectSid } catch { $objectDomainName = $distinguishedName.Split(",").Where{ $_ -like "DC=*" } -replace '^DC=' -join "." $cred = $Parameters | ConvertTo-PSFHashtable -Include Credential Get-ADObject -Server $objectDomainName @cred -Identity $distinguishedName -ErrorAction Stop -Properties SamAccountName, objectSid } } } function New-MemberRemovalResult { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] param ( $ADObject, $ADMember, [switch] $AssignmentsUnresolved, $ResultDefaults ) $configObject = [PSCustomObject]@{ Assignment = $null ADMember = $adMember } $identifier = $ADMember.SamAccountName if (-not $identifier) { try { $identifier = Resolve-Principal -Name $ADMember.ObjectSid -OutputType SamAccountName -ErrorAction Stop } catch { $identifier = $ADMember.ObjectSid } } if (-not $identifier) { $identifier = $ADMember.ObjectSid } if ($AssignmentsUnresolved -and ($ADMember.ObjectClass -eq 'foreignSecurityPrincipal')) { # Currently a member, is foreignSecurityPrincipal and we cannot be sure we resolved everything that should be member New-TestResult @resultDefaults -Type Unidentified -Identity "$($ADObject.Name) þ $($ADMember.ObjectClass) þ $($identifier)" -Configuration $configObject -ADObject $ADObject } else { $change = [PSCustomObject]@{ PSTypeName = 'DomainManagement.GroupMember.Change' Action = 'Remove' Group = $ADObject.Name Member = $identifier Type = $ADMember.ObjectClass } Add-Member -InputObject $change -MemberType ScriptMethod -Name ToString -Value { 'Remove: {0} -> {1}' -f $this.Member, $this.Group } -Force New-TestResult @resultDefaults -Type Delete -Identity "$($ADObject.Name) þ $($ADMember.ObjectClass) þ $($identifier)" -Configuration $configObject -ADObject $ADObject -Changed $change } } #endregion Functions } process { #region Configured Memberships $groupsProcessed = [System.Collections.ArrayList]@() :main foreach ($groupMembershipName in $script:groupMemberShips.Keys) { $resolvedGroupName = Resolve-String -Text $groupMembershipName $processingMode = 'Constrained' if ($script:groupMemberShips[$groupMembershipName].__Configuration.ProcessingMode) { $processingMode = $script:groupMemberShips[$groupMembershipName].__Configuration.ProcessingMode } #region Resolve Assignments $failedResolveAssignment = $false $assignments = foreach ($assignment in $script:groupMemberShips[$groupMembershipName].Values) { if ($assignment.PSObject.TypeNames -contains 'DomainManagement.GroupMembership.Configuration') { continue } #region Explicit Entity if ($assignment.Name) { $param = @{ Domain = Resolve-String -Text $assignment.Domain } + $parameters if ((Resolve-String -Text $assignment.Name) -as [System.Security.Principal.SecurityIdentifier]) { $param['Sid'] = Resolve-String -Text $assignment.Name } else { $param['Name'] = Resolve-String -Text $assignment.Name $param['ObjectClass'] = $assignment.ItemType } try { $adResult = Get-Principal @param } catch { # If it's a member that is allowed to NOT exist, simply skip the entry if ($assignment.Mode -in 'MemberIfExists', 'MayBeMemberIfExists') { continue } Write-PSFMessage -Level Warning -String 'Test-DMGroupMembership.Assignment.Resolve.Connect' -StringValues (Resolve-String -Text $assignment.Domain), (Resolve-String -Text $assignment.Name), $assignment.ItemType -ErrorRecord $_ -Target $assignment $failedResolveAssignment = $true [PSCustomObject]@{ Assignment = $assignment ADMember = $null Type = 'Explicit' } continue } if (-not $adResult) { # If it's a member that is allowed to NOT exist, simply skip the entry if ($assignment.Mode -in 'MemberIfExists', 'MayBeMemberIfExists') { continue } Write-PSFMessage -Level Warning -String 'Test-DMGroupMembership.Assignment.Resolve.NotFound' -StringValues (Resolve-String -Text $assignment.Domain), (Resolve-String -Text $assignment.Name), $assignment.ItemType -Target $assignment $failedResolveAssignment = $true [PSCustomObject]@{ Assignment = $assignment ADMember = $null Type = 'Explicit' } continue } [PSCustomObject]@{ Assignment = $assignment ADMember = $adResult Type = 'Explicit' } } #endregion Explicit Entity #region Object Category elseif ($assignment.Category) { try { $adObjects = Find-DMObjectCategoryItem @parameters -Name $assignment.Category -Property ObjectSID, SamAccountName -EnableException } catch { Stop-PSFFunction -String 'Test-DMGroupMembership.Category.Error' -StringValues $assignment.Category, $assignment.Group -ErrorRecord $_ -Continue -ContinueLabel main -EnableException $EnableException -Target $assignment } foreach ($adObject in $adObjects) { [PSCustomObject]@{ Assignment = $assignment ADMember = $adObject Type = 'Category' } } } #endregion Object Category } #endregion Resolve Assignments #region Check Current AD State try { $adObject = Get-ADGroup @parameters -Identity $resolvedGroupName -Properties Members -ErrorAction Stop $null = $groupsProcessed.Add($adObject.SamAccountName) $adMembers = Get-GroupMember -ADObject $adObject -Parameters $parameters } catch { Stop-PSFFunction -String 'Test-DMGroupMembership.Group.Access.Failed' -StringValues $resolvedGroupName -ErrorRecord $_ -EnableException $EnableException -Continue } #endregion Check Current AD State #region Compare Assignments to existing state foreach ($assignment in $assignments) { if (-not $assignment.ADMember) { # Principal that should be member could not be found New-TestResult @resultDefaults -Type Unresolved -Identity "$(Resolve-String -Text $assignment.Assignment.Group) þ $($assignment.Assignment.ItemType) þ $(Resolve-String -Text $assignment.Assignment.Name)" -Configuration $assignment -ADObject $adObject continue } # Skip if membership is optional if ($assignment.Assignment.Mode -in 'MayBeMember', 'MayBeMemberIfExists') { continue } if ($adMembers | Where-Object ObjectSID -EQ $assignment.ADMember.objectSID) { continue } $change = [PSCustomObject]@{ PSTypeName = 'DomainManagement.GroupMember.Change' Action = 'Add' Group = Resolve-String -Text $assignment.Assignment.Group Member = Resolve-String -Text $assignment.ADMember.SamAccountName Type = $assignment.ADMember.ObjectClass } [PSFramework.Object.ObjectHost]::AddScriptMethod($change, 'ToString', { 'Add: {0} -> {1}' -f $this.Member, $this.Group }) New-TestResult @resultDefaults -Type Add -Identity "$(Resolve-String -Text $assignment.Assignment.Group) þ $($assignment.ADMember.ObjectClass) þ $(Resolve-String -Text $assignment.ADMember.SamAccountName)" -Configuration $assignment -ADObject $adObject -Changed $change } #endregion Compare Assignments to existing state if ($processingMode -eq 'Additive') { continue } #region Compare existing state to assignments foreach ($adMember in $adMembers) { if ("$($adMember.ObjectSID)" -in ($assignments.ADMember.ObjectSID | ForEach-Object { "$_" })) { continue } New-MemberRemovalResult -ADObject $adObject -ADMember $adMember -AssignmentsUnresolved:$failedResolveAssignment -ResultDefaults $resultDefaults } #endregion Compare existing state to assignments } #endregion Configured Memberships #region Groups without configured Memberships if ($script:contentMode.ExcludeComponents.GroupMembership) { return } $foundGroups = foreach ($searchBase in (Resolve-ContentSearchBase @parameters)) { Get-ADGroup @parameters -LDAPFilter '(name=*)' -SearchBase $searchBase.SearchBase -SearchScope $searchBase.SearchScope -Properties Members | Where-Object { $_.SamAccountName -NotIn $groupsProcessed -and @($_.Members).Count -gt 0 } } foreach ($adObject in $foundGroups) { $adMembers = Get-GroupMember -ADObject $adObject -Parameters $parameters foreach ($adMember in $adMembers) { New-MemberRemovalResult -ADObject $adObject -ADMember $adMember -ResultDefaults $resultDefaults } } #endregion Groups without configured Memberships } } |