Providers/AD/Public/New-ADTestEdgeCase.ps1

function New-ADTestEdgeCase {
    <#
    .EXTERNALHELP TestEnvironment-Help.xml
    .SYNOPSIS
        Creates the awkward directory states that CSV-driven test data cannot express
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '',
        Justification = 'Colour-coded console progress is intentional; results are returned as objects.')]
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter()]
        [ValidateSet('All', 'AclDelegation', 'OrphanedSid', 'LegacyEncryption',
                     'PasswordExpiry', 'AmbiguousName', 'MoveTarget', 'CommaName',
                     'MissingUpn', 'MixedMembership', 'FineGrainedPolicy',
                     'GovernanceAttribute')]
        [string[]]$EdgeCase = 'All',

        [switch]$PassThru
    )

    begin {
        $correlationId = [System.Guid]::NewGuid()
        Write-Verbose "Starting New-ADTestEdgeCase - CorrelationId: $correlationId"

        $domain = Get-ADTestDomain
        $testDataOU = "OU=$($script:ADTestRootName),$($domain.DomainDN)"
        $edgeOU = "OU=EdgeCases,$testDataOU"

        $wanted = { param($name) $EdgeCase -contains 'All' -or $EdgeCase -contains $name }

        $script:Created = [System.Collections.Generic.List[object]]::new()
        $script:EdgeErrors = [System.Collections.Generic.List[string]]::new()

    }

    process {
        try {
            Write-TestMessage -Message "Creating Active Directory Test Edge Cases" -Type Header

            if (-not (Get-ADOrganizationalUnit -Filter ("DistinguishedName -eq " +
                "'$testDataOU'") -ErrorAction SilentlyContinue)) {
                throw "Test OU structure not found at $testDataOU. Run New-ADTestOUStructure first."
            }

            if (-not $PSCmdlet.ShouldProcess($edgeOU, 'Create edge case OU structure')) {
                Write-TestMessage -Message "Would create edge cases under $edgeOU" -Type Info
                return
            }

            $null = (New-ADTestOU -Name 'EdgeCases' -Path $testDataOU `
                -Description 'Deliberately awkward directory states for script testing' `
                    -Unprotected).DistinguishedName

            #region AclDelegation
            if (& $wanted 'AclDelegation') {
                Write-TestMessage -Message "Creating delegated permissions..." -Type Info

                try {
                    $delegatedOU = (New-ADTestOU -Name 'Delegated' -Path $edgeOU `
                        -Description 'Objects an edge case group holds explicit rights over' `
                            -Unprotected).DistinguishedName

                    $delegateName = 'EdgeCase Delegated Admins'
                    $delegate = Get-ADGroup -Filter "Name -eq '$delegateName'" -ErrorAction SilentlyContinue

                    if (-not $delegate) {
                        $delegate = New-ADGroup -Name $delegateName -SamAccountName 'EdgeCaseDelegatedAdmins' `
                            -GroupScope Global -GroupCategory Security -Path $edgeOU `
                            -Description 'Holds delegated rights over OU=Delegated' -PassThru
                    }

                    # Something for the delegation to apply to, so a permissions report has
                    # rows to return rather than an empty result that reads as "no rights".
                    foreach ($n in 1..3) {
                        $sam = "EdgeCaseTarget$n"
                        if (-not (Get-ADGroup -Filter "SamAccountName -eq '$sam'" -ErrorAction SilentlyContinue)) {
                            New-ADGroup -Name "EdgeCase Target $n" -SamAccountName $sam `
                                -GroupScope Global -GroupCategory Security -Path $delegatedOU `
                                -Description "Inherits the delegation applied to OU=Delegated"
                        }
                    }

                    # GenericAll with All inheritance: the group gets one explicit entry on
                    # the OU and an inherited entry on every object inside it, which is what
                    # real delegation looks like and lets a report distinguish the two.
                    $sid = New-Object System.Security.Principal.SecurityIdentifier($delegate.SID)
                    $acl = Get-Acl -Path "AD:\$delegatedOU"
                    $rule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
                        $sid,
                        [System.DirectoryServices.ActiveDirectoryRights]::GenericAll,
                        [System.Security.AccessControl.AccessControlType]::Allow,
                        [System.DirectoryServices.ActiveDirectorySecurityInheritance]::All)
                    $acl.AddAccessRule($rule)
                    Set-Acl -Path "AD:\$delegatedOU" -AclObject $acl

                    $script:Created.Add([PSCustomObject]@{
                        EdgeCase = 'AclDelegation'
                        Detail   = "$delegateName granted GenericAll over $delegatedOU (inherited by 3 objects)"
                    })
                }
                catch {
                    $script:EdgeErrors.Add("AclDelegation: $($_.Exception.Message)")
                }
            }
            #endregion

            #region OrphanedSid
            if (& $wanted 'OrphanedSid') {
                Write-TestMessage -Message "Planting orphaned SIDs..." -Type Info

                try {
                    $orphanOU = (New-ADTestOU -Name 'Orphaned' -Path $edgeOU `
                        -Description 'Objects carrying access control entries for deleted principals' `
                            -Unprotected).DistinguishedName

                    foreach ($n in 1..2) {
                        $sam = "EdgeCaseOrphanHost$n"
                        if (-not (Get-ADGroup -Filter "SamAccountName -eq '$sam'" -ErrorAction SilentlyContinue)) {
                            New-ADGroup -Name "EdgeCase Orphan Host $n" -SamAccountName $sam `
                                -GroupScope Global -GroupCategory Security -Path $orphanOU `
                                -Description 'Carries an access control entry for a SID that no longer resolves'
                        }
                    }

                    # A RID far above anything the domain has issued. The SID is well-formed
                    # and belongs to this domain, so it is stored, but it resolves to nothing
                    # - exactly the residue a deleted account leaves in an ACL. Get-Acl hands
                    # such an entry back as a SecurityIdentifier rather than an NTAccount,
                    # which is how cleanup scripts recognise it.
                    $domainSid = (Get-ADDomain).DomainSID.Value
                    $orphanRid = 90000

                    foreach ($target in @(Get-ADGroup -Filter * -SearchBase $orphanOU)) {
                        $orphanRid++
                        $orphanSid = New-Object System.Security.Principal.SecurityIdentifier(
                            "$domainSid-$orphanRid")

                        $acl = Get-Acl -Path "AD:\$($target.DistinguishedName)"
                        $rule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
                            $orphanSid,
                            [System.DirectoryServices.ActiveDirectoryRights]::GenericRead,
                            [System.Security.AccessControl.AccessControlType]::Allow)
                        $acl.AddAccessRule($rule)
                        Set-Acl -Path "AD:\$($target.DistinguishedName)" -AclObject $acl

                        $script:Created.Add([PSCustomObject]@{
                            EdgeCase = 'OrphanedSid'
                            Detail   = "$domainSid-$orphanRid planted on $($target.Name)"
                        })
                    }
                }
                catch {
                    $script:EdgeErrors.Add("OrphanedSid: $($_.Exception.Message)")
                }
            }
            #endregion

            #region LegacyEncryption
            if (& $wanted 'LegacyEncryption') {
                Write-TestMessage -Message "Creating accounts with legacy Kerberos encryption..." -Type Info

                try {
                    $kerbOU = (New-ADTestOU -Name 'Kerberos' -Path $edgeOU `
                        -Description 'Accounts carrying legacy Kerberos encryption settings' `
                            -Unprotected).DistinguishedName

                    # The values matter individually.
                    #
                    # 0 no explicit value beyond the default, which is the RC4-only state
                    # an RC4-to-AES migration looks for
                    # 1 DES-CBC-CRC alone
                    # 2 DES-CBC-MD5 alone
                    # 3 both DES types
                    # 24 AES128+AES256, already remediated - the negative control
                    #
                    # 1 and 2 exist specifically because an AD filter written as
                    # "-band 3" requires BOTH bits and silently passes over an account
                    # offering only one, which is the more common misconfiguration and was a
                    # real defect in the DES remediation script.
                    $kerbAccount = @(
                        @{ Sam = 'EdgeCaseKerbRc4'; Name = 'EdgeCase Kerb RC4 Only'; Enc = 0
                           Desc = 'RC4-only: no explicit encryption types set' }
                        @{ Sam = 'EdgeCaseKerbDesCrc'; Name = 'EdgeCase Kerb DES CRC'; Enc = 1
                           Desc = 'DES-CBC-CRC only: missed by a -band 3 filter' }
                        @{ Sam = 'EdgeCaseKerbDesMd5'; Name = 'EdgeCase Kerb DES MD5'; Enc = 2
                           Desc = 'DES-CBC-MD5 only: missed by a -band 3 filter' }
                        @{ Sam = 'EdgeCaseKerbDesBoth'; Name = 'EdgeCase Kerb DES Both'; Enc = 3
                           Desc = 'Both DES types set' }
                        @{ Sam = 'EdgeCaseKerbAes'; Name = 'EdgeCase Kerb AES'; Enc = 24
                           Desc = 'AES only: negative control, must not be selected for remediation' }
                    )

                    foreach ($account in $kerbAccount) {
                        $null = New-ADTestEdgeUser -Name $account.Name -SamAccountName $account.Sam `
                            -Path $kerbOU -Description $account.Desc

                        # Written as a literal 0, not cleared. An absent attribute and an
                        # attribute set to 0 are different things to an LDAP filter: the
                        # RC4-to-AES script selects on "-eq 0", which an absent attribute
                        # does not satisfy, so clearing it produced a fixture that script
                        # could never see.
                        Set-ADUser -Identity $account.Sam `
                            -Replace @{ 'msDS-SupportedEncryptionTypes' = $account.Enc }

                        $script:Created.Add([PSCustomObject]@{
                            EdgeCase = 'LegacyEncryption'
                            Detail   = "$($account.Sam): msDS-SupportedEncryptionTypes = $($account.Enc)"
                        })
                    }

                    # USE_DES_KEY_ONLY, the other half of the DES filter. Set by OR-ing the
                    # bit into the existing userAccountControl rather than assigning it, so
                    # the account's other flags survive.
                    #
                    # 'EdgeCaseKerbDesOnly', not 'EdgeCaseKerbDesKeyOnly': sAMAccountName is
                    # capped at 20 characters for user objects and the longer name failed
                    # with "The name provided is not a properly formed account name", which
                    # does not obviously mean "too long".
                    $desOnlySam = 'EdgeCaseKerbDesOnly'
                    $null = New-ADTestEdgeUser -Name 'EdgeCase Kerb DES Key Only' -SamAccountName $desOnlySam `
                        -Path $kerbOU -Description 'USE_DES_KEY_ONLY set in userAccountControl'

                    $current = (Get-ADUser -Identity $desOnlySam -Properties userAccountControl).userAccountControl
                    Set-ADUser -Identity $desOnlySam `
                        -Replace @{ userAccountControl = ($current -bor 0x200000) }

                    $script:Created.Add([PSCustomObject]@{
                        EdgeCase = 'LegacyEncryption'
                        Detail   = "${desOnlySam}: USE_DES_KEY_ONLY (0x200000) set in userAccountControl"
                    })
                }
                catch {
                    $script:EdgeErrors.Add("LegacyEncryption: $($_.Exception.Message)")
                }
            }
            #endregion

            #region PasswordExpiry
            if (& $wanted 'PasswordExpiry') {
                Write-TestMessage -Message "Creating accounts with varied password expiry..." -Type Info

                try {
                    $pwOU = (New-ADTestOU -Name 'PasswordPolicy' -Path $edgeOU `
                        -Description 'Accounts exercising each password expiry outcome' `
                            -Unprotected).DistinguishedName

                    # Every account the rest of the module creates has PasswordNeverExpires
                    # set, so a password expiry report only ever reaches one of its branches.
                    #
                    # msDS-UserPasswordExpiryTimeComputed has three distinct outcomes, and
                    # these accounts produce all three (values confirmed against the live
                    # directory, not assumed):
                    #
                    # expires a real filetime -> a date and a day count
                    # never Int64.MaxValue -> reported as never expiring
                    # mustchange 0, from pwdLastSet = 0 -> also reported as no expiry,
                    # but for a different reason
                    #
                    # Int64.MaxValue comes from PasswordNeverExpires. It is worth being
                    # explicit that it does NOT come from requiring a smart card, which is
                    # the intuitive guess: a smart-card account's password still expires on
                    # the domain schedule. The smartcard account below is therefore not extra
                    # branch coverage - it is kept because it is a real configuration a
                    # report will meet, and because it documents that non-equivalence.
                    $pwExpires = @{
                        Name           = 'EdgeCase Password Expires'
                        SamAccountName = 'EdgeCasePwExpires'
                        Path           = $pwOU
                        Description    = 'Password expires on the domain schedule'
                        Extra          = @{ PasswordNeverExpires = $false }
                    }
                    $null = New-ADTestEdgeUser @pwExpires

                    $pwNever = @{
                        Name           = 'EdgeCase Password Never Expires'
                        SamAccountName = 'EdgeCasePwNever'
                        Path           = $pwOU
                        Description    = 'PasswordNeverExpires: reports no expiry'
                        Extra          = @{ PasswordNeverExpires = $true }
                    }
                    $null = New-ADTestEdgeUser @pwNever

                    $pwMustChange = @{
                        Name           = 'EdgeCase Password Must Change'
                        SamAccountName = 'EdgeCasePwMustChange'
                        Path           = $pwOU
                        Description    = 'Must change at next logon: expiry computes to 0'
                        Extra          = @{ PasswordNeverExpires = $false; ChangePasswordAtLogon = $true }
                    }
                    $null = New-ADTestEdgeUser @pwMustChange

                    $pwSmartcard = @{
                        Name           = 'EdgeCase Password Smartcard'
                        SamAccountName = 'EdgeCasePwSmartcard'
                        Path           = $pwOU
                        Description    = 'Smart card required: password still expires normally'
                        Extra          = @{ PasswordNeverExpires = $false; SmartcardLogonRequired = $true }
                    }
                    $null = New-ADTestEdgeUser @pwSmartcard

                    $script:Created.Add([PSCustomObject]@{
                        EdgeCase = 'PasswordExpiry'
                        Detail   = 'Four accounts covering expires / never / must-change / smart-card'
                    })
                }
                catch {
                    $script:EdgeErrors.Add("PasswordExpiry: $($_.Exception.Message)")
                }
            }
            #endregion

            #region AmbiguousName
            if (& $wanted 'AmbiguousName') {
                Write-TestMessage -Message "Creating an ambiguous group name..." -Type Info

                try {
                    $ambiguousOU = (New-ADTestOU -Name 'Ambiguous' -Path $edgeOU `
                        -Description 'Holds the second group sharing an ambiguous display name' `
                            -Unprotected).DistinguishedName

                    # A common name is unique only within its container, so the same display
                    # name can exist twice in one domain as long as the two live in different
                    # OUs. sAMAccountName is domain-wide unique and so still differs.
                    #
                    # Several scripts resolve a group by Name and implement a branch for
                    # "matched more than one" - a branch nothing in a generated environment
                    # can otherwise reach, because generated names never collide.
                    $ambiguousName = 'EdgeCase Ambiguous Group'

                    foreach ($pair in @(@{ Path = $edgeOU; Sam = 'EdgeCaseAmbiguousA' },
                                        @{ Path = $ambiguousOU; Sam = 'EdgeCaseAmbiguousB' })) {
                        if (-not (Get-ADGroup -Filter ("SamAccountName -eq " +
                            "'$($pair.Sam)'") -ErrorAction SilentlyContinue)) {
                            New-ADGroup -Name $ambiguousName -SamAccountName $pair.Sam `
                                -GroupScope Global -GroupCategory Security -Path $pair.Path `
                                -Description "Shares a display name with another group in a different OU"
                        }
                    }

                    $script:Created.Add([PSCustomObject]@{
                        EdgeCase = 'AmbiguousName'
                        Detail   = "'$ambiguousName' exists twice (EdgeCaseAmbiguousA, EdgeCaseAmbiguousB)"
                    })
                }
                catch {
                    $script:EdgeErrors.Add("AmbiguousName: $($_.Exception.Message)")
                }
            }
            #endregion

            #region MoveTarget
            if (& $wanted 'MoveTarget') {
                Write-TestMessage -Message "Creating a move target OU..." -Type Info

                try {
                    $moveOU = (New-ADTestOU -Name 'MoveTarget' -Path $edgeOU `
                        -Description 'Empty destination for group move and migration scripts' `
                            -Unprotected).DistinguishedName

                    $script:Created.Add([PSCustomObject]@{
                        EdgeCase = 'MoveTarget'
                        Detail   = "Empty OU available at $moveOU"
                    })
                }
                catch {
                    $script:EdgeErrors.Add("MoveTarget: $($_.Exception.Message)")
                }
            }
            #endregion

            #region CommaName
            if (& $wanted 'CommaName') {
                Write-TestMessage -Message "Creating objects whose name contains a comma..." -Type Info

                try {
                    $commaOU = (New-ADTestOU -Name 'CommaNames' -Path $edgeOU `
                        -Description 'Objects whose common name contains a comma' `
                            -Unprotected).DistinguishedName

                    # "Surname, Given" is one of the most common naming conventions there is,
                    # and AD stores the resulting CN escaped: CN=Nakamura\, Yuki. Code that
                    # recovers a name with Split(',')[0] returns "Nakamura\", and code that
                    # strips the leading RDN with a regex like ^CN=[^,]+, stops at the escaped
                    # comma and reports a parent container that does not exist. Neither
                    # throws, so both produce confident nonsense.
                    #
                    # A group and a user, because the two parse sites this exercises operate
                    # on different object types.
                    $commaGroupSam = 'EdgeCaseCommaGroup'
                    if (-not (Get-ADGroup -Filter ("SamAccountName -eq " +
                        "'$commaGroupSam'") -ErrorAction SilentlyContinue)) {
                        New-ADGroup -Name 'Acme, Inc Contractors' -SamAccountName $commaGroupSam `
                            -GroupScope Global -GroupCategory Security -Path $commaOU `
                            -Description 'Common name contains a comma; AD stores it escaped'
                    }

                    $null = New-ADTestEdgeUser -Name 'Nakamura, Yuki' -SamAccountName 'EdgeCaseCommaUser' `
                        -Path $commaOU -Description 'Common name in Surname, Given form'

                    Add-ADGroupMember -Identity $commaGroupSam -Members 'EdgeCaseCommaUser' `
                        -ErrorAction SilentlyContinue

                    # A child group nested INTO the comma-named one. This is the part that
                    # matters: a nesting report names the PARENT, so the escaped comma only
                    # reaches the parsing code when the awkward name is somebody's parent.
                    # A comma-named leaf on its own proves nothing.
                    $commaChildSam = 'EdgeCaseCommaChild'
                    if (-not (Get-ADGroup -Filter ("SamAccountName -eq " +
                        "'$commaChildSam'") -ErrorAction SilentlyContinue)) {
                        New-ADGroup -Name 'EdgeCase Comma Child' -SamAccountName $commaChildSam `
                            -GroupScope Global -GroupCategory Security -Path $commaOU `
                            -Description 'Nested into a group whose name contains a comma'
                    }

                    Add-ADGroupMember -Identity $commaGroupSam -Members $commaChildSam `
                        -ErrorAction SilentlyContinue

                    $script:Created.Add([PSCustomObject]@{
                        EdgeCase = 'CommaName'
                        Detail   = "'Acme, Inc Contractors' and 'Nakamura, Yuki' created with escaped commas"
                    })
                }
                catch {
                    $script:EdgeErrors.Add("CommaName: $($_.Exception.Message)")
                }
            }
            #endregion

            #region MissingUpn
            if (& $wanted 'MissingUpn') {
                Write-TestMessage -Message "Creating an account with no userPrincipalName..." -Type Info

                try {
                    $null = New-ADTestEdgeUser -Name 'EdgeCase No UPN' -SamAccountName 'EdgeCaseNoUpn' `
                        -Path $edgeOU -Description 'No userPrincipalName: exercises the SamAccountName fallback' `
                        -NoUserPrincipalName

                    $script:Created.Add([PSCustomObject]@{
                        EdgeCase = 'MissingUpn'
                        Detail   = 'EdgeCaseNoUpn has no userPrincipalName'
                    })
                }
                catch {
                    $script:EdgeErrors.Add("MissingUpn: $($_.Exception.Message)")
                }
            }
            #endregion

            #region MixedMembership
            if (& $wanted 'MixedMembership') {
                Write-TestMessage -Message "Creating a group with non-user, non-group members..." -Type Info

                try {
                    $mixedOU = (New-ADTestOU -Name 'MixedMembership' -Path $edgeOU `
                        -Description 'A group whose members are not all users or groups' `
                            -Unprotected).DistinguishedName

                    $mixedSam = 'EdgeCaseMixedMembers'
                    if (-not (Get-ADGroup -Filter ("SamAccountName -eq " +
                        "'$mixedSam'") -ErrorAction SilentlyContinue)) {
                        New-ADGroup -Name 'EdgeCase Mixed Members' -SamAccountName $mixedSam `
                            -GroupScope Global -GroupCategory Security -Path $mixedOU `
                            -Description 'Contains a computer and a contact as well as a user'
                    }

                    if (-not (Get-ADComputer -Filter "Name -eq 'EDGECASE-PC01'" -ErrorAction SilentlyContinue)) {
                        New-ADComputer -Name 'EDGECASE-PC01' -SamAccountName 'EDGECASE-PC01$' `
                            -Path $mixedOU -Description 'Group member that is a computer'
                    }

                    # A contact has no sAMAccountName and no SID at all, which is a stronger
                    # test than the computer: code that reaches for either gets nothing back
                    # rather than getting something unexpected.
                    $contactDn = "CN=EdgeCase Contact,$mixedOU"
                    if (-not (Get-ADObject -Filter ("DistinguishedName -eq " +
                        "'$contactDn'") -ErrorAction SilentlyContinue)) {
                        New-ADObject -Name 'EdgeCase Contact' -Type 'contact' -Path $mixedOU `
                            -OtherAttributes @{ mail = "edgecase.contact@$($domain.DNSName)" }
                    }

                    $null = New-ADTestEdgeUser -Name 'EdgeCase Mixed User' -SamAccountName 'EdgeCaseMixedUser' `
                        -Path $mixedOU -Description 'The one member that is an ordinary user'

                    # The computer and the user go in with Add-ADGroupMember. The contact
                    # cannot: Add-ADGroupMember resolves -Members as a security principal and
                    # a contact has no SID, so it fails with "Cannot find an object with
                    # identity" even though the object plainly exists. Writing the member
                    # attribute directly is the way to put a non-principal in a group - and
                    # that asymmetry is itself worth having in the fixture.
                    foreach ($memberDn in @((Get-ADComputer 'EDGECASE-PC01').DistinguishedName,
                                            (Get-ADUser 'EdgeCaseMixedUser').DistinguishedName)) {
                        Add-ADGroupMember -Identity $mixedSam -Members $memberDn -ErrorAction SilentlyContinue
                    }

                    $mixedGroupDn = (Get-ADGroup -Identity $mixedSam).DistinguishedName
                    if (@((Get-ADGroup -Identity $mixedSam -Properties member).member) -notcontains $contactDn) {
                        Set-ADObject -Identity $mixedGroupDn -Add @{ member = $contactDn }
                    }

                    $script:Created.Add([PSCustomObject]@{
                        EdgeCase = 'MixedMembership'
                        Detail   = "'EdgeCase Mixed Members' holds a computer, a contact and a user"
                    })
                }
                catch {
                    $script:EdgeErrors.Add("MixedMembership: $($_.Exception.Message)")
                }
            }
            #endregion

            #region FineGrainedPolicy
            if (& $wanted 'FineGrainedPolicy') {
                Write-TestMessage -Message "Creating a fine-grained password policy..." -Type Info

                try {
                    $fgppSubjectSam = 'EdgeCaseFgppSubjects'
                    if (-not (Get-ADGroup -Filter ("SamAccountName -eq " +
                        "'$fgppSubjectSam'") -ErrorAction SilentlyContinue)) {
                        New-ADGroup -Name 'EdgeCase FGPP Subjects' -SamAccountName $fgppSubjectSam `
                            -GroupScope Global -GroupCategory Security -Path $edgeOU `
                            -Description 'Members are governed by the edge case password settings object'
                    }

                    $null = New-ADTestEdgeUser -Name 'EdgeCase FGPP User' -SamAccountName 'EdgeCaseFgppUser' `
                        -Path $edgeOU -Description 'Password expiry comes from an FGPP, not the domain default' `
                        -Extra @{ PasswordNeverExpires = $false }

                    Add-ADGroupMember -Identity $fgppSubjectSam -Members 'EdgeCaseFgppUser' `
                        -ErrorAction SilentlyContinue

                    # A one-day maximum age against a domain default of several weeks. That
                    # difference is the whole point: an expiry report that reads the computed
                    # attribute reports about a day for this account and the domain default
                    # for everyone else, which is what proves the FGPP is being honoured.
                    #
                    # It also gives the environment its only account expiring imminently, so
                    # a "expiring within the next N days" query returns something. A genuinely
                    # past-due expiry cannot be manufactured: pwdLastSet is set by the
                    # directory and cannot be backdated.
                    $policyName = 'EdgeCase Short Expiry'
                    if (-not (Get-ADFineGrainedPasswordPolicy -Filter ("Name -eq " +
                        "'$policyName'") -ErrorAction SilentlyContinue)) {
                        New-ADFineGrainedPasswordPolicy -Name $policyName `
                            -Precedence 500 `
                            -MaxPasswordAge '1.00:00:00' `
                            -MinPasswordAge '00:00:00' `
                            -MinPasswordLength 8 `
                            -PasswordHistoryCount 1 `
                            -ComplexityEnabled $true `
                            -Description 'Short maximum password age for edge case testing'
                    }

                    Add-ADFineGrainedPasswordPolicySubject -Identity $policyName `
                        -Subjects $fgppSubjectSam -ErrorAction SilentlyContinue

                    $script:Created.Add([PSCustomObject]@{
                        EdgeCase = 'FineGrainedPolicy'
                        Detail   = "'$policyName' (1 day max age) applied to $fgppSubjectSam"
                    })
                }
                catch {
                    $script:EdgeErrors.Add("FineGrainedPolicy: $($_.Exception.Message)")
                }
            }
            #endregion

            #region GovernanceAttribute
            if (& $wanted 'GovernanceAttribute') {
                Write-TestMessage -Message "Populating governance attributes..." -Type Info

                try {
                    $govOU = (New-ADTestOU -Name 'Governance' -Path $edgeOU `
                        -Description 'Groups carrying base-schema attributes a governance export can read' `
                            -Unprotected).DistinguishedName

                    # See the note in .NOTES: extensionAttribute1-15 need the Exchange schema,
                    # so these are the base-schema attributes that come closest in meaning and
                    # were confirmed writable on the group class in this forest.
                    $govGroup = @(
                        @{ Sam = 'EdgeCaseGovPayroll'; Name = 'EdgeCase Gov Payroll'
                           Note = 'SOX in scope; recertified quarterly'
                           Doc = 'https://runbooks.example/groups/payroll'
                           MultiValued = 'Payroll' }
                        @{ Sam = 'EdgeCaseGovSourceCtl'; Name = 'EdgeCase Gov Source Control'
                           Note = 'Export-controlled content'
                           Doc = 'https://runbooks.example/groups/source-control'
                           MultiValued = 'SourceControl' }
                        @{ Sam = 'EdgeCaseGovUnmanaged'; Name = 'EdgeCase Gov Unmanaged'
                           Note = ''; Doc = ''; MultiValued = '' }
                    )

                    foreach ($item in $govGroup) {
                        if (-not (Get-ADGroup -Filter ("SamAccountName -eq " +
                            "'$($item.Sam)'") -ErrorAction SilentlyContinue)) {
                            New-ADGroup -Name $item.Name -SamAccountName $item.Sam `
                                -GroupScope Global -GroupCategory Security -Path $govOU `
                                -Description 'Carries governance attributes for export testing'
                        }

                        # The third group is left deliberately blank so an export has both a
                        # populated and an unpopulated row to render.
                        $attribute = @{}
                        if ($item.Note) { $attribute['info'] = $item.Note }
                        if ($item.Doc) { $attribute['wWWHomePage'] = $item.Doc }

                        # extensionName as well, and it is NOT in the recommended map above.
                        # It is multi-valued, and an export that writes an attribute straight
                        # into a CSV cell renders a multi-valued one as the literal string
                        # "Microsoft.ActiveDirectory.Management.ADPropertyValueCollection"
                        # rather than its contents. Kept populated on purpose so that
                        # behaviour stays reproducible by pointing a map at extensionName.
                        if ($item.MultiValued) { $attribute['extensionName'] = $item.MultiValued }

                        if ($attribute.Count -gt 0) {
                            Set-ADGroup -Identity $item.Sam -Replace $attribute
                        }
                    }

                    $script:Created.Add([PSCustomObject]@{
                        EdgeCase = 'GovernanceAttribute'
                        Detail   = 'extensionName / info / wWWHomePage populated on 2 of 3 ' +
                               'groups under OU=Governance'
                    })
                }
                catch {
                    $script:EdgeErrors.Add("GovernanceAttribute: $($_.Exception.Message)")
                }
            }
            #endregion

            $results = [PSCustomObject]@{
                CorrelationId = $correlationId
                EdgeCaseRoot  = $edgeOU
                Requested     = $EdgeCase
                Created       = $script:Created
                Errors        = @($script:EdgeErrors)
            }

            Write-TestMessage -Message "Edge Case Creation Summary" -Type Success
            Write-Host " Root: $edgeOU" -ForegroundColor Cyan

            foreach ($item in $script:Created) {
                Write-Host " [$($item.EdgeCase)] $($item.Detail)" -ForegroundColor Green
            }

            # Every object above now carries the seed tag, which is what teardown requires
            # before it will delete anything. Swept once here rather than threaded through a
            # dozen creation calls across four nested containers - the thirteenth call added
            # later would be the one that got forgotten, and an untagged object is one teardown
            # leaves behind for somebody to find by hand.
            #
            # The password settings object is named separately because it lives in the Password
            # Settings Container, not with the objects it applies to.
            if (-not $WhatIfPreference) {
                $pso = Get-ADFineGrainedPasswordPolicy -Filter "Name -eq 'EdgeCase Short Expiry'" -ErrorAction SilentlyContinue
                $stamped = Set-ADTestSeedTag -SearchBase $edgeOU -Identity @($pso.DistinguishedName) -Confirm:$false
                Write-Verbose "Stamped the seed tag on $stamped edge case object(s)"
            }
            if ($script:EdgeErrors.Count -gt 0) {
                Write-Host " Errors: $($script:EdgeErrors.Count)" -ForegroundColor Red
                $script:EdgeErrors | ForEach-Object { Write-Host " - $_" -ForegroundColor Red }
            }

            Write-Host ''
            $edgeNote = ' Remove-ADEnvironment removes OU=EdgeCases and everything in it,'
            Write-Host $edgeNote -ForegroundColor Yellow
            Write-Host ' including the access control entries written above.' -ForegroundColor Yellow

            if ($PassThru) {
                return $results
            }
        }
        catch {
            Write-Error "Failed to create edge cases: $($_.Exception.Message)" -ErrorAction Stop
        }
    }

    end {
        Write-Verbose "Completed New-ADTestEdgeCase - CorrelationId: $correlationId"
    }
}