Skip to content

Commit

Permalink
Merge pull request #96 from ActiveDirectoryManagementFramework/develo…
Browse files Browse the repository at this point in the history
…pment

1.9.10
  • Loading branch information
FriedrichWeinmann authored Dec 13, 2024
2 parents 9ef00b9 + 5ecb9f2 commit 602324a
Show file tree
Hide file tree
Showing 15 changed files with 183 additions and 52 deletions.
4 changes: 2 additions & 2 deletions DomainManagement/DomainManagement.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
RootModule = 'DomainManagement.psm1'

# Version number of this module.
ModuleVersion = '1.8.205'
ModuleVersion = '1.9.210'

# ID used to uniquely identify this module
GUID = '0a405382-ebc2-445b-8325-541535810193'
Expand All @@ -26,7 +26,7 @@
# Modules that must be imported into the global environment prior to importing
# this module
RequiredModules = @(
@{ ModuleName = 'PSFramework'; ModuleVersion = '1.10.318' }
@{ ModuleName = 'PSFramework'; ModuleVersion = '1.12.346' }

# Additional Dependencies, cannot declare due to bug in dependency handling in PS5.1
# @{ ModuleName = 'ADSec'; ModuleVersion = '1.0.0' }
Expand Down
8 changes: 8 additions & 0 deletions DomainManagement/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 1.9.210 (2024-12-13)

- Upd: Content Mode - added ability to exclude individual Components from constrained Content Mode
- Upd: GroupMembers - extended membership scan for all groups under management, not just those with an explicit configuration entry
- Fix: Users - stopped update results when not defining GivenName and Surname
- Fix: Get-DMGroupMembership - ignores `-Name` parameter
- Fix: Unregister-DMGroupMembership - fails to unregister processing modes

## 1.8.205 (2024-10-22)

- Upd: Exchange - added extra validation to successful deployment runs
Expand Down
1 change: 1 addition & 0 deletions DomainManagement/en-us/strings.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@
'Resolve-PolicyRevision.Result.Result.SuccessNotYetManaged' = 'Policy found: {0}. Has not yet been managed, will need to be overwritten.' # $Policy.DisplayName
'Resolve-PolicyRevision.Result.Success' = 'Found GPO: {0}. Last export ID: {1}. Last updated on {2}' # $Policy.DisplayName, $result.ExportID, $result.Timestamp

'Set-DMContentMode.Error.UnknownExcludedComponent' = 'Error excluding a Component from the Domain Content Mode. Unexpected Component: {0}. Ensure the Component specified not only exists, but also supports being excluded from Domain Content Mode.' # $pair.Key
'Set-DMRedForestContext.Connection.Failed' = 'Failed to connect to {0}' # $Server

'Test-DMAccessRule.DefaultPermission.Failed' = 'Failed to retrieve default permissions from Schema when connecting to {0}' # $Server
Expand Down
2 changes: 2 additions & 0 deletions DomainManagement/functions/acls/Test-DMAcl.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@
}
#endregion processing configuration

if ($script:contentMode.ExcludeComponents.ACLs) { return }

#region check if all ADObjects are managed
$foundADObjects = foreach ($searchBase in (Resolve-ContentSearchBase @parameters -NoContainer)) {
Get-ADObject @parameters -LDAPFilter '(objectCategory=*)' -SearchBase $searchBase.SearchBase -SearchScope $searchBase.SearchScope -Properties AdminCount
Expand Down
5 changes: 4 additions & 1 deletion DomainManagement/functions/gplinks/Test-DMGPLink.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
$ous[$resolvedOU].ProcessingMode = 'Constrained'
}
}
#region Explicit OUs
#endregion Explicit OUs

#region Filter-Based OUs
foreach ($filter in $script:groupPolicyLinksDynamic.Keys) {
Expand Down Expand Up @@ -315,6 +315,9 @@
New-TestResult @resultDefaults -Type 'Update' -Changed $updates
}
}
#endregion Process Configuration

