Skip to content

Commit 5ecb9f2

Browse files
Merge pull request #95 from ActiveDirectoryManagementFramework/group-membership
1.9.210
2 parents a3ac45b + a2595f7 commit 5ecb9f2

File tree

15 files changed

+183
-52
lines changed

15 files changed

+183
-52
lines changed

DomainManagement/DomainManagement.psd1

Lines changed: 2 additions & 2 deletions
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

Lines changed: 8 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 4 additions & 1 deletion
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

Lines changed: 2 additions & 1 deletion
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 96 additions & 41 deletions
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

Lines changed: 19 additions & 4 deletions
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

Lines changed: 2 additions & 0 deletions
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
}

0 commit comments

Comments
 (0)