OmadaWeb.PS.psm1

#######################################################################################################################################################
# WARNING: DO NOT EDIT THIS FILE AS IT IS GENERATED AND WILL BE OVERWRITTEN ON THE NEXT UPDATE! #
# #
# Generated via psake on: 2025-01-08T11:28:03.855Z #
# Version: 2025.1.8.5 #
# Copyright Fortigi (C) 2025 #
#######################################################################################################################################################



PARAM(
    [parameter(Mandatory = $false)]
    [hashtable]$Parameters
)

$ModuleName = "OmadaWeb.PS"
"Loading {0} Module" -f $ModuleName | Write-Verbose

$PowerShellType = "Core"
if ($PSVersionTable.PSVersion.Major -le 5) {
    "Selenium is restricted to version (v4.23) due compatibility issues in Windows PowerShell Desktop 5. Consider using PowerShell 7 LTS instead, you can get it here: https://aka.ms/powershell-release?tag=stable" | Write-Warning
    $PowerShellType = "Desktop"
}

$BinPath = (New-Item (Join-Path ([System.Environment]::GetEnvironmentVariable("LOCALAPPDATA")) -ChildPath "$ModuleName\Bin\$PowerShellType") -ItemType Directory -Force).FullName
$DefaultParams = @{
    WebDriverBasePath     = $BinPath
    InstalledEdgeBasePath = "C:\Program Files (x86)\Microsoft\Edge\Application"
    NewtonsoftJsonPath    = $BinPath
    SystemTextJsonPath    = $BinPath
    SystemRuntimePath     = $BinPath
    OmadaWebAuthCookie    = $null
    UpdateDependencies    = $false
    LastSessionType       = "Normal"
}

$DefaultParams.GetEnumerator() | ForEach-Object {
    New-Variable -Name $_.Key -Value $_.Value -Force
}
if ($Parameters -eq $null) {
    $Parameters = @{}
}
$Parameters.GetEnumerator() | ForEach-Object {
    "Processing parameter {0}" -f $_.Key | Write-Verbose
    if ($_.Key -notin $DefaultParams.Keys) {
        "Invalid parameter provided '{0}'" -f $_.Key | Write-Error -ErrorAction "Stop"
    }
    New-Variable -Name $_.Key -Value $_.Value -Force
}

try {
    $null = New-Item $WebDriverBasePath -ItemType Directory -Force
}
catch {}

"PsBoundParameters = {0}" -f ($PsBoundParameters | ConvertTo-Json) | Write-Verbose


$Script:EdgeDriverPath = [System.IO.Path]::Combine($WebDriverBasePath, "msedgedriver.exe")
"{0} - {1}" -f $MyInvocation.MyCommand, $Script:EdgeDriverPath | Write-Verbose


$Script:NewtonsoftJsonPath = [System.IO.Path]::Combine($($NewtonsoftJsonPath), "Newtonsoft.Json.dll")
"{0} - {1}" -f $MyInvocation.MyCommand, $($Script:NewtonsoftJsonPath) | Write-Verbose


$Script:SystemTextJsonPath = [System.IO.Path]::Combine($($SystemTextJsonPath), "System.Text.Json.dll")
"{0} - {1}" -f $MyInvocation.MyCommand, $($Script:SystemTextJsonPath) | Write-Verbose


$Script:SystemRuntimePath = [System.IO.Path]::Combine($($SystemRuntimePath), "System.Runtime.dll")
"{0} - {1}" -f $MyInvocation.MyCommand, $($Script:SystemRuntimePath) | Write-Verbose


$Script:WebDriverPath = [System.IO.Path]::Combine($WebDriverBasePath, "WebDriver.dll")
"{0} - {1}" -f $MyInvocation.MyCommand, $Script:WebDriverPath | Write-Verbose


$Script:InstalledEdgeFilePath = [System.IO.Path]::Combine($InstalledEdgeBasePath, "msedge.exe")
"{0} - {1}" -f $MyInvocation.MyCommand, $Script:InstalledEdgeFilePath | Write-Verbose
if ($PSBoundParameters["InstalledEdgeBasePath"] -and -not (Test-Path $Script:InstalledEdgeFilePath -PathType Leaf)) {
    "Cannot find path '{0}'. Please make sure that it exists!" -f $Script:InstalledEdgeFilePath | Write-Error -ErrorAction "Stop"
}


if ($null -ne $PsBoundParameters["OmadaWebAuthCookie"]) {
    "Using provided OmadaWebAuthCookie when loading module" | Write-Verbose
    New-Variable OmadaWebAuthCookie -Value $PsBoundParameters["OmadaWebAuthCookie"] -Force -Scope Global | Out-Null
}
elseif ([string]::IsNullOrEmpty($Script:OmadaWebAuthCookie)) {
    "Initialize OmadaWebAuthCookie" | Write-Verbose
    New-Variable OmadaWebAuthCookie -Value $null -Force -Scope Global | Out-Null
}

if ($UpdateDependencies) {
    "Update Dependencies" | Write-Verbose
    try {
        Get-ChildItem $WebDriverBasePath | Remove-Item -Force -ErrorAction SilentlyContinue
    }
    catch {
        "Failed to initiate dependency updates. Retry restarting this PowerShell session or manually remove the contents of folder '{0}'. Error:`r`n {1}" -f $WebDriverBasePath, $_.Exception | Write-Warning
    }
}

#region public functions
function Invoke-OmadaRestMethod {
    [Alias("Invoke-OmadaODataMethod")]
    [CmdletBinding(DefaultParameterSetName = "StandardMethod")]
    PARAM()

    DynamicParam {
        $Script:FunctionName = "Invoke-RestMethod"
        return Set-DynamicParameter -FunctionName $Script:FunctionName
    }
    process {
        try {
            "{0}" -f $MyInvocation.MyCommand | Write-Verbose
            $BoundParams = $PsCmdLet.MyInvocation.BoundParameters
            return (Invoke-OmadaRequest @BoundParams)
        }
        catch {
            Throw $_
        }
    }
}

function Invoke-OmadaWebRequest {
    [CmdletBinding(DefaultParameterSetName = "StandardMethod")]
    PARAM()

    DynamicParam {
        $Script:FunctionName = "Invoke-WebRequest"
        return Set-DynamicParameter -FunctionName $Script:FunctionName
    }
    process {
        try {
            "{0}" -f $MyInvocation.MyCommand | Write-Verbose
            $BoundParams = $PsCmdLet.MyInvocation.BoundParameters
            return (Invoke-OmadaRequest @BoundParams)
        }
        catch {
            Throw $_
        }
    }
}

#endregion

#region private functions
function Close-EdgeDriver {
    "{0}" -f $MyInvocation.MyCommand | Write-Verbose
    if ($null -ne $EdgeDriver) {
        if ($EdgeDriver.HasActiveDevToolsSession) {
            $null = $EdgeDriver.Close()
        }
        $null = $EdgeDriver.Dispose()
    }
}

function Expand-DownloadFile {
    PARAM(
        [parameter(Mandatory = $true)]
        [validateScript({ Test-Path $_ -PathType Leaf })]
        $FilePath

    )

    $FilePath = Get-Item $FilePath | Move-Item -Destination ("{0}.zip" -f $FilePath) -PassThru -Force
    $ZipOutputPath = (Join-Path (Get-Item $FilePath).PsParentPath -ChildPath $($FilePath.BaseName.Substring(0, $FilePath.BaseName.IndexOf("."))))
    $ZipOutputPath = New-Item $ZipOutputPath -ItemType Directory -Force
    Get-Item $FilePath | Expand-Archive -DestinationPath $($ZipOutputPath.FullName)

    return $($ZipOutputPath)
}

function Get-EdgeProfile {

    $UserDataDir = Join-Path $Env:LOCALAPPDATA -ChildPath "Microsoft\Edge\User Data"

    if (Test-Path $UserDataDir -PathType Container) {

        $Profiles = Get-ChildItem -Directory -Path $UserDataDir | Where-Object {
            $_.Name -match "^Default$|^Profile \d+$"
        }

        $ProfileInfo = foreach ($Profile in $Profiles) {
            $PreferencesFile = Join-Path -Path $Profile.FullName -ChildPath "Preferences"
            if (Test-Path $PreferencesFile) {

                $Preferences = Get-Content -Path $PreferencesFile -Raw | ConvertFrom-Json
                [PSCustomObject]@{
                    Folder = $Profile.Name
                    Name   = $Preferences.profile.name
                }
            }
            else {
                [PSCustomObject]@{
                    Folder = $Profile.Name
                    Name   = "Default"
                }
            }
        }

        return $ProfileInfo
    }
    else {
        "Edge user data directory not found at '{0}'. Trying to use the default profile!" -f $UserDataDir | Write-Warning
    }
}

function Get-GalleryModuleVersion {
    param (
        [string]$ModuleName
    )

    try {
        $ApiEndpoint = "https://www.powershellgallery.com/api/v2/FindPackagesById()?id='{0}'" -f $ModuleName
        $Response = Invoke-RestMethod -Uri $ApiEndpoint -Method Get -Headers @{
            "Accept" = "application/xml"
        } -ConnectionTimeoutSeconds 1

        if ($null -ne $Response) {
            $LatestVersion = $Response | Sort-Object updated -Descending | Select-Object -First 1
            return $LatestVersion.Properties.version
        }
        else {
            return $null
        }
    }
    catch {
        return $null
    }
}

