222 lines
14 KiB
PowerShell
222 lines
14 KiB
PowerShell
#region Connect-OmadaController
|
|
Function Connect-OmadaController
|
|
{
|
|
<#
|
|
.SYNOPSIS
|
|
Establishes a connection context for an Omada controller, preferring the controller interface and degrading to the Open API.
|
|
|
|
.DESCRIPTION
|
|
Two transports exist and they are not equivalent.
|
|
|
|
The controller interface, reached under /{omadacId}/api/v2, is what the Omada web interface itself consumes. It exposes the
|
|
complete data set including switch uplinks and downlinks, per port LLDP neighbour detail, VLAN definitions with their gateway
|
|
subnets, and the real SSID configuration. It is authenticated with a controller login and is not a published contract, so it
|
|
can change between controller releases.
|
|
|
|
The Open API, reached under /openapi/v1, is a published and versioned contract authenticated with a client identifier and
|
|
secret. It is stable but narrower. It has no uplink information at all and does not expose SSID configuration, which forces
|
|
SSIDs to be inferred from active client sessions.
|
|
|
|
This function therefore prefers the controller interface and falls back to the Open API, so that the richer topology is used
|
|
when it is available and collection still succeeds when it is not. The returned context records which mode was selected and
|
|
carries whichever credentials were successfully established, so a caller can degrade a single request rather than the whole run.
|
|
|
|
.PARAMETER BaseURL
|
|
The base URL of the Omada controller.
|
|
|
|
.PARAMETER Credential
|
|
The controller login used for the preferred transport. When omitted, the Open API is used directly.
|
|
|
|
.PARAMETER ClientID
|
|
The Open API client identifier used for the fallback transport.
|
|
|
|
.PARAMETER ClientSecret
|
|
The Open API client secret used for the fallback transport.
|
|
|
|
.PARAMETER SkipCertificateCheck
|
|
Ignores server certificate validation errors. Usually required because controllers present a self signed certificate.
|
|
|
|
.EXAMPLE
|
|
$OmadaContext = Connect-OmadaController -BaseURL $BaseURL -Credential $Credential -ClientID $ClientID -ClientSecret $ClientSecret -SkipCertificateCheck
|
|
|
|
.NOTES
|
|
Author: Grace Solutions
|
|
Version: 2026.08.02.0000
|
|
#>
|
|
|
|
[CmdletBinding()]
|
|
Param
|
|
(
|
|
[Parameter(Mandatory=$True)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[System.URI]$BaseURL,
|
|
|
|
[Parameter(Mandatory=$False)]
|
|
[AllowNull()]
|
|
[System.Management.Automation.PSCredential]$Credential,
|
|
|
|
[Parameter(Mandatory=$False)]
|
|
[AllowEmptyString()]
|
|
[String]$ClientID = '',
|
|
|
|
[Parameter(Mandatory=$False)]
|
|
[AllowEmptyString()]
|
|
[String]$ClientSecret = '',
|
|
|
|
[Parameter(Mandatory=$False)]
|
|
[Switch]$SkipCertificateCheck
|
|
)
|
|
|
|
Try
|
|
{
|
|
$BaseAddress = "$("$($BaseURL.AbsoluteUri)".TrimEnd('/'))"
|
|
|
|
#region The controller identifier is unauthenticated and is required by both transports
|
|
$ControllerInformationResponse = Invoke-RestAPIRequest -URI ([System.URI]"$($BaseAddress)/api/info") -Method 'GET' -SkipCertificateCheck:($SkipCertificateCheck.IsPresent)
|
|
|
|
Switch ($ControllerInformationResponse.IsSuccess)
|
|
{
|
|
{($_ -eq $False)}
|
|
{
|
|
Throw "The Omada controller information could not be retrieved from `"$($BaseAddress)`". [Status Code: $($ControllerInformationResponse.StatusCode)] [$($ControllerInformationResponse.ErrorMessage)]"
|
|
}
|
|
}
|
|
|
|
$OmadacID = "$($ControllerInformationResponse.Content.result.omadacId)"
|
|
|
|
Switch ([String]::IsNullOrWhiteSpace($OmadacID))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
Throw "The Omada controller at `"$($BaseAddress)`" did not return a controller identifier."
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
$ContextProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$ContextProperties.BaseAddress = "$($BaseAddress)"
|
|
$ContextProperties.OmadacID = "$($OmadacID)"
|
|
$ContextProperties.ControllerVersion = "$($ControllerInformationResponse.Content.result.controllerVer)"
|
|
$ContextProperties.SkipCertificateCheck = $SkipCertificateCheck.IsPresent
|
|
$ContextProperties.Mode = 'None'
|
|
$ContextProperties.ControllerHeader = $Null
|
|
$ContextProperties.OpenAPIHeader = $Null
|
|
|
|
#region Preferred transport, which is the controller interface
|
|
Switch ($Null -ine $Credential)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$LoginBody = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$LoginBody.username = "$($Credential.UserName)"
|
|
$LoginBody.password = "$($Credential.GetNetworkCredential().Password)"
|
|
|
|
$LoginResponse = Invoke-RestAPIRequest -URI ([System.URI]"$($BaseAddress)/$($OmadacID)/api/v2/login") -Method 'POST' -Body ($LoginBody) -SkipCertificateCheck:($SkipCertificateCheck.IsPresent) -AcceptableStatusCodeList @('*')
|
|
|
|
Switch (("$($LoginResponse.Content.errorCode)" -eq '0') -and ([String]::IsNullOrWhiteSpace("$($LoginResponse.Content.result.token)") -eq $False))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
#region The shared client does not maintain a cookie container, so the session cookie is replayed by hand
|
|
$SessionCookieList = New-Object -TypeName 'System.Collections.Generic.List[String]'
|
|
|
|
ForEach ($SetCookieValue In "$($LoginResponse.ResponseHeader['Set-Cookie'])".Split(';,', [System.StringSplitOptions]::RemoveEmptyEntries))
|
|
{
|
|
Switch ("$($SetCookieValue)".Trim() -imatch '^(TPOMADA_SESSIONID|TPEAP_SESSIONID|JSESSIONID)=')
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$SessionCookieList.Add("$($SetCookieValue)".Trim())
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
$ControllerHeader = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$ControllerHeader.'Csrf-Token' = "$($LoginResponse.Content.result.token)"
|
|
$ControllerHeader.Accept = 'application/json'
|
|
|
|
Switch ($SessionCookieList.Count -gt 0)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$ControllerHeader.Cookie = "$($SessionCookieList -Join '; ')"
|
|
}
|
|
}
|
|
|
|
$ContextProperties.ControllerHeader = $ControllerHeader
|
|
$ContextProperties.Mode = 'Controller'
|
|
|
|
Write-Verbose -Message "[$([System.DateTime]::UtcNow.ToString('yyyy/MM/dd HH:mm:ss.FFF'))] - [INFO] - Signed in to the Omada controller interface as `"$($Credential.UserName)`". [Controller Version: $($ContextProperties.ControllerVersion)]"
|
|
}
|
|
|
|
{($_ -eq $False)}
|
|
{
|
|
Write-Warning -Message "[$([System.DateTime]::UtcNow.ToString('yyyy/MM/dd HH:mm:ss.FFF'))] - [WARNING] - The Omada controller sign in failed, so collection will fall back to the Open API. [Error Code: $($LoginResponse.Content.errorCode)] [$($LoginResponse.Content.msg)]"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Fallback transport, which is the Open API
|
|
Switch (([String]::IsNullOrWhiteSpace($ClientID) -eq $False) -and ([String]::IsNullOrWhiteSpace($ClientSecret) -eq $False))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$TokenRequestBody = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$TokenRequestBody.omadacId = "$($OmadacID)"
|
|
$TokenRequestBody.client_id = "$($ClientID)"
|
|
$TokenRequestBody.client_secret = "$($ClientSecret)"
|
|
|
|
$TokenResponse = Invoke-RestAPIRequest -URI ([System.URI]"$($BaseAddress)/openapi/authorize/token?grant_type=client_credentials") -Method 'POST' -Body ($TokenRequestBody) -SkipCertificateCheck:($SkipCertificateCheck.IsPresent) -AcceptableStatusCodeList @('*')
|
|
|
|
Switch (("$($TokenResponse.Content.errorCode)" -eq '0') -and ([String]::IsNullOrWhiteSpace("$($TokenResponse.Content.result.accessToken)") -eq $False))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$OpenAPIHeader = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$OpenAPIHeader.Authorization = "AccessToken=$($TokenResponse.Content.result.accessToken)"
|
|
$OpenAPIHeader.Accept = 'application/json'
|
|
|
|
$ContextProperties.OpenAPIHeader = $OpenAPIHeader
|
|
|
|
Switch ($ContextProperties.Mode -ieq 'None')
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$ContextProperties.Mode = 'OpenAPI'
|
|
}
|
|
}
|
|
|
|
Write-Verbose -Message "[$([System.DateTime]::UtcNow.ToString('yyyy/MM/dd HH:mm:ss.FFF'))] - [INFO] - Acquired an Omada Open API access token, which remains available as a fallback."
|
|
}
|
|
|
|
{($_ -eq $False)}
|
|
{
|
|
Write-Warning -Message "[$([System.DateTime]::UtcNow.ToString('yyyy/MM/dd HH:mm:ss.FFF'))] - [WARNING] - An Omada Open API access token could not be acquired. [Error Code: $($TokenResponse.Content.errorCode)] [$($TokenResponse.Content.msg)]"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
Switch ($ContextProperties.Mode -ieq 'None')
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
Throw "No usable Omada transport could be established against `"$($BaseAddress)`". Supply either a controller login or an Open API client identifier and secret."
|
|
}
|
|
}
|
|
|
|
Write-Verbose -Message "[$([System.DateTime]::UtcNow.ToString('yyyy/MM/dd HH:mm:ss.FFF'))] - [INFO] - Omada collection will use the $(Switch ($ContextProperties.Mode) {{($_ -ieq 'Controller')} {'controller interface, with the Open API held in reserve'} Default {'Open API'}})."
|
|
|
|
Write-Output -InputObject (New-Object -TypeName 'System.Management.Automation.PSObject' -Property ($ContextProperties))
|
|
}
|
|
Catch
|
|
{
|
|
Throw
|
|
}
|
|
}
|
|
#endregion
|