Skip to content

Commit 602324a

Browse files
Merge pull request #96 from ActiveDirectoryManagementFramework/development
1.9.10
2 parents 9ef00b9 + 5ecb9f2 commit 602324a

15 files changed

+183
-52
lines changed

DomainManagement/DomainManagement.psd1

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
RootModule = 'DomainManagement.psm1'
44

55
# Version number of this module.
6-
ModuleVersion = '1.8.205'
6+
ModuleVersion = '1.9.210'
77

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

3131
# Additional Dependencies, cannot declare due to bug in dependency handling in PS5.1
3232
# @{ ModuleName = 'ADSec'; ModuleVersion = '1.0.0' }

DomainManagement/changelog.md

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## 1.9.210 (2024-12-13)
4+
5+
- Upd: Content Mode - added ability to exclude individual Components from constrained Content Mode
6+
- Upd: GroupMembers - extended membership scan for all groups under management, not just those with an explicit configuration entry
7+
- Fix: Users - stopped update results when not defining GivenName and Surname
8+
- Fix: Get-DMGroupMembership - ignores `-Name` parameter
9+
- Fix: Unregister-DMGroupMembership - fails to unregister processing modes
10+
311
## 1.8.205 (2024-10-22)
412

513
- Upd: Exchange - added extra validation to successful deployment runs

DomainManagement/en-us/strings.psd1

+1
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@
190190
'Resolve-PolicyRevision.Result.Result.SuccessNotYetManaged' = 'Policy found: {0}. Has not yet been managed, will need to be overwritten.' # $Policy.DisplayName
191191
'Resolve-PolicyRevision.Result.Success' = 'Found GPO: {0}. Last export ID: {1}. Last updated on {2}' # $Policy.DisplayName, $result.ExportID, $result.Timestamp
192192

193+
'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
193194
'Set-DMRedForestContext.Connection.Failed' = 'Failed to connect to {0}' # $Server
194195

195196
'Test-DMAccessRule.DefaultPermission.Failed' = 'Failed to retrieve default permissions from Schema when connecting to {0}' # $Server

DomainManagement/functions/acls/Test-DMAcl.ps1

+2
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@
139139
}
140140
#endregion processing configuration
141141

142+
if ($script:contentMode.ExcludeComponents.ACLs) { return }
143+
142144
#region check if all ADObjects are managed
143145
$foundADObjects = foreach ($searchBase in (Resolve-ContentSearchBase @parameters -NoContainer)) {
144146
Get-ADObject @parameters -LDAPFilter '(objectCategory=*)' -SearchBase $searchBase.SearchBase -SearchScope $searchBase.SearchScope -Properties AdminCount

DomainManagement/functions/gplinks/Test-DMGPLink.ps1

+4-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
$ous[$resolvedOU].ProcessingMode = 'Constrained'
6060
}
6161
}
62-
#region Explicit OUs
62+
#endregion Explicit OUs
6363

6464
#region Filter-Based OUs
6565
foreach ($filter in $script:groupPolicyLinksDynamic.Keys) {
@@ -315,6 +315,9 @@
315315
New-TestResult @resultDefaults -Type 'Update' -Changed $updates
316316
}
317317
}
318+
#endregion Process Configuration
319+
320+
if ($script:contentMode.ExcludeComponents.GPLinks) { return }
318321

319322
#region Process Managed Estate
320323
# OneLevel needs to be converted to base, as searching for OUs with "OneLevel" would return unmanaged OUs.

DomainManagement/functions/groupmemberships/Get-DMGroupMembership.ps1

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
$Group = '*',
2626

2727
[string]
28-
$Name = '*'
28+
$Name
2929
)
3030

3131
process
@@ -35,6 +35,7 @@
3535

3636
if ($script:groupMemberShips[$key].Count -gt 0) {
3737
foreach ($innerKey in $script:groupMemberShips[$key].Keys) {
38+
if ($Name -and $innerKey -notlike $Name) { continue }
3839
$script:groupMemberShips[$key][$innerKey]
3940
}
4041
}

