Public/Connect-VMetric.ps1

function Connect-VMetric {
    <#
    .SYNOPSIS
    Signs in to VirtualMetric.
 
    .DESCRIPTION
    Connect-VMetric signs you in to a VirtualMetric console so the other VirtualMetric cmdlets can use its API.
 
    Give the console's address or the API's; they may be on different origins. The module asks the API for
    its metadata (GET /api/v1/cli/metadata), or reads the console's config.js, to find the other one, and
    validates both: https (plain http only to this machine), no user name, query or fragment. The browser
    goes to the console; every request goes to the API.
 
    By default it opens your browser: you sign in to the console as usual, pick the tenant, and approve the
    request; the browser then returns to PowerShell and you can close the tab. The module listens on
    127.0.0.1 for that return and checks it with PKCE and a random state, so nothing else can finish the
    sign-in in your place. Cancel in the console stops the sign-in at once.
 
    Where no browser can be shown (Linux without a display, an SSH session) or with -UseDeviceAuthentication,
    it prints a code instead: open the address it gives on any device, enter the code, and approve.
 
    With -ApiToken it uses an API token as it is, for automation. The token's own tenant applies.
 
    The session is held in memory for this PowerShell session only. Signed-in sessions refresh themselves
    while you work and last up to 30 days; Disconnect-VMetric ends them.
 
    .PARAMETER Uri
    The console's address, such as https://console.example.com, or the API's, such as
    https://api.example.com/api/. A bare host name gets https://.
 
    .PARAMETER Tenant
    The tenant to use, by name or id. It is preselected in the console when you sign in; if you approve
    another one there, the module switches to this one afterwards (the session approved in the console is
    then replaced by the one for this tenant). If that switch fails, the approved session is kept but not
    made current, the current context stays as it was, and the error says how to use it.
 
    .PARAMETER UseDeviceAuthentication
    Sign in with a code entered on another device instead of the browser.
 
    .PARAMETER ApiToken
    An API token, as a SecureString: Get-Secret from Microsoft.PowerShell.SecretManagement, or Read-Host
    -AsSecureString.
 
    .PARAMETER SkipCertificateCheck
    Do not verify the server's TLS certificate. Only for a test server with a self-signed certificate: anyone
    between you and the server could read your tokens.
 
    .PARAMETER PassThru
    Write the new context (VirtualMetric.Context) to the pipeline.
 
    .EXAMPLE
    Connect-VMetric -Uri https://console.example.com
 
    Opens the browser to sign in.
 
    .EXAMPLE
    Connect-VMetric -Uri https://console.example.com -UseDeviceAuthentication
 
    Prints a code to enter at https://console.example.com/cli/device from any device.
 
    .EXAMPLE
    Connect-VMetric -Uri https://console.example.com -ApiToken (Get-Secret VMetricToken)
 
    Uses an API token kept in a SecretManagement vault.
 
    .EXAMPLE
    Connect-VMetric -Uri https://console.example.com -Tenant Contoso -PassThru
 
    Signs in and switches to the Contoso tenant (an MSSP's subtenant, for example), then shows the context.
 
    .OUTPUTS
    VirtualMetric.Context, with -PassThru.
 
    .LINK
    Get-VMetricContext
 
    .LINK
    Set-VMetricContext
 
    .LINK
    Disconnect-VMetric
    #>

    [CmdletBinding(DefaultParameterSetName = 'Browser')]
    [OutputType('VirtualMetric.Context')]
    param(
        [Parameter(Mandatory, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string] $Uri,

        [Parameter(ParameterSetName = 'Browser')]
        [Parameter(ParameterSetName = 'DeviceCode')]
        [ValidateNotNullOrEmpty()]
        [string] $Tenant,

        # In its own set, so it picks that set; not Mandatory, as the catalog holds a switch never is.
        [Parameter(ParameterSetName = 'DeviceCode')]
        [switch] $UseDeviceAuthentication,

        [Parameter(Mandatory, ParameterSetName = 'ApiToken')]
        [System.Security.SecureString] $ApiToken,

        [switch] $SkipCertificateCheck,

        [switch] $PassThru
    )

    # Sign-in prompts and the result line are information messages, shown unless -InformationAction says
    # otherwise.
    if (-not $PSBoundParameters.ContainsKey('InformationAction')) {
        $InformationPreference = 'Continue'
    }

    try {
        if ($SkipCertificateCheck) {
            Write-Warning 'Certificate checks are off: the server''s identity is not verified, so anyone between you and it could read your tokens. Use this only with a test server.'
        }
        # The address given may be the console's or the API's; they may be apart.
        $endpoint = Resolve-VMetricEndpoint -Uri $Uri -SkipCertificateCheck:$SkipCertificateCheck
        $address = @{ ApiUrl = $endpoint.ApiUrl; ConsoleUrl = $endpoint.ConsoleUrl }

        if ($PSCmdlet.ParameterSetName -eq 'ApiToken') {
            $session = New-VMetricSession @address -AuthMethod ApiToken -ApiToken $ApiToken -SkipCertificateCheck:$SkipCertificateCheck
            Initialize-VMetricApiTokenSession -Session $session
        }
        else {
            $answer = $null
            $method = 'DeviceCode'
            if (-not $UseDeviceAuthentication) {
                if (Test-VMetricHeadless) {
                    Write-Information 'No browser can be shown here; signing in with a device code.'
                }
                else {
                    $answer = Invoke-VMetricBrowserSignIn @address -TenantHint $Tenant -SkipCertificateCheck:$SkipCertificateCheck
                    if ($answer) {
                        $method = 'Browser'
                    }
                }
            }
            if (-not $answer) {
                $answer = Invoke-VMetricDeviceSignIn @address -TenantHint $Tenant -SkipCertificateCheck:$SkipCertificateCheck
            }
            $session = New-VMetricSession @address -AuthMethod $method -TokenResponse $answer -SkipCertificateCheck:$SkipCertificateCheck

            if ($Tenant -and -not (Test-VMetricSessionTenant -Session $session -Tenant $Tenant)) {
                $session = Switch-VMetricSignInTenant -Session $session -Tenant $Tenant
            }
        }

        # Signing in again to the same place replaces the session held for it; the replaced one is ended.
        Register-VMetricSession -Session $session

        $account = if ($session.AuthMethod -eq 'ApiToken') { 'an API token' } else { ConvertTo-VMetricSafeText -Text ([string]$session.Email) }
        $tenantText = if ($session.TenantName) { " in tenant '$(ConvertTo-VMetricSafeText -Text ([string]$session.TenantName))'" } else { '' }
        $apiText = ''
        if (([uri]$session.ApiUrl).GetLeftPart([System.UriPartial]::Authority) -ne $session.ConsoleUrl) {
            $apiText = " (API $($session.ApiUrl))"
        }
        Write-Information "Connected to $($session.ConsoleUrl)$apiText as $account$tenantText."
        if ($PassThru) {
            ConvertTo-VMetricContextObject -Session $session
        }
    }
    catch {
        if (Test-VMetricFlowControl -ErrorRecord $_) {
            throw
        }
        Write-VMetricCmdletError -Cmdlet $PSCmdlet -ErrorRecord $_ -Terminating
    }
}