function Get-GitHubRelease {
    PARAM(
        $Org = "JamesNK",
        $Repo = "Newtonsoft.Json",
        $TagFilter,
        [System.Text.RegularExpressions.Regex]$AssetFilter,
        [switch]$IncludePreRelease,
        [switch]$IncludeDraft
    )

    $ApiBaseUrl = "https://api.github.com"
    $ApiReleasePath = "/repos/{0}/{1}/releases" -f $Org, $Repo
    $Uri = $ApiBaseUrl, $ApiReleasePath -join ""
    try {
        $Arguments = @{
            Method      = "Get"
            Uri         = $Uri
            ErrorAction = "SilentlyContinue"
        }
        if ($PSVersionTable.PSVersion.Major -lt 6) {
            $Arguments.Add("UseBasicParsing", $true)
        }
        $Releases = Invoke-RestMethod @Arguments
    }
    catch {
        $WebReleasePath = "https://github.com/{0}/{1}/releases" -f $Org, $Repo
        "Could not find any release for '{0}'. Please download it manually from '{1}'" -f $Repo, $WebReleasePath | Write-Error -ErrorAction "Stop"
    }

    if ($TagFilter) {
        if ($TagFilter.contains('*')) {
            $TagFilter = " `$_.tag_name -like '{0}' " -f $TagFilter
        }
        else {
            $TagFilter = " `$_.tag_name -eq '{0}' " -f $TagFilter
        }
    }
    $PreReleaseFilter = " `$_.prerelease -eq `${0} " -f $IncludePreRelease.IsPresent
    $DraftFilter = " `$_.draft -eq `${0} " -f $IncludeDraft.IsPresent

    $FilterScriptString = (" {0} " -f ($TagFilter, $PreReleaseFilter, $DraftFilter -join " -and ")).Trim().TrimStart("-and")
    [System.Management.Automation.Scriptblock]$FilterScript = [System.Management.Automation.Scriptblock]::Create($FilterScriptString)

    $LatestRelease = $Releases | Where-Object -FilterScript $FilterScript | Sort-Object published_at | Select-Object -Last 1
    "Retrieving '{0}' version {1}" -f $Repo, ($LatestRelease.tag_name) | Write-Host

    if ($AssetFilter) {
        $Asset = $LatestRelease.assets | Where-Object { $_.name -match $AssetFilter }
    }
    else {
        $Asset = $LatestRelease.assets
    }

    if (($Asset | Measure-Object).Count -eq 0) {
        "Could not find any asset for '{0}'" -f $AssetFilter | Write-Error -ErrorAction "Stop"
    }
    elseif (($Asset | Measure-Object).Count -gt 1) {
        "Found multiple assets. Use an asset filter to narrow to the expected asset" | Write-Error -ErrorAction "Stop"
    }

    $TempFile = Invoke-DownloadFile -DownloadUrl $Asset.'browser_download_url'
    $TempPath = Expand-DownloadFile -FilePath $TempFile

    return $TempPath
}

function Get-InstalledModuleInfo {
    param (
        [string]$ModuleName
    )

    $Module = Get-Module -ListAvailable -Name $ModuleName | Sort-Object Version -Descending | Select-Object -First 1
    if ($Module) {
        $ModuleInfo = @{
            Name             = $Module.Name
            Version          = $Module.Version
            RepositorySource = $Module.RepositorySourceLocation
        }
        return $ModuleInfo
    }
    else {
        return $null
    }
}


function Get-NuGetPackage {
    [CmdletBinding(DefaultParameterSetName = 'RequiredVersion')]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidAssignmentToAutomaticVariable', 'Matches', Justification = 'It is only cleared to avoid using the same variable from a previous loop run')]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Dependency', Justification = 'It is left there to be used in a later release, it is an private function so no issue for the end user.')]
    PARAM(
        [parameter(Mandatory = $true, ParameterSetName = 'RequiredVersion')]
        [parameter(Mandatory = $true, ParameterSetName = 'VersionRange')]
        [string]$PackageName,
        [parameter(Mandatory = $false, ParameterSetName = 'RequiredVersion')]
        [parameter(Mandatory = $false, ParameterSetName = 'VersionRange')]
        [string]$TargetFramework = '.NETFramework4.6.2',
        [parameter(Mandatory = $false, ParameterSetName = 'RequiredVersion')]
        [System.Version]$RequiredVersion,
        [parameter(Mandatory = $false, ParameterSetName = 'VersionRange')]
        [System.Version]$MinimumVersion,
        [parameter(Mandatory = $false, ParameterSetName = 'VersionRange')]
        [System.Version]$MaximumVersion,
        [switch]$ExcludeDependencies
    )

    $NuGetIndexUri = "https://api.nuget.org/v3/index.json"

    $BaseReturnObjects = @()
    function GetPackage {
        [CmdletBinding(DefaultParameterSetName = 'RequiredVersion')]
        PARAM(
            [parameter(Mandatory = $true, ParameterSetName = 'RequiredVersion')]
            [parameter(Mandatory = $true, ParameterSetName = 'VersionRange')]
            [string]$PackageName,
            [parameter(Mandatory = $false, ParameterSetName = 'RequiredVersion')]
            [parameter(Mandatory = $false, ParameterSetName = 'VersionRange')]
            [string]$TargetFramework = '.NETFramework4.6.2',
            [parameter(Mandatory = $false, ParameterSetName = 'RequiredVersion')]
            [System.Version]$RequiredVersion,
            [parameter(Mandatory = $false, ParameterSetName = 'VersionRange')]
            [System.Version]$MinimumVersion,
            [parameter(Mandatory = $false, ParameterSetName = 'VersionRange')]
            [System.Version]$MaximumVersion,
            [switch]$Dependency,
            [switch]$ExcludeDependencies
        )

        $ReturnObjects = @()
        try {
            $ReturnObject = @{
                PackageName     = $PackageName
                TargetFramework = $TargetFramework
                PackageTempPath = $null
                PackageEntries  = @()
                Dependencies    = @()
            }

            "Downloading '{0}', retrieve NuGetIndex from '{1}'" -f $PackageName, $NuGetIndexUri | Write-Verbose
            $NuGetIndex = Invoke-RestMethod -Uri $NuGetIndexUri

            $PackageSearchUri = ($NuGetIndex.resources | Where-Object '@type' -EQ 'SearchQueryService' | Select-Object -First 1).'@id'

            $PackageSearchUri = $PackageSearchUri, $PackageName -join "?q="

            "PackageSearchUri: {0}" -f $PackageSearchUri | Write-Verbose
            $SearchResult = Invoke-RestMethod -Uri $PackageSearchUri
            $PackageSource = ($SearchResult.data | Where-Object title -EQ $PackageName).'@id'

            "PackageSource: {0}" -f $PackageSource | Write-Verbose
            $Package = Invoke-RestMethod -Uri $PackageSource

            $Package = $Package.items | Select-Object -Last 1

            if ($PSCmdlet.ParameterSetName -eq 'RequiredVersion' -and $null -ne $RequiredVersion -and $RequiredVersion.Major -gt -1) {
                "Required version '{0}''" -f $RequiredVersion.ToString() | Write-Verbose
                $PackageFilter = $RequiredVersion.ToString()
                $Id = $Package.items.'@id' | Where-Object { $_.Split("/")[-1] -like "$PackageFilter*" } | Select-Object -Last 1
            }
            elseif ($PSCmdlet.ParameterSetName -eq 'VersionRange' -and (($null -ne $MinimumVersion -and $MinimumVersion.Major -gt -1) -or ($null -ne $MaximumVersion -and $MaximumVersion.Major -gt -1))) {
                if ($null -eq $MinimumVersion -or $MinimumVersion.Major -lt 0) {
                    "Minimum version not valid" | Write-Verbose
                    [System.Version]$MinimumVersion = "0.0.0"
                }
                if ($null -eq $MaximumVersion -or $MinimumVersion.Major -lt 0) {
                    "Minimum version not valid" | Write-Verbose
                    [System.Version]$MaximumVersion = $MinimumVersion
                }
                "Minimum version: '{0}', maximum version: '{1}'" -f $MinimumVersion.ToString(), $MaximumVersion.ToString() | Write-Verbose

                [regex]$VersionRegex = '.*/([\d.]+)\.json$'
                $ValidPackages = @()
                $Package.items.'@id' | ForEach-Object {
                    $Version = $VersionRegex.Match($_)
                    if ($Version.Success -and [version]$Version.Groups[1].Value -ge $MinimumVersion -and [version]$Version.Groups[1].Value -le $MaximumVersion) {
                        "Valid package found!" | Write-Verbose
                        $ValidPackages += $_
                    }
                }
                $Id = $ValidPackages | Select-Object -Last 1
            }
            else {
                $Id = $Package.items.'@id' | Select-Object -Last 1
            }
            if ($null -eq $Id) {
                "Could not find any valid package for '{0}'" -f $PackageName | Write-Error -ErrorAction "Stop"
            }
            "Package Id: {0}" -f $Id | Write-Verbose
            $PackageVersion = Invoke-RestMethod -Uri $Id
            $PackageContent = Invoke-DownloadFile -DownloadUrl $PackageVersion.packageContent

            $ReturnObject.PackageTempPath = Expand-DownloadFile -FilePath $PackageContent


            "catalogEntry: {0}" -f $PackageVersion.catalogEntry | Write-Verbose
            $PackageCatalogInfo = Invoke-RestMethod -Uri $PackageVersion.catalogEntry

            $PackageCatalogInfo.packageEntries | Where-Object { $_.fullName -like "lib/*/*.dll" } | ForEach-Object {
                $ReturnObject.PackageEntries += Join-Path $($ReturnObject.PackageTempPath).FullName -ChildPath $_.fullName.Replace("/", "\")
            }
            if (!$ExcludeDependencies) {

                $Dependencies = $PackageCatalogInfo.dependencyGroups | Where-Object { $_.targetframework -like "$($ReturnObject.TargetFramework)" } | Select-Object -Last 1
                $ReturnObject.Dependencies = $Dependencies.dependencies
                :Dependency foreach ($Package in $ReturnObject.Dependencies) {
                    if ($null -eq $Package) { continue Dependency}
                    "Retrieve dependency '{0}'" -f $Package.Id | Write-Verbose
                    $Matches = $null
                    [System.Version]$MinimumVersion = "0.0.0"
                    [System.Version]$MaximumVersion = "99.99.9999"
                    if ($Package.range -match "^([\d.])+$") {
                        $Type = "Range"
                        [System.Version]$MinimumVersion = $Matches[1]
                        [System.Version]$MaximumVersion = "99.99.9999"
                    }
                    elseif ($Package.range -match "^\[([\d.]+),\W?\)" ) {
                        $Type = "Range"

                        [System.Version]$MinimumVersion = $Matches[1]
                        [System.Version]$MaximumVersion = "99.99.9999"
                    }
                    elseif ($Package.range -match "^\(([\d.]+),\W?\)" ) {
                        $Type = "Range"
                        [System.Version]$MinimumVersion = $Matches[1]
                        [System.Version]$MaximumVersion = "99.99.9999"
                        $MinimumVersion.Build++
                    }
                    elseif ($Package.range -match "^\[([\d.]+)W?\]" ) {
                        $Type = "Range"
                        [System.Version]$RequiredVersion = $Matches[1]
                    }
                    elseif ($Package.range -match "^\(,\W?([\d.]+)\]" ) {
                        $Type = "Range"
                        [System.Version]$MinimumVersion = "0.0.0"
                        [System.Version]$MaximumVersion = $Matches[1]
                    }
                    elseif ($Package.range -match "^\(,\W?([\d.]+)\)" ) {
                        $Type = "Range"
                        [System.Version]$MinimumVersion = "0.0.0"
                        [System.Version]$MaximumVersion = $Matches[1]

                        "Build", "Minor", "Major" | ForEach-Object {
                            if ($MaximumVersion.Minor -le 0) {
                                [System.Version]$MaximumVersion = "0.0.0"
                            }
                            elseif ($MaximumVersion.Minor -eq 0 -and $MaximumVersion.Build -eq 0) {
                                [System.Version]$MaximumVersion = "{0}.99.99999" - $MaximumVersion.Major--
                            }
                            elseif ($MaximumVersion.Minor -eq 0 -and $MaximumVersion.Build -gt 0) {
                                [System.Version]$MaximumVersion = "{0}.{1}.{2}" - $MaximumVersion.Major, $MaximumVersion.Minor, $MaximumVersion.Build--
                            }
                            else {
                                [System.Version]$MaximumVersion = "{0}.{1}.99999" - $MaximumVersion.Major, $MaximumVersion.Minor--
                            }
                        }
                    }
                    elseif ($Package.range -match "^\[([\d.]+),\W?([\d.]+)\]" ) {
                        $Type = "Range"
                        [System.Version]$MaximumVersion = $Matches[1]
                        [System.Version]$MinimumVersion = $Matches[2]
                    }
                    elseif ($Package.range -match "^\(([\d.]+),\W?([\d.]+)\)" ) {
                        $Type = "Range"
                        [System.Version]$MaximumVersion = $Matches[1]
                        [System.Version]$MinimumVersion = $Matches[2]
                        $MinimumVersion.Build++

                        "Build", "Minor", "Major" | ForEach-Object {
                            if ($MaximumVersion.Minor -le 0) {
                                [System.Version]$MaximumVersion = "0.0.0"
                            }
                            elseif ($MaximumVersion.Minor -eq 0 -and $MaximumVersion.Build -eq 0) {
                                [System.Version]$MaximumVersion = "{0}.99.9999" - $MaximumVersion.Major--
                            }
                            elseif ($MaximumVersion.Minor -eq 0 -and $MaximumVersion.Build -gt 0) {
                                [System.Version]$MaximumVersion = "{0}.{1}.{2}" - $MaximumVersion.Major, $MaximumVersion.Minor, $MaximumVersion.Build--
                            }
                            else {
                                [System.Version]$MaximumVersion = "{0}.{1}.9999" - $MaximumVersion.Major, $MaximumVersion.Minor--
                            }
                        }
                    }
                    elseif ($Package.range -match "^\[([\d.]+),\W?([\d.]+)\)" ) {
                        $Type = "Range"
                        [System.Version]$MaximumVersion = $Matches[1]
                        [System.Version]$MinimumVersion = $Matches[2]

                        "Build", "Minor", "Major" | ForEach-Object {
                            if ($MaximumVersion.Minor -le 0) {
                                [System.Version]$MaximumVersion = "0.0.0"
                            }
                            elseif ($MaximumVersion.Minor -eq 0 -and $MaximumVersion.Build -eq 0) {
                                [System.Version]$MaximumVersion = "{0}.99.9990" -f $MaximumVersion.Major--
                            }
                            elseif ($MaximumVersion.Minor -eq 0 -and $MaximumVersion.Build -gt 0) {
                                [System.Version]$MaximumVersion = "{0}.{1}.{2}" -f $MaximumVersion.Major, $MaximumVersion.Minor, $MaximumVersion.Build--
                            }
                            else {
                                [System.Version]$MaximumVersion = "{0}.{1}.9990" -f $MaximumVersion.Major, $MaximumVersion.Minor--
                            }
                        }
                    }
                    else {
                        "Invalid range '{0}'" -f $Package.range | Write-Error -ErrorAction "Stop"
                    }
                    switch ($Type) {
                        "Range" {
                            "MinimumVersion: '{0}', MaximumVersion: '{1}'" -f $MinimumVersion.ToString(), $MaximumVersion.ToString() | Write-Verbose
                            $ReturnObjects += GetPackage -PackageName $Package.Id -TargetFramework $($ReturnObject.TargetFramework) -MinimumVersion $MinimumVersion -MaximumVersion $MaximumVersion -Dependency
                        }
                        "Exact" {
                            "Exact version '{0}'" -f $RequiredVersion.ToString() | Write-Verbose
                            $ReturnObjects += GetPackage -PackageName $Package.Id -TargetFramework $($ReturnObject.TargetFramework) -RequiredVersion $DependencyRequiredVersion -Dependency
                        }
                        default {
                            "Invalid range '{0}'" -f $Package.range | Write-Error -ErrorAction "Stop"
                        }
                    }
                }
            }
            $ReturnObjects += $ReturnObject
        }
        catch {
            $_
        }
        return $ReturnObjects
    }

    if ($PSCmdlet.ParameterSetName -eq 'RequiredVersion') {
        $BaseReturnObjects = GetPackage -PackageName $PackageName -TargetFramework $TargetFramework -RequiredVersion $RequiredVersion -ExcludeDependencies:$ExcludeDependencies
    }
    elseif ($PSCmdlet.ParameterSetName -eq 'VersionRange') {
        $BaseReturnObjects = GetPackage -PackageName $PackageName -TargetFramework $TargetFramework -MinimumVersion $MinimumVersion -MaximumVersion $MaximumVersion -ExcludeDependencies:$ExcludeDependencies
    }
    else {
        $BaseReturnObjects = GetPackage -PackageName $PackageName -TargetFramework $TargetFramework
    }

    return $BaseReturnObjects
}

