Aruba Instant On API / Documenting Aruba Instant On Sites
We have recently started to trial Aruba Instant On as a replacement for Unifi, as it is becoming more and more clear that Unifi is not interested in the MSP market any more. Plus they have had some concerning security issues and recent updates have had critical bugs and reduce functionality.
One of the biggest concerns I have had is the lack of an API for Instant On allowing us to automate documentation of our customer’s sites. This morning I got up early to catch up on Bad Batch while my other half was still in bed (she hates Star Wars) and I decided to have a dig around the Aruba Instant on portal to try and see where it is getting it’s data from. It turns out there is a really nice undocumented API where it pulls all it’s data from. It appears to use Oauth2 PKCE authentication. After a morning of prodding things and reverse engineering their authentication I was able to pull pretty much all data from the API.
NOTE: HPE /Aruba’s official line is that this API doesn’t exist. It is undocumented and unsupported. There is no guarantee that this won’t break or that they won’t just revoke access.
To use the API you will need to create a new user with no MFA and assign it to all your sites.
API Endpoints
To see how to login to the API I recommend you check out the scripts below as they go through the process.
These are the endpoints I discovered (all GET requests):
https://nb.portal.arubainstanton.com/api/sites/ | This provides a list of sites you have access to. |
https://nb.portal.arubainstanton.com/api/sites/{SITE_ID}/landingPage | This provides summary details on the site |
https://nb.portal.arubainstanton.com/api/sites/{SITE_ID}/administration | This lists the site administrators |
https://nb.portal.arubainstanton.com/api/sites/{SITE_ID}/timezone | This shows the site Timezone details |
https://nb.portal.arubainstanton.com/api/sites/{SITE_ID}/maintenance | This shows the site’s maintenance settings |
https://nb.portal.arubainstanton.com/api/sites/{SITE_ID}/alerts | This show all alerts for the site |
https://nb.portal.arubainstanton.com/api/sites/{SITE_ID}/alertsSummary | This lists the total number of open alerts |
https://nb.portal.arubainstanton.com/api/sites/{SITE_ID}/applicationCategoryUsage | This show application usage for the sies |
https://nb.portal.arubainstanton.com/api/sites/{SITE_ID}/inventory | This lists all Aruba devices in the site |
https://nb.portal.arubainstanton.com/api/sites/{SITE_ID}/clientSummary | This lists WiFi Clients |
https://nb.portal.arubainstanton.com/api/sites/{SITE_ID}/wiredClientSummary | This lists wired clients |
https://nb.portal.arubainstanton.com/api/sites/{SITE_ID}/wiredNetworks | This lists wired networks |
https://nb.portal.arubainstanton.com/api/sites/{SITE_ID}/networksSummary | This gives WiFi network details |
https://nb.portal.arubainstanton.com/api/sites/{SITE_ID}/capabilities | This shows what permissions your account has |
https://nb.portal.arubainstanton.com/api/sites/{SITE_ID}/radiusNasSettings | This shows some NAS settings |
https://nb.portal.arubainstanton.com/api/sites/{SITE_ID}/reservedIpSubnets | This shows reserved Subnets |
https://nb.portal.arubainstanton.com/api/sites/{SITE_ID}/defaultWiredNetwork | This shows your default network |
https://nb.portal.arubainstanton.com/api/sites/{SITE_ID}/guestPortalSettings | This shows guest portal settings |
https://nb.portal.arubainstanton.com/api/sites/{SITE_ID}/clientBlacklist | This shows your client blacklist |
https://nb.portal.arubainstanton.com/api/sites/{SITE_ID}/applicationCategoryUsageConfiguration | This shows application usage configuration |
I have created two versions of a script showing what data you can pull. One which will create HTML documents per site and one for Hudu
HTML Script
Github: https://github.com/lwhitelock/HuduAutomation/blob/main/Aruba-Instant-On/Arubu-Instant-On-HTML.ps1
#### Settings ####
$ArubaInstantOnUser = '[email protected]'
$ArubaInstantOnPass = 'Make a long randomly generated password for the account that you save securely'
#### Functions ####
function Get-URLEncode{
param(
[Byte[]]$Bytes
)
# Convert to Base 64
$EncodedText =[Convert]::ToBase64String($Bytes)
# Calculate Number of Padding Chars
$Found = $false
$EndPos = $EncodedText.Length
do{
if ($EncodedText[$EndPos] -ne '='){
$found = $true
}
$EndPos = $EndPos -1
} while ($found -eq $false)
# Trim the Padding Chars
$Stripped = $EncodedText.Substring(0, $EndPos)
# Add the number of padding chars to the end
$PaddingNumber = "$Stripped$($EncodedText.Length - ($EndPos + 1))"
# Replace Characters
$URLEncodedString = $PaddingNumber -replace [RegEx]::Escape("+"), '-' -replace [RegEx]::Escape("/"), '_'
return $URLEncodedString
}
#### Start ####
If (Get-Module -ListAvailable -Name "PsWriteHTML") {
Import-module PswriteHTML
}
Else {
Install-Module PsWriteHTML -Force
Import-Module PsWriteHTML
}
# The API appears to use PKCE. A detailed explination of the steps can be found here https://auth0.com/docs/flows/call-your-api-using-the-authorization-code-flow-with-pkce
# Generate the Code Verified and Code Challange used in OAUth
$RandomNumberGenerator = New-Object System.Security.Cryptography.RNGCryptoServiceProvider
$Bytes = New-Object Byte[] 32
$RandomNumberGenerator.GetBytes($Bytes)
$CodeVerifier = (Get-URLEncode($Bytes)).Substring(0, 43)
$StateRandomNumberGenerator = New-Object System.Security.Cryptography.RNGCryptoServiceProvider
$StateBytes = New-Object Byte[] 32
$StateRandomNumberGenerator.GetBytes($StateBytes)
$State = (Get-URLEncode($StateBytes)).Substring(0, 43)
$hasher = [System.Security.Cryptography.HashAlgorithm]::Create('sha256')
$hash = $hasher.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($CodeVerifier))
$CodeChallenge = (Get-URLEncode($hash)).Substring(0, 43)
#Create the form body for the initial login
$LoginRequest = [ordered]@{
username = $ArubaInstantOnUser
password = $ArubaInstantOnPass
}
# Perform the initial authorisation
$ContentType = 'application/x-www-form-urlencoded'
$Token = (Invoke-WebRequest -Method POST -Uri "https://sso.arubainstanton.com/aio/api/v1/mfa/validate/full" -body $LoginRequest -ContentType $ContentType).content | ConvertFrom-Json
# Dowmload the global settings and get the Client ID incase this changes.
$OAuthSettings = (Invoke-WebRequest -Method Get -Uri "https://portal.arubainstanton.com/settings.json") | ConvertFrom-Json
$ClientID = $OAuthSettings.ssoClientIdAuthZ
# Use the initial token to perform the authorisation
$URL = "https://sso.arubainstanton.com/as/authorization.oauth2?client_id=$ClientID&redirect_uri=https://portal.arubainstanton.com&response_type=code&scope=profile%20openid&state=$State&code_challenge_method=S256&code_challenge=$CodeChallenge&sessionToken=$($Token.access_token)"
$AuthCode = Invoke-WebRequest -Method GET -Uri $URL -MaximumRedirection 1
# Extract the code returned in the redirect URL
if ($null -ne $AuthCode.BaseResponse.ResponseUri) {
# This is for Powershell 5
$redirectUri = $AuthCode.BaseResponse.ResponseUri
}
elseif ($null -ne $AuthCode.BaseResponse.RequestMessage.RequestUri) {
# This is for Powershell core
$redirectUri = $AuthCode.BaseResponse.RequestMessage.RequestUri
}
$QueryParams = [System.Web.HttpUtility]::ParseQueryString($redirectUri.Query)
$i = 0
$ParsedQueryParams = foreach ($QueryStringObject in $QueryParams) {
$queryObject = New-Object -TypeName psobject
$queryObject | Add-Member -MemberType NoteProperty -Name Name -Value $QueryStringObject
$queryObject | Add-Member -MemberType NoteProperty -Name Value -Value $QueryParams[$i]
$queryObject
$i++
}
$LoginCode = ($ParsedQueryParams | where-object { $_.name -eq 'code' }).value
# Build the form data to request an actual token
$TokenAuth = @{
client_id = $ClientID
redirect_uri = 'https://portal.arubainstanton.com'
code = $LoginCode
code_verifier = $CodeVerifier
grant_type = 'authorization_code'
}
# Obtain the Bearer Token
$Bearer = (Invoke-WebRequest -Method POST -Uri "https://sso.arubainstanton.com/as/token.oauth2" -body $TokenAuth -ContentType $ContentType).content | ConvertFrom-Json
# Get the headers ready for talking to the API. Note you get 500 errors if you don't include x-ion-api-version 7 for some endpoints and don't get full data on others
$ContentType = 'application/json'
$headers = @{
Authorization = "Bearer $($Bearer.access_token)"
'x-ion-api-version' = 7
}
# Get all sites under account
$Sites = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# Loop through each site and create documentation
foreach ($site in $sites.Elements) {
Write-Host "Processing $($Site.name)"
#Gather all Data
#Site Details
$LandingPage = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/landingPage" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
$administration = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/administration" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
$timezone = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/timezone" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
$maintenance = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/maintenance" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
$Alerts = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/alerts" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
$AlertsSummary = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/alertsSummary" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
$applicationCategoryUsage = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/applicationCategoryUsage" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# Devices
$Inventory = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/inventory" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# Clients
$ClientSummary = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/clientSummary" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
$WiredClientSummary = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/wiredClientSummary" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# Networks
$WiredNetworks = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/wiredNetworks" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
$networksSummary = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/networksSummary" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# Not Used in this example
# $Summary = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# $capabilities = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/capabilities" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# $radiusNasSettings = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/radiusNasSettings" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# $reservedIpSubnets = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/reservedIpSubnets" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# $defaultWiredNetwork = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/defaultWiredNetwork" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# $guestPortalSettings = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/guestPortalSettings" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# $ClientBlacklist = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/clientBlacklist" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# $applicationCategoryUsageConfiguration = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/applicationCategoryUsageConfiguration" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
New-HTML -Title 'Aruba Instant On Details' -FilePath "C:\Temp\ArubaION\$($Site.name).html" {
New-HTMLTAb -Name 'Site Summary' {
New-HTMLSection -HeaderText 'Summary' {
$LandingPage | Select-Object -ExcludeProperty kind | convertto-html -as list -Fragment
}
New-HTMLSection -HeaderText 'Other Settings' {
"Timezone: $($timezone.timezoneIana)<br/>"
"Maintenance Time: $($maintenance.day) - $($maintenance.startTime)<br/>"
}
New-HTMLSection -HeaderText 'Admins' {
$administration.accounts | select-object email, isActivated, isPrimaryAccount | convertto-html -Fragment
}
New-HTMLSection -HeaderText 'Alerts Summary' {
"Active Info Alerts: $($AlertsSummary.activeInfoAlertsCount) <br/>"
"Active Minor Alerts: $($AlertsSummary.activeMinorAlertsCount) <br/>"
"Active Major Alerts: $($AlertsSummary.activeMajorAlertsCount) <br/>"
}
New-HTMLSection -HeaderText 'Alerts' {
New-HTMLTable -DataTable ($alerts.elements | Select-Object kind, type, severity, @{n = 'Created'; e = { (Get-Date 01.01.1970) + ([System.TimeSpan]::fromseconds($_.raisedTime)) } }, @{n = 'Resolved'; e = { (Get-Date 01.01.1970) + ([System.TimeSpan]::fromseconds($_.clearedTime)) } })
}
New-HTMLSection -HeaderText 'Application Usage' {
New-HTMLTable -DataTable ($applicationCategoryUsage.elements | sort-object downstreamDataTransferredDuringLast24HoursInBytes -Descending | Select-Object networkSsid, applicationCategory, downstreamDataTransferredDuringLast24HoursInBytes, upstreamDataTransferredDuringLast24HoursInBytes)
}
}
New-HTMLTab -Name 'Networks' {
New-HTMLSection -HeaderText 'Wireless Networks' {
New-HTMLTable -DataTable ($networksSummary.elements | select-object networkName, type, isEnabled, isSsidHidden, authentication, security, preSharedKey)
}
New-HTMLSection -HeaderText 'Wired Networks' {
New-HTMLTable -DataTable ($WiredNetworks.elements | select-object wiredNetworkName, isManagement, isEnabled)
}
}
New-HTMLTab -Name 'Devices' {
New-HTMLSection -HeaderText 'Devices' {
New-HTMLTable -DataTable ($Inventory.elements | Select-Object deviceType, name, status, operationalState, ipAddress, macAddress, model, serialNumber, uptimeInSeconds)
}
}
New-HTMLTab -Name 'Clients' {
New-HTMLSection -HeaderText 'Wireless Clients' {
New-HTMLTable -DataTable ($ClientSummary.elements | Select-Object name, NetworkSsid, ipAddress, apName, wirelessProtocol, wirelessSecurity, connectionDurationInSeconds, signalQuality, signalInDbm, noiseInDbm, snrInDb)
}
New-HTMLSection -HeaderText 'Wired Clients' {
New-HTMLTable -DataTable ($WiredClientSummary.elements | Select-Object name, macAddress, clientType, isVoiceDevice, ipAddress)
}
}
}
}
Hudu Script