if ($script:contentMode.ExcludeComponents.GPLinks) { return }

#region Process Managed Estate
# OneLevel needs to be converted to base, as searching for OUs with "OneLevel" would return unmanaged OUs.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
$Group = '*',

[string]
$Name = '*'
$Name
)

process
Expand All @@ -35,6 +35,7 @@

if ($script:groupMemberShips[$key].Count -gt 0) {
foreach ($innerKey in $script:groupMemberShips[$key].Keys) {
if ($Name -and $innerKey -notlike $Name) { continue }
$script:groupMemberShips[$key][$innerKey]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
$script:groupMemberShips[$Group]['__Configuration'] = [PSCustomObject]@{
PSTypeName = 'DomainManagement.GroupMembership.Configuration'
ProcessingMode = $GroupProcessingMode
Group = $Group
}
}
}
Expand Down
137 changes: 96 additions & 41 deletions DomainManagement/functions/groupmemberships/Test-DMGroupMembership.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,87 @@
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
}

$resultDefaults = @{
Server = $Server
ObjectType = 'GroupMembership'
}

#region Resolve Assignments
$failedResolveAssignment = $false
$assignments = foreach ($assignment in $script:groupMemberShips[$groupMembershipName].Values) {
Expand Down Expand Up @@ -124,15 +191,8 @@
#region Check Current AD State
try {
$adObject = Get-ADGroup @parameters -Identity $resolvedGroupName -Properties Members -ErrorAction Stop
$adMembers = $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 = $PSBoundParameters | ConvertTo-PSFHashtable -Include Credential
Get-ADObject -Server $objectDomainName @cred -Identity $distinguishedName -ErrorAction Stop -Properties SamAccountName, objectSid
}
}
$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
Expand All @@ -158,7 +218,7 @@
Member = Resolve-String -Text $assignment.ADMember.SamAccountName
Type = $assignment.ADMember.ObjectClass
}
Add-Member -InputObject $change -MemberType ScriptMethod -Name ToString -Value { 'Add: {0} -> {1}' -f $this.Member, $this.Group } -Force
[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
Expand All @@ -170,34 +230,29 @@
if ("$($adMember.ObjectSID)" -in ($assignments.ADMember.ObjectSID | ForEach-Object { "$_" })) {
continue
}
$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 ($failedResolveAssignment -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
}
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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
.PARAMETER Group
The group being granted membership in.
.PARAMETER ProcessingMode
The processing mode to apply for the group's membership management.
.EXAMPLE
PS C:\> Get-DMGroupMembership | Unregister-DMGroupMembership
Expand All @@ -23,23 +26,35 @@
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Identity')]
[string]
$Name,

[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Identity')]
[ValidateSet('User', 'Group', 'foreignSecurityPrincipal', 'Computer', '<Empty>')]
[string]
$ItemType,

[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Processing')]
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Identity')]
[string]
$Group,

[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Processing')]
[string]
$Group
$ProcessingMode
)

process
{
if (-not $script:groupMemberShips[$Group]) { return }
if ($ProcessingMode) {
$null = $script:groupMemberShips[$Group].Remove('__Configuration')
if (-not $script:groupMemberShips[$Group].Count) {
$null = $script:groupMemberShips.Remove($Group)
}
return
}
if ($Name -eq '<empty>') {
$null = $script:groupMemberShips.Remove($Group)
return
Expand Down
2 changes: 2 additions & 0 deletions DomainManagement/functions/groups/Test-DMGroup.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@
#endregion Existing Groups, might need updates
}

if ($script:contentMode.ExcludeComponents.Groups) { return }

$foundGroups = foreach ($searchBase in (Resolve-ContentSearchBase @parameters)) {
Get-ADGroup @parameters -LDAPFilter '(!(isCriticalSystemObject=TRUE))' -SearchBase $searchBase.SearchBase -SearchScope $searchBase.SearchScope
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@
}
#endregion Process Configured OUs

if ($script:contentMode.ExcludeComponents.OrganizationalUnits) { return }

#region Process Managed Containers
$foundOUs = foreach ($searchBase in (Resolve-ContentSearchBase @parameters -IgnoreMissingSearchbase)) {
Get-ADOrganizationalUnit @parameters -LDAPFilter '(!(isCriticalSystemObject=TRUE))' -SearchBase $searchBase.SearchBase -SearchScope $searchBase.SearchScope -Properties nTSecurityDescriptor | Where-Object DistinguishedName -Ne $searchBase.SearchBase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@
}
#endregion Process Configured Objects

if ($script:contentMode.ExcludeComponents.ServiceAccounts) { return }

#region Process Non-Configuted AD-Objects
$foundServiceAccounts = foreach ($searchBase in (Resolve-ContentSearchBase @parameters)) {
Get-ADServiceAccount @parameters -LDAPFilter '(!(isCriticalSystemObject=TRUE))' -SearchBase $searchBase.SearchBase -SearchScope $searchBase.SearchScope
Expand Down
31 changes: 30 additions & 1 deletion DomainManagement/functions/system/Set-DMContentMode.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,23 @@
Whether to remove unknown, undefined WMI Filters.
Only relevant when defining the WMI Filter component.
By default, WMI filters defined outside of the configuration will not be deleted if found.
.PARAMETER ExcludeComponents
Components to exclude from the Domain Content Mode.
By including them here, non-configured objects of that type will no longer get deleted.
(Details may vary, depending on the specific Component. See their respective documentation.)
Each entry should use the Component name as Key and a boolean as Value in the hashtable.
If the value is considered $true, the Component is excluded.
Settings from multiple configuration sets will be merged, rather than fully replacing the old hashtable with a new one.
Supported Components:
- ACLs: Excluding them will not test only configured values for ownership and inheritance.
- GPLinks: Excluding them will have it ignore all GPLinks on OUs that have no GP Links configured. OUs with any GP Links defined will be managed as per applicable processing mode.
- GroupMembership: Excluding them will cause groups that have no membership configuration to be fundamentally ignored.
- Groups: Excluding them will stop all group deletions other than explicit "Delete" configurations.
- OrganizationalUnits: Excluding them will stop all OU deletions other than explicit "Delete" configurations.
- ServiceAccounts: Excluding them will stop all Service Account deletions other than explicit "Delete" configurations.
.EXAMPLE
PS C:\> Set-DMContentMode -Mode 'Constrained' -Include 'OU=Administration,%DomainDN%'
Expand All @@ -70,7 +87,10 @@
$UserExcludePattern,

[bool]
$RemoveUnknownWmiFilter
$RemoveUnknownWmiFilter,

[hashtable]
$ExcludeComponents
)

process
Expand All @@ -80,5 +100,14 @@
if (Test-PSFParameterBinding -ParameterName Exclude) { $script:contentMode.Exclude = $Exclude }
if (Test-PSFParameterBinding -ParameterName UserExcludePattern) { $script:contentMode.UserExcludePattern = $UserExcludePattern }
if (Test-PSFParameterBinding -ParameterName RemoveUnknownWmiFilter) { $script:contentMode.RemoveUnknownWmiFilter = $RemoveUnknownWmiFilter }
if ($ExcludeComponents) {
foreach ($pair in $ExcludeComponents.GetEnumerator()) {
if ($script:contentMode.ExcludeComponents.Keys -notcontains $pair.Key) {
Write-PSFMessage -Level Warning -String 'Set-DMContentMode.Error.UnknownExcludedComponent' -StringValues $pair.Key
continue
}
$script:contentMode.ExcludeComponents[$pair.Key] = $pair.Value -as [bool]
}
}
}
}
Loading

0 comments on commit 602324a

Please sign in to comment.