function Install-EdgeDriver {
    $EdgeDriverFileName = "msedgedriver.exe"
    if (Test-Path (Join-Path (Split-Path $Script:WebDriverPath) -ChildPath $EdgeDriverFileName) -PathType Leaf) {
        "Failed to update '{0}'. Retry restarting this PowerShell session or manually remove the contents of folder '{1}'. Reuse current version for now. Error:`r`n {2}" -f $EdgeDriverFileName, $WebDriverBasePath, $_.Exception | Write-Warning
        break
    }

    $null = New-Item (Split-Path $Script:EdgeDriverPath) -ItemType Directory -Force

    $TempFile = [System.IO.Path]::GetTempFileName()
    "Invoke-WebEdgeDriverFramework: {0}" -f $$ | Write-Verbose

    $ComputerInfo = Get-CimInstance -ClassName Win32_ComputerSystem
    switch ($ComputerInfo.SystemType) {
        "x64-based PC" { $Arch = "win64" }
        "x86-based PC" { $Arch = "win32" }
        default { $Arch = "win64" }
    }

    $EdgeWebdriverDownloadBaseUrl = "https://msedgedriver.azureedge.net/"
    $EdgeWebdriverFileName = "edgedriver_{0}.zip" -f $Arch
    $EdgeWebdriverDownloadUrl = "{0}{1}/{2}" -f $EdgeWebdriverDownloadBaseUrl, $($InstalledEdgeVersion.VersionInfo.ProductVersion), $EdgeWebdriverFileName
    "Download URL: {0}" -f $EdgeWebdriverDownloadUrl | Write-Verbose

    $TempFile = Invoke-DownloadFile -DownloadUrl $EdgeWebdriverDownloadUrl

    $TempZipPath = Expand-DownloadFile -FilePath $TempFile

    try {
        Get-Item (Join-Path $TempZipPath -ChildPath $EdgeDriverFileName ) | Move-Item -Destination (Split-Path $Script:EdgeDriverPath) -Force
    }
    catch {
        if (Test-Path (Join-Path (Split-Path $Script:WebDriverPath) -ChildPath $DllFileName) -PathType Leaf) {
            "Failed to update '{0}'. Retry restarting this PowerShell session or manually remove the contents of folder '{1}'. Reuse current version for now. Error:`r`n {2}" -f $EdgeDriverFileName, $WebDriverBasePath, $_.Exception | Write-Warning
            return $false
        }
        else {
            Throw $_
        }
    }

    Remove-Item $($TempZipPath.FullName) -Force -Confirm:$false -Recurse

    return $false
}

function Install-NewtonSoftJson {

    $DllFileName = "Newtonsoft.Json.dll"
    if (Test-Path (Join-Path (Split-Path $Script:WebDriverPath) -ChildPath $DllFileName) -PathType Leaf) {
        "Failed to update '{0}'. Retry restarting this PowerShell session or manually remove the contents of folder '{1}'. Reuse current version for now. " -f $DllFileName, $WebDriverBasePath | Write-Warning
        break
    }

    "'{0}' needs to be downloaded. Downloading from GitHub" -f $DllFileName | Write-Host
    $null = New-Item (Split-Path $Script:NewtonsoftJsonPath) -ItemType Directory -Force
    $TempZipPath = Get-GitHubRelease -Org "JamesNK" -Repo "Newtonsoft.Json" -TagFilter "13**" -AssetFilter "Json130.*.zip"

    try {
        Get-ChildItem ((Get-ChildItem (Join-Path $($TempZipPath.FullName) -ChildPath "bin") -Filter "netstandard2.0" | Select-Object -Last 1)).FullName -Filter $DllFileName | Copy-Item -Destination (Split-Path $Script:WebDriverPath) -Force
    }
    catch {
        if (Test-Path (Join-Path (Split-Path $Script:WebDriverPath) -ChildPath $DllFileName) -PathType Leaf) {
            "Failed to update '{0}'. Retry restarting this PowerShell session or manually remove the contents of folder '{1}'. Reuse current version for now. Error:`r`n {2}" -f $DllFileName, $WebDriverBasePath, $_.Exception | Write-Warning
            return $false
        }
        else {
            Throw $_
        }
    }

    "Installed '{0}' version {1}" -f $DllFileName, (Get-Item (Join-Path (Split-Path $Script:WebDriverPath) -ChildPath $DllFileName)).VersionInfo.ProductVersion | Write-Host
    Remove-Item $($TempZipPath.FullName) -Force -Confirm:$false -Recurse

    return $false
}

