Skip to content

Commit

Permalink
Merge pull request #73 from ActiveDirectoryManagementFramework/2023-m…
Browse files Browse the repository at this point in the history
…arch

1.8.198
  • Loading branch information
FriedrichWeinmann authored May 15, 2023
2 parents 51e68e4 + 562dd43 commit 90f1dd0
Show file tree
Hide file tree
Showing 17 changed files with 117 additions and 73 deletions.
2 changes: 1 addition & 1 deletion 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.188'
ModuleVersion = '1.8.198'

# ID used to uniquely identify this module
GUID = '0a405382-ebc2-445b-8325-541535810193'
Expand Down
13 changes: 13 additions & 0 deletions DomainManagement/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## 1.8.198 (2023-05-15)

- Upd: Acl - Will no longer try to enable inheritance for objects protected under the AdminSDHolder
- Upd: DomainLevel - Test Result includes a proper `Changed` field, better displaying the pending changes
- Upd: GPLink - improved logging messages to include individual changes on update.
- Upd: GPLink - OUFilter now supports full range of wildcard patterns
- Upd: Groups - improved test result user experience
- Upd: ServiceAccount - changed KDS Root Key evaluation order to first look for an existing gMSA.
- Upd: ServiceAccount - improved test result user experience
- Fix: DomainLevel - Invoking against a non-PDC Emulator fails.
- Fix: Groups - fails to detect undesired group objects if they were once flagged as system critical
- Fix: Users - fails to detect undesired user objects if they were once flagged as system critical

## 1.8.188 (2023-02-10)

- Upd: ServiceAccount - extended KDS Root Key validation to accept presence of a gMSA as proof of existence, even if we cannot see the key.
Expand Down
4 changes: 3 additions & 1 deletion DomainManagement/en-us/strings.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,12 @@
'Invoke-DMGPLink.Delete.AllEnabled' = 'Removing all ({0}) policy links (all of which are enabled)' # $countActual
'Invoke-DMGPLink.GpoMissing' = 'Skipping GP Link application for {0} - cannot resolve Group Policies "{1}"' # $testItem.ADObject, (($testItem.Changed | Where-Object Action -eq 'GpoMissing').Policy -join ", ")
'Invoke-DMGPLink.New' = 'Linking {0} group policies (all new links)' # $countConfigured
'Invoke-DMGPLink.New.GpoNotFound' = 'Unable to find Group POlicy Object: {0}' # (Resolve-String -Text $_.PolicyName)
'Invoke-DMGPLink.New.GpoNotFound' = 'Unable to find Group Policy Object: {0}' # (Resolve-String -Text $_.PolicyName)
'Invoke-DMGPLink.New.NewGPLinkString' = 'Finished gPLink string being applied to {0}: {1}' # $ADObject.DistinguishedName, $gpLinkString
'Invoke-DMGPLink.Update.AllEnabled' = 'Updating GPLink - {0} links configured, {1} links present, {2} links present that are not in configuration (All present and undesired links are enabled)' # $countConfigured, $countActual, $countNotInConfig
'Invoke-DMGPLink.Update.Change' = ' Link update: {0} - {1} ({2})' # $change.Action, $change.Policy, $ADObject.DistinguishedName
'Invoke-DMGPLink.Update.NewGPLinkString' = 'Finished gPLink string being applied to {0}: {1}' # $ADObject.DistinguishedName, $gpLinkString
'Invoke-DMGPLink.Update.OldGPLinkString' = 'Previous gPLink string being overwritten on {0}: {1}' # $ADObject.DistinguishedName, $ADObject.gPLink

'Invoke-DMGPOwner.Invalid.Input' = 'The input object was not recognized as a valid test result for Group Policy Ownership: {0' # $testResult
'Invoke-DMGPOwner.Update.Owner' = 'Changing the owner from {0} to {1} on Group Policy {2}' # $testResult.Changed.Old, $testResult.Changed.New, $testResult.Identity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@

