functions/Test-KrbPasswordReset.ps1

function Test-KrbPasswordReset
{
<#
    .SYNOPSIS
        Tests the account reset and synchronization functionality.
     
    .DESCRIPTION
        Tests the account reset and synchronization functionality.
        This is a dry run of what Reset-KrbPassword would do, executed using a temporary user account.
     
    .PARAMETER PDCEmulator
        The PDC Emulator to operate against.
     
    .PARAMETER DomainController
        The domain controller to synchronize with.
     
    .PARAMETER Credential
        The credentials to use for this operation.
     
    .PARAMETER MaxDurationSeconds
        The maximum number of seconds a switch may take before being considered a failure.
        Defaults to 180 seconds
     
    .PARAMETER DCSuccessPercent
        The percent of DCs that need to successfully finish execution in order for this test to be considered a success.
        Defaults to 80 percent
     
    .PARAMETER EnableException
        This parameters disables user-friendly warnings and enables the throwing of exceptions.
        This is less user friendly, but allows catching exceptions in calling scripts.
     
    .EXAMPLE
        PS C:\> Test-KrbPasswordReset -PDCEmulator 'dc1.domain.com' -DomainController 'dc2', 'dc3'
     
        Tests the account password reset using a dummy account and returns, whether the execution would have been successful.
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")]
    [CmdletBinding()]
    param (
        [string]
        $PDCEmulator = (Get-ADDomain).PDCEmulator,
        
        [PSFComputer[]]
        $DomainController,
        
        [PSCredential]
        $Credential,
        
        [int]
        $MaxDurationSeconds = (Get-PSFConfigValue -FullName 'Krbtgt.Reset.MaxDurationSeconds' -Fallback 100),
        
        [int]
        $DCSuccessPercent = (Get-PSFConfigValue -FullName 'Krbtgt.Reset.DCSuccessPercent' -Fallback 100),
        
        [switch]
        $EnableException
    )
    
    begin
    {
        #region Ensure Domain Controller parameter is filled
        $parameters = @{ Server = $PDCEmulator }
        $credParam = @{ }
        if ($Credential)
        {
            $parameters['Credential'] = $Credential
            $credParam = @{ Credential = $Credential }
        }
        
        if (-not $DomainController)
        {
            try
            {
                $DomainController = (Get-ADDomainController @parameters -Filter * -ErrorAction Stop).HostName | Where-Object {
                    $_ -ne $PDCEmulator
                }
            }
            catch
            {
                Stop-PSFFunction -String 'Test-KrbPasswordReset.FailedDCResolution' -StringValues $PDCEmulator -ErrorRecord $_
                return
            }
        }
        #endregion Ensure Domain Controller parameter is filled
        
        #region Create a test account to test SO replication with
        try
        {
            $randomName = "krbtgt_test_$(Get-Random -Minimum 100 -Maximum 999)"
            Write-PSFMessage -String 'Test-KrbPasswordReset.CreatingCanary' -StringValues $randomName
            $canaryAccount = New-ADUser -Name $randomName -PassThru @parameters -ErrorAction Stop
        }
        catch
        {
            Stop-PSFFunction -String 'Test-KrbPasswordReset.FailedCanaryCreation' -StringValues $randomName -ErrorRecord $_
            return
        }
        try {
            $null = Sync-LdapObjectParallel @credParam -Object $canaryAccount -Server $DomainController -Target $PDCEmulator -Reverse
        }
        catch {
            # We don't care
        }
        #endregion Create a test account to test SO replication with
    }
    process
    {
        if (Test-PSFFunctionInterrupt) { return }
        
        $result = [PSCustomObject]@{
            PSTypeName  = 'Krbtgt.TestResult'
            PDCEmulator = $PDCEmulator
            Start        = $null
            End            = $null
            Duration    = $null
            Reset        = $false
            Sync        = @()
            DCTotal        = ($DomainController | Measure-Object).Count
            DCSuccess   = 0
            DCSuccessPercent = 0
            DCFailed    = @()
            Errors        = @()
            Success        = $true
            Status        = $null
            RWDCs        = $DomainController
        }
        
        $result.Start = Get-Date
        
        #region Test 1: Password Reset
        Write-PSFMessage -String 'Test-KrbPasswordReset.ResettingPassword' -StringValues $canaryAccount.DistinguishedName -Target $canaryAccount.DistinguishedName
        try
        {
            Reset-UserPassword @parameters -Identity $canaryAccount.DistinguishedName -EnableException
            $result.Reset = $true
        }
        catch
        {
            Write-PSFMessage -Level Warning -String 'Test-KrbPasswordReset.ResettingPasswordFailed' -StringValues $canaryAccount.DistinguishedName -Target $canaryAccount.DistinguishedName -ErrorRecord $_
            $result.Reset = $false
            $result.Errors += $_
            $result.Success = $false
            $result.Status = $result.Status, 'ResetError' -join ", "
        }
        #endregion Test 1: Password Reset
        
        #region Test 2: Resync Domain Controllers
        Write-PSFMessage -String 'Test-KrbPasswordReset.SynchronizingCanary' -StringValues $canaryAccount.DistinguishedName -Target $canaryAccount.DistinguishedName
        $result.Sync = Sync-KrbAccount @credParam -SourceDC $DomainController -TargetDC $PDCEmulator -Identity $canaryAccount.DistinguishedName -EnableException:$false
        $result.End = Get-Date
        $result.Duration = $result.End - $result.Start
        $result.DCSuccess = $result.Sync | Where-Object Success
        $result.DCFailed =  $result.Sync | Where-Object Success -EQ $false
        $result.DCSuccessPercent = ($result.DCSuccess | Measure-Object).Count / $result.DCTotal * 100
        $result.Sync.Error | ForEach-Object {
            if ($_) { $result.Errors += $_ }
        }
        if ($result.Duration.TotalSeconds -gt $MaxDurationSeconds)
        {
            $result.Success = $false
            $result.Status = $result.Status, 'TooSlowError' -join ", "
        }
        if ($result.DCSuccessPercent -lt $DCSuccessPercent)
        {
            $result.Success = $false
            $result.Status = $result.Status, 'SyncErrorRateError' -join ", "
        }
        Write-PSFMessage -String 'Test-KrbPasswordReset.Concluded' -StringValues $result.Success, $result.Status, $canaryAccount.DistinguishedName -Target $canaryAccount.DistinguishedName
        #endregion Test 2: Resync Domain Controllers
        
        $result
    }
    end
    {
        if (Test-PSFFunctionInterrupt) { return }
        
        # Remove the test account after finishing its work
        try { $canaryAccount | Remove-ADUser @parameters -Confirm:$false -ErrorAction Stop }
        catch
        {
            Stop-PSFFunction -String 'Test-KrbPasswordReset.FailedCanaryCleanup' -StringValues $canaryAccount.DistinguishedName
        }
    }
}