functions/Connect-XdrByCredential.ps1

function Connect-XdrByCredential {
    <#
    .SYNOPSIS
        Authenticates to Microsoft Defender XDR using username, password, and optional TOTP MFA.

    .DESCRIPTION
        Performs a full Entra ID web login flow programmatically (no browser required),
        handling password submission and MFA challenges, then establishes an authenticated
        session to the Defender XDR portal.

        Supported MFA methods:
          - PhoneAppOTP: Authenticator app TOTP code (computed automatically from -TotpSecret)
          - PhoneAppNotification: Push notification (polls for user approval, displays number match)
          - OneWaySMS: SMS code (prompts user to enter code from phone)

        The authentication chain is:
          1. Submit credentials to Entra ID web login endpoints
          2. Handle MFA via SAS (Server Authentication State) endpoints
          3. Extract ESTSAUTH cookie from the completed login
          4. Pass ESTSAUTH cookie to Connect-XdrByEstsCookie to get sccauth + XSRF-TOKEN

        Note: This method may be blocked by Conditional Access policies that require
        device compliance or a specific client application. It will work with MFA-only policies.

    .PARAMETER Credential
        A PSCredential object containing username and password. When provided, -Username and
        -Password are ignored. If no parameters are provided at all, you will be prompted
        interactively for credentials.

    .PARAMETER Username
        The user principal name (e.g., admin@contoso.com). Not needed if -Credential is used.

    .PARAMETER Password
        The password as a SecureString. Not needed if -Credential is used.
        If you have a plain string, convert it:
          $pw = ConvertTo-SecureString "MyPassword" -AsPlainText -Force

    .PARAMETER TotpSecret
        Base32-encoded TOTP secret for automatic MFA code generation.
        This is the secret from the QR code when setting up Microsoft Authenticator
        (otpauth://totp/...?secret=JBSWY3DPEHPK3PXP).
        If not provided and MFA is required, the function will attempt push notification
        or prompt for a code.

    .PARAMETER MfaMethod
        Preferred MFA method. Valid values: PhoneAppOTP, PhoneAppNotification, OneWaySMS.
        If not specified, the function auto-selects PhoneAppOTP only when -TotpSecret is provided
        and that method is actually offered. When multiple supported inline methods are available,
        you are prompted to choose.

    .PARAMETER TenantId
        The Defender XDR tenant ID to connect to. If not provided, the default tenant is used.

    .PARAMETER UserAgent
        User-Agent string for HTTP requests. Defaults to Edge browser user agent.

    .PARAMETER DebugCaptureDirectory
        Optional directory for writing detailed credential-auth debug captures.
        When provided, request bodies, response bodies, headers, parsed state, and cookie snapshots
        are written under the specified path for each major authentication stage.

    .EXAMPLE
        Connect-XdrByCredential

        Prompts interactively for username and password, then authenticates.
        If MFA is required, push notification or SMS prompt will be used.

    .EXAMPLE
        Connect-XdrByCredential -TotpSecret "JBSWY3DPEHPK3PXP"

        Prompts interactively for username and password, then handles MFA automatically via TOTP.

    .EXAMPLE
        Connect-XdrByCredential -Credential (Get-Credential) -TotpSecret "JBSWY3DPEHPK3PXP"

        Uses the Get-Credential dialog for username/password, then auto-completes TOTP MFA.

    .EXAMPLE
        $pw = ConvertTo-SecureString "MyPassword" -AsPlainText -Force
        Connect-XdrByCredential -Username "admin@contoso.com" -Password $pw -TotpSecret "JBSWY3DPEHPK3PXP"

        Fully non-interactive: all credentials and MFA passed as parameters.

    .EXAMPLE
        Connect-XdrByCredential -Credential (Get-Credential) -TotpSecret "JBSWY3DPEHPK3PXP" -TenantId "847b5907-ca15-40f4-b171-eb18619dbfab"

        Authenticates and connects to a specific XDR tenant.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '')]
    [CmdletBinding(DefaultParameterSetName = 'Interactive')]
    param (
        [Parameter(ParameterSetName = 'Credential')]
        [PSCredential]$Credential,

        [Parameter(ParameterSetName = 'Explicit')]
        [string]$Username,

        [Parameter(ParameterSetName = 'Explicit')]
        [SecureString]$Password,

        [string]$TotpSecret,

        [ValidateSet('PhoneAppOTP', 'PhoneAppNotification', 'OneWaySMS')]
        [string]$MfaMethod,

        [string]$TenantId,

        [string]$DebugCaptureDirectory,

        [string]$UserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0'
    )

    process {
        # Resolve credentials from whichever input method was used.
        # Prompt only for values the caller did not supply.
        if ($PSCmdlet.ParameterSetName -eq 'Credential' -and $Credential) {
            $resolvedUsername = $Credential.UserName
            $resolvedPassword = $Credential.Password
        } elseif ($PSCmdlet.ParameterSetName -eq 'Explicit') {
            $resolvedUsername = $Username
            $resolvedPassword = $Password

            if (-not $resolvedUsername -and -not $resolvedPassword) {
                Write-Host "Enter credentials for Defender XDR authentication:"
                $cred = Get-Credential -Message "Enter your Entra ID credentials for Defender XDR"
                if (-not $cred) {
                    throw "No credentials provided."
                }

                $resolvedUsername = $cred.UserName
                $resolvedPassword = $cred.Password
            } else {
                if (-not $resolvedUsername) {
                    $resolvedUsername = Read-Host "Username"
                }
                if (-not $resolvedPassword) {
                    $resolvedPassword = Read-Host -AsSecureString "Password for $resolvedUsername"
                }
            }
        } else {
            Write-Host "Enter credentials for Defender XDR authentication:"
            $cred = Get-Credential -Message "Enter your Entra ID credentials for Defender XDR"
            if (-not $cred) {
                throw "No credentials provided."
            }
            $resolvedUsername = $cred.UserName
            $resolvedPassword = $cred.Password
        }

        if (-not $resolvedUsername) {
            throw "No username provided."
        }
        if (-not $resolvedPassword) {
            throw "No password provided."
        }

        Write-Host "Authenticating as $resolvedUsername with credential flow..."

        $credParams = @{
            Username  = $resolvedUsername
            Password  = $resolvedPassword
            UserAgent = $UserAgent
        }
        if ($TotpSecret) { $credParams.TotpSecret = $TotpSecret }
        if ($MfaMethod) { $credParams.MfaMethod = $MfaMethod }
        if ($DebugCaptureDirectory) { $credParams.DebugCaptureDirectory = $DebugCaptureDirectory }

        $estsAuth = Invoke-XdrCredentialAuthentication @credParams

        if (-not $estsAuth) {
            throw "Credential authentication failed - no ESTS cookie was returned."
        }

        $connectParams = @{
            EstsAuthCookieValue = $estsAuth
            UserAgent           = $UserAgent
        }
        if ($TenantId) { $connectParams.TenantId = $TenantId }

        Connect-XdrByEstsCookie @connectParams
    }
}