#region Foreach non-existent default rule: Create unless configured otherwise
$domainControllersOUFilter = '*{0}' -f ('OU=Domain Controllers,%DomainDN%' | Resolve-String)
:outer foreach ($defaultRule in $DefaultRules | Where-Object { $_ -notin $defaultRulesPresent.ToArray()}) {
:outer foreach ($defaultRule in $DefaultRules | Where-Object { $_ -notin $defaultRulesPresent.ToArray() }) {
# Do not apply restore to Domain Controllers OU, as it is already deployed intentionally diverging from the OU defaults
if ($ADObject -like $domainControllersOUFilter) { break }

Expand Down
7 changes: 5 additions & 2 deletions DomainManagement/functions/acls/Test-DMAcl.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@
$null = $changes.Add((New-Change -Identity $ADObject -Type Owner -OldValue $aclObject.Owner -NewValue ($Category.Owner | Resolve-String) -OldSID $ownerSID -NewSID $configuredSID))
}
if ($Category.NoInheritance -ne $aclObject.AreAccessRulesProtected) {
$null = $changes.Add((New-Change -Identity $ADObject -Type NoInheritance -OldValue $aclObject.AreAccessRulesProtected -NewValue $Category.NoInheritance))
# If AdminCount -eq 1, then inheritance should be disabled, no matter the configuration
if (-not ($aclObject.AreAccessRulesProtected -and $ADObject.AdminCount)) {
$null = $changes.Add((New-Change -Identity $ADObject -Type NoInheritance -OldValue $aclObject.AreAccessRulesProtected -NewValue $Category.NoInheritance))
}
}

if ($changes.Count) {
Expand Down Expand Up @@ -138,7 +141,7 @@

#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
Get-ADObject @parameters -LDAPFilter '(objectCategory=*)' -SearchBase $searchBase.SearchBase -SearchScope $searchBase.SearchScope -Properties AdminCount
}

$resolvedConfiguredPaths = $script:acls.Values.Path | Resolve-String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,12 @@
{
'Raise'
{
# Raising the Domain Functional Level MUST target the PDC Emulator
$clonedParam = $parameters.Clone()
$clonedParam.Server = (Resolve-Domain @parameters).PDCEmulator

Invoke-PSFProtectedCommand -ActionString 'Invoke-DMDomainLevel.Raise.Level' -ActionStringValues $testItem.Configuration.Level -Target $testItem.ADObject -ScriptBlock {
Set-ADDomainMode @parameters -DomainMode $testItem.Configuration.DesiredLevel -Identity $testItem.ADObject -ErrorAction Stop -Confirm:$false
Set-ADDomainMode @clonedParam -DomainMode $testItem.Configuration.DesiredLevel -Identity $testItem.ADObject -ErrorAction Stop -Confirm:$false
} -EnableException $EnableException -PSCmdlet $PSCmdlet -Continue
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@
$domain = Get-ADDomain @parameters
if ($domain.DomainMode -lt $desiredLevel)
{
New-TestResult -ObjectType DomainLevel -Type Raise -Identity $domain -Server $Server -Configuration ([pscustomobject]$tempConfiguration) -ADObject $domain
New-TestResult -ObjectType DomainLevel -Type Raise -Identity $domain -Server $Server -Configuration ([pscustomobject]$tempConfiguration) -ADObject $domain -Changed (
New-AdcChange -Property DomainLevel -OldValue $domain.DomainMode -NewValue $tempConfiguration['DesiredLevel'] -Identity $domain -Type DomainLevel
)
}
}
}
62 changes: 36 additions & 26 deletions DomainManagement/functions/gplinks/Invoke-DMGPLink.ps1
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
function Invoke-DMGPLink
{
function Invoke-DMGPLink {
<#
.SYNOPSIS
Applies the desired group policy linking configuration.
Expand Down Expand Up @@ -67,8 +66,7 @@
$EnableException
)

begin
{
begin {
#region Utility Functions
function Clear-Link {
[CmdletBinding()]
Expand All @@ -90,7 +88,7 @@
Set-ADObject @parameters -Identity $ADObject -Clear gPLink -ErrorAction Stop
return
}
Set-ADObject @parameters -Identity $ADObject -Replace @{ gPLink = ($ADObject.gPLink -replace ";\d\]",";1]") } -ErrorAction Stop -Confirm:$false
Set-ADObject @parameters -Identity $ADObject -Replace @{ gPLink = ($ADObject.gPLink -replace ";\d\]", ";1]") } -ErrorAction Stop -Confirm:$false
}

function New-Link {
Expand All @@ -113,16 +111,16 @@
$parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include Server, Credential

$gpLinkString = ($Configuration.Include | Sort-Object -Property @{ Expression = { $_.Tier }; Descending = $false }, Precedence -Descending | ForEach-Object {
$gpoDN = $GpoNameMapping[(Resolve-String -Text $_.PolicyName)]
if (-not $gpoDN) {
Write-PSFMessage -Level Warning -String 'Invoke-DMGPLink.New.GpoNotFound' -StringValues (Resolve-String -Text $_.PolicyName) -Target $ADObject -FunctionName Invoke-DMGPLink
return
}
$stateID = "0"
if ($_.State -eq 'Enforced') { $stateID = "2" }
if ($_.State -eq 'Disabled') { $stateID = "1" }
"[LDAP://$gpoDN;$stateID]"
}) -Join ""
$gpoDN = $GpoNameMapping[(Resolve-String -Text $_.PolicyName)]
if (-not $gpoDN) {
Write-PSFMessage -Level Warning -String 'Invoke-DMGPLink.New.GpoNotFound' -StringValues (Resolve-String -Text $_.PolicyName) -Target $ADObject -FunctionName Invoke-DMGPLink
return
}
$stateID = "0"
if ($_.State -eq 'Enforced') { $stateID = "2" }
if ($_.State -eq 'Disabled') { $stateID = "1" }
"[LDAP://$gpoDN;$stateID]"
}) -Join ""
Write-PSFMessage -Level Debug -String 'Invoke-DMGPLink.New.NewGPLinkString' -StringValues $ADObject.DistinguishedName, $gpLinkString -Target $ADObject -FunctionName Invoke-DMGPLink
Set-ADObject @parameters -Identity $ADObject -Replace @{ gPLink = $gpLinkString } -ErrorAction Stop -Confirm:$false
}
Expand All @@ -145,22 +143,34 @@
$Disable,

[Hashtable]
$GpoNameMapping
$GpoNameMapping,

$Changes
)
$parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include Server, Credential