function Install-Selenium {
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'CheckJsonLibrary', Justification = 'The CheckJsonLibrary variable is used in a function called from here')]
    PARAM()
    $DllFileName = "WebDriver.dll"
    if (Test-Path (Join-Path (Split-Path $Script:WebDriverPath) -ChildPath $DllFileName) -PathType Leaf) {
        "Failed to update '{0}'. Retry restarting this PowerShell session or manually remove the contents of folder '{1}'. Reuse current version for now. " -f $DllFileName, $WebDriverBasePath | Write-Warning
        break
    }

    $CheckJsonLibrary = $false

    "WebDriver.dll needs to be downloaded. Downloading from GitHub" | Write-Host
    $null = New-Item (Split-Path $Script:WebDriverPath) -ItemType Directory -Force

    $Org = "SeleniumHQ"
    $Repo = "selenium"
    $AssetFilter = ".*dotnet.(?!strongnamed).*\.0\.zip"

    if ($PSVersionTable.PSVersion.Major -le 5) {
        "Using version 4.23 of Selenium because newer versions are currently not compatible with PowerShell 5.1" | Write-Warning

        $TempZipPath = Get-GitHubRelease -Org $Org -Repo $Repo -TagFilter "*4.23*" -AssetFilter $AssetFilter
    }
    else {
        $TempZipPath = Get-GitHubRelease -Org $Org -Repo $Repo -AssetFilter $AssetFilter
    }

    $Package = Get-ChildItem $($TempZipPath.FullName) -Filter "*WebDriver*.nupkg"
    $NuPkgZip = Get-Item $($Package.FullName) | Rename-Item -NewName ("{0}.zip" -f $Package.FullName) -PassThru
    $NuPkgPath = New-Item (Join-Path (Get-Item $NuPkgZip).PsParentPath -ChildPath $NuPkgZip.BaseName) -ItemType Directory -Force
    Get-Item $NuPkgZip | Expand-Archive -Destination $($NuPkgPath.FullName) -Force
    if ((((Get-ChildItem (Join-Path $($NuPkgPath.FullName) -ChildPath "lib") -Filter "net4*")) | Measure-Object).Count -gt 0) {
        "Use net4* DLL" | Write-Verbose
        try {
            Get-ChildItem ((Get-ChildItem (Join-Path $($NuPkgPath.FullName) -ChildPath "lib") -Filter "net4*" | Select-Object -Last 1)).FullName -Filter $DllFileName | Copy-Item -Destination (Split-Path $Script:WebDriverPath) -Force
        }
        catch {
            if (Test-Path (Join-Path (Split-Path $Script:WebDriverPath) -ChildPath $DllFileName) -PathType Leaf) {
                "Failed to update 'Webdriver.dll'. Retry restarting this PowerShell session or manually remove the contents of folder '{0}'. Reuse current version for now. Error:`r`n {1}" -f $WebDriverBasePath, $_.Exception | Write-Warning
            }
            else {
                Throw $_
            }
        }

    }
    else {
        "net4* DLL missing, using net2* DLL" | Write-Verbose
        try {
            Get-ChildItem ((Get-ChildItem (Join-Path $($NuPkgPath.FullName) -ChildPath "lib") -Filter "netstandard2.0" | Select-Object -Last 1)).FullName -Filter "WebDriver.dll" | Copy-Item -Destination (Split-Path $Script:WebDriverPath) -Force
        }
        catch {
            if (Test-Path (Join-Path (Split-Path $Script:WebDriverPath) -ChildPath $DllFileName) -PathType Leaf) {
                "Failed to update '{0}'. Retry restarting this PowerShell session or manually remove the contents of folder '{1}'. Reuse current version for now. Error:`r`n {2}" -f $DllFileName, $WebDriverBasePath, $_.Exception | Write-Warning
                return $false
            }
            else {
                Throw $_
            }
        }

        $CheckJsonLibrary = $true
    }

    "Installed '{0}' version {1}" -f $DllFileName, (Get-Item (Join-Path (Split-Path $Script:WebDriverPath) -ChildPath $DllFileName)).VersionInfo.ProductVersion | Write-Host
    Remove-Item $($TempZipPath.FullName) -Force -Confirm:$false -Recurse
}

function Install-SystemRunTime {
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'CheckJsonLibrary', Justification = 'The CheckJsonLibrary returned from this function')]
    PARAM()
    $DllFileName = "System.Runtime.dll"
    if (Test-Path (Join-Path (Split-Path $Script:WebDriverPath) -ChildPath $DllFileName) -PathType Leaf) {
        "Failed to update '{0}'. Retry restarting this PowerShell session or manually remove the contents of folder '{1}'. Reuse current version for now. " -f $DllFileName, $WebDriverBasePath | Write-Warning
        break
    }

    "'{0}' needs to be downloaded. Downloading from NuGet" -f $DllFileName | Write-Host


    $NuGetResults = Get-NuGetPackage -PackageName "System.Runtime" -TargetFramework ".NETFramework4*"

    $NuGetResults | ForEach-Object {
        $NuGetResult = $_
        $NuGetResult.PackageEntries | Where-Object { $_ -like "*lib\net4*" } | ForEach-Object {
            if (Test-Path $_ -PathType Leaf) {
                "Copy net4 version" | Write-Verbose
                Copy-Item -Path $_ -Destination (Split-Path $Script:WebDriverPath) -Force
            }
            else {
                "Net4 version not found, using netstandard2.0 version" | Write-Verbose
                Get-Item ($NuGetResult.PackageEntries | Where-Object { $_ -like "*lib\netstandard2.0*" } | Select-Object -Last 1) | Copy-Item -Destination (Split-Path $Script:WebDriverPath) -Force

                if ($_ -like "*$DllFileName") {
                    $CheckJsonLibrary = $true
                }
            }
        }
        Remove-Item $($_.PackageTempPath) -Force -Confirm:$false -Recurse
    }
    "Installed '{0}' version {1}" -f $DllFileName, (Get-Item (Join-Path (Split-Path $Script:WebDriverPath) -ChildPath $DllFileName)).VersionInfo.ProductVersion | Write-Host
    return $CheckJsonLibrary
}

function Install-SystemTextJson {
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'CheckJsonLibrary', Justification = 'The CheckJsonLibrary returned from this function')]
    PARAM()
    $DllFileName = "System.Text.Json.dll"
    if (Test-Path (Join-Path (Split-Path $Script:WebDriverPath) -ChildPath $DllFileName) -PathType Leaf) {
        "Failed to update '{0}'. Retry restarting this PowerShell session or manually remove the contents of folder '{1}'. Reuse current version for now. " -f $DllFileName, $WebDriverBasePath | Write-Warning
        break
    }

    $CheckJsonLibrary = $false

    "'{0}' needs to be downloaded. Downloading from NuGet" -f $DllFileName | Write-Host
    $null = New-Item (Split-Path $Script:WebDriverPath) -ItemType Directory -Force
    $NuGetResults = Get-NuGetPackage -PackageName "System.Text.Json" -TargetFramework ".NETFramework4*" -RequiredVersion "8.0.5"
    $NuGetResults | ForEach-Object {
        $NuGetResult = $_
        $NuGetResult.PackageEntries | Where-Object { $_ -like "*lib\net4*" } | ForEach-Object {
            if (Test-Path $_ -PathType Leaf) {
                "Copy net4 version" | Write-Verbose
                Copy-Item -Path $_ -Destination (Split-Path $Script:WebDriverPath) -Force
            }
            else {
                "Net4 version not found, using netstandard2.0 version" | Write-Verbose
                Get-Item ($NuGetResult.PackageEntries | Where-Object { $_ -like "*lib\netstandard2.0*" } | Select-Object -Last 1) | Copy-Item -Destination (Split-Path $Script:WebDriverPath) -Force

                if ($_ -like "*$DllFileName") {
                    $CheckJsonLibrary = $true
                }
            }
        }
        Remove-Item $($_.PackageTempPath) -Force -Confirm:$false -Recurse
    }
    "Installed '{0}' version {1}" -f $DllFileName, (Get-Item (Join-Path (Split-Path $Script:WebDriverPath) -ChildPath $DllFileName)).VersionInfo.ProductVersion | Write-Host
    return $CheckJsonLibrary
}

function Invoke-BasicAuthentication {
    "{0} - Set Basic authentication" -f $MyInvocation.MyCommand, $_ | Write-Verbose

    if ($BoundParams.keys -notcontains "Credential") {
        $BoundParams.Add("Credential", (Get-Credential -Message "Please enter your authentication credentials"))
    }
    $CredentialPair = "{0}:{1}" -f $BoundParams.Credential.UserName, $BoundParams.Credential.GetNetworkCredential().Password
    $EncodedCredential = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($CredentialPair))
    $BoundParams.Headers.Add("Authorization" , ("Basic {0}" -f $EncodedCredential))

}