DomainManagement/functions/groupmemberships/Register-DMGroupMembership.ps1

+1
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@
131131
$script:groupMemberShips[$Group]['__Configuration'] = [PSCustomObject]@{
132132
PSTypeName = 'DomainManagement.GroupMembership.Configuration'
133133
ProcessingMode = $GroupProcessingMode
134+
Group = $Group
134135
}
135136
}
136137
}

DomainManagement/functions/groupmemberships/Test-DMGroupMembership.ps1

+96-41
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,87 @@
4040
Invoke-Callback @parameters -Cmdlet $PSCmdlet
4141
Assert-Configuration -Type GroupMemberShips -Cmdlet $PSCmdlet
4242
Set-DMDomainContext @parameters
43+
44+
$resultDefaults = @{
45+
Server = $Server
46+
ObjectType = 'GroupMembership'
47+
}
48+
49+
#region Functions
50+
function Get-GroupMember {
51+
[CmdletBinding()]
52+
param (
53+
$ADObject,
54+
55+
[hashtable]
56+
$Parameters
57+
)
58+
59+
$ADObject.Members | ForEach-Object {
60+
$distinguishedName = $_
61+
try { Get-ADObject @parameters -Identity $_ -ErrorAction Stop -Properties SamAccountName, objectSid }
62+
catch {
63+
$objectDomainName = $distinguishedName.Split(",").Where{ $_ -like "DC=*" } -replace '^DC=' -join "."
64+
$cred = $Parameters | ConvertTo-PSFHashtable -Include Credential
65+
Get-ADObject -Server $objectDomainName @cred -Identity $distinguishedName -ErrorAction Stop -Properties SamAccountName, objectSid
66+
}
67+
}
68+
}
69+
70+
function New-MemberRemovalResult {
71+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
72+
[CmdletBinding()]
73+
param (
74+
$ADObject,
75+
76+
$ADMember,
77+
78+
[switch]
79+
$AssignmentsUnresolved,
80+
81+
$ResultDefaults
82+
)
83+
84+
$configObject = [PSCustomObject]@{
85+
Assignment = $null
86+
ADMember = $adMember
87+
}
88+
89+
$identifier = $ADMember.SamAccountName
90+
if (-not $identifier) {
91+
try { $identifier = Resolve-Principal -Name $ADMember.ObjectSid -OutputType SamAccountName -ErrorAction Stop }
92+
catch { $identifier = $ADMember.ObjectSid }
93+
}
94+
if (-not $identifier) { $identifier = $ADMember.ObjectSid }
95+
if ($AssignmentsUnresolved -and ($ADMember.ObjectClass -eq 'foreignSecurityPrincipal')) {
96+
# Currently a member, is foreignSecurityPrincipal and we cannot be sure we resolved everything that should be member
97+
New-TestResult @resultDefaults -Type Unidentified -Identity "$($ADObject.Name) þ $($ADMember.ObjectClass) þ $($identifier)" -Configuration $configObject -ADObject $ADObject
98+
}
99+
else {
100+
$change = [PSCustomObject]@{
101+
PSTypeName = 'DomainManagement.GroupMember.Change'
102+
Action = 'Remove'
103+
Group = $ADObject.Name
104+
Member = $identifier
105+
Type = $ADMember.ObjectClass
106+
}
107+
Add-Member -InputObject $change -MemberType ScriptMethod -Name ToString -Value { 'Remove: {0} -> {1}' -f $this.Member, $this.Group } -Force
108+
New-TestResult @resultDefaults -Type Delete -Identity "$($ADObject.Name) þ $($ADMember.ObjectClass) þ $($identifier)" -Configuration $configObject -ADObject $ADObject -Changed $change
109+
}
110+
}
111+
#endregion Functions
43112
}
44113
process {
114+
#region Configured Memberships
115+
$groupsProcessed = [System.Collections.ArrayList]@()
116+
45117
:main foreach ($groupMembershipName in $script:groupMemberShips.Keys) {
46118
$resolvedGroupName = Resolve-String -Text $groupMembershipName
47119
$processingMode = 'Constrained'
48120
if ($script:groupMemberShips[$groupMembershipName].__Configuration.ProcessingMode) {
49121
$processingMode = $script:groupMemberShips[$groupMembershipName].__Configuration.ProcessingMode
50122
}
51123

52-
$resultDefaults = @{
53-
Server = $Server
54-
ObjectType = 'GroupMembership'
55-
}
56-
57124
#region Resolve Assignments
58125
$failedResolveAssignment = $false
59126
$assignments = foreach ($assignment in $script:groupMemberShips[$groupMembershipName].Values) {
@@ -124,15 +191,8 @@
124191
#region Check Current AD State
125192
try {
126193
$adObject = Get-ADGroup @parameters -Identity $resolvedGroupName -Properties Members -ErrorAction Stop
127-
$adMembers = $adObject.Members | ForEach-Object {
128-
$distinguishedName = $_
129-
try { Get-ADObject @parameters -Identity $_ -ErrorAction Stop -Properties SamAccountName, objectSid }
130-
catch {
131-
$objectDomainName = $distinguishedName.Split(",").Where{ $_ -like "DC=*" } -replace '^DC=' -join "."
132-
$cred = $PSBoundParameters | ConvertTo-PSFHashtable -Include Credential
133-
Get-ADObject -Server $objectDomainName @cred -Identity $distinguishedName -ErrorAction Stop -Properties SamAccountName, objectSid
134-
}
135-
}
194+
$null = $groupsProcessed.Add($adObject.SamAccountName)
195+
$adMembers = Get-GroupMember -ADObject $adObject -Parameters $parameters
136196
}
137197
catch { Stop-PSFFunction -String 'Test-DMGroupMembership.Group.Access.Failed' -StringValues $resolvedGroupName -ErrorRecord $_ -EnableException $EnableException -Continue }
138198
#endregion Check Current AD State
@@ -158,7 +218,7 @@
158218
Member = Resolve-String -Text $assignment.ADMember.SamAccountName
159219
Type = $assignment.ADMember.ObjectClass
160220
}
161-
Add-Member -InputObject $change -MemberType ScriptMethod -Name ToString -Value { 'Add: {0} -> {1}' -f $this.Member, $this.Group } -Force
221+
[PSFramework.Object.ObjectHost]::AddScriptMethod($change, 'ToString', { 'Add: {0} -> {1}' -f $this.Member, $this.Group })
162222
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
163223
}
164224
#endregion Compare Assignments to existing state
@@ -170,34 +230,29 @@
170230
if ("$($adMember.ObjectSID)" -in ($assignments.ADMember.ObjectSID | ForEach-Object { "$_" })) {
171231
continue
172232
}
173-
$configObject = [PSCustomObject]@{
174-
Assignment = $null
175-
ADMember = $adMember
176-
}
177-
178-
$identifier = $adMember.SamAccountName
179-
if (-not $identifier) {
180-
try { $identifier = Resolve-Principal -Name $adMember.ObjectSid -OutputType SamAccountName -ErrorAction Stop }
181-
catch { $identifier = $adMember.ObjectSid }
182-
}
183-
if (-not $identifier) { $identifier = $adMember.ObjectSid }
184-
if ($failedResolveAssignment -and ($adMember.ObjectClass -eq 'foreignSecurityPrincipal')) {
185-
# Currently a member, is foreignSecurityPrincipal and we cannot be sure we resolved everything that should be member
186-
New-TestResult @resultDefaults -Type Unidentified -Identity "$($adObject.Name) þ $($adMember.ObjectClass) þ $($identifier)" -Configuration $configObject -ADObject $adObject
187-
}
188-
else {
189-
$change = [PSCustomObject]@{
190-
PSTypeName = 'DomainManagement.GroupMember.Change'
191-
Action = 'Remove'
192-
Group = $adObject.Name
193-
Member = $identifier
194-
Type = $adMember.ObjectClass
195-
}
196-
Add-Member -InputObject $change -MemberType ScriptMethod -Name ToString -Value { 'Remove: {0} -> {1}' -f $this.Member, $this.Group } -Force
197-
New-TestResult @resultDefaults -Type Delete -Identity "$($adObject.Name) þ $($adMember.ObjectClass) þ $($identifier)" -Configuration $configObject -ADObject $adObject -Changed $change
198-
}
233+
New-MemberRemovalResult -ADObject $adObject -ADMember $adMember -AssignmentsUnresolved:$failedResolveAssignment -ResultDefaults $resultDefaults
199234
}
200235
#endregion Compare existing state to assignments
201236
}
237+
#endregion Configured Memberships
238+
239+
#region Groups without configured Memberships
240+
if ($script:contentMode.ExcludeComponents.GroupMembership) { return }
241+
242+
$foundGroups = foreach ($searchBase in (Resolve-ContentSearchBase @parameters)) {
243+
Get-ADGroup @parameters -LDAPFilter '(name=*)' -SearchBase $searchBase.SearchBase -SearchScope $searchBase.SearchScope -Properties Members | Where-Object {
244+
$_.SamAccountName -NotIn $groupsProcessed -and
245+
@($_.Members).Count -gt 0
246+
}
247+
}
248+
249+
foreach ($adObject in $foundGroups) {
250+
$adMembers = Get-GroupMember -ADObject $adObject -Parameters $parameters
251+
252+
foreach ($adMember in $adMembers) {
253+
New-MemberRemovalResult -ADObject $adObject -ADMember $adMember -ResultDefaults $resultDefaults
254+
}
255+
}
256+
#endregion Groups without configured Memberships
202257
}
203258
}

