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.
 
    Or name a VirtualMetric environment with -Environment (eu-west, us-east, aus-east, preprod, dev), which
    stands for its API's address. With neither -Uri nor -Environment, the environment
    $env:VMETRIC_ENVIRONMENT names is used.
 
    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.
 
    With -Persist the sign-in is also kept for new PowerShell windows, in your default
    Microsoft.PowerShell.SecretManagement vault: the first VirtualMetric cmdlet a new window runs signs in with
    it, without a browser, and each window gets a session of its own, so none signs another out. What is kept
    is a saved sign-in the server issued for this one (or the API token itself), never this session's tokens.
    It acts as you in the tenant you signed in to, ends with this sign-in (30 days at most), is listed with your
    PowerShell sessions in the console, and Disconnect-VMetric signs it out. It replaces a sign-in saved before.
 
    .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 Environment
    A VirtualMetric environment instead of -Uri: eu-west, us-east, aus-east, preprod or dev. Without -Uri and
    -Environment, the environment variable VMETRIC_ENVIRONMENT names it.
 
    .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 Persist
    Keep the sign-in for new PowerShell windows, in the default SecretManagement vault (see the description).
    Needs Microsoft.PowerShell.SecretManagement and a registered vault; without one, nothing is signed in and
    the error says how to set one up. Save-VMetricContext does the same for a sign-in already made.
 
    .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 -Environment eu-west
 
    Signs in to the eu-west environment. After $env:VMETRIC_ENVIRONMENT = 'eu-west', Connect-VMetric alone
    does the same.
 
    .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.
 
    .EXAMPLE
    Connect-VMetric -Uri https://console.example.com -Persist
 
    Signs in in the browser and keeps the sign-in for new PowerShell windows, which then sign in on their own.
 
    .OUTPUTS
    VirtualMetric.Context, with -PassThru.
 
    .LINK
    Get-VMetricContext
 
    .LINK
    Set-VMetricContext
 
    .LINK
    Save-VMetricContext
 
    .LINK
    Disconnect-VMetric
    #>

    [CmdletBinding(DefaultParameterSetName = 'Browser')]
    [OutputType('VirtualMetric.Context')]
    param(
        # Not Mandatory: -Environment or $env:VMETRIC_ENVIRONMENT may name the address instead.
        [Parameter(Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string] $Uri,

        # The names of Get-VMetricEnvironmentApi, in its order.
        [ValidateSet('eu-west', 'us-east', 'aus-east', 'preprod', 'dev')]
        [string] $Environment,

        [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] $Persist,

        [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 ($Persist) {
            # Before the browser opens: a sign-in that cannot be kept is not the one asked for.
            $null = Get-VMetricSaveVault
        }
        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. An environment is its API's.
        $given = Resolve-VMetricConnectAddress -Uri $Uri -Environment $Environment
        $endpoint = Resolve-VMetricEndpoint -Uri $given -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
        # A window that signed in itself never loads the saved sign-in over its own.
        $script:VMetricSavedContextState = 'Done'

        $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."
    }
    catch {
        if (Test-VMetricFlowControl -ErrorRecord $_) {
            throw
        }
        Write-VMetricCmdletError -Cmdlet $PSCmdlet -ErrorRecord $_ -Terminating
    }

    # The sign-in stands whatever happens to keeping it: a failure here is an error, not a sign-out.
    if ($Persist) {
        try {
            $vault = Save-VMetricSavedContext -Session $session
            Write-Information "Kept for new PowerShell windows in vault '$(ConvertTo-VMetricSafeText -Text $vault)'; Disconnect-VMetric signs it out."
        }
        catch {
            if (Test-VMetricFlowControl -ErrorRecord $_) {
                throw
            }
            Write-VMetricCmdletError -Cmdlet $PSCmdlet -ErrorRecord $_
        }
    }
    if ($PassThru) {
        ConvertTo-VMetricContextObject -Session $session
    }
}