function Invoke-BrowserAuthentication {

    "{0} - Set Browser authentication" -f $MyInvocation.MyCommand | Write-Verbose

    if ($BoundParams.ForceAuthentication) {
        $Script:OmadaWebAuthCookie = $null
    }
    $Script:Credential = $null
    if ($BoundParams.keys -contains "Credential") {
        $Script:Credential = $BoundParams.Credential
    }
    switch ($Script:LastSessionType) {
        { $_ -eq "Normal" -and $($BoundParams.InPrivate).IsPresent -eq $true } {
            "{0} - Reset OmadaWebAuthCookie because session has changed to InPrivate" -f $MyInvocation.MyCommand | Write-Verbose
            $Script:OmadaWebAuthCookie = $null
            $Script:LastSessionType = "InPrivate"
        }
        { $_ -eq "InPrivate" -and $($BoundParams.InPrivate).IsPresent -eq $false } {
            "{0} - Reset OmadaWebAuthCookie because session has changed from InPrivate to not InPrivate" -f $MyInvocation.MyCommand | Write-Verbose
            $Script:OmadaWebAuthCookie = $null
            $Script:LastSessionType = "Normal"
        }
        default {}
    }

    if ($null -ne $($Script:OmadaWebAuthCookie) -and ($Script:OmadaWebBaseUrl -like "*$($Script:OmadaWebAuthCookie.domain)*" )) {
        "{0} - OmadaWebAuthCookie exists for this domain: {1}" -f $MyInvocation.MyCommand, $Script:OmadaWebBaseUrl | Write-Verbose
        if ("Cookie" -notin $BoundParams.Headers.Keys) {
            $BoundParams.Headers.Add("Cookie", ($($Script:OmadaWebAuthCookie).Name, $($Script:OmadaWebAuthCookie).Value -join "="))
        }
        else {
            $BoundParams.Headers.Cookie = ($($Script:OmadaWebAuthCookie).Name, $($Script:OmadaWebAuthCookie).Value -join "=")
        }
        $Session.Cookies.Add((New-Object System.Net.Cookie("oisauthtoken", $($Script:OmadaWebAuthCookie.Value), "/", $($Script:OmadaWebAuthCookie.domain))))
    }
    else {
        "{0} - OmadaWebAuthCookie not exists or is for different domain. Need to authenticate!" -f $MyInvocation.MyCommand | Write-Verbose
        $EdgeDriverData = Invoke-DataFromWebDriver -EdgeProfile $BoundParams.EdgeProfile -InPrivate:$($BoundParams.InPrivate).IsPresent
        $Script:OmadaWebAuthCookie = $EdgeDriverData[0]

        $BoundParams.UserAgent = $EdgeDriverData[1]

        $Session.UserAgent = $EdgeDriverData[1]

        if ("Cookie" -notin $BoundParams.Headers.Keys) {
            $BoundParams.Headers.Add("Cookie", ($($Script:OmadaWebAuthCookie).Name, $($Script:OmadaWebAuthCookie).Value -join "="))
        }
        else {
            $BoundParams.Headers.Cookie = ($($Script:OmadaWebAuthCookie).Name, $($Script:OmadaWebAuthCookie).Value -join "=")
        }

        $Session.Cookies.Add((New-Object System.Net.Cookie("oisauthtoken", $($Script:OmadaWebAuthCookie.Value), "/", $($Script:OmadaWebAuthCookie.domain))))
    }
    if (![string]::IsNullOrEmpty($($BoundParams.OmadaWebAuthCookieExportLocation))) {
        "Exporting cookie to {0}" -f $($BoundParams.OmadaWebAuthCookieExportLocation) | Write-Verbose
        $CookieObject = [PSCustomObject]@{
            OmadaWebAuthCookie = $Script:OmadaWebAuthCookie
        }
        $CookieObject | Export-Clixml (Join-Path $($BoundParams.OmadaWebAuthCookieExportLocation) -ChildPath ("{0}.cookie" -f $Script:OmadaWebAuthCookie.domain)) -Force
    }
}

