Functions/GenXdev.Webbrowser/Select-WebbrowserTab.ps1

################################################################################
<#
.SYNOPSIS
Selects a webbrowser tab for use with automation cmdlets.
 
.DESCRIPTION
Selects and connects to a browser tab for use with automation cmdlets like
Invoke-WebbrowserEvaluation, Close-WebbrowserTab and others. Without parameters
it shows a list of available tabs.
 
.PARAMETER Id
Tab identifier from the list shown when no id is provided.
 
.PARAMETER Name
Selects first tab containing this name in its URL.
 
.PARAMETER Edge
Force selection in Microsoft Edge browser.
 
.PARAMETER Chrome
Force selection in Google Chrome browser.
 
.PARAMETER ByReference
Select tab using reference from Get-ChromiumSessionReference.
 
.PARAMETER Force
Forces browser restart if needed.
 
.EXAMPLE
Select-WebbrowserTab -Id 3 -Chrome
 
.EXAMPLE
st -ch 14
#>

function Select-WebbrowserTab {

    [CmdletBinding(DefaultParameterSetName = "ById")]
    [Alias("st", "Select-BrowserTab")]

    param(
        ########################################################################
        [Parameter(
            Mandatory = $false,
            Position = 0,
            ParameterSetName = "ById",
            HelpMessage = "Tab identifier from the shown list"
        )]
        [ValidateRange(0, [int]::MaxValue)]
        [int] $Id = -1,

        ########################################################################
        [Parameter(
            Mandatory = $true,
            Position = 0,
            ParameterSetName = "ByName",
            HelpMessage = "Selects first tab containing this name in URL"
        )]
        [ValidateNotNullOrEmpty()]
        [string] $Name,

        ########################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Force selection in Microsoft Edge"
        )]
        [Alias("e")]
        [switch] $Edge,

        ########################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Force selection in Google Chrome"
        )]
        [Alias("ch")]
        [switch] $Chrome,

        ########################################################################
        [Parameter(
            ParameterSetName = "ByReference",
            Mandatory = $true,
            HelpMessage = "Select tab using reference from Get-ChromiumSessionReference"
        )]
        [Alias("r")]
        [ValidateNotNull()]
        [PSCustomObject] $ByReference,

        ########################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Forces browser restart if needed"
        )]
        [switch] $Force
    )

    begin {

        # determine debug port based on parameters
        $debugPort = if ($null -ne $ByReference) {
            $ByReference.webSocketDebuggerUrl -replace "ws://localhost:(\d+)/.*", '$1'
        }
        elseif ($Edge) {
            Get-EdgeRemoteDebuggingPort
        }
        elseif ($Chrome) {
            Get-ChromeRemoteDebuggingPort
        }
        else {
            Get-ChromiumRemoteDebuggingPort
        }

        # ensure global data hashtable exists
        if ($Global:Data -isnot [HashTable]) {
            $Global:Data = @{}
        }

        Write-Verbose "Using debug port $debugPort"
    }

    process {

        # helper function to display tab list
        function Show-TabList {
            $index = 0
            $Global:chromeSessions | ForEach-Object {
                if (![String]::IsNullOrWhiteSpace($PSItem.url)) {
                    $bullet = $PSItem.id -eq `
                        $Global:chromeSession.id ? "*" : " "

                    $url = $PSItem.url
                    $title = $PSItem.title

                    [PSCustomObject]@{
                        id    = $index
                        A     = $bullet
                        url   = $url
                        title = $title
                    }
                    $index++
                }
            }
        }

        # create or update chrome automation object if needed
        if ($Global:chrome -isnot [Hashtable] -or
            $Global:chrome.Port -ne $debugPort -or
            $null -eq $Global:chrome.Browser -or
            (-not $Global:chrome.Browser.IsConnected)) {

            Write-Verbose "Creating new chromium automation object"
            $Global:chrome = @{

                Debugurl = "http://localhost:$debugPort"
                Port     = $debugPort
                Browser  = Connect-PlaywrightViaDebuggingPort -WsEndpoint "http://localhost:$debugPort" -Verbose
            }

            $Global:CurrentChromiumDebugPort = $debugPort
        }

        # handle no id or name specified
        if (($null -eq $ByReference -and $Id -lt 0) -or
            ![string]::IsNullOrWhiteSpace($Name)) {

            Write-Verbose "Getting available sessions"

            try {
                $sessions = @(
                    (Invoke-WebRequest -Uri "http://localhost:$debugPort/json").Content |
                    ConvertFrom-Json |
                    Where-Object -Property "type" -EQ "page" |
                    Where-Object -Property "url" -Match "^https?://"
                )
            }
            catch {
                if ($Force -and ($null -eq $ByReference)) {

                    $null = Get-Process msedge, chrome | Stop-Process -Force
                    $null = Close-Webbrowser -Chrome:$Chrome -Edge:$Edge -Force -Chromium

                    if ([string]::IsNullOrWhiteSpace($Name)) {
                        $null = Open-Webbrowser -Chrome:$Chrome -Edge:$Edge -Force -Chromium
                        return Select-WebbrowserTab @PSBoundParameters
                    }
                    else {
                        $null = Open-Webbrowser -Chrome:$Chrome -Edge:$Edge -Force `
                            -Url $Name -Chromium
                        return Select-WebbrowserTab @PSBoundParameters
                    }
                }
                else {
                    if ($Global:Host.Name.Contains("Visual Studio")) {
                        return "Press F5 to start debugging first.."
                    }
                    else {
                        return "Use Open-Webbrowser (wb) to start browser with debugging"
                    }
                }

                $Global:chromeSessions = @()
                $Global:chromeController = $null
                $Global:chrome = $null
                $Global:chromeSession = $null
                return
            }

            $Global:chromeSessions = $sessions
            Write-Verbose "Found $($sessions.Count) sessions"

            # find first matching session
            $sessionId = 0
            while ($sessionId -lt ($sessions.Count - 1) -and (
                    (![string]::IsNullOrWhiteSpace($Name) -and
                    ($sessions[$sessionId].url -notlike "$Name")) -or
                    (($null -ne $ByReference) -and
                    ($sessions[$sessionId].id -ne $ByReference.id))
                )) {

                Write-Verbose "Skipping $($sessions[$sessionId].url)"
                $sessionId++
            }

            $sessionId = [Math]::Min($sessionId, $sessions.Count - 1)

            $origId = $Global:chromeSession ? $Global:chromeSession.id : $null;
            $origData = $Global:chromeSession ? $Global:chromeSession.data : $null;

            $Global:chromeSession = $sessions[$sessionId]

            $newId = $Global:chromeSession ? $Global:chromeSession.id : $null;
            $newData = $Global:chromeSession ? $Global:chromeSession.data : $null;

            if ($origId -ne $newId) {

                Write-Verbose "Selected session: $($sessions[$sessionId].url)"
            }
            else {

                Add-Member -InputObject $Global:chromeSession -MemberType NoteProperty -Name "data" -Value $origData -Force
                Write-Verbose "Selected session: $($sessions[$sessionId].url) (unchanged)"
            }

            if (($null -ne $Global:chrome) -and
                    ($null -ne $Global:chrome.Browser) -and
                    ($null -ne $Global:chrome.Browser.Contexts) -and
                    ($null -ne $Global:chrome.Browser.Contexts[0])) {

                $Global:chromeController = $Global:chrome.Browser.Contexts[0].Pages | ForEach-Object {

                    $session = $Global:chrome.Browser.Contexts[0].NewCDPSessionAsync($PSItem).Result;
                    $info = $session.sendAsync("Target.getTargetInfo").Result | ConvertFrom-Json
                    if ($info.targetInfo.targetId -eq $Global:chromeSession.id) {

                        $PSItem
                    }
                } | Select-Object -First 1;
                Write-Verbose "Selected session: $($sessions[$sessionId].url)"
            }
            else {
                throw "No browser automation object available"
            }
        }
        else {
            if ($null -eq $ByReference) {

                # id specified

                $sessions = $Global:chromeSessions

                if ($Id -ge $sessions.Count) {
                    $sessions = @((Invoke-WebRequest -Uri "http://localhost:$debugPort/json").Content | ConvertFrom-Json | Where-Object -Property "type" -EQ "page" | Where-Object -Property "url" -Match "^https?://")
                    $Global:chromeSessions = $sessions
                    Write-Verbose "Refreshed sessions, found $($sessions.Count)"

                    Show-TabList
                    throw "Session expired, select new session with Select-WebbrowserTab -> st"
                }

                $Global:chromeSession = $sessions[$Id]
                $Global:chromeController = $Global:chrome.Browser.Contexts[0].Pages | ForEach-Object {
                    $session = $Global:chrome.Browser.Contexts[0].NewCDPSessionAsync($PSItem).Result;
                    $info = $session.sendAsync("Target.getTargetInfo").Result | ConvertFrom-Json
                    if ($info.targetInfo.targetId -eq $Global:chromeSession.id) {

                        $PSItem
                    }
                } | Select-Object -First 1;

                try {
                    $sessions = @((Invoke-WebRequest -Uri "http://localhost:$debugPort/json").Content | ConvertFrom-Json | Where-Object -Property "type" -EQ "page" | Where-Object -Property "url" -Match "^https?://")
                    $Global:chromeSessions = $sessions
                }
                catch {
                    throw "Session expired, select new session with Select-WebbrowserTab -> st"
                }

                Write-Verbose "Updated session list"
            }
            else {

                # reference specified
                $Global:chromeSession = $ByReference
            }

            $Global:chromeController = $Global:chrome.Browser.Contexts[0].Pages | ForEach-Object {
                $session = $Global:chrome.Browser.Contexts[0].NewCDPSessionAsync($PSItem).Result;
                $info = $session.sendAsync("Target.getTargetInfo").Result | ConvertFrom-Json
                if ($info.targetInfo.targetId -eq $Global:chromeSession.id) {

                    $PSItem
                }
            } | Select-Object -First 1;

            # validate session still exists
            $found = $null -ne $Global:chromeController

            if (!$found) {

                if ($null -eq $ByReference) {

                    Show-TabList
                }
                throw "Session expired, select new session with st"
            }
        }

        if ($null -eq $ByReference) {

            Show-TabList
        }
    }

    end {
    }
}
################################################################################