public/Connect-Unraid.ps1

function Connect-Unraid {
    <#
    .SYNOPSIS
        Connects to an Unraid server via Web Session or API Key.

    .DESCRIPTION
        Establishes a session with an Unraid server.
        Supports legacy web-login (scraping CSRF) and API Key.

    .PARAMETER ComputerName
        Server hostname or IP address.

    .PARAMETER Credential
        Username and password.

    .PARAMETER ProvideAPIKey
        Prompts for an API key.

    .PARAMETER UseStoredAuth
        Uses saved credentials/key from local storage.

    .PARAMETER SkipCertificateCheck
        Skips SSL validation (useful for self-signed) certs.

    .PARAMETER NoDefaultSession
        Returns the session object but doesn't set it as the default session.

    .PARAMETER SaveAuth
        Saves auth details for reuse.

    .OUTPUTS
        [UnraidSession]
    #>

    [CmdletBinding(DefaultParameterSetName = 'Credential')]
    [OutputType('UnraidSession')]
    param(
        [Parameter(Mandatory, Position = 0, ValueFromPipeline)]
        [string]$ComputerName,

        [Parameter(Mandatory, Position = 1, ParameterSetName = 'Credential')]
        [pscredential]$Credential,

        [Parameter(Mandatory, ParameterSetName = 'ApiKey')]
        [switch]$ProvideAPIKey,

        [Parameter(Mandatory, ParameterSetName = 'StoredAuth')]
        [switch]$UseStoredAuth,

        [Parameter()]
        [switch]$SkipCertificateCheck,

        [Parameter()]
        [switch]$NoDefaultSession,

        [Parameter()]
        [switch]$SaveAuth
    )

    process {
        # Normalize the URL schema
        if ($ComputerName -notmatch "^http(s)?://") {
            $baseUrl = "https://$ComputerName"
        }
        else {
            $baseUrl = $ComputerName
            $ComputerName = $ComputerName -replace "^http(s)?://", "" 
        }
        
        $graphqlUrl = "$baseUrl/graphql"
        $loginUrl = "$baseUrl/login"

        # Check where we're getting auth info from
        if ($UseStoredAuth) {
            Write-Verbose "Checking local storage for $ComputerName..."
            $storedAuth = Get-UnraidAuth -ServerName $ComputerName
            
            if (!$storedAuth) { throw "No stored auth for '$ComputerName'. You need to connect once with -SaveAuth first." }
    
            if ($storedAuth.AuthType -eq 'ApiKey') { $ApiKey = $storedAuth.ApiKey  }
            else { $Credential = $storedAuth.Credential  }
        }
        
        elseif ($ProvideAPIKey) {
            $secureKey = Read-Host -Prompt "Enter valid $ComputerName API key" -AsSecureString
            $ptr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureKey)
            $ApiKey = [Runtime.InteropServices.Marshal]::PtrToStringAuto($ptr)
        }

        if ($ApiKey) { $authType = 'ApiKey'  }
        else { $authType = 'Credential' }

        Write-Verbose "Connecting to $baseUrl ($authType)"

        $requestParams = @{
            Uri         = $graphqlUrl
            Method      = "Post"
            ContentType = "application/json"
            ErrorAction = "Stop"
        }

        # SSL cert trust handling
        if ($SkipCertificateCheck) {
            if ($PSVersionTable.PSVersion.Major -ge 6) { 
                $requestParams.Add('SkipCertificateCheck', $true) 
            }
            # PS 5.1/legacy cert bypass
            else { [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }    }
        }

        try {
            if ($authType -eq 'ApiKey') { 
                $requestParams.Headers = @{ "x-api-key" = $ApiKey }  
            }
            else {

                $webSession = New-Object Microsoft.PowerShell.Commands.WebRequestSession
                
                $loginBody = @{
                    username = $Credential.UserName
                    password = $Credential.GetNetworkCredential().Password
                }

                $loginParams = @{
                    Uri         = $loginUrl
                    Method      = "Post"
                    Body        = $loginBody
                    WebSession  = $webSession
                    ErrorAction = "Stop"
                }

                if ($SkipCertificateCheck -and $PSVersionTable.PSVersion.Major -ge 6) {
                    $loginParams.Add('SkipCertificateCheck', $true)
                }

                Write-Verbose "Logging in at $loginUrl..."
                $loginResponse = Invoke-WebRequest @loginParams

                if ($loginResponse.Content -match "Invalid username or password") { 
                    throw [UnraidAuthException]::new("Incorrect credentials; server rejected login.") 
                }

                # Try to find CSRF token in the HTML
                # This is incredibly fragile - but seems to be the only way to use creds for now
                # API key seems to be the preferred per the API docs, but keeping this in for now for convenience
                $csrfToken = Get-UnraidCsrfToken -HtmlContent $loginResponse.Content -BaseUrl $baseUrl -WebSession $webSession -SkipCertificateCheck $SkipCertificateCheck.IsPresent
                $requestParams.WebSession = $webSession
                $requestParams.Headers = @{ "x-csrf-token" = $csrfToken }
            }

            # Verify connection with a test query
            $testPayload = @{ query = "query { __typename }" } | ConvertTo-Json
            $requestParams.Body = $testPayload

            Write-Verbose "Testing the line..."
            $gqlResponse = Invoke-RestMethod @requestParams

            if (!$gqlResponse.data) {
                 throw [UnraidAuthException]::new("Connection verified but data was empty. Something is wrong.") 
            }
            
            # Build the Unraid session obj
            if ($authType -eq 'ApiKey') {
                $newSession = [UnraidSession]::new($ComputerName, $graphqlUrl, $ApiKey, $SkipCertificateCheck.IsPresent)
            }
            else {
                $newSession = [UnraidSession]::new($ComputerName, $graphqlUrl, $Credential.UserName, $csrfToken, $webSession, $SkipCertificateCheck.IsPresent)
            }

            # Persist session unless told not to with switch
            if (!$NoDefaultSession) {
                $Script:DefaultUnraidSession = $newSession
                Write-Verbose "Default session set to $ComputerName"
            }

            if ($SaveAuth) {
                if ($authType -eq 'ApiKey') {   Save-UnraidAuth -ServerName $ComputerName -ApiKey $ApiKey -Force  }
                else { Save-UnraidAuth -ServerName $ComputerName -Credential $Credential -Force }
                Write-Verbose "Credentials saved."
            }

            Write-Information "Connected to $ComputerName" -InformationAction Continue
            return $newSession

        }
        catch {
            $thrownError = $_.Exception
            
            # Throw a quasi helpful error for SSL issues
            if (($thrownError -is [System.Net.WebException] -and $thrownError.Status -eq 'TrustFailure') -or
                $thrownError.Message -match "The remote certificate is invalid" -or  
                $thrownError.Message -match "SSL connection could not be established") {

                Write-Error "SSL Trust Error: The certificate for '$ComputerName' isn't trusted.`nUse -SkipCertificateCheck to bypass untrusted cert."
            }
            elseif ($thrownError -is [UnraidAuthException]) { throw $_  }
            else {          
                $msg = "Connection failed to $ComputerName"
                if ($thrownError -is [System.Net.WebException] -and $thrownError.Response) { 
                    $msg += " [HTTP $($thrownError.Response.StatusCode)]" 
                }
                
                throw "$msg. Details: $($thrownError.Message)"
            }
        }
    }
}