function Invoke-DataFromWebDriver {
    PARAM(
        [string]$EdgeProfile,
        [switch]$InPrivate
    )

    $AuthCookie = $null

    "Opening Edge to retrieve authentication cookie" | Write-Host
    $EdgeDriver = Start-EdgeDriver -InPrivate:$InPrivate.IsPresent -EdgeProfile $EdgeProfile

    Start-EdgeDriverLogin

    $AgentString = $EdgeDriver.ExecuteScript("return navigator.userAgent")

    Start-Sleep -Seconds 1
    $LoginMessageShown = $false
    $MfaRequestDisplayed = $false
    $PhoneLinkActive = $false
    if ((Get-Process | Where-Object { $_.ProcessName -eq "PhoneExperienceHost" } | Measure-Object).Count -gt 0) {
        $PhoneLinkActive = $true
    }
    do {

        if (-not $LoginMessageShown) {
            Write-Host "`r`nBrowser opened, please login! Waiting for login." -NoNewline -ForegroundColor Yellow
            if ($Script:Credential -and ![string]::IsNullOrWhiteSpace($Script:Credential.UserName)) {
                " Execute automated login steps for user: {0}" -f $Script:Credential.UserName | Write-Host -ForegroundColor Yellow -NoNewline
            }
            $LoginMessageShown = $true
        }

        Write-Host "." -NoNewline -ForegroundColor Yellow
        Start-Sleep -Milliseconds 500

        if ($Script:Credential -and ![string]::IsNullOrWhiteSpace($Script:Credential.UserName)) {

            if ($EdgeDriver.url -like "https://login.microsoftonline.com/*") {
                try {
                    $UserNameElementId = "i0116"
                    $PasswordElementId = "i0118"
                    $SubmitButton = "idSIButton9"
                    $CantAccessAccountId = "cantAccessAccount"
                    $MfaElementId = "idRichContext_DisplaySign"
                    $MfaRetryId1 = "idA_SAASTO_Resend"
                    $MfaRetryId2 = "idA_SAASDS_Resend"
                    $ButtonBackId = "idBtn_Back"
                    $ButtonSubmitId = "idSIButton9"

                    $Elements = $EdgeDriver.FindElements([OpenQA.Selenium.By]::XPath("//*[@id]"))
                    $IdAttributes = $Elements.GetAttribute("id")

                    if (     $IdAttributes -contains $UserNameElementId `
                            -and $IdAttributes -contains $PasswordElementId `
                            -and $IdAttributes -notcontains $ButtonBackId `
                            -and $IdAttributes -contains $ButtonSubmitId `
                            -and $IdAttributes -contains $CantAccessAccountId `
                            -and $EdgeDriver.FindElement([OpenQA.Selenium.By]::Id($ButtonSubmitId)).ComputedAccessibleLabel -eq "Next" `
                    ) {
                        Start-Sleep -Milliseconds 500
                        "Enter username" | Write-Verbose
                        $EdgeDriver.FindElements([OpenQA.Selenium.By]::Id($UserNameElementId))[0].SendKeys($Script:Credential.UserName)
                        $EdgeDriver.FindElement([OpenQA.Selenium.By]::Id($SubmitButton)).Click()
                    }

                    if (     $IdAttributes -notcontains $UserNameElementId `
                            -and $IdAttributes -contains $PasswordElementId `
                            -and $IdAttributes -contains $ButtonSubmitId `
                            -and $IdAttributes -notcontains $CantAccessAccountId `
                            -and $EdgeDriver.FindElement([OpenQA.Selenium.By]::Id($ButtonSubmitId)).ComputedAccessibleLabel -eq "Sign in"
                    ) {
                        "Enter password" | Write-Verbose
                        $EdgeDriver.FindElements([OpenQA.Selenium.By]::Id($PasswordElementId))[0].SendKeys($Script:Credential.GetNetworkCredential().Password)
                        $EdgeDriver.FindElement([OpenQA.Selenium.By]::Id($SubmitButton)).Click()
                    }

                    if (     $IdAttributes -notcontains $UserNameElementId `
                            -and $IdAttributes -notcontains $PasswordElementId `
                            -and $IdAttributes -contains $SelectUserElementId
                    ) {
                        "Select logged-in account" | Write-Verbose
                        if ($AccountElement.GetAttribute("data-test-id") -eq $Script:Credential.UserName) {
                            $AccountElement.Click()
                        }
                    }

                    if (     $IdAttributes -notcontains $UserNameElementId `
                            -and $IdAttributes -notcontains $PasswordElementId `
                            -and $IdAttributes -notcontains $SelectUserElementId `
                            -and $IdAttributes -contains $ButtonBackId `
                            -and $IdAttributes -contains $ButtonSubmitId `
                            -and $EdgeDriver.FindElement([OpenQA.Selenium.By]::Id($ButtonBackId)).ComputedAccessibleLabel -eq "No" `
                            -and $EdgeDriver.FindElement([OpenQA.Selenium.By]::Id($ButtonSubmitId)).ComputedAccessibleLabel -eq "Yes" `
                    ) {
                        "Decline Stay signed in? " | Write-Verbose
                        $EdgeDriver.FindElement([OpenQA.Selenium.By]::Id($ButtonBackId)).Click()
                    }

                    if (    $IdAttributes -notcontains $UserNameElementId `
                            -and $IdAttributes -notcontains $PasswordElementId `
                            -and $IdAttributes -notcontains $ButtonBackId `
                            -and ($EdgeDriver.FindElements([OpenQA.Selenium.By]::XPath("//*[@data-test-id]")) | Measure-Object).Count -gt 0) {

                        "Select logged-in user " | Write-Verbose
                        $EdgeDriver.FindElements([OpenQA.Selenium.By]::XPath("//*[@data-test-id]")) | ForEach-Object {
                            if ($_.GetAttribute("data-test-id") -eq $Script:Credential.UserName) {
                                $_.Click()
                            }
                        }
                    }

                    if (    $MfaRequestDisplayed -ne $true `
                            -and $IdAttributes -notcontains $UserNameElementId `
                            -and $IdAttributes -notcontains $PasswordElementId `
                            -and $IdAttributes -contains $MFAElementId `
                            -and $IdAttributes -notcontains $MfaRetryId `
                            -and ($EdgeDriver.FindElements([OpenQA.Selenium.By]::XPath("//*[@data-test-id]")) | Measure-Object).Count -eq 0
                    ) {
                        $Message = "`nWaiting for your approve this sign-in request."
                        if ($null -ne $EdgeDriver.FindElements([OpenQA.Selenium.By]::Id($MfaElementId)).Text) {
                            if ($PhoneLinkActive) {
                                $Message = "{0}. {1} (This value is now in your clipboard so you can paste it into your Authenticator app using PhoneLink)." -f $Message.TrimEnd("."), $EdgeDriver.FindElements([OpenQA.Selenium.By]::Id($MfaElementId)).Text
                                $EdgeDriver.FindElements([OpenQA.Selenium.By]::Id($MfaElementId)).Text | Clip
                            }
                            else {
                                $Message = "{0}. {1}" -f $Message.TrimEnd("."), $EdgeDriver.FindElements([OpenQA.Selenium.By]::Id($MfaElementId)).Text
                            }
                        }
                        $Message | Write-Host -ForegroundColor Yellow
                        $MfaRequestDisplayed = $true
                    }

                    if (    $MfaRequestDisplayed `
                            -and $IdAttributes -notcontains $UserNameElementId `
                            -and $IdAttributes -notcontains $PasswordElementId `
                            -and $IdAttributes -notcontains $MFAElementId `
                            -and (
                            $IdAttributes -contains $MfaRetryId1 `
                                -or $IdAttributes -contains $MfaRetryId2 )`
                            -and ($EdgeDriver.FindElements([OpenQA.Selenium.By]::XPath("//*[@data-test-id]")) | Measure-Object).Count -eq 0
                    ) {
                        "`nMFA failed! Please retry!" | Write-Warning
                        $MfaRequestDisplayed = $false
                        $LoginMessageShown = $false
                    }
                }
                catch {}
            }
        }

        if ($EdgeDriver.url -notlike "*$($Script:OmadaWebBaseUrl)/home*") {


            if ($null -eq $EdgeDriver -or $null -eq $EdgeDriver.WindowHandles) {
                if ($Script:LoginRetryCount -ge 3) {
                    Close-EdgeDriver
                    "`nLogin retry count exceeded! Please check your credentials as no cookie could be retrieved!" | Write-Error -ErrorAction "Stop"
                }
                else {
                    "`n{0} - Login retry count: {1}" -f $MyInvocation.MyCommand, $Script:LoginRetryCount | Write-Verbose
                }

                "" | Write-Host
                "Edge window seems to be closed before authentication was completed. Re-open Edge driver!" | Write-Host -ForegroundColor Yellow
                $LoginMessageShown = $false
                Close-EdgeDriver
                $EdgeDriver = Start-EdgeDriver -InPrivate:$InPrivate.IsPresent -EdgeProfile $EdgeProfile
                Start-EdgeDriverLogin
                $Script:LoginRetryCount++
            }
        }
        else {
            $AuthCookie = $EdgeDriver.Manage().Cookies.AllCookies | Where-Object { $_.Name -eq 'oisauthtoken' }
        }
    }
    until($null -ne $AuthCookie)
    "{0} (Line {1}): {2}" -f $MyInvocation.MyCommand, $MyInvocation.ScriptLineNumber, $$ | Write-Verbose

    Close-EdgeDriver
    if ($null -ne $AuthCookie) {
        $Script:LoginRetryCount = 0
        return $AuthCookie, $AgentString
    }
    else {
        "Could not authenticate to '{0}" -f $Script:OmadaWebBaseUrl | Write-Error -ErrorAction "Stop"
    }
}

function Invoke-DownloadFile {
    PARAM(
        [parameter(Mandatory = $true)]
        [string]$DownloadUrl,
        [parameter(Mandatory = $false)]
        [validateScript({ Test-Path (Split-Path $_) -PathType 'Container' })]
        $OutputFile
    )

    try {
        if ([String]::IsNullOrWhiteSpace($OutputFile)) {
            $OutputFile = [System.IO.Path]::GetTempFileName()
        }
        else {
            $OutputFile = $OutputFile
        }
        $OutputFile | Write-Verbose
        $DownloadUrl | Write-Verbose
        $WebClient = New-Object System.Net.WebClient
        $WebClient.DownloadFile($DownloadUrl, $OutputFile)

        return $OutputFile
    }
    catch {
        Throw $_.Exception.Message
    }
}

function Invoke-EntraIdOAuth {
    "{0} - Request bearer token" -f $MyInvocation.MyCommand, $_ | Write-Verbose
    if ($null -eq $BoundParams.Credential) {
        "{0} - Credentials not provided! This mandatory for OAuth authentication!" -f $MyInvocation.MyCommand | Write-Error -ErrorAction "Stop"
    }
    if ($null -eq $BoundParams.EntraIdTenantId) {
        "{0} - EntraIdTenantId not provided! This mandatory for OAuth authentication!" -f $MyInvocation.MyCommand | Write-Error -ErrorAction "Stop"
    }

    $RequestBody = @{
        scope         = ("{0}/.default" -f $Script:OmadaWebBaseUrl)
        client_id     = $($BoundParams.Credential.UserName)
        grant_type    = 'client_credentials'
        client_secret = $($BoundParams.Credential.GetNetworkCredential().Password)
    }

    $Arguments = @{
        Method      = "Post"
        Uri         = ("https://login.microsoftonline.com/{0}/oauth2/v2.0/token" -f $BoundParams.EntraIdTenantId)
        Body        = $RequestBody
        ContentType = 'application/x-www-form-urlencoded'
        ErrorAction = "SilentlyContinue"
    }

    if ($PSVersionTable.PSVersion.Major -lt 6) {
        $Arguments.Add("UseBasicParsing", $true)
    }

    $BearerToken = Invoke-RestMethod @Arguments
    $BearerToken = $BearerToken
    $BoundParams.Headers.Add("Authorization" , "Bearer {0}" -f $BearerToken.access_token)
}

function Invoke-IntegratedAuthentication {
    "{0} - Set integrated authentication" -f $MyInvocation.MyCommand, $_ | Write-Verbose
    $BoundParams.Add("UseDefaultCredentials", $true)
}

function Invoke-OmadaRequest {
    [CmdletBinding(DefaultParameterSetName = "StandardMethod")]
    PARAM()

    DynamicParam {
        return Set-DynamicParameter -FunctionName $Script:FunctionName
    }
    process {
        try {

            "{0} called for {1} by {2}" -f $MyInvocation.MyCommand, $Script:FunctionName, (Get-PSCallStack)[1].Command | Write-Verbose

            $BoundParams = $PsCmdLet.MyInvocation.BoundParameters

            if ("UserAgent" -notin $BoundParams.Keys) {
                $BoundParams.Add("UserAgent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0")
            }

            if ("Headers" -notin $BoundParams.Keys) {
                $BoundParams.Add("Headers", @{})
            }

            $Uri = [System.Uri]::new($BoundParams.Uri)
            if ($null -ne $Uri) {
                $Script:OmadaWebBaseUrl = "{0}://{1}" -f $Uri.Scheme, $Uri.Host
                if (!$Uri.IsDefaultPort) {
                    $Script:OmadaWebBaseUrl = "{0}://{1}:{2}" -f $Uri.Scheme, $Uri.Host, $Uri.Port
                }
                "{0} - BaseUrl: {1}" -f $MyInvocation.MyCommand, $Script:OmadaWebBaseUrl | Write-Verbose
            }
            else {
                "Could not determine the base URL from '{0}', is the URL correct?" -f $BoundParams.Uri | Write-Error -ErrorAction "Stop"
            }

            if ("UserAgent" -notin $BoundParams.Keys) {
                $BoundParams.Add("UserAgent", $DefaultUserAgent)
            }

            $Session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
            $Session.UserAgent = $BoundParams.UserAgent

            if ("AuthenticationType" -notin $BoundParams.Keys) {
                $BoundParams.Add("AuthenticationType", "Browser")
            }

            if ($BoundParams.Keys -contains "OmadaWebAuthCookieFile") {
                $Script:OmadaWebAuthCookie = (Import-Clixml $BoundParams.OmadaWebAuthCookieFile).OmadaWebAuthCookie
            }
            if ("OmadaWebAuthCookieFile" -in $BoundParams.Keys) {
                $BoundParams.Remove("OmadaWebAuthCookieFile") | Out-Null
            }

            "{0} - Authentication type: {1}" -f $MyInvocation.MyCommand, $($BoundParams.AuthenticationType) | Write-Verbose

            switch ($BoundParams.AuthenticationType) {
                "Windows" {
                    "{0} - {1} Authentication" -f $MyInvocation.MyCommand, $_ | Write-Verbose
                    Invoke-WindowsAuthentication
                }
                "Browser" {
                    "{0} - {1} Authentication" -f $MyInvocation.MyCommand, $_ | Write-Verbose
                    Invoke-BrowserAuthentication
                }
                "OAuth" {
                    "{0} - {1} Authentication" -f $MyInvocation.MyCommand, $_ | Write-Verbose
                    Invoke-EntraIdOAuth
                }
                "Integrated" {
                    "{0} - {1} Authentication " -f $MyInvocation.MyCommand, $_ | Write-Verbose
                    Invoke-IntegratedAuthentication
                }
                "Basic" {
                    "{0} - {1} Authentication" -f $MyInvocation.MyCommand, $_ | Write-Verbose
                    Invoke-BasicAuthentication
                }
                default {
                    "{0} - {1} not supported!" -f $MyInvocation.MyCommand, $_ | Write-Error -ErrorAction "Stop"
                }
            }

            if ($BoundParams.Method -in @('PUT', 'POST', 'PATCH')) {
                "{0} - {1} - Add Body" -f $MyInvocation.MyCommand, $BoundParams.Method | Write-Verbose
                Set-Body
            }

            $BoundParams.Add("WebSession", $Session)

            if ($PSVersionTable.PSVersion.Major -lt 6) {
                $Arguments.Add("UseBasicParsing", $true)
            }

            "{0} - {1}" -f $MyInvocation.MyCommand, ($BoundParams | ConvertTo-Json) | Write-Verbose
            try {
                switch ($Script:FunctionName) {
                    "Invoke-RestMethod" {

                        if ("Accept" -notin $BoundParams.Headers.Keys) {
                            $BoundParams.Headers.Add("Accept", "application/json")
                        }

                        if ("ContentType" -in $BoundParams.Keys) {
                            $BoundParams.Headers.Add("Content-Type", $BoundParams.ContentType)
                            $BoundParams.Remove("ContentType") | Out-Null
                        }
                        elseif ("Content-Type" -notin $BoundParams.Headers.Keys) {
                            $BoundParams.Headers.Add("Content-Type", "application/json")
                        }
                        $Parameters = Set-RequestParameter
                        return (Invoke-RestMethod @Parameters)
                    }
                    "Invoke-WebRequest" {
                        $Parameters = Set-RequestParameter
                        return (Invoke-WebRequest @Parameters)
                    }
                    default {
                    }
                }
            }

            catch {
                if (($BoundParams.AuthenticationType) -eq "Browser" -and $_.Exception.Response.StatusCode -eq 401) {

                    "Re-authentication needed!" | Write-Host
                    "{0} - Re-Authentication - Error message:" -f $MyInvocation.MyCommand, ($_ | ConvertTo-Json) | Write-Verbose
                    $EdgeDriverData = Invoke-DataFromWebDriver -EdgeProfile $BoundParams.EdgeProfile -InPrivate:$($BoundParams.InPrivate).IsPresent
                    $Script:OmadaWebAuthCookie = $EdgeDriverData[0]
                    $BoundParams.UserAgent = $EdgeDriverData[1]

                    try {
                        $Parameters = Set-RequestParameter
                        return (Invoke-OmadaRequest @Parameters)
                    }
                    catch {
                        Throw $_
                    }
                }
                else {
                    Throw $_
                }
            }
        }
        catch {
            Throw $_
        }
    }
}

function Invoke-WebEdgeDriverFramework {
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'CheckJsonLibrary', Justification = 'The CheckJsonLibrary variable is used in a function called from here')]
    PARAM()
    $InstallOrUpdateEdgeDriver = $false
    $InstalledEdgeVersion = Get-Item $Script:InstalledEdgeFilePath
    if ($Null -ne $InstalledEdgeVersion) {
        $InstalledEdgeVersionMajor = "{0:d5}" -f [int]($($InstalledEdgeVersion.VersionInfo.ProductVersion).Substring(0, $($InstalledEdgeVersion.VersionInfo.ProductVersion).IndexOf(".")))
    }
    else {
        "Cannot find Edge at '{0}'. Is it installed?" -f $Script:InstalledEdgeFilePath | Write-Error -ErrorAction "Stop"
    }
    if (!(Test-Path $Script:EdgeDriverPath -PathType Leaf)) {
        "msedgedriver.exe needs to be downloaded. Downloading from Microsoft" | Write-Host
        $InstallOrUpdateEdgeDriver = $true
    }
    elseif (Test-Path $Script:EdgeDriverPath -PathType Leaf) {
        $MsEdgeDriverFileInfo = Get-Item $Script:EdgeDriverPath
        $MsEdgeDriverFileVersionMajor = "{0:d5}" -f [int]($($MsEdgeDriverFileInfo.VersionInfo.ProductVersion).Substring(0, $($MsEdgeDriverFileInfo.VersionInfo.ProductVersion).IndexOf(".")))
        if ($InstalledEdgeVersionMajor -ne $MsEdgeDriverFileVersionMajor) {
            "msedgedriver.exe must be updated, downloading correct version from Microsoft" | Write-Host
            $InstallOrUpdateEdgeDriver = $true
        }
    }

    $CheckJsonLibrary = $false
    $JsonLibraryType = $null
    if ($InstallOrUpdateEdgeDriver) {
        $InstallOrUpdateEdgeDriver = Install-EdgeDriver
    }

    if (!(Test-Path $Script:WebDriverPath -PathType Leaf)) {
        Install-Selenium
    }

    $WebDriverDll = Get-Item $Script:WebDriverPath
    switch ($WebDriverDll.VersionInfo.ProductMajorPart) {
        { $_ -eq 4 } {
            switch ($WebDriverDll.VersionInfo.ProductMinorPart) {
                { $_ -ge 12 -and $_ -lt 24 } {
                    $JsonLibraryType = "Newtonsoft.Json"
                    if (!(Test-Path $Script:NewtonsoftJsonPath -PathType Leaf)) {
                        $CheckJsonLibrary = Install-NewtonSoftJson
                    }
                }
                { $PSVersionTable.PSVersion.Major -le 5 -and $_ -ge 24 } {
                    $JsonLibraryType = "System.Text.Json"

                    if (!(Test-Path $Script:SystemTextJsonPath -PathType Leaf)) {
                        $CheckJsonLibrary = Install-SystemTextJson
                    }
                    if (!(Test-Path $Script:SystemRuntimePath -PathType Leaf)) {
                        $CheckJsonLibrary = Install-SystemRunTime
                    }
                }
                default {}
            }
        }
        { $_ -gt 4 } {
            if ($PSVersionTable.PSVersion.Major -le 5 -and $WebDriverDll.VersionInfo.ProductMinorPart -ge 24) {
                $JsonLibraryType = "System.Text.Json"
                if (!(Test-Path $Script:SystemTextJsonPath -PathType Leaf)) {
                    $CheckJsonLibrary = Install-SystemTextJson
                }
                if (!(Test-Path $Script:SystemRuntimePath -PathType Leaf)) {
                    $CheckJsonLibrary = Install-SystemRunTime
                }
            }
        }
        default {
            "Version {0} is not supported" -f $_.FileVersion.ProductVersion | Write-Error -ErrorAction Stop
            break
        }
    }

    $Missing = $false
    $MissingString = @()
    if (!(Test-Path $Script:EdgeDriverPath -PathType Leaf)) {
        $Missing = $true
        $MissingString += "'msedgedriver.exe'"

    }
    if (!(Test-Path $Script:WebDriverPath -PathType Leaf)) {
        $Missing = $true
        $MissingString += "'WebDriver.dll'"
    }
    if ($Missing) {
        "{0} cannot be found in folder '{1}'" -f ($MissingString -Join " and "), (Split-Path $Script:EdgeDriverPath) | Write-Error -ErrorAction "Stop"
    }
    else{
        "Using '{0}' version {1}" -f (Get-Item $Script:EdgeDriverPath).Name,(Get-Item $Script:EdgeDriverPath).VersionInfo.ProductVersion | Write-Host
    }

    return $JsonLibraryType
}

