Skip to content

Commit 69dd5f1

Browse files
Merge pull request #107 from ActiveDirectoryManagementFramework/july25
1.9.227
2 parents bd41c83 + fc3a0c8 commit 69dd5f1

File tree

17 files changed

+138
-68
lines changed

17 files changed

+138
-68
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,7 @@ DomainManagement/DomainManagement.psproj
1919
TestResults/*
2020

2121
# ignore the publishing Directory
22-
publish/*
22+
publish/*
23+
24+
# Local Planning Folder
25+
planning/*

DomainManagement/DomainManagement.psd1

Lines changed: 1 addition & 1 deletion
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.9.218'
6+
ModuleVersion = '1.9.228'
77

88
# ID used to uniquely identify this module
99
GUID = '0a405382-ebc2-445b-8325-541535810193'

DomainManagement/changelog.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## 1.9.228 (2025-10-02)
4+
5+
- Upd: Group Memberships - added option "ConfigOnly", allowing to define the group processing mode, without specifying any actual memberships.
6+
- Upd: Group Memberships - added option "GroupOptional", disabling the warning message if the group does not exist
7+
- Upd: Organizational Units - test results sorted correctly for invocation already during test, to help with Invoke-AdmfItem execution.
8+
- Upd: Users - UserPrincipalName made optional, $null and empty string are now considered equal.
9+
- Upd: Users - Added support for DomainData in name resolution
10+
- Fix: Access Rules - in additive mode, Restore changes should not be generated
11+
- Fix: Organizational Units - test results not sorted correctly during invocation, when specifying explicit results to invoke (#101)
12+
- Fix: Organizational Units - Group Policy Inheritance fails to correctly detect its state, when gpOptions are set to 0 instead of $null
13+
- Fix: Users - A user without a UserAccountControl property will fail all PasswordNeverExpires comparissons, no matter the configuration
14+
- Fix: Wmi Filter - fails to apply filters with a single query correctly on Windows PowerShell
15+
316
## 1.9.218 (2025-05-28)
417

518
- Upd: Organizational Units - added ability to define GP inheritance blocking. Defaults to NOT block.

DomainManagement/functions/AccessRule/Register-DMAccessRule.ps1

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@
3333
3434
.PARAMETER InheritanceType
3535
How the Access Rule is being inherited.
36+
None: Indicates no inheritance. The ACE information is only used on the object on which the ACE is set. ACE information is not inherited by any descendents of the object.
37+
All: Indicates inheritance that includes the object to which the ACE is applied, the object's immediate children, and the descendents of the object's children.
38+
Descendents: Indicates inheritance that includes the object's immediate children and the descendants of the object's children, but not the object itself.
39+
SelfAndChildren: Indicates inheritance that includes the object itself and its immediate children. It does not include the descendents of its children.
40+
Children: Indicates inheritance that includes the object's immediate children only, not the object itself or the descendents of its children.
3641
3742
.PARAMETER InheritedObjectType
3843
Name or Guid of property or right affected by this rule.

DomainManagement/functions/gplinks/Invoke-DMGPLink.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,4 +227,4 @@
227227
}
228228
#endregion Executing Test-Results
229229
}
230-
}
230+
}

DomainManagement/functions/groupmemberships/Register-DMGroupMembership.ps1

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
2626
.PARAMETER Group
2727
The group to define members for.
28+
29+
.PARAMETER GroupOptional
30+
Group needs not exist.
31+
If this is set to true for all assignments to a group, the group need not actually exist, disabling the corresponding error message.
32+
This also always applies to groups with no assignments.
2833
2934
.PARAMETER Empty
3035
Whether the specified group should be empty.
@@ -44,6 +49,9 @@
4449
- Constrained: Existing Group Memberships not defined will be removed
4550
- Additive: Group Memberships defined will be applied, but non-configured memberships will be ignored.
4651
If no setting is defined, it will default to 'Constrained'
52+
53+
.PARAMETER ConfigOnly
54+
Define only the Group Processing Mode, without making any statement about actual memberships.
4755
4856
.PARAMETER ContextName
4957
The name of the context defining the setting.
@@ -55,7 +63,7 @@
5563
5664
Imports all defined groupmemberships from the targeted json configuration file.
5765
#>
58-
66+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")]
5967
[CmdletBinding(DefaultParameterSetName = 'Entry')]
6068
param (
6169
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Entry')]
@@ -78,8 +86,12 @@
7886
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Entry')]
7987
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Category')]
8088
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Empty')]
89+
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Config')]
8190
[string]
8291
$Group,
92+
93+
[bool]
94+
$GroupOptional,
8395

8496
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Empty')]
8597
[bool]
@@ -90,10 +102,17 @@
90102
[string]
91103
$Mode = 'Default',
92104

93-
[Parameter(ValueFromPipelineByPropertyName = $true)]
105+
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Entry')]
106+
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Category')]
107+
[Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Empty')]
108+
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Config')]
94109
[ValidateSet('Constrained', 'Additive')]
95110
[string]
96111
$GroupProcessingMode,
112+
113+
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Config')]
114+
[switch]
115+
$ConfigOnly,
97116

98117
[string]
99118
$ContextName = '<Undefined>'
@@ -105,22 +124,24 @@
105124
}
106125
if ($Name) {
107126
$script:groupMemberShips[$Group]["$($ItemType):$($Name)"] = [PSCustomObject]@{
108-
PSTypeName = 'DomainManagement.GroupMembership'
109-
Name = $Name
110-
Domain = $Domain
111-
ItemType = $ItemType
112-
Group = $Group
113-
Mode = $Mode
114-
ContextName = $ContextName
127+
PSTypeName = 'DomainManagement.GroupMembership'
128+
Name = $Name
129+
Domain = $Domain
130+
ItemType = $ItemType
131+
Group = $Group
132+
GroupOptional = $GroupOptional
133+
Mode = $Mode
134+
ContextName = $ContextName
115135
}
116136
}
117137
elseif ($ObjectCategory) {
118138
$script:groupMemberShips[$Group]["ObjectCategory:$($ObjectCategory)"] = [PSCustomObject]@{
119-
PSTypeName = 'DomainManagement.GroupMembership'
120-
Category = $ObjectCategory
121-
Group = $Group
122-
Mode = $Mode
123-
ContextName = $ContextName
139+
PSTypeName = 'DomainManagement.GroupMembership'
140+
Category = $ObjectCategory
141+
Group = $Group
142+
GroupOptional = $GroupOptional
143+
Mode = $Mode
144+
ContextName = $ContextName
124145
}
125146
}
126147
elseif ($Empty) {

DomainManagement/functions/groupmemberships/Test-DMGroupMembership.ps1

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,10 @@
123123

124124
#region Resolve Assignments
125125
$failedResolveAssignment = $false
126+
$groupNeedsNotExist = $true
126127
$assignments = foreach ($assignment in $script:groupMemberShips[$groupMembershipName].Values) {
127128
if ($assignment.PSObject.TypeNames -contains 'DomainManagement.GroupMembership.Configuration') { continue }
129+
if (-not $assignment.GroupOptional) { $groupNeedsNotExist = $false }
128130

129131
#region Explicit Entity
130132
if ($assignment.Name) {
@@ -194,7 +196,13 @@
194196
$null = $groupsProcessed.Add($adObject.SamAccountName)
195197
$adMembers = Get-GroupMember -ADObject $adObject -Parameters $parameters
196198
}
197-
catch { Stop-PSFFunction -String 'Test-DMGroupMembership.Group.Access.Failed' -StringValues $resolvedGroupName -ErrorRecord $_ -EnableException $EnableException -Continue }
199+
catch {
200+
if ($groupNeedsNotExist) {
201+
Write-PSFMessage -Level Debug -String 'Test-DMGroupMembership.Group.Access.Failed' -StringValues $resolvedGroupName -ErrorRecord $_
202+
continue
203+
}
204+
Stop-PSFFunction -String 'Test-DMGroupMembership.Group.Access.Failed' -StringValues $resolvedGroupName -ErrorRecord $_ -EnableException $EnableException -Continue
205+
}
198206
#endregion Check Current AD State
199207

200208
#region Compare Assignments to existing state

DomainManagement/functions/organizationalunits/Invoke-DMOrganizationalUnit.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,10 @@
7575
}
7676
#endregion Sort Script
7777
if (-not $InputObject) {
78-
$InputObject = Test-DMOrganizationalUnit @parameters | Sort-Object $sortScript -Descending
78+
$InputObject = Test-DMOrganizationalUnit @parameters
7979
}
8080

81-
:main foreach ($testItem in $InputObject) {
81+
:main foreach ($testItem in $InputObject | Sort-Object $sortScript -Descending) {
8282
# Catch invalid input - can only process test results
8383
if ($testItem.PSObject.TypeNames -notcontains 'DomainManagement.OrganizationalUnit.TestResult') {
8484
Stop-PSFFunction -String 'General.Invalid.Input' -StringValues 'Test-DMOrganizationalUnit', $testItem -Target $testItem -Continue -EnableException $EnableException

DomainManagement/functions/organizationalunits/Test-DMOrganizationalUnit.ps1

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,18 @@
3535
Invoke-Callback @parameters -Cmdlet $PSCmdlet
3636
Assert-Configuration -Type OrganizationalUnits -Cmdlet $PSCmdlet
3737
Set-DMDomainContext @parameters
38+
39+
#region Sort Script
40+
$sortScript = {
41+
if ($_.Type -eq 'ShouldDelete') { $_.ADObject.DistinguishedName.Split(",").Count }
42+
else { 1000 - $_.Identity.Split(",").Count }
43+
}
44+
#endregion Sort Script
3845
}
3946
process
4047
{
4148
#region Process Configured OUs
42-
:main foreach ($ouDefinition in $script:organizationalUnits.Values) {
49+
$results = :main foreach ($ouDefinition in $script:organizationalUnits.Values) {
4350
$resolvedDN = Resolve-String -Text $ouDefinition.DistinguishedName
4451

4552
$resultDefaults = @{
@@ -98,9 +105,9 @@
98105

99106
[System.Collections.ArrayList]$changes = @()
100107
Compare-Property -Property Description -Configuration $ouDefinition -ADObject $adObject -Changes $changes -Resolve -AsString -AsUpdate -Type OrganizationalUnit
101-
$gpInheritIntended = $null
108+
$gpInheritIntended = $null, 0
102109
if ($ouDefinition.BlockGPInheritance) { $gpInheritIntended = 1 }
103-
if ($gpInheritIntended -ne $adObject.gpOptions) {
110+
if ($adObject.gpOptions -notin $gpInheritIntended) {
104111
$null = $changes.Add(
105112
(New-Change -Property 'GPBlocked' -Identity $adObject.DistinguishedName -Type OrganizationalUnit -NewValue $ouDefinition.BlockGPInheritance -OldValue (-not $ouDefinition.BlockGPInheritance))
106113
)
@@ -112,7 +119,10 @@
112119
}
113120
#endregion Process Configured OUs
114121

115-
if ($script:contentMode.ExcludeComponents.OrganizationalUnits) { return }
122+
if ($script:contentMode.ExcludeComponents.OrganizationalUnits) {
123+
$results | Sort-Object $sortScript
124+
return
125+
}
116126

117127
#region Process Managed Containers
118128
$foundOUs = foreach ($searchBase in (Resolve-ContentSearchBase @parameters -IgnoreMissingSearchbase)) {
@@ -126,11 +136,13 @@
126136
ObjectType = 'OrganizationalUnit'
127137
}
128138

129-
foreach ($existingOU in $foundOUs) {
139+
$resultsDelete = foreach ($existingOU in $foundOUs) {
130140
if ($existingOU.DistinguishedName -in $resolvedConfiguredNames) { continue } # Ignore configured OUs - they were previously configured for moving them, if they should not be in these containers
131141

132142
New-TestResult @resultDefaults -Type Delete -ADObject $existingOU -Identity $existingOU.DistinguishedName
133143
}
144+
145+
$results, $resultsDelete | Remove-PSFNull -Enumerate | Sort-Object $sortScript
134146
#endregion Process Managed Containers
135147
}
136148
}

DomainManagement/functions/users/Invoke-DMUser.ps1

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -72,29 +72,29 @@
7272
} -EnableException $EnableException.ToBool() -PSCmdlet $PSCmdlet -Continue
7373
}
7474
'Create' {
75-
$targetOU = Resolve-String -Text $testItem.Configuration.Path
75+
$targetOU = Resolve-String -Text $testItem.Configuration.Path @parameters
7676
try { $null = Get-ADObject @parameters -Identity $targetOU -ErrorAction Stop }
7777
catch { Stop-PSFFunction -String 'Invoke-DMUser.User.Create.OUExistsNot' -StringValues $targetOU, $testItem.Identity -Target $testItem -EnableException $EnableException -Continue -ContinueLabel main }
7878
Invoke-PSFProtectedCommand -ActionString 'Invoke-DMUser.User.Create' -Target $testItem -ScriptBlock {
7979
$newParameters = $parameters.Clone()
8080
$newParameters += @{
81-
Name = (Resolve-String -Text $testItem.Configuration.SamAccountName)
82-
SamAccountName = (Resolve-String -Text $testItem.Configuration.SamAccountName)
83-
UserPrincipalName = (Resolve-String -Text $testItem.Configuration.UserPrincipalName)
81+
Name = (Resolve-String -Text $testItem.Configuration.SamAccountName @parameters)
82+
SamAccountName = (Resolve-String -Text $testItem.Configuration.SamAccountName @parameters)
83+
UserPrincipalName = (Resolve-String -Text $testItem.Configuration.UserPrincipalName @parameters)
8484
PasswordNeverExpires = $testItem.Configuration.PasswordNeverExpires
8585
Path = $targetOU
8686
AccountPassword = (New-Password -Length 128 -AsSecureString)
8787
Enabled = $testItem.Configuration.Enabled # Both True and Undefined will result in $true
8888
Confirm = $false
8989
}
90-
if ($testItem.Configuration.Description) { $newParameters['Description'] = Resolve-String -Text $testItem.Configuration.Description }
91-
if ($testItem.Configuration.GivenName) { $newParameters['GivenName'] = Resolve-String -Text $testItem.Configuration.GivenName }
92-
if ($testItem.Configuration.Surname) { $newParameters['Surname'] = Resolve-String -Text $testItem.Configuration.Surname }
90+
if ($testItem.Configuration.Description) { $newParameters['Description'] = Resolve-String -Text $testItem.Configuration.Description @parameters }
91+
if ($testItem.Configuration.GivenName) { $newParameters['GivenName'] = Resolve-String -Text $testItem.Configuration.GivenName @parameters }
92+
if ($testItem.Configuration.Surname) { $newParameters['Surname'] = Resolve-String -Text $testItem.Configuration.Surname @parameters }
9393

9494
# Other Attributes
9595
$otherAttributes = @{}
9696
foreach ($attribute in $testItem.Configuration.Attributes.Keys) { $otherAttributes[$attribute] = $testItem.Configuration.Attributes.$attribute }
97-
foreach ($attribute in $testItem.Configuration.AttributesResolved.Keys) { $otherAttributes[$attribute] = Resolve-String -Text $testItem.Configuration.AttributesResolved.$attribute }
97+
foreach ($attribute in $testItem.Configuration.AttributesResolved.Keys) { $otherAttributes[$attribute] = Resolve-String -Text $testItem.Configuration.AttributesResolved.$attribute @parameters }
9898
if ($otherAttributes.Count -gt 0) { $newParameters.OtherAttributes = $otherAttributes }
9999

100100
New-ADUser @newParameters
@@ -104,16 +104,16 @@
104104
Stop-PSFFunction -String 'Invoke-DMUser.User.MultipleOldUsers' -StringValues $testItem.Identity, ($testItem.ADObject.Name -join ', ') -Target $testItem -EnableException $EnableException -Continue -Tag 'user', 'critical', 'panic'
105105
}
106106
'Rename' {
107-
Invoke-PSFProtectedCommand -ActionString 'Invoke-DMUser.User.Rename' -ActionStringValues (Resolve-String -Text $testItem.Configuration.SamAccountName) -Target $testItem -ScriptBlock {
107+
Invoke-PSFProtectedCommand -ActionString 'Invoke-DMUser.User.Rename' -ActionStringValues (Resolve-String -Text $testItem.Configuration.SamAccountName @parameters) -Target $testItem -ScriptBlock {
108108
Set-ADUser @parameters -Identity $testItem.ADObject.ObjectGUID -SamAccountName $testItem.Configuration.SamAccountName -ErrorAction Stop
109-
if ($testItem.ADObject.Name -ne (Resolve-String -Text $testItem.Configuration.Name)) {
110-
Rename-ADObject @parameters -Identity $testItem.ADObject.ObjectGUID -NewName (Resolve-String -Text $testItem.Configuration.Name) -ErrorAction Stop
109+
if ($testItem.ADObject.Name -ne (Resolve-String -Text $testItem.Configuration.Name @parameters)) {
110+
Rename-ADObject @parameters -Identity $testItem.ADObject.ObjectGUID -NewName (Resolve-String -Text $testItem.Configuration.Name @parameters) -ErrorAction Stop
111111
}
112112
} -EnableException $EnableException.ToBool() -PSCmdlet $PSCmdlet -Continue
113113
}
114114
'Changed' {
115115
if ($testItem.Changed.Property -contains 'Path') {
116-
$targetOU = Resolve-String -Text $testItem.Configuration.Path
116+
$targetOU = Resolve-String -Text $testItem.Configuration.Path @parameters
117117
try { $null = Get-ADObject @parameters -Identity $targetOU -ErrorAction Stop }
118118
catch { Stop-PSFFunction -String 'Invoke-DMUser.User.Update.OUExistsNot' -StringValues $testItem.Identity, $targetOU -Target $testItem -EnableException $EnableException -Continue -ContinueLabel main }
119119

@@ -141,8 +141,8 @@
141141
} -EnableException $EnableException -PSCmdlet $PSCmdlet -Continue
142142
}
143143
if ($testItem.Changed.Property -contains 'Name') {
144-
Invoke-PSFProtectedCommand -ActionString 'Invoke-DMUser.User.Update.Name' -ActionStringValues (Resolve-String -Text $testItem.Configuration.Name) -Target $testItem -ScriptBlock {
145-
Rename-ADObject @parameters -Identity $testItem.ADObject.ObjectGUID -NewName (Resolve-String -Text $testItem.Configuration.Name) -ErrorAction Stop -Confirm:$false
144+
Invoke-PSFProtectedCommand -ActionString 'Invoke-DMUser.User.Update.Name' -ActionStringValues (Resolve-String -Text $testItem.Configuration.Name @parameters) -Target $testItem -ScriptBlock {
145+
Rename-ADObject @parameters -Identity $testItem.ADObject.ObjectGUID -NewName (Resolve-String -Text $testItem.Configuration.Name @parameters) -ErrorAction Stop -Confirm:$false
146146
} -EnableException $EnableException -PSCmdlet $PSCmdlet -Continue
147147
}
148148
if ($testItem.Changed.Property -contains 'PasswordNeverExpires') {

0 commit comments

Comments
 (0)