DomainManagement/functions/groupmemberships/Unregister-DMGroupMembership.ps1

+19-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
1616
.PARAMETER Group
1717
The group being granted membership in.
18+
19+
.PARAMETER ProcessingMode
20+
The processing mode to apply for the group's membership management.
1821
1922
.EXAMPLE
2023
PS C:\> Get-DMGroupMembership | Unregister-DMGroupMembership
@@ -23,23 +26,35 @@
2326
#>
2427
[CmdletBinding()]
2528
param (
26-
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
29+
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Identity')]
2730
[string]
2831
$Name,
2932

30-
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
33+
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Identity')]
3134
[ValidateSet('User', 'Group', 'foreignSecurityPrincipal', 'Computer', '<Empty>')]
3235
[string]
3336
$ItemType,
3437

35-
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
38+
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Processing')]
39+
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Identity')]
40+
[string]
41+
$Group,
42+
43+
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Processing')]
3644
[string]
37-
$Group
45+
$ProcessingMode
3846
)
3947

4048
process
4149
{
4250
if (-not $script:groupMemberShips[$Group]) { return }
51+
if ($ProcessingMode) {
52+
$null = $script:groupMemberShips[$Group].Remove('__Configuration')
53+
if (-not $script:groupMemberShips[$Group].Count) {
54+
$null = $script:groupMemberShips.Remove($Group)
55+
}
56+
return
57+
}
4358
if ($Name -eq '<empty>') {
4459
$null = $script:groupMemberShips.Remove($Group)
4560
return

DomainManagement/functions/groups/Test-DMGroup.ps1

+2
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@
124124
#endregion Existing Groups, might need updates
125125
}
126126

127+
if ($script:contentMode.ExcludeComponents.Groups) { return }
128+
127129
$foundGroups = foreach ($searchBase in (Resolve-ContentSearchBase @parameters)) {
128130
Get-ADGroup @parameters -LDAPFilter '(!(isCriticalSystemObject=TRUE))' -SearchBase $searchBase.SearchBase -SearchScope $searchBase.SearchScope
129131
}

DomainManagement/functions/organizationalunits/Test-DMOrganizationalUnit.ps1

+2
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@
105105
}
106106
#endregion Process Configured OUs
107107