This Script creates Assets for Sites and Devices as well as a Magic Dash with Summary information.
Github: https://github.com/lwhitelock/HuduAutomation/blob/main/Aruba-Instant-On/Aruba-Instant-On-Hudu.ps1
# Aruba Settings
$ArubaInstantOnUser = '[email protected]'
$ArubaInstantOnPass = 'Make a long randomly generated password for the account that you save securely'
# Hudu Settings
$HuduAPIKey = 'abcdefghijk1234567'
$HuduBaseDomain = 'https://your.hududomain.com'
$HuduAssetLayoutNameSite = "Aruba Instant On - Site"
$HuduAssetLayoutNameDevice = "Aruba Instant On - Device"
#$TableStyling = "<th>", "<th style=`"background-color:#F5831F`">"
$TableStyling = ""
function Get-URLEncode{
param(
[Byte[]]$Bytes
)
# Convert to Base 64
$EncodedText =[Convert]::ToBase64String($Bytes)
# Calculate Number of Padding Chars
$Found = $false
$EndPos = $EncodedText.Length
do{
if ($EncodedText[$EndPos] -ne '='){
$found = $true
}
$EndPos = $EndPos -1
} while ($found -eq $false)
# Trim the Padding Chars
$Stripped = $EncodedText.Substring(0, $EndPos)
# Add the number of padding chars to the end
$PaddingNumber = "$Stripped$($EncodedText.Length - ($EndPos + 1))"
# Replace Characters
$URLEncodedString = $PaddingNumber -replace [RegEx]::Escape("+"), '-' -replace [RegEx]::Escape("/"), '_'
return $URLEncodedString
}
#### Start ####
if (Get-Module -ListAvailable -Name HuduAPI) {
Import-Module HuduAPI
}
else {
Install-Module HuduAPI -Force
Import-Module HuduAPI
}
#Set Hudu logon information
New-HuduAPIKey $HuduAPIKey
New-HuduBaseUrl $HuduBaseDomain
# Prepare Asset Layouts
$SiteLayout = Get-HuduAssetLayouts -name $HuduAssetLayoutNameSite
if (!$SiteLayout) {
$SiteAssetLayoutFields = @(
@{
label = 'Site Name'
field_type = 'Text'
show_in_list = 'true'
position = 1
},
@{
label = 'Site Details'
field_type = 'RichText'
show_in_list = 'false'
position = 2
},
@{
label = 'Admins'
field_type = 'RichText'
show_in_list = 'false'
position = 3
},
@{
label = 'Alerts'
field_type = 'RichText'
show_in_list = 'false'
position = 4
},
@{
label = 'Wired Networks'
field_type = 'RichText'
show_in_list = 'false'
position = 5
},
@{
label = 'Wireless Networks'
field_type = 'RichText'
show_in_list = 'false'
position = 6
},
@{
label = 'Application Usage'
field_type = 'RichText'
show_in_list = 'false'
position = 7
},
@{
label = 'Clients'
field_type = 'RichText'
show_in_list = 'false'
position = 7
}
)
Write-Host "Creating New Asset Layout $HuduAssetLayoutNameSite"
$null = New-HuduAssetLayout -name $HuduAssetLayoutNameSite -icon "fas fa-network-wired" -color "#4CAF50" -icon_color "#ffffff" -include_passwords $false -include_photos $false -include_comments $false -include_files $false -fields $SiteAssetLayoutFields
$SiteLayout = Get-HuduAssetLayouts -name $HuduAssetLayoutNameSite
}
$DeviceLayout = Get-HuduAssetLayouts -name $HuduAssetLayoutNameDevice
if (!$DeviceLayout) {
$SiteAssetLayoutFields = @(
@{
label = 'Device Name'
field_type = 'Text'
show_in_list = 'true'
position = 1
},
@{
label = 'Site'
field_type = 'AssetTag'
linkable_id = $SiteLayout.id
show_in_list = 'true'
position = 2
},
@{
label = 'Management URL'
field_type = 'RichText'
show_in_list = 'false'
position = 2
},
@{
label = 'Type'
field_type = 'Text'
show_in_list = 'true'
position = 3
},
@{
label = 'IP'
field_type = 'Text'
show_in_list = 'true'
position = 4
},
@{
label = 'MAC'
field_type = 'Text'
show_in_list = 'false'
position = 5
},
@{
label = 'Serial Number'
field_type = 'Text'
show_in_list = 'false'
position = 6
},
@{
label = 'Model'
field_type = 'Text'
show_in_list = 'false'
position = 7
},
@{
label = 'Uptime'
field_type = 'Text'
show_in_list = 'false'
position = 8
},
@{
label = 'Radios'
field_type = 'RichText'
show_in_list = 'false'
position = 9
},
@{
label = 'Ethernet Ports'
field_type = 'RichText'
show_in_list = 'false'
position = 10
},
@{
label = 'Alerts'
field_type = 'RichText'
show_in_list = 'false'
position = 11
},
@{
label = 'Clients'
field_type = 'RichText'
show_in_list = 'false'
position = 12
}
)
Write-Host "Creating New Asset Layout $HuduAssetLayoutNameDevice"
$null = New-HuduAssetLayout -name $HuduAssetLayoutNameDevice -icon "fas fa-network-wired" -color "#4CAF50" -icon_color "#ffffff" -include_passwords $false -include_photos $false -include_comments $false -include_files $false -fields $SiteAssetLayoutFields
$DeviceLayout = Get-HuduAssetLayouts -name $HuduAssetLayoutNameDevice
}
# The API appears to use PKCE. A detailed explination of the steps can be found here https://auth0.com/docs/flows/call-your-api-using-the-authorization-code-flow-with-pkce
# Generate the Code Verified and Code Challange used in OAUth
$RandomNumberGenerator = New-Object System.Security.Cryptography.RNGCryptoServiceProvider
$Bytes = New-Object Byte[] 32
$RandomNumberGenerator.GetBytes($Bytes)
$CodeVerifier = (Get-URLEncode($Bytes)).Substring(0, 43)
$StateRandomNumberGenerator = New-Object System.Security.Cryptography.RNGCryptoServiceProvider
$StateBytes = New-Object Byte[] 32
$StateRandomNumberGenerator.GetBytes($StateBytes)
$State = (Get-URLEncode($StateBytes)).Substring(0, 43)
$hasher = [System.Security.Cryptography.HashAlgorithm]::Create('sha256')
$hash = $hasher.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($CodeVerifier))
$CodeChallenge = (Get-URLEncode($hash)).Substring(0, 43)
#Create the form body for the initial login
$LoginRequest = [ordered]@{
username = $ArubaInstantOnUser
password = $ArubaInstantOnPass
}
# Perform the initial authorisation
$ContentType = 'application/x-www-form-urlencoded'
$Token = (Invoke-WebRequest -Method POST -Uri "https://sso.arubainstanton.com/aio/api/v1/mfa/validate/full" -body $LoginRequest -ContentType $ContentType).content | ConvertFrom-Json
# Dowmload the global settings and get the Client ID incase this changes.
$OAuthSettings = (Invoke-WebRequest -Method Get -Uri "https://portal.arubainstanton.com/settings.json") | ConvertFrom-Json
$ClientID = $OAuthSettings.ssoClientIdAuthZ
# Use the initial token to perform the authorisation
$URL = "https://sso.arubainstanton.com/as/authorization.oauth2?client_id=$ClientID&redirect_uri=https://portal.arubainstanton.com&response_type=code&scope=profile%20openid&state=$State&code_challenge_method=S256&code_challenge=$CodeChallenge&sessionToken=$($Token.access_token)"
$AuthCode = Invoke-WebRequest -Method GET -Uri $URL -MaximumRedirection 1
# Extract the code returned in the redirect URL
if ($null -ne $AuthCode.BaseResponse.ResponseUri) {
# This is for Powershell 5
$redirectUri = $AuthCode.BaseResponse.ResponseUri
}
elseif ($null -ne $AuthCode.BaseResponse.RequestMessage.RequestUri) {
# This is for Powershell core
$redirectUri = $AuthCode.BaseResponse.RequestMessage.RequestUri
}
$QueryParams = [System.Web.HttpUtility]::ParseQueryString($redirectUri.Query)
$i = 0
$ParsedQueryParams = foreach ($QueryStringObject in $QueryParams) {
$queryObject = New-Object -TypeName psobject
$queryObject | Add-Member -MemberType NoteProperty -Name Name -Value $QueryStringObject
$queryObject | Add-Member -MemberType NoteProperty -Name Value -Value $QueryParams[$i]
$queryObject
$i++
}
$LoginCode = ($ParsedQueryParams | where-object { $_.name -eq 'code' }).value
# Build the form data to request an actual token
$TokenAuth = @{
client_id = $ClientID
redirect_uri = 'https://portal.arubainstanton.com'
code = $LoginCode
code_verifier = $CodeVerifier
grant_type = 'authorization_code'
}
# Obtain the Bearer Token
$Bearer = (Invoke-WebRequest -Method POST -Uri "https://sso.arubainstanton.com/as/token.oauth2" -body $TokenAuth -ContentType $ContentType).content | ConvertFrom-Json
# Get the headers ready for talking to the API. Note you get 500 errors if you don't include x-ion-api-version 7 for some endpoints and don't get full data on others
$ContentType = 'application/json'
$headers = @{
Authorization = "Bearer $($Bearer.access_token)"
'x-ion-api-version' = 7
}
# Get all sites under account
$Sites = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# Loop through each site and create documentation
foreach ($site in $sites.Elements) {
#First we will see if there is an Asset that matches the site name with this Asset Layout
Write-Host "Attempting to map $($Site.name)"
$SiteAsset = Get-HuduAssets -name $($Site.name) -assetlayoutid $SiteLayout.id
if (!$SiteAsset) {
#Check on company name
$Company = Get-HuduCompanies -name $($Site.name)
if (!$company) {
Write-Host "A company in Hudu could not be matched to the site. Please create a blank '$HuduAssetLayoutNameSite' asset, with a name of `"$($Site.name)`" under the company in Hudu you wish to map this site to." -ForegroundColor Red
continue
}
}
Write-Host "Processing $($Site.name)"
#Gather all Data
#Site Details
$LandingPage = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/landingPage" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
$administration = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/administration" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
$timezone = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/timezone" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
$maintenance = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/maintenance" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
$Alerts = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/alerts" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
$AlertsSummary = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/alertsSummary" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
$applicationCategoryUsage = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/applicationCategoryUsage" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# Devices
$Inventory = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/inventory" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
$ClientSummary = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/clientSummary" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
$WiredClientSummary = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/wiredClientSummary" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# Networks
$WiredNetworks = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/wiredNetworks" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
$networksSummary = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/networksSummary" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# Not Used in this example
# $Summary = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# $capabilities = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/capabilities" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# $radiusNasSettings = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/radiusNasSettings" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# $reservedIpSubnets = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/reservedIpSubnets" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# $defaultWiredNetwork = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/defaultWiredNetwork" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# $guestPortalSettings = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/guestPortalSettings" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# $ClientBlacklist = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/clientBlacklist" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
# $applicationCategoryUsageConfiguration = (Invoke-WebRequest -Method GET -Uri "https://nb.portal.arubainstanton.com/api/sites/$($Site.id)/applicationCategoryUsageConfiguration" -ContentType $ContentType -Headers $headers).content | ConvertFrom-Json
$SiteDetails = [PSCustomObject]@{
'Wired Clients' = $LandingPage.wiredClientsCount
'Wireless Clients' = $LandingPage.wirelessClientsCount
'Active Wired Networks' = "$($LandingPage.currentlyActiveWiredNetworksCount) / $($LandingPage.configuredWiredNetworksCount)"
'Active Wireless Networks' = "$($LandingPage.currentlyActiveWirelessNetworksCount) / $($LandingPage.configuredWirelessNetworksCount)"
'Data Transferred in the last 24 hours (GB)' = [math]::round(($LandingPage.totalDataTransferredDuringLast24HoursInBytes / 1024 / 1024 / 1024),2)
'Health' = $LandingPage.health
'Health Reason' = $LandingPage.healthReason
'Timezone' = $($timezone.timezoneIana)
'Maintenance Window' = "$($maintenance.day) - $($maintenance.startTime)"
}
$SiteDetailsHTML = ($SiteDetails | ConvertTo-Html -As List -fragment | Out-String) -replace $TableStyling
$AdminsHTML = ($administration.accounts | Select-Object @{n = 'Email'; e = { $_.email } }, @{n = 'Active'; e = { $_.isActivated } }, @{n = 'Primary Account'; e = { $_.isPrimaryAccount } } | ConvertTo-Html -fragment | Out-String) -replace $TableStyling
$AlertsHTML = ($alerts.elements | Select-Object @{n = 'Created'; e = { (Get-Date 01.01.1970) + ([System.TimeSpan]::fromseconds($_.raisedTime)) } }, @{n = 'Resolved'; e = { (Get-Date 01.01.1970) + ([System.TimeSpan]::fromseconds($_.clearedTime)) } }, @{n = 'Type'; e = { $_.type } }, @{n = 'Severity'; e = { $_.severity } } | ConvertTo-Html -fragment | Out-String) -replace $TableStyling
$WiredNetworksHTML = ($WiredNetworks.elements | select-object @{n = 'Name'; e = { $_.wiredNetworkName } }, @{n = 'Management'; e = { $_.isManagement } }, @{n = 'Enabled'; e = { $_.isEnabled } }, @{n = 'Wireless Networks'; e = { $_.wirelessnetworks.networkname -join ', ' } } | ConvertTo-Html -fragment | Out-String) -replace $TableStyling
$WirelessNetworksHTML = ($networksSummary.elements | select-object @{n = 'Name'; e = { $_.networkName } }, @{n = 'Type'; e = { $_.type } }, @{n = 'Enabled'; e = { $_.isEnabled } }, @{n = 'SSID Hidden'; e = { $_.isSsidHidden } }, @{n = 'Authentication'; e = { $_.authentication } }, @{n = 'Security'; e = { $_.security } }, @{n = 'Captive Portal Enabled'; e = { $_.isCaptivePortalEnabled } } | ConvertTo-Html -fragment | Out-String) -replace $TableStyling
$ApplicationUsageHTML = ($applicationCategoryUsage.elements | where-object { $_.downstreamDataTransferredDuringLast24HoursInBytes -gt 0 -or $_.upstreamDataTransferredDuringLast24HoursInBytes -gt 0 } `
| sort-object downstreamDataTransferredDuringLast24HoursInBytes -Descending `
| Select-Object @{n = 'Name'; e = { $_.networkSsid } }, `
@{n = 'Category'; e = { $_.applicationCategory } }, `
@{n = 'Downloaded in last 24 hours (GBs)'; e = { [math]::Round(($_.downstreamDataTransferredDuringLast24HoursInBytes / 1024 / 1024 / 1024), 2) } }, `
@{n = 'Uploaded in last 24 hours (GBs)'; e = { [math]::Round(($_.upstreamDataTransferredDuringLast24HoursInBytes / 1024 / 1024 / 1024), 2) } } `
| ConvertTo-Html -fragment | Out-String) -replace $TableStyling
$WirelessClientsHTML = ($ClientSummary.elements | Select-Object @{n = 'Name'; e = { $_.name } }, @{n = 'Network'; e = { $_.NetworkSsid } }, @{n = 'IP Address'; e = { $_.ipAddress } }, @{n = 'AP'; e = { $_.apName } }, @{n = 'Protocol'; e = { $_.wirelessProtocol } }, @{n = 'Security'; e = { $_.wirelessSecurity } }, @{n = 'Connected (Hours)'; e = { [math]::Round(($_.connectionDurationInSeconds / 60 / 60), 2) } }, @{n = 'Signal Quality'; e = { $_.signalQuality } }, @{n = 'Signal'; e = { $_.signalInDbm } }, @{n = 'Noise'; e = { $_.noiseInDbm } }, @{n = 'SNR'; e = { $_.snrInDb } } | ConvertTo-Html -fragment | Out-String) -replace $TableStyling
$WiredClientsHTML = ($WiredClientSummary.elements | Select-Object @{n = 'Name'; e = { $_.name } }, @{n = 'MAC'; e = { $_.macAddress } }, @{n = 'Type'; e = { $_.clientType } }, @{n = 'Voice Device'; e = { $_.isVoiceDevice } }, @{n = 'IP Address'; e = { $_.ipAddress } } | ConvertTo-Html -fragment | Out-String) -replace $TableStyling
$SiteFields = @{
'site_name' = $($Site.name)
'site_details' = $SiteDetailsHTML
'admins' = $AdminsHTML
'alerts' = $AlertsHTML
'wired_networks' = $WiredNetworksHTML
'wireless_networks' = $WirelessNetworksHTML
'application_usage' = $ApplicationUsageHTML
'clients' = "<h3>Wireless Clients</h3>$WirelessClientsHTML<h3>Wired Clients</h3>$WiredClientsHTML"
}
$AssetName = $($Site.name)
if (!$SiteAsset) {
$companyid = $company.id
Write-Host "Creating new Asset"
$SiteAsset = (New-HuduAsset -name $AssetName -company_id $companyid -asset_layout_id $SiteLayout.id -fields $SiteFields).asset
}
else {
$companyid = $SiteAsset.company_id
Write-Host "Updating Asset"
$SiteAsset = (Set-HuduAsset -asset_id $SiteAsset.id -name $AssetName -company_id $companyid -asset_layout_id $SiteLayout.id -fields $SiteFields).asset
}
$LinkRaw = @{
id = $SiteAsset.id
name = $SiteAsset.name
}
$Link = $LinkRaw | convertto-json -compress -AsArray | Out-String
$DeviceAssets = foreach ($device in $Inventory.elements) {
$RadiosHTML = ($device.radios | Select-Object @{n = 'MAC'; e = { $_.id } }, @{n = 'Band'; e = { $_.band } }, @{n = 'Channel'; e = { $_.channel } }, @{n = 'Clients'; e = { $_.wirelessClientsCount } }, @{n = 'Radio Power'; e = { $_.radioPower } }, @{n = 'Power Dbm'; e = { $_.txPowerEirpInDbm } }, @{n = 'In Use'; e = { $_.isRadioInUse } } | ConvertTo-Html -fragment | Out-String) -replace $TableStyling
# The port map status table is based off Kelvin Tegelaar's Unifi documentation script
if (($Device.ethernetports | measure-object).count -gt 1) {
$SwitchPortsStatusHTML = $(
"<h3>Ports Status</h3><table><tr>"
foreach ($Port in $Device.ethernetports) {
"<th>$($port.portNumber)</th>"
}
"</tr><tr>"
foreach ($Port in $Device.ethernetports) {
$colour = if ($port.isLinkUp -eq $true) { '02ab26' } else { 'ad2323' }
$speed = switch ($port.speed) {
"mbps10000" { "10Gb" }
"mbps1000" { "1Gb" }
"mbps100" { "100Mb" }
"mbps10" { "10Mb" }
"mbps0" { "Port off" }
}
"<td style='background-color:#$($colour)'>$speed</td>"
}
'</tr><tr>'
foreach ($Port in $Device.ethernetports) {
$poestate = if ($port.poePseStatus -eq 'deliveringPower') { 'PoE on'; $colour = '02ab26' } elseif ($port.poePseStatus -eq 'searching') { 'No PoE'; $colour = '#696363' } elseif ($port.poePseStatus -eq 'otherFault') { 'Fault'; $colour = '#696363' }else { "PoE Off"; $colour = 'ad2323' }
"<td style='background-color:#$($colour)'>$Poestate</td >"
}
'</tr></table><br/>'
)
}
$SwitchPortsDetailHTML = ($device.ethernetports | Select-Object @{n = 'Name'; e = { $_.name } }, `
@{n = 'No'; e = { $_.portNumber } }, `
@{n = 'PoE Prov'; e = { $_.isProvidingPower } }, `
@{n = 'PoE MW Prov'; e = { $_.powerProvidedInMilliwatts } }, `
@{n = 'PoE MW Req'; e = { $_.powerRequestedInMilliwatts } }, `
@{n = 'PoE Status'; e = { $_.poePseStatus } }, `
@{n = 'PoE HW Status'; e = { $_.poePseHardwareStatus } }, `
@{n = 'Speed'; e = { $_.speed } }, `
@{n = 'Link Up'; e = { $_.isLinkUp } }, `
@{n = 'Loop'; e = { $_.isLoopDetected } }, `
@{n = 'Direct Device'; e = { $_.directlyConnectedDeviceName } }, `
@{n = 'Uplink Device'; e = { $_.uplinkDeviceName } }, `
@{n = 'Downloaded GBs'; e = { [math]::Round(($_.downstreamDataTransferredInBytes / 1024 / 1024 / 1024), 2) } }, `
@{n = 'Uploaded GBs'; e = { [math]::Round(($_.upstreamDataTransferredInBytes / 1024 / 1024 / 1024), 2) } } `
| ConvertTo-Html -fragment | Out-String) -replace $TableStyling
$SwitchPortHTML = "$($SwitchPortsStatusHTML)<h3>Port Details</h3>$SwitchPortsDetailHTML"
$ActiveDeviceAlertsHTML = ($Device.ActiveAlerts | Select-Object @{n = 'Created'; e = { (Get-Date 01.01.1970) + ([System.TimeSpan]::fromseconds($_.raisedTime)) } }, @{n = 'Open for (hours)'; e = { [math]::round(($_.numberOfSecondsSinceRaised / 60 / 60), 2) } }, @{n = 'Type'; e = { $_.type } }, @{n = 'Severity'; e = { $_.severity } } | ConvertTo-Html -fragment | Out-String) -replace $TableStyling
$DeviceClients = $ClientSummary.elements | Where-Object { $_.apName -eq $device.name }
$DeviceClientsHTML = ($DeviceClients | Select-Object @{n = 'Name'; e = { $_.name } }, @{n = 'Network'; e = { $_.NetworkSsid } }, @{n = 'IP Address'; e = { $_.ipAddress } }, @{n = 'AP'; e = { $_.apName } }, @{n = 'Protocol'; e = { $_.wirelessProtocol } }, @{n = 'Security'; e = { $_.wirelessSecurity } }, @{n = 'Connected (Hours)'; e = { [math]::Round(($_.connectionDurationInSeconds / 60 / 60), 2) } }, @{n = 'Signal Quality'; e = { $_.signalQuality } }, @{n = 'Signal'; e = { $_.signalInDbm } }, @{n = 'Noise'; e = { $_.noiseInDbm } }, @{n = 'SNR'; e = { $_.snrInDb } } | ConvertTo-Html -fragment | Out-String) -replace $TableStyling
$DeviceFields = @{
'device_name' = $device.name
'site' = $link
'management_url' = "<a href=https://portal.arubainstanton.com/#/site/$($Site.ID)/home/view/inventory/devices>https://portal.arubainstanton.com/#/site/$($Site.ID)/home/view/inventory/devices</a>"
'type' = $device.deviceType
'ip' = $device.ipAddress
'mac' = $device.macAddress
'serial_number' = $device.serialNumber
'model' = $device.model
'uptime' = "$([math]::Round(($device.uptimeInSeconds /60 / 60 / 24),2)) Days"
'radios' = $RadiosHTML
'ethernet_ports' = $SwitchPortHTML
'alerts' = $ActiveDeviceAlertsHTML
'clients' = $DeviceClientsHTML
}
Write-Host "Pushing $($device.name) to Hudu"
$AssetName = $device.name
$companyid = $SiteAsset.company_id
#Check if there is already an asset
$DeviceAsset = Get-HuduAssets -name $AssetName -companyid $companyid -assetlayoutid $DeviceLayout.id
if (!$DeviceAsset) {
Write-Host "Creating new Asset"
(New-HuduAsset -name $AssetName -company_id $companyid -asset_layout_id $DeviceLayout.id -fields $DeviceFields).asset
}
else {
Write-Host "Updating Asset"
(Set-HuduAsset -asset_id $DeviceAsset.id -name $AssetName -company_id $companyid -asset_layout_id $DeviceLayout.id -fields $DeviceFields).asset
}
}
$Shade = "success"
if ($AlertsSummary.activeInfoAlertsCount -gt 0 -or $AlertsSummary.activeMinorAlertsCount -gt 0) {
$Shade = "warning"
}
if ($AlertsSummary.activeMajorAlertsCount -gt 0) {
$Shade = "danger"
}
$DeviceCount = ($Inventory.elements | measure-object).count
$UpDevices = ($Inventory.elements | where-object { $_.status -eq "up" } | measure-object).count
$SiteManagementURL = "https://portal.arubainstanton.com/#/site/$($Site.ID)/home/view/inventory/devices"
$LinkDeviceHTML = foreach ($LinkDevice in $DeviceAssets) {
"<div class='basic_info__section'>
<h2>$($LinkDevice.name)</h2>
<p>
<a href=$($LinkDevice.url)>View Device in Hudu</a> | <a href=$SiteManagementURL>View Device in Aruba</a>
</p>
</div>"
}
$LinkedDevicesHTML = "<div class='nasa__block'>
<header class='nasa__block-header'>
<h1><i class='fas fa-info-circle icon'></i>Devices</h1>
</header>
<main>
<article>
$LinkDeviceHTML
</article>
</main>
</div>"
$SiteDetailsFormattedHTML = "<div class='nasa__block'>
<header class='nasa__block-header'>
<h1><i class='fas fa-info-circle icon'></i>Site Details</h1>
</header>
<main>
<article>
$SiteDetailsHTML
</article>
</main>
</div>"
$body = "<div class='nasa__block'>
<header class='nasa__block-header'>
<h1><a href=$($SiteAsset.url)><i class='fas fa-wifi icon'></i>$($site.name)</a></h1>
</header>
</div>
<br/>
<div class=`"nasa__content`">
$SiteDetailsFormattedHTML
$LinkedDevicesHTML
</div>
<br/>
<div class='nasa__block'>
<header class='nasa__block-header'>
<h1><i class='fas fa-exclamation-triangle'></i> Alerts</h1>
</header>
<div>$AlertsHTML</div>
</div>
"
# Create a Magic Dash
$null = Set-HuduMagicDash -title "Aruba IO - $($site.name)" -company_name $SiteAsset.company_name -message "$UpDevices / $DeviceCount Online" -icon "fas fa-wifi" -content $body -shade $Shade
}
Hi Luke,
Thanks for the great work. I’m working on the Aruba Instant On API at the moment. I’m testing it out on Postman and I’ve been able to get all the information from your mentioned endpoints. Do you know if it’s possible to make changes via this API? If yes then what’s the procedure for that?