$gpLinkString = ''
if ($Disable) {
$desiredDNs = $Configuration.ExtendedInclude.PolicyName | Resolve-String | ForEach-Object { $GpoNameMapping[$_] }
$gpLinkString += ($ADobject.LinkedGroupPolicyObjects | Where-Object DistinguishedName -NotIn $desiredDNs | Sort-Object -Property Precedence -Descending | ForEach-Object {
"[LDAP://$($_.DistinguishedName);1]"
}) -join ""
"[LDAP://$($_.DistinguishedName);1]"
}) -join ""
}

$gpLinkString += ($Configuration.ExtendedInclude | Where-Object DistinguishedName | Sort-Object -Property @{ Expression = { $_.Tier }; Descending = $false }, Precedence -Descending | ForEach-Object {
$_.ToLink()
}) -Join ""
Write-PSFMessage -Level Debug -String 'Invoke-DMGPLink.Update.NewGPLinkString' -StringValues $ADObject.DistinguishedName, $gpLinkString -Target $ADObject -FunctionName Invoke-DMGPLink
$gpLinkString += ($Configuration.ExtendedInclude | Where-Object DistinguishedName | Sort-Object -Property @{ Expression = { $_.Tier }; Descending = $false }, Precedence -Descending | ForEach-Object {
$_.ToLink()
}) -Join ""
$msgParam = @{
Level = 'SomewhatVerbose'
Tag = 'change'
Target = $ADObject
FunctionName = 'Invoke-DMGPLink'
}
Write-PSFMessage @msgParam -String 'Invoke-DMGPLink.Update.OldGPLinkString' -StringValues $ADObject.DistinguishedName, $ADObject.gPLink
foreach ($change in $Changes) {
Write-PSFMessage @msgParam -String 'Invoke-DMGPLink.Update.Change' -StringValues $change.Action, $change.Policy, $ADObject.DistinguishedName
}
Write-PSFMessage @msgParam -String 'Invoke-DMGPLink.Update.NewGPLinkString' -StringValues $ADObject.DistinguishedName, $gpLinkString
Set-ADObject @parameters -Identity $ADObject -Replace @{ gPLink = $gpLinkString } -ErrorAction Stop -Confirm:$false
}
#endregion Utility Functions
Expand All @@ -178,7 +188,7 @@
$gpoDNToDisplay[$adPolicyObject.DistinguishedName] = $adPolicyObject.DisplayName
}
}
process{
process {
if (-not $InputObject) {
$InputObject = Test-DMGPLink @parameters
}
Expand All @@ -192,7 +202,7 @@

$countConfigured = ($testItem.Configuration | Measure-Object).Count
$countActual = ($testItem.ADObject.LinkedGroupPolicyObjects | Measure-Object).Count
$countNotInConfig = ($testItem.ADObject.LinkedGroupPolicyObjects | Where-Object DistinguishedName -notin ($testItem.Configuration.PolicyName | Remove-PSFNull| Resolve-String | ForEach-Object { $gpoDisplayToDN[$_] }) | Measure-Object).Count
$countNotInConfig = ($testItem.ADObject.LinkedGroupPolicyObjects | Where-Object DistinguishedName -NotIn ($testItem.Configuration.PolicyName | Remove-PSFNull | Resolve-String | ForEach-Object { $gpoDisplayToDN[$_] }) | Measure-Object).Count

switch ($testItem.Type) {
'Delete' {
Expand All @@ -207,11 +217,11 @@
}
'Update' {
Invoke-PSFProtectedCommand -ActionString 'Invoke-DMGPLink.Update.AllEnabled' -ActionStringValues $countConfigured, $countActual, $countNotInConfig -Target $testItem -ScriptBlock {
Update-Link @parameters -ADObject $testItem.ADObject -Configuration $testItem.Configuration -Disable $Disable -GpoNameMapping $gpoDisplayToDN -ErrorAction Stop
Update-Link @parameters -ADObject $testItem.ADObject -Configuration $testItem.Configuration -Disable $Disable -GpoNameMapping $gpoDisplayToDN -Changes $testItem.Changed -ErrorAction Stop
} -EnableException $EnableException.ToBool() -PSCmdlet $PSCmdlet -Continue
}
'GpoMissing' {
Write-PSFMessage -Level Warning -String 'Invoke-DMGPLink.GpoMissing' -StringValues $testItem.ADObject, (($testItem.Changed | Where-Object Action -eq 'GpoMissing').Policy -join ", ")
Write-PSFMessage -Level Warning -String 'Invoke-DMGPLink.GpoMissing' -StringValues $testItem.ADObject, (($testItem.Changed | Where-Object Action -EQ 'GpoMissing').Policy -join ", ")
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions DomainManagement/functions/groups/Test-DMGroup.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@

#region Case: One old version present
1 {
New-TestResult @resultDefaults -Type Rename -ADObject $oldGroups
New-TestResult @resultDefaults -Type Rename -ADObject $oldGroups -Changed (New-AdcChange -Identity $adObject -Property Name -OldValue $oldGroups.Name -NewValue $resolvedName)
$oldNamesFound += $oldGroups.Name
continue main
}
Expand Down Expand Up @@ -120,7 +120,7 @@
}

$foundGroups = foreach ($searchBase in (Resolve-ContentSearchBase @parameters)) {
Get-ADGroup @parameters -LDAPFilter '(!(isCriticalSystemObject=*))' -SearchBase $searchBase.SearchBase -SearchScope $searchBase.SearchScope
Get-ADGroup @parameters -LDAPFilter '(!(isCriticalSystemObject=TRUE))' -SearchBase $searchBase.SearchBase -SearchScope $searchBase.SearchScope
}

$resolvedConfiguredNames = $script:groups.Values.Name | Resolve-String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@

#region Process Managed Containers
$foundOUs = foreach ($searchBase in (Resolve-ContentSearchBase @parameters -IgnoreMissingSearchbase)) {
Get-ADOrganizationalUnit @parameters -LDAPFilter '(!(isCriticalSystemObject=*))' -SearchBase $searchBase.SearchBase -SearchScope $searchBase.SearchScope -Properties nTSecurityDescriptor | Where-Object DistinguishedName -Ne $searchBase.SearchBase
Get-ADOrganizationalUnit @parameters -LDAPFilter '(!(isCriticalSystemObject=TRUE))' -SearchBase $searchBase.SearchBase -SearchScope $searchBase.SearchScope -Properties nTSecurityDescriptor | Where-Object DistinguishedName -Ne $searchBase.SearchBase
}

$resolvedConfiguredNames = $script:organizationalUnits.Values.DistinguishedName | Resolve-String
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
function Test-DMServiceAccount {
<#
<#
.SYNOPSIS
Tests whether the currently deployed service accoaunts match the configured desired state.
Expand Down Expand Up @@ -44,14 +44,22 @@
$NewValue
)

[pscustomobject]@{
$object = [pscustomobject]@{
PSTypeName = 'DomainManagement.Change.ServiceAccount'
Identity = $Identity
Type = $Type
Type = $Type
Property = $Property
Previous = $Previous
NewValue = $NewValue
Old = $Previous
New = $NewValue
}
Add-Member -InputObject $object -MemberType ScriptMethod -Name ToString -Value {
switch ($this.Type) {
'Create' { "Create: $($this.Identity)" }
'Delete' { "Delete: $($this.Identity)" }
# Move, Rename, Update
default { "$($this.Type): $($this.Old) -> $($this.New)" }
}
} -Force -PassThru
}
#endregion Utility Functions

Expand All @@ -78,34 +86,34 @@
$resolvedPath = Resolve-String -Text $serviceAccountDefinition.Path @parameters

$resultDefaults = @{
Server = $Server
Server = $Server
ObjectType = 'ServiceAccount'
Identity = $resolvedName
Identity = $resolvedName
Configuration = $serviceAccountDefinition
}
$adObject = $null

try { $adObject = Get-ADServiceAccount @parameters -Identity $resolvedName -ErrorAction Stop -Properties * }
catch {
foreach ($oldName in $serviceAccountDefinition.OldNames) {
try { $adObject = Get-ADServiceAccount @parameters -Identity ($oldName | Resolve-String @parameters) -ErrorAction Stop -Properties * }
catch { continue }
# No Need to rename when deleting it anyway
if (-not $serviceAccountDefinition.Present) { break }
New-TestResult -Type RenameSam @resultDefaults -ADObject $adObject
foreach ($oldName in $serviceAccountDefinition.OldNames) {
try { $adObject = Get-ADServiceAccount @parameters -Identity ($oldName | Resolve-String @parameters) -ErrorAction Stop -Properties * }
catch { continue }
# No Need to rename when deleting it anyway
if (-not $serviceAccountDefinition.Present) { break }
New-TestResult -Type RenameSam @resultDefaults -ADObject $adObject -Changed (New-AdcChange -Property SamAccountName -OldValue $adObject.SamAccountName -NewValue $resolvedName -Identity $adObject)
$renameCurrentSAM += $adObject.SamAccountName
break
}
break
}
}

if (-not $adObject) {
# .Present is of type TriBool, so itself would be $true for both 'true' and 'undefined' cases,
# and we do not want to create if undefined
if ($serviceAccountDefinition.Present -eq 'true') {
New-TestResult -Type Create @resultDefaults (New-Change -Identity $resolvedName -Type Create)
}
if (-not $adObject) {
# .Present is of type TriBool, so itself would be $true for both 'true' and 'undefined' cases,
# and we do not want to create if undefined
if ($serviceAccountDefinition.Present -eq 'true') {
New-TestResult -Type Create @resultDefaults (New-Change -Identity $resolvedName -Type Create)
}
continue
}
}
$resultDefaults.ADObject = $adObject

if (-not $serviceAccountDefinition.Present) {
Expand All @@ -130,9 +138,9 @@
if ($adObject.ServicePrincipalName -or $serviceAccountDefinition.ServicePrincipalName) {
Compare-Property -Property ServicePrincipalName -Configuration $serviceAccountDefinition -ADObject $adObject -Changes $changes -Resolve -Parameters $parameters
}
if ($adObject.KerberosEncryptionType[0] -ne $serviceAccountDefinition.KerberosEncryptionType) {
$null = $changes.Add('KerberosEncryptionType')
}
if ($adObject.KerberosEncryptionType[0] -ne $serviceAccountDefinition.KerberosEncryptionType) {
$null = $changes.Add('KerberosEncryptionType')
}

if ($serviceAccountDefinition.Attributes.Count -gt 0) {
$attributesObject = [PSCustomObject]$serviceAccountDefinition.Attributes
Expand Down Expand Up @@ -241,7 +249,7 @@
}

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

Expand Down
2 changes: 1 addition & 1 deletion DomainManagement/functions/users/Invoke-DMUser.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,4 @@
}
}
}
}
}
4 changes: 2 additions & 2 deletions DomainManagement/functions/users/Test-DMUser.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@

#region Process Managed Containers
$foundUsers = foreach ($searchBase in (Resolve-ContentSearchBase @parameters)) {
Get-ADUser @parameters -LDAPFilter '(!(isCriticalSystemObject=*))' -SearchBase $searchBase.SearchBase -SearchScope $searchBase.SearchScope
Get-ADUser @parameters -LDAPFilter '(!(isCriticalSystemObject=TRUE))' -SearchBase $searchBase.SearchBase -SearchScope $searchBase.SearchScope
}

$resolvedConfiguredNames = $script:users.Values.SamAccountName | Resolve-String
Expand All @@ -152,4 +152,4 @@
}
#endregion Process Managed Containers
}
}
}
Loading

0 comments on commit 90f1dd0

Please sign in to comment.