108+
if ($script:contentMode.ExcludeComponents.OrganizationalUnits) { return }
109+
108110
#region Process Managed Containers
109111
$foundOUs = foreach ($searchBase in (Resolve-ContentSearchBase @parameters -IgnoreMissingSearchbase)) {
110112
Get-ADOrganizationalUnit @parameters -LDAPFilter '(!(isCriticalSystemObject=TRUE))' -SearchBase $searchBase.SearchBase -SearchScope $searchBase.SearchScope -Properties nTSecurityDescriptor | Where-Object DistinguishedName -Ne $searchBase.SearchBase

DomainManagement/functions/serviceaccounts/Test-DMServiceAccount.ps1

+2
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@
238238
}
239239
#endregion Process Configured Objects
240240

241+
if ($script:contentMode.ExcludeComponents.ServiceAccounts) { return }
242+
241243
#region Process Non-Configuted AD-Objects
242244
$foundServiceAccounts = foreach ($searchBase in (Resolve-ContentSearchBase @parameters)) {
243245
Get-ADServiceAccount @parameters -LDAPFilter '(!(isCriticalSystemObject=TRUE))' -SearchBase $searchBase.SearchBase -SearchScope $searchBase.SearchScope

DomainManagement/functions/system/Set-DMContentMode.ps1

+30-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,23 @@
4444
Whether to remove unknown, undefined WMI Filters.
4545
Only relevant when defining the WMI Filter component.
4646
By default, WMI filters defined outside of the configuration will not be deleted if found.
47+
48+
.PARAMETER ExcludeComponents
49+
Components to exclude from the Domain Content Mode.
50+
By including them here, non-configured objects of that type will no longer get deleted.
51+
(Details may vary, depending on the specific Component. See their respective documentation.)
52+
53+
Each entry should use the Component name as Key and a boolean as Value in the hashtable.
54+
If the value is considered $true, the Component is excluded.
55+
Settings from multiple configuration sets will be merged, rather than fully replacing the old hashtable with a new one.
56+
57+
Supported Components:
58+
- ACLs: Excluding them will not test only configured values for ownership and inheritance.
59+
- 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.
60+
- GroupMembership: Excluding them will cause groups that have no membership configuration to be fundamentally ignored.
61+
- Groups: Excluding them will stop all group deletions other than explicit "Delete" configurations.
62+
- OrganizationalUnits: Excluding them will stop all OU deletions other than explicit "Delete" configurations.
63+
- ServiceAccounts: Excluding them will stop all Service Account deletions other than explicit "Delete" configurations.
4764
4865
.EXAMPLE
4966
PS C:\> Set-DMContentMode -Mode 'Constrained' -Include 'OU=Administration,%DomainDN%'
@@ -70,7 +87,10 @@
7087
$UserExcludePattern,
7188

7289
[bool]
73-
$RemoveUnknownWmiFilter
90+
$RemoveUnknownWmiFilter,
91+
92+
[hashtable]
93+
$ExcludeComponents
7494
)
7595

7696
process
@@ -80,5 +100,14 @@
80100
if (Test-PSFParameterBinding -ParameterName Exclude) { $script:contentMode.Exclude = $Exclude }
81101
if (Test-PSFParameterBinding -ParameterName UserExcludePattern) { $script:contentMode.UserExcludePattern = $UserExcludePattern }
82102
if (Test-PSFParameterBinding -ParameterName RemoveUnknownWmiFilter) { $script:contentMode.RemoveUnknownWmiFilter = $RemoveUnknownWmiFilter }
103+
if ($ExcludeComponents) {
104+
foreach ($pair in $ExcludeComponents.GetEnumerator()) {
105+
if ($script:contentMode.ExcludeComponents.Keys -notcontains $pair.Key) {
106+
Write-PSFMessage -Level Warning -String 'Set-DMContentMode.Error.UnknownExcludedComponent' -StringValues $pair.Key
107+
continue
108+
}
109+
$script:contentMode.ExcludeComponents[$pair.Key] = $pair.Value -as [bool]
110+
}
111+
}
83112
}
84113
}

0 commit comments

Comments
 (0)