Public/Authentication/Test-Authentication.ps1

function Test-Authentication {
    <#
    .SYNOPSIS
    Test if a user's credentials are valid (Domain or Local machine)

    .DESCRIPTION
    Returns $true / $false depending on whether the provided credentials are valid.
    Supports validating against:
      - Active Directory domain (Context = Domain)
      - Local SAM on this machine (Context = Local)
      - Auto-detect (default): .\user => Local, DOMAIN\user => Domain, user@upn => Domain, otherwise Domain first then Local.

    .PARAMETER Username
    Username to test. Supports: user, DOMAIN\user, .\user, COMPUTER\user, user@domain.tld

    .PARAMETER SecurePassword
    SecureString password.

    .PARAMETER UnencryptedPassword
    Plain-text password.

    .PARAMETER Credential
    PSCredential containing username and password.

    .PARAMETER Context
    Auto (default), Domain, or Local.

    .PARAMETER Domain
    Optional domain name to force when Context=Domain and Username is not DOMAIN\user / UPN.

    .EXAMPLE
    Test-ADAuthentication -Username "contoso\alice" -UnencryptedPassword "Password123"

    .EXAMPLE
    Test-ADAuthentication -Username ".\localuser" -UnencryptedPassword "Password123" -Context Local

    .EXAMPLE
    $cred = Get-Credential
    Test-ADAuthentication -Credential $cred
    #>

    [CmdletBinding(DefaultParameterSetName = 'Passed')]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'Passed', Position = 0, ValueFromPipeline = $true)]
        [Parameter(Mandatory = $true, ParameterSetName = 'Unencrypted', Position = 0, ValueFromPipelineByPropertyName = $true)]
        [Alias("SamAccountName")]
        [string] $Username,

        [Parameter(Mandatory = $true, ParameterSetName = 'Passed', Position = 1)]
        [Alias("Password")]
        [System.Security.SecureString] $SecurePassword,

        [Parameter(Mandatory = $true, ParameterSetName = 'Unencrypted', Position = 1)]
        [string] $UnencryptedPassword,

        [Parameter(Mandatory = $true, ParameterSetName = 'Credential', Position = 0, ValueFromPipeline = $true)]
        [System.Management.Automation.PSCredential] $Credential,

        [ValidateSet('Auto', 'Domain', 'Local')]
        [string] $Context = 'Auto',

        [string] $Domain
    )

    begin {
        try {
            Add-Type -AssemblyName System.DirectoryServices.AccountManagement -ErrorAction Stop | Out-Null
        } catch {
            throw "System.DirectoryServices.AccountManagement is not available. This function requires Windows / .NET support. Details: $($_.Exception.Message)"
        }
    }

    process {
        # Normalize inputs into $u (username), $p (plain password)
        switch ($PSCmdlet.ParameterSetName) {
            'Unencrypted' {
                $u = $Username
                $p = $UnencryptedPassword
            }
            'Passed' {
                $Credential = [pscredential]::new($Username, $SecurePassword)
                $u = $Credential.UserName
                $p = $Credential.GetNetworkCredential().Password
            }
            'Credential' {
                $u = $Credential.UserName
                $p = $Credential.GetNetworkCredential().Password
            }
        }

        # Parse username forms
        $parsed = [ordered]@{
            RawUser     = $u
            User        = $null
            Qualifier   = $null   # DOMAIN, COMPUTERNAME, '.', or $null
            IsUPN       = $false
            IsDotLocal  = $false
        }

        if ($u -match '^(?<qual>[^\\]+)\\(?<usr>.+)$') {
            $parsed.Qualifier = $Matches.qual
            $parsed.User      = $Matches.usr
            $parsed.IsDotLocal = ($parsed.Qualifier -eq '.')
        }
        elseif ($u -match '^(?<usr>[^@]+)@(?<upn>.+)$') {
            $parsed.User = $u  # keep UPN intact for ValidateCredentials
            $parsed.IsUPN = $true
        }
        else {
            $parsed.User = $u
        }

        $machineName = $env:COMPUTERNAME

        function Test-WithContext {
            param(
                [Parameter(Mandatory)] [ValidateSet('Domain','Local')] [string] $Which
            )
            try {
                $ct = if ($Which -eq 'Local') {
                    [System.DirectoryServices.AccountManagement.ContextType]::Machine
                } else {
                    [System.DirectoryServices.AccountManagement.ContextType]::Domain
                }

                # Decide what to pass to ValidateCredentials
                # - For Domain context:
                # * If UPN, pass UPN directly
                # * If DOMAIN\user, pass "user" and let domain be inferred from DC discovery (or override with -Domain)
                # - For Local context:
                # * If ".\user" or "COMPUTER\user", pass "user"
                # * Else pass raw user (it still works for many cases)
                $usernameToValidate = $parsed.User
                $contextName = $null

                if ($Which -eq 'Domain') {
                    if ($parsed.IsUPN) {
                        $usernameToValidate = $parsed.User
                    } elseif ($parsed.Qualifier -and $parsed.Qualifier -ne '.' -and $parsed.Qualifier -ne $machineName) {
                        # DOMAIN\user => validate as "user" in that domain
                        $usernameToValidate = $parsed.User
                        $contextName = $parsed.Qualifier
                    } elseif ($Domain) {
                        $contextName = $Domain
                    }
                }
                else {
                    if ($parsed.Qualifier -eq '.' -or $parsed.Qualifier -eq $machineName) {
                        $usernameToValidate = $parsed.User
                    }
                }

                $pc = if ($contextName) {
                    [System.DirectoryServices.AccountManagement.PrincipalContext]::new($ct, $contextName)
                } else {
                    [System.DirectoryServices.AccountManagement.PrincipalContext]::new($ct)
                }

                # Use Negotiate so it works for both Kerberos/NTLM where applicable
                return $pc.ValidateCredentials($usernameToValidate, $p, [System.DirectoryServices.AccountManagement.ContextOptions]::Negotiate)
            }
            catch {
                return $false
            }
        }

        switch ($Context) {
            'Local'  { return (Test-WithContext -Which Local) }
            'Domain' { return (Test-WithContext -Which Domain) }
            'Auto' {
                # If user explicitly indicates local, prefer local
                if ($parsed.Qualifier -eq '.' -or $parsed.Qualifier -eq $machineName) {
                    return (Test-WithContext -Which Local)
                }
                # If user indicates a domain or UPN, prefer domain
                if (($parsed.Qualifier -and $parsed.Qualifier -ne '.' -and $parsed.Qualifier -ne $machineName) -or $parsed.IsUPN) {
                    return (Test-WithContext -Which Domain)
                }

                # Otherwise: try Domain first (typical enterprise expectation), then Local fallback
                $ok = Test-WithContext -Which Domain
                if ($ok) { return $true }
                return (Test-WithContext -Which Local)
            }
        }
    }
}