Public/Connect-NSPFortinetSupport.ps1

function Connect-NSPFortinetSupport {
    <#
    .SYNOPSIS
        Signs in to the Fortinet support portal (support.fortinet.com) for the firmware download
        commands, by picking up the API token of a browser login.
    .DESCRIPTION
        The portal sits behind FortiCloud SSO (often with MFA), so this never handles a password.
        By default it opens Edge (or Chrome) on a dedicated profile and waits for you to log in.
        It then reads the API token the portal page sends with its own requests, watching the
        page's network traffic over the browser's DevTools port on 127.0.0.1. The profile keeps
        the SSO login, so later runs usually need no typing at all.

        Portal tokens last 30 minutes. The browser window stays open so the firmware commands can
        renew the token by reloading the page, which works while the SSO session lasts (about 6
        hours). Close it with Disconnect-NSPFortinetSupport.

        Instead, -FromClipboard takes a request copied from the browser's DevTools: on the portal,
        F12 > Network, right-click a request to support.fortinet.com/app/ > Copy > Copy as cURL.
        That token can't be renewed, so it is good for 30 minutes.

        The session is kept for this PowerShell session only. Returns Username, Email, Company and
        Expires (when the current token runs out).
    .PARAMETER Browser
        Edge (default) or Chrome.
    .PARAMETER ProfilePath
        The browser profile folder this command uses (kept separate from your everyday profile).
        Default: %LOCALAPPDATA%\NSP\FortinetSupportBrowser.
    .PARAMETER Port
        The loopback port for the browser's DevTools protocol. Default 9333.
    .PARAMETER TimeoutSeconds
        How long to wait for the login. Default 300.
    .PARAMETER FromClipboard
        Read a DevTools 'Copy as cURL' (or the token itself) from the clipboard.
    .PARAMETER Token
        A DevTools 'Copy as cURL', its request headers, or the token itself.
    .EXAMPLE
        Connect-NSPFortinetSupport
    .EXAMPLE
        Connect-NSPFortinetSupport -FromClipboard
    #>

    [CmdletBinding(DefaultParameterSetName = 'Browser')]
    [OutputType([pscustomobject])]
    param(
        [Parameter(ParameterSetName = 'Browser')][ValidateSet('Edge', 'Chrome')][string]$Browser = 'Edge',
        [Parameter(ParameterSetName = 'Browser')][string]$ProfilePath = (Join-Path $env:LOCALAPPDATA 'NSP\FortinetSupportBrowser'),
        [Parameter(ParameterSetName = 'Browser')][ValidateRange(1024, 65535)][int]$Port = 9333,
        [Parameter(ParameterSetName = 'Browser')][ValidateRange(10, 3600)][int]$TimeoutSeconds = 300,
        [Parameter(ParameterSetName = 'Clipboard', Mandatory)][switch]$FromClipboard,
        [Parameter(ParameterSetName = 'Token', Mandatory)][string]$Token
    )

    if ($PSCmdlet.ParameterSetName -eq 'Browser') {
        $exe = if ($Browser -eq 'Edge') { 'msedge.exe' } else { 'chrome.exe' }
        $folder = if ($Browser -eq 'Edge') { 'Microsoft\Edge\Application' } else { 'Google\Chrome\Application' }
        $candidates = @(
            (Get-ItemProperty -LiteralPath "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\$exe" -ErrorAction SilentlyContinue).'(default)'
            foreach ($root in ${env:ProgramFiles(x86)}, $env:ProgramFiles, $env:LOCALAPPDATA) { if ($root) { Join-Path $root "$folder\$exe" } }
        )
        $browserPath = $candidates | Where-Object { $_ -and (Test-Path -LiteralPath $_) } | Select-Object -First 1
        if (-not $browserPath) { throw "$Browser was not found. Use -Browser, or -FromClipboard with a request copied from the browser's DevTools." }
        $captured = Get-NSPFortinetBrowserToken -Port $Port -BrowserPath $browserPath -ProfilePath $ProfilePath -TimeoutSeconds $TimeoutSeconds -Wait
        $session = @{ Token = $captured.Token; UserAgent = $captured.UserAgent; Expires = $captured.Expires; Port = $Port }
    } else {
        $text = if ($FromClipboard) { [string](Get-Clipboard -Raw) } else { $Token }
        $parsed = ConvertFrom-NSPFortinetTokenText -Text $text
        $expires = ConvertFrom-NSPFortinetToken -Token $parsed.Token
        if ($expires -lt (Get-Date)) { throw "That portal token expired at $expires. Copy a fresh request from the portal." }
        $session = @{ Token = $parsed.Token; UserAgent = $parsed.UserAgent; Expires = $expires; Port = $null }
    }

    $script:NSPFortinetSupportSession = $session
    try { $userProfile = Invoke-NSPFortinetSupportApi -Path 'common/api/user/profile' -Method Get }
    catch { $script:NSPFortinetSupportSession = $null; throw }
    [pscustomobject]@{
        PSTypeName = 'NSP.FortiGate.FortinetSupportSession'
        Username   = $userProfile.username
        Email      = $userProfile.email
        Company    = $userProfile.companyName
        Expires    = $session.Expires
    }
}