function Invoke-WindowsAuthentication {
    "{0} - Set Windows authentication" -f $MyInvocation.MyCommand, $_ | Write-Verbose
    if ($BoundParams.keys -notcontains "Credential") {
        $BoundParams.Add("Credential", (Get-Credential -Message "Please enter your authentication credentials"))
    }
    $CredentialPair = "{0}:{1}" -f $BoundParams.Credential.UserName, $BoundParams.Credential.GetNetworkCredential().Password
    $EncodedCredential = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($CredentialPair))
    $BoundParams.Headers.Add("Authorization" , ("Basic {0}" -f $EncodedCredential))
}

Function New-DynamicParam {

    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Position', Justification = 'It is left there to be used in a later release, it is an private function so no issue for the end user.')]
    param(
        [string]$Name,
        [System.Object]$Type = [string], # Accept Object to handle deserialized types
        [string[]]$Alias = @(),
        [string[]]$ValidateSet,
        $ValidateScript,
        [switch]$Mandatory,
        [string[]]$ParameterSetName = "__AllParameterSets",
        [int]$Position,
        [switch]$ValueFromPipelineByPropertyName,
        [string]$HelpMessage,
        [ValidateScript({
                if (-not ($_.GetType().FullName -eq 'System.Management.Automation.RuntimeDefinedParameterDictionary' -or -not $_)) {
                    Throw "DPDictionary must be a RuntimeDefinedParameterDictionary or null."
                }
                $True
            })]$DPDictionary = $false,
        $Value
    )

    if (-not ($Type -is [System.Type])) {
        if ($Type -is [string]) {
            $ResolvedType = [Type]::GetType($Type) # Try to resolve the type from its name
            if (-not $ResolvedType) {
                $ResolvedType = [AppDomain]::CurrentDomain.GetAssemblies() |
                    ForEach-Object { $_.GetType($Type, $false, $false) } |
                    Where-Object { $_ -ne $null }
            }
            if ($ResolvedType) {
                $Type = $ResolvedType
            }
            else {
                $Type = [string]
            }
        }
        else {
            Throw "The provided Type is not a valid System.Type object."
        }
    }

    $AttributeCollection = New-Object 'Collections.ObjectModel.Collection[System.Attribute]'
    foreach ($SetName in $ParameterSetName) {
        $ParamAttr = New-Object System.Management.Automation.ParameterAttribute
        $ParamAttr.ParameterSetName = $SetName
        if ($Mandatory) { $ParamAttr.Mandatory = $True }
        if ($ValueFromPipelineByPropertyName) { $ParamAttr.ValueFromPipelineByPropertyName = $True }
        if ($HelpMessage) { $ParamAttr.HelpMessage = $HelpMessage }

        $AttributeCollection.Add($ParamAttr)
    }

    if ($ValidateSet) {
        $ParamOptions = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $ValidateSet
        $AttributeCollection.Add($ParamOptions)
    }
    if ($ValidateScript) {
        $ParamOptions = New-Object System.Management.Automation.ValidateScriptAttribute -ArgumentList $ValidateScript
        $AttributeCollection.Add($ParamOptions)
    }

    if ($Alias.Count -gt 0) {
        $ParamAlias = New-Object System.Management.Automation.AliasAttribute -ArgumentList $Alias
        $AttributeCollection.Add($ParamAlias)
    }

    $Parameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList @($Name, $Type, $AttributeCollection)
    if ($Value) { $Parameter.Value = $Value }

    if ($DPDictionary) {
        $DPDictionary.Add($Name, $Parameter)
    }
    else {
        $Dictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
        $Dictionary.Add($Name, $Parameter)
        $Dictionary
    }
}

function Set-Body {

    "{0} - {1} - Add Body" -f $MyInvocation.MyCommand, $BoundParams.Method | Write-Verbose
    if ($null -eq $BoundParams.Body) {
        "{0} - Provided -Body is empty this is mandatory for a {1} command" -f $MyInvocation.MyCommand , $BoundParams.Method | Write-Error -ErrorAction "Stop"
    }
    $BoundParams.Headers.Add("Content-Type", "application/json")
    if ($BoundParams.Body -is [hashtable]) {
        $BoundParams.Body = $BoundParams.Body | ConvertTo-Json
    }
    if ($BoundParams.Body -isnot [hashtable] -and $BoundParams.Body -isnot [string]) {
        "{0} - Content parameter should be a hashtable to string!" -f $MyInvocation.MyCommand | Write-Error -ErrorAction "Stop"
    }
    $BoundParams.Body = $BoundParams.Body
    "{0} - {1}" -f $MyInvocation.MyCommand, ($BoundParams | ConvertTo-Json) | Write-Verbose

}

