Providers/AD/Public/New-ADTestPasswordPolicy.ps1

function New-ADTestPasswordPolicy {
    <#
    .EXTERNALHELP TestEnvironment-Help.xml
    .SYNOPSIS
        Creates the seeded fine-grained password policies from Data\ADPasswordPolicies.csv
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter()]
        [string[]]$PolicyName,

        [Parameter()]
        [switch]$PassThru
    )

    $seed = Get-ADTestSeedMarker

    $csvPath = Join-Path -Path (Get-ADTestDataPath) -ChildPath 'ADPasswordPolicies.csv'
    $rows = @(Import-Csv -Path $csvPath -Encoding UTF8)
    if ($PolicyName) {
        $rows = @($rows | Where-Object { $PolicyName -contains $_.Name })
        $unknown = @($PolicyName | Where-Object { $rows.Name -notcontains $_ })
        if ($unknown) { throw "No definition in $csvPath for: $($unknown -join ', ')" }
    }

    $result = [PSCustomObject]@{
        TotalPolicies   = $rows.Count
        CreatedPolicies = 0
        UpdatedPolicies = 0
        SubjectsApplied = 0
        Policies        = @()
        Errors          = @()
    }

    # A day count of zero means "never" for both an age and a lockout, and that is the value
    # the directory stores, so it is passed through rather than treated as absent.
    $days = { param($value) [TimeSpan]::FromDays([int]$value) }
    $minutes = { param($value) [TimeSpan]::FromMinutes([int]$value) }

    foreach ($row in $rows) {
        $name = '{0}{1}' -f $seed.Prefix, $row.Name
        $groupName = '{0}{1}' -f $seed.Prefix, $row.AppliesToGroup
        if (-not $PSCmdlet.ShouldProcess($name, 'Create AD fine-grained password policy')) { continue }

        try {
            $settings = @{
                Precedence                  = [int]$row.Precedence
                MinPasswordLength           = [int]$row.MinPasswordLength
                MaxPasswordAge              = & $days $row.MaxPasswordAgeDays
                MinPasswordAge              = & $days $row.MinPasswordAgeDays
                PasswordHistoryCount        = [int]$row.HistoryCount
                ComplexityEnabled           = ($row.ComplexityEnabled -eq 'TRUE')
                ReversibleEncryptionEnabled = ($row.ReversibleEncryption -eq 'TRUE')
                LockoutThreshold            = [int]$row.LockoutThreshold
                LockoutObservationWindow    = & $minutes $row.LockoutWindowMinutes
                LockoutDuration             = & $minutes $row.LockoutDurationMinutes
            }

            $existing = Get-ADFineGrainedPasswordPolicy -Filter "Name -eq '$($name.Replace("'", "''"))'" -ErrorAction SilentlyContinue
            if ($existing) {
                Set-ADFineGrainedPasswordPolicy -Identity $existing.DistinguishedName @settings -ErrorAction Stop
                $result.UpdatedPolicies++
                Write-Verbose "Updated password policy $name"
            }
            else {
                # Protection off on purpose: a protected object cannot be deleted without
                # clearing the flag first, and this module has to be able to remove what it made.
                $null = New-ADFineGrainedPasswordPolicy -Name $name @settings `
                    -Description $row.Purpose `
                    -ProtectedFromAccidentalDeletion $false `
                    -OtherAttributes @{ adminDescription = $seed.Tag } `
                    -ErrorAction Stop
                $result.CreatedPolicies++
                Write-Verbose "Created password policy $name"
            }

            # The subject is the seeded group. A policy applied to nothing governs nobody,
            # so a group that is missing is an error on the row rather than a silent pass.
            $group = Get-ADGroup -Filter "Name -eq '$($groupName.Replace("'", "''"))'" -ErrorAction SilentlyContinue
            if ($group) {
                Add-ADFineGrainedPasswordPolicySubject -Identity $name -Subjects $group.DistinguishedName -ErrorAction Stop
                $result.SubjectsApplied++
            }
            else {
                $message = "Password policy '$name' names the group '$groupName', which does not exist. Applied to nobody."
                $result.Errors += $message
                Write-Error $message
            }

            $result.Policies += [PSCustomObject]@{
                Key        = $row.Name
                Name       = $name
                Precedence = [int]$row.Precedence
                AppliesTo  = $groupName
            }
        }
        catch {
            $message = "Failed to create password policy '$name': $($_.Exception.Message)"
            $result.Errors += $message
            Write-Error $message
        }
    }

    Write-Verbose ("Password policies: $($result.CreatedPolicies) created, $($result.UpdatedPolicies) updated, " +
        "$($result.SubjectsApplied) applied, $($result.Errors.Count) problems")
    if ($PassThru) { return $result }
}