function Set-DynamicParameter {
    [CmdletBinding()]
    PARAM(
        $FunctionName
    )
    $Dictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
    $FunctionObject = Get-Command -Name $FunctionName

    $ExcludedParameters = @("Debug",
        "ErrorAction",
        "ErrorVariable",
        "InformationAction",
        "InformationVariable",
        "OutVariable",
        "OutBuffer",
        "PipelineVariable",
        "ProgressAction",
        "Verbose",
        "WarningAction",
        "WarningVariable",
        "Session",
        "WebSession",
        "Authentication",
        "SessionVariable",
        "UseDefaultCredentials",
        "UseBasicParsing"
    )

    $ParameterObjects = @()
    foreach ($ParameterSet in $FunctionObject.ParameterSets) {
        foreach ($Parameter in $ParameterSet.Parameters) {
            if ($Parameter.Name -notin $ExcludedParameters) {
                if ($Parameter.Name -notin $ParameterObjects.Name) {
                    $ParameterSetName = @($($ParameterSet.Name))
                    $ParameterObjects += @{
                        Name                            = $Parameter.Name
                        Type                            = $Parameter.ParameterType
                        Alias                           = $Parameter.Aliases
                        ValidateSet                     = $Parameter.ValidateSet
                        Mandatory                       = $Parameter.IsMandatory
                        ParameterSetName                = $ParameterSetName
                        Position                        = $Parameter.Position
                        ValueFromPipelineByPropertyName = $Parameter.ValueFromPipelineByPropertyName
                        HelpMessage                     = $Parameter.HelpMessage
                        DPDictionary                    = $Dictionary
                    }
                }
                else {
($ParameterObjects | Where-Object {$_.Name -eq $Parameter.Name}).ParameterSetName += $($ParameterSet.Name)
                }
            }
        }
    }

    [string[]]$ParameterObjectSetNames = $null
    if (($ParameterObjects.ParameterSetName | Select-Object -Unique | Measure-Object).Count -eq 1 -and [string]::IsNullOrWhiteSpace($ParameterObjects.ParameterSetName)) {
        $ParameterObjectSetNames += "__AllParameterSets"
        $ParameterObjects | ForEach-Object { $_.ParameterSetName = "__AllParameterSets" }
    }
    else {
        $ParameterObjectSetNames += $ParameterObjects.ParameterSetName | Select-Object -Unique
    }

    foreach ($ParameterObject in $ParameterObjects) {
        New-DynamicParam @ParameterObject
    }

    New-DynamicParam -Name "AuthenticationType" -Type "string" -ValidateSet ("OAuth", "Integrated", "Basic", "Browser", "Windows") -ParameterSetName $ParameterObjectSetNames -DPDictionary $Dictionary -Value "Browser" -HelpMessage "The type of authentication to use for the request. Default is `Browser`. The acceptable values for this parameter are:
- Basic
- Browser
- Integrated
- OAuth
- Windows"

    New-DynamicParam -Name "EntraIdTenantId" -Type "string" -ParameterSetName $ParameterObjectSetNames -DPDictionary $Dictionary -HelpMessage "The tenant id or name for -AuthenticationType OAuth." -Alias "AzureAdTenantId"
    New-DynamicParam -Name "OmadaWebAuthCookieFile" -Type "string" -ParameterSetName $ParameterObjectSetNames -DPDictionary $Dictionary -HelpMessage "Use a previously exported Omada authentication cookie using -OmadaWebAuthCookieExportLocation. This must be to the cookie file." -ValidateScript { Test-Path -Path $_ }
    New-DynamicParam -Name "OmadaWebAuthCookieExportLocation" -Type "string" -ParameterSetName $ParameterObjectSetNames -DPDictionary $Dictionary -HelpMessage "Export the Omada authentication cookie to as a CliXml file."
    New-DynamicParam -Name "ForceAuthentication" -Type "string" -ParameterSetName $ParameterObjectSetNames -DPDictionary $Dictionary -HelpMessage "Force authentication to Omada even when the cookie is still valid."
    New-DynamicParam -Name "EdgeProfile" -Type "string" -ValidateSet $Script:EdgeProfiles.Name -ParameterSetName $ParameterObjectSetNames -DPDictionary $Dictionary -HelpMessage "Use the specified Edge profile for the authentication request."
    New-DynamicParam -Name "InPrivate" -Type "System.Management.Automation.SwitchParameter" -ParameterSetName $ParameterObjectSetNames -DPDictionary $Dictionary -HelpMessage "Use InPrivate mode for the authentication request."

    return $Dictionary
}

function Set-RequestParameter {

    $ExcludedParameters = @("OmadaWebAuthCookieExportLocation", "InPrivate", "ForceAuthentication", "AuthenticationType", "EntraIdTenantId", "RequestType", "EdgeProfile")

    $Parameters = @{}
    $BoundParams.Keys | ForEach-Object {
        if ($_ -notin $ExcludedParameters) {
            $Parameters.Add($_, $BoundParams[$_])
        }
    }

    "Parameters" | Write-Verbose
    $Parameters | ConvertTo-Json | Write-Verbose
    return $Parameters
}

function Start-EdgeDriver {
    PARAM(
        [string]$EdgeProfile,
        [switch]$InPrivate
    )
    $JsonLibraryType = Invoke-WebEdgeDriverFramework
    try {
        Add-Type -Path $($Script:WebDriverPath)
    }
    catch {
        [void] [System.Reflection.Assembly]::LoadFrom($Script:WebDriverPath)
    }
    try {
        switch ($JsonLibraryType) {
            "Newtonsoft.Json" {
                Add-Type -Path $($Script:NewtonsoftJsonPath)
            }
            "System.Text.Json" {
                if ($PSVersionTable.PSVersion.Major -le 5) {



                    Add-Type -Path $($Script:SystemRuntimePath)
                    Add-Type -Path $($Script:SystemTextJsonPath)
                }
            }
        }
    }
    catch {
        [void] [System.Reflection.Assembly]::LoadWithPartialName("OpenQA.Selenium.Edge")
    }
    [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
    [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

    $WindowWidth = 564
    $WindowHeight = 973

    $ScreenSize = [System.Windows.Forms.Screen]::PrimaryScreen
    $CenterScreenWidth = [System.Math]::Ceiling((($ScreenSize.WorkingArea.Width - $WindowWidth) / 2))
    $CenterScreenHeight = [System.Math]::Ceiling((($ScreenSize.WorkingArea.Height - $WindowHeight) / 2))

    $EdgeOptions = New-Object  OpenQA.Selenium.Edge.EdgeOptions
    $EdgeOptions.AddArgument("--disable-logging");
    $EdgeOptions.AddArgument("--no-first-run");
    $EdgeOptions.AddArgument("--window-size=$WindowWidth,$WindowHeight" )
    $EdgeOptions.AddArgument("--window-position=$CenterScreenWidth,$CenterScreenHeight" )
    $EdgeOptions.AddArgument("--content-shell-hide-toolbar")
    $EdgeOptions.AddArgument("--top-controls-hide-threshold")
    $EdgeOptions.AddArgument("--app-auto-launched")
    $EdgeOptions.AddArgument("--disable-blink-features=AutomationControlled")
    $EdgeOptions.AddArgument("--disable-infobars")
    $EdgeOptions.AddArgument("--log-level=3")
    $EdgeOptions.AddArgument("--lang=en")
    $EdgeOptions.AddExcludedArgument("enable-automation")
    $EdgeOptions.AddAdditionalOption("useAutomationExtension", $false)

    if ($InPrivate) {
        if (![string]::IsNullOrWhiteSpace($EdgeProfile)) {
            "InPrivate mode is enabled. The -EdgeProfile parameter will be ignored." | Write-Warning
        }
        $EdgeOptions.AddArgument("--inprivate")
    }
    elseif (![string]::IsNullOrWhiteSpace($EdgeProfile) -and $EdgeProfile -ne "Default") {
        "Loading Edge profile: '{0}'" -f $EdgeProfile | Write-Verbose
        $ProfileFolderName = ($Script:EdgeProfiles | Where-Object { $_.Name -eq $EdgeProfile }).Folder
        $ProfileArgument = '--profile-directory="{0}"' -f $ProfileFolderName
        "Profile argument: '{0}'" -f $ProfileArgument | Write-Verbose
        $EdgeOptions.AddArgument($ProfileArgument)
        $UserProfileDir = New-Item (Join-Path $env:LOCALAPPDATA -ChildPath "OmadaWeb.PS\Profiles\$ProfileFolderName") -ItemType Directory -Force
        "Using profile user-data-dir: '{0}'" -f $UserProfileDir.FullName | Write-Verbose
        $UserDataDirArgument = 'user-data-dir="{0}"' -f $UserProfileDir.FullName
        "User data argument: '{0}'" -f $UserDataDirArgument | Write-Verbose
        $EdgeOptions.AddArgument($UserDataDirArgument)
    }

    $EdgeDriverService = [OpenQA.Selenium.Edge.EdgeDriverService]::CreateDefaultService($($Script:EdgeDriverPath))
    $EdgeDriverService.HideCommandPromptWindow = $true
    $EdgeDriverService.SuppressInitialDiagnosticInformation = $true;
    try {
        $EdgeDriver = New-Object OpenQA.Selenium.Edge.EdgeDriver($EdgeDriverService, $EdgeOptions)
    }
    catch {
        if (![string]::IsNullOrWhiteSpace($EdgeProfile) -and $EdgeProfile -ne "Default" -and $_.Exception.Message -match "DevToolsActivePort") {
            "It seems that Edge profile '{0}' is currently running. It is not possible use this profile when it is active. To use this profile, please close that browser session. You can also choose to omit -EdgeProfile parameter." -f $EdgeProfile | Write-Error -ErrorAction "Stop" -ErrorId "EdgeProfileActive"
        }
        else {
            Throw $_
        }
        Close-EdgeDriver
        break
    }
    return $EdgeDriver
}

function Start-EdgeDriverLogin {

    if ($null -eq $EdgeDriver) {
        "Browser authentication failed to start!" | Write-Error -ErrorAction "Stop"
    }


    try {
        $EdgeDriver.Navigate().GoToUrl($Script:OmadaWebBaseUrl) | Out-Null
        $EdgeDriver.SwitchTo().Window($EdgeDriver.CurrentWindowHandle) | Out-Null
    }
    catch {
        if ($_.Exception.Message -like "*failed to check if window was closed: disconnected: not connected to DevTools*") {
            "Edge window seems to be closed before authentication was completed. Re-open Edge driver!" | Write-Host -ForegroundColor Yellow
        }
        else {
            $_
        }
    }
}

#endregion



"Validate version" | Write-Verbose
try {
    $InstalledModule = Get-InstalledModuleInfo -ModuleName $ModuleName

    if (-not $InstalledModule.RepositorySource -or $InstalledModule.RepositorySource -notlike "*powershellgallery.com*") {
        "Module '{0}' was not sourced from the PowerShell Gallery. Skipping version check." -f $ModuleName | Write-Verbose
    }
    else {
        $GalleryVersion = Get-GalleryModuleVersion -ModuleName $ModuleName

        if (-not $GalleryVersion) {
        }
        else {
            if ([version]$InstalledModule.Version -lt [version]$GalleryVersion) {
                "The installed version {0} of '{1}' is outdated. Latest version: {2}. Execute Update-Module {1} to update to the latest version!" -f ($($InstalledModule.Version)), $ModuleName, $GalleryVersion | Write-Warning
            }
            elseif ([version]$InstalledModule.Version -eq [version]$GalleryVersion) {
                "The installed version {0} of '{1}' is up-to-date." -f ($($InstalledModule.Version)) , $ModuleName | Write-Verbose
            }
            else {
                "The installed version {0} of '{1}' is newer than the gallery version {2}." -f ($($InstalledModule.Version)), $ModuleName, $GalleryVersion | Write-Warning
            }
        }
    }

}
catch {}

$Script:EdgeProfiles = Get-EdgeProfile
$Script:LoginRetryCount = 0
$Script:LoginRetryCount = 0
Export-ModuleMember -Function @("Invoke-OmadaRestMethod", "Invoke-OmadaWebRequest") -Alias *