Initial commit. Working and linked on PSGallery
This commit is contained in:
213
Public/Connect-UnifiController.ps1
Normal file
213
Public/Connect-UnifiController.ps1
Normal file
@@ -0,0 +1,213 @@
|
||||
function Connect-UnifiController {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory, Position = 0)]
|
||||
[string]$Controller,
|
||||
|
||||
[int]$Port = 443,
|
||||
|
||||
# If omitted, a credential prompt will appear
|
||||
[PSCredential]$Credential,
|
||||
|
||||
# TOTP code for accounts with 2FA enabled.
|
||||
# If not supplied and the controller requires 2FA, you will be prompted interactively.
|
||||
[string]$Token,
|
||||
|
||||
# Disable TLS certificate validation — required for controllers using self-signed certs
|
||||
[switch]$SkipCertificateCheck,
|
||||
|
||||
# Persist controller URL, username, and TLS setting to disk (password is never saved)
|
||||
[switch]$Save
|
||||
)
|
||||
|
||||
# Normalize to a full URL
|
||||
$baseUrl = if ($Controller -match '^https?://') {
|
||||
$Controller.TrimEnd('/')
|
||||
} else {
|
||||
"https://${Controller}:${Port}"
|
||||
}
|
||||
|
||||
if (-not $Credential) {
|
||||
$Credential = Get-Credential -Message "Unifi credentials for $baseUrl"
|
||||
if (-not $Credential) {
|
||||
Write-Warning "No credentials provided."
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Step 1 — credentials only. TOTP never goes here; it belongs in step 2.
|
||||
# -------------------------------------------------------------------------
|
||||
$loginBody = @{
|
||||
username = $Credential.UserName
|
||||
password = $Credential.GetNetworkCredential().Password
|
||||
remember = $true
|
||||
} | ConvertTo-Json -Compress
|
||||
|
||||
Write-Verbose "Step 1: POST $baseUrl/api/login (user: $($Credential.UserName))"
|
||||
|
||||
$step1Params = @{
|
||||
Method = 'POST'
|
||||
Uri = "$baseUrl/api/login"
|
||||
Body = $loginBody
|
||||
ContentType = 'application/json'
|
||||
SessionVariable = 'webSession'
|
||||
ErrorAction = 'Stop'
|
||||
}
|
||||
if ($SkipCertificateCheck) { $step1Params.SkipCertificateCheck = $true }
|
||||
|
||||
$response = $null
|
||||
try {
|
||||
$response = Invoke-WebRequest @step1Params
|
||||
}
|
||||
catch {
|
||||
$errBody = $null
|
||||
$serverMsg = $null
|
||||
if ($_.ErrorDetails.Message) {
|
||||
try {
|
||||
$errBody = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction Stop
|
||||
$serverMsg = $errBody.meta.msg
|
||||
} catch {}
|
||||
}
|
||||
|
||||
Write-Verbose "Step 1 error: $serverMsg"
|
||||
Write-Verbose "Raw response body: $($_.ErrorDetails.Message)"
|
||||
|
||||
if ($serverMsg -eq 'api.err.Ubic2faTokenRequired') {
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 2 — re-submit credentials + TOTP. The mfa_cookie JWT from Step 1
|
||||
# is a server-side signal only and is not sent back.
|
||||
# ------------------------------------------------------------------
|
||||
$defaultMfaId = $errBody.data[0].default_mfa
|
||||
$authenticators = $errBody.data[0].authenticators
|
||||
|
||||
Write-Verbose "Step 2: 2FA challenge received (default method: $defaultMfaId)"
|
||||
|
||||
$totpToken = if ($Token) {
|
||||
Write-Verbose "Using token supplied via -Token parameter"
|
||||
$Token
|
||||
} else {
|
||||
Write-Host "2FA required. Registered authenticators:" -ForegroundColor Yellow
|
||||
foreach ($auth in $authenticators) {
|
||||
$marker = if ($auth.id -eq $defaultMfaId) { ' <-- default' } else { '' }
|
||||
Write-Host " [$($auth.type.ToUpper())] $($auth.name)$marker"
|
||||
}
|
||||
Read-Host "Enter your 2FA code"
|
||||
}
|
||||
|
||||
# Re-send full credentials with the TOTP code appended.
|
||||
# mfa_cookie is NOT sent back — it was a server signal only.
|
||||
# strict:true is required by the Ubiquiti SSO flow.
|
||||
$mfaBody = @{
|
||||
username = $Credential.UserName
|
||||
password = $Credential.GetNetworkCredential().Password
|
||||
remember = $true
|
||||
strict = $true
|
||||
ubic_2fa_token = $totpToken
|
||||
} | ConvertTo-Json -Compress
|
||||
|
||||
Write-Verbose "Step 2: POST $baseUrl/api/login (fields: username, password, remember, strict, ubic_2fa_token)"
|
||||
|
||||
$step2Params = @{
|
||||
Method = 'POST'
|
||||
Uri = "$baseUrl/api/login"
|
||||
Body = $mfaBody
|
||||
ContentType = 'application/json'
|
||||
SessionVariable = 'webSession'
|
||||
ErrorAction = 'Stop'
|
||||
}
|
||||
if ($SkipCertificateCheck) { $step2Params.SkipCertificateCheck = $true }
|
||||
|
||||
try {
|
||||
$response = Invoke-WebRequest @step2Params
|
||||
}
|
||||
catch {
|
||||
$mfaMsg = $null
|
||||
if ($_.ErrorDetails.Message) {
|
||||
try {
|
||||
$mfaMsg = ($_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction Stop).meta.msg
|
||||
} catch {}
|
||||
}
|
||||
Write-Error "2FA verification failed$(if ($mfaMsg) { " ($mfaMsg)" }). The code may have expired — try again immediately after reading it from your authenticator."
|
||||
Write-Verbose "Raw step 2 exception: $_"
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
# Not a 2FA challenge — report the error cleanly
|
||||
if ($serverMsg) {
|
||||
switch -Exact ($serverMsg) {
|
||||
'api.err.Invalid' {
|
||||
Write-Error "Login failed: invalid credentials for $baseUrl.`nIf using a Ubiquiti cloud account, the username is your SSO email address."
|
||||
break
|
||||
}
|
||||
default {
|
||||
Write-Error "Login failed ($serverMsg) on $baseUrl."
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Error "Connection to $baseUrl failed. Run with -Verbose for details."
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Both step 1 (no 2FA) and step 2 (2FA complete) land here
|
||||
# -------------------------------------------------------------------------
|
||||
$parsed = $response.Content | ConvertFrom-Json
|
||||
if ($parsed.meta.rc -ne 'ok') {
|
||||
Write-Error "Login rejected: $($parsed.meta.msg)"
|
||||
return
|
||||
}
|
||||
|
||||
# Dump all response headers and cookies under -Verbose to aid CSRF debugging
|
||||
Write-Verbose "Login response headers:"
|
||||
foreach ($h in $response.Headers.GetEnumerator()) {
|
||||
Write-Verbose " $($h.Key): $($h.Value -join ', ')"
|
||||
}
|
||||
Write-Verbose "Session cookies for $baseUrl :"
|
||||
foreach ($c in $webSession.Cookies.GetCookies([uri]$baseUrl)) {
|
||||
$preview = if ($c.Value.Length -gt 60) { $c.Value.Substring(0,60) + '...' } else { $c.Value }
|
||||
Write-Verbose " $($c.Name) = $preview"
|
||||
}
|
||||
|
||||
# UniFi requires X-Csrf-Token header on all write operations (PUT/POST/DELETE).
|
||||
# The token is returned at login — check the response header first, then cookies.
|
||||
$csrfToken = $null
|
||||
$csrfHeader = $response.Headers['X-Csrf-Token']
|
||||
if ($csrfHeader) {
|
||||
$csrfToken = @($csrfHeader)[0]
|
||||
Write-Verbose "CSRF token found in response header."
|
||||
}
|
||||
if (-not $csrfToken) {
|
||||
$csrfCookie = $webSession.Cookies.GetCookies([uri]$baseUrl) |
|
||||
Where-Object Name -eq 'csrf_token' |
|
||||
Select-Object -First 1
|
||||
if ($csrfCookie) {
|
||||
$csrfToken = $csrfCookie.Value
|
||||
Write-Verbose "CSRF token found in 'csrf_token' cookie."
|
||||
}
|
||||
}
|
||||
if (-not $csrfToken) {
|
||||
Write-Verbose "WARNING: No CSRF token found — write operations may return 401."
|
||||
}
|
||||
|
||||
$script:UnifiSession = $webSession
|
||||
$script:UnifiConfig = @{
|
||||
ControllerUrl = $baseUrl
|
||||
Username = $Credential.UserName
|
||||
DefaultSite = 'default'
|
||||
SkipCertificateCheck = $SkipCertificateCheck.IsPresent
|
||||
ConnectedAt = (Get-Date -Format 'o')
|
||||
CsrfToken = $csrfToken
|
||||
}
|
||||
|
||||
if ($Save) {
|
||||
Write-UnifiConfig -Config $script:UnifiConfig
|
||||
Write-Verbose "Config saved to $(Get-UnifiConfigPath)"
|
||||
}
|
||||
|
||||
Write-Host "Connected to $baseUrl as $($Credential.UserName)" -ForegroundColor Green
|
||||
}
|
||||
34
Public/Disconnect-UnifiController.ps1
Normal file
34
Public/Disconnect-UnifiController.ps1
Normal file
@@ -0,0 +1,34 @@
|
||||
function Disconnect-UnifiController {
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
if (-not $script:UnifiSession -or -not $script:UnifiConfig) {
|
||||
Write-Warning "Not currently connected to a controller."
|
||||
return
|
||||
}
|
||||
|
||||
$baseUrl = $script:UnifiConfig.ControllerUrl
|
||||
|
||||
$params = @{
|
||||
Method = 'POST'
|
||||
Uri = "$baseUrl/api/logout"
|
||||
WebSession = $script:UnifiSession
|
||||
}
|
||||
|
||||
if ($script:UnifiConfig.SkipCertificateCheck) {
|
||||
$params.SkipCertificateCheck = $true
|
||||
}
|
||||
|
||||
try {
|
||||
Invoke-RestMethod @params | Out-Null
|
||||
}
|
||||
catch {
|
||||
# Session may already be expired — clear local state regardless
|
||||
Write-Verbose "Logout request failed (session may have already expired): $_"
|
||||
}
|
||||
|
||||
$script:UnifiSession = $null
|
||||
$script:UnifiConfig = $null
|
||||
|
||||
Write-Host "Disconnected." -ForegroundColor Yellow
|
||||
}
|
||||
31
Public/Get-UnifiClient.ps1
Normal file
31
Public/Get-UnifiClient.ps1
Normal file
@@ -0,0 +1,31 @@
|
||||
function Get-UnifiClient {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$Site,
|
||||
|
||||
# Limit to currently connected clients only (default returns all known clients)
|
||||
[switch]$Active
|
||||
)
|
||||
|
||||
if (-not $script:UnifiSession) {
|
||||
Write-Error "Not connected. Run Connect-UnifiController first."
|
||||
return
|
||||
}
|
||||
|
||||
$siteId = Resolve-UnifiSite $Site
|
||||
$endpoint = if ($Active) { '/stat/sta' } else { '/stat/alluser' }
|
||||
$result = Invoke-UnifiRequest -Endpoint $endpoint -Site $siteId
|
||||
|
||||
if (-not $result.data -or $result.data.Count -eq 0) {
|
||||
Write-Warning "No clients found on site '$siteId'."
|
||||
return
|
||||
}
|
||||
|
||||
$result.data | Select-Object `
|
||||
@{ N = 'Hostname'; E = { if ($_.hostname) { $_.hostname } elseif ($_.name) { $_.name } else { '-' } } },
|
||||
@{ N = 'IP'; E = { if ($_.ip) { $_.ip } elseif ($_.fixed_ip) { "$($_.fixed_ip)*" } else { '-' } } },
|
||||
@{ N = 'MAC'; E = { $_.mac } },
|
||||
@{ N = 'Network'; E = { if ($_.essid) { $_.essid } elseif ($_.network) { $_.network } else { '-' } } },
|
||||
@{ N = 'Type'; E = { if ($_.is_wired) { 'Wired' } else { 'Wireless' } } },
|
||||
@{ N = 'Signal'; E = { if (-not $_.is_wired -and $_.rssi) { "$($_.signal) dBm (RSSI $($_.rssi))" } else { '-' } } }
|
||||
}
|
||||
25
Public/Get-UnifiConnectionStatus.ps1
Normal file
25
Public/Get-UnifiConnectionStatus.ps1
Normal file
@@ -0,0 +1,25 @@
|
||||
function Get-UnifiConnectionStatus {
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
if (-not $script:UnifiSession -or -not $script:UnifiConfig) {
|
||||
Write-Host "Status: Not connected" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "Run: Connect-UnifiController <host> or unifi-cli connect <host>"
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "Status: " -NoNewline -ForegroundColor White
|
||||
Write-Host "Connected" -ForegroundColor Green
|
||||
Write-Host "Controller: $($script:UnifiConfig.ControllerUrl)"
|
||||
Write-Host "Username: $($script:UnifiConfig.Username)"
|
||||
Write-Host "Connected: $($script:UnifiConfig.ConnectedAt)"
|
||||
Write-Host "Site: " -NoNewline -ForegroundColor White
|
||||
Write-Host $script:UnifiConfig.DefaultSite -ForegroundColor Cyan
|
||||
Write-Host " (change with: unifi-cli use site <id>)" -ForegroundColor DarkGray
|
||||
|
||||
if ($script:UnifiConfig.SkipCertificateCheck) {
|
||||
Write-Host "TLS: " -NoNewline -ForegroundColor White
|
||||
Write-Host "Certificate check disabled" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
41
Public/Get-UnifiDevice.ps1
Normal file
41
Public/Get-UnifiDevice.ps1
Normal file
@@ -0,0 +1,41 @@
|
||||
function Get-UnifiDevice {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$Site
|
||||
)
|
||||
|
||||
if (-not $script:UnifiSession) {
|
||||
Write-Error "Not connected. Run Connect-UnifiController first."
|
||||
return
|
||||
}
|
||||
|
||||
$siteId = Resolve-UnifiSite $Site
|
||||
$result = Invoke-UnifiRequest -Endpoint '/stat/device' -Site $siteId
|
||||
|
||||
if (-not $result.data -or $result.data.Count -eq 0) {
|
||||
Write-Warning "No devices found on site '$siteId'."
|
||||
return
|
||||
}
|
||||
|
||||
$result.data | Select-Object `
|
||||
@{ N = 'Name'; E = { if ($_.name) { $_.name } else { $_.mac } } },
|
||||
@{ N = 'Type'; E = { switch ($_.type) {
|
||||
'uap' { 'Access Point' }
|
||||
'usw' { 'Switch' }
|
||||
'ugw' { 'Gateway' }
|
||||
'udm' { 'Dream Machine'}
|
||||
default { $_.type }
|
||||
} } },
|
||||
@{ N = 'Model'; E = { $_.model } },
|
||||
@{ N = 'IP'; E = { $_.ip } },
|
||||
@{ N = 'MAC'; E = { $_.mac } },
|
||||
@{ N = 'State'; E = { switch ($_.state) {
|
||||
0 { 'Disconnected' }
|
||||
1 { 'Connected' }
|
||||
2 { 'Pending' }
|
||||
4 { 'Upgrading' }
|
||||
5 { 'Provisioning' }
|
||||
default { "State $($_.state)" }
|
||||
} } },
|
||||
@{ N = 'Version'; E = { $_.version } }
|
||||
}
|
||||
21
Public/Get-UnifiSite.ps1
Normal file
21
Public/Get-UnifiSite.ps1
Normal file
@@ -0,0 +1,21 @@
|
||||
function Get-UnifiSite {
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
if (-not $script:UnifiSession) {
|
||||
Write-Error "Not connected. Run Connect-UnifiController first."
|
||||
return
|
||||
}
|
||||
|
||||
$result = Invoke-UnifiRequest -Endpoint '/api/self/sites'
|
||||
|
||||
if (-not $result.data -or $result.data.Count -eq 0) {
|
||||
Write-Warning "No sites returned."
|
||||
return
|
||||
}
|
||||
|
||||
$result.data | Select-Object `
|
||||
@{ N = 'SiteId'; E = { $_.name } },
|
||||
@{ N = 'Description'; E = { $_.desc } },
|
||||
@{ N = 'Role'; E = { $_.role } }
|
||||
}
|
||||
49
Public/Get-UnifiWlan.ps1
Normal file
49
Public/Get-UnifiWlan.ps1
Normal file
@@ -0,0 +1,49 @@
|
||||
function Get-UnifiWlan {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$Site,
|
||||
|
||||
# Filter by SSID name
|
||||
[string]$Ssid,
|
||||
|
||||
# Include the passphrase in output — omitted by default
|
||||
[switch]$ShowPassword
|
||||
)
|
||||
|
||||
if (-not $script:UnifiSession) {
|
||||
Write-Error "Not connected. Run Connect-UnifiController first."
|
||||
return
|
||||
}
|
||||
|
||||
$siteId = Resolve-UnifiSite $Site
|
||||
$result = Invoke-UnifiRequest -Endpoint '/rest/wlanconf' -Site $siteId
|
||||
|
||||
if (-not $result.data -or $result.data.Count -eq 0) {
|
||||
Write-Warning "No WLANs found on site '$siteId'."
|
||||
return
|
||||
}
|
||||
|
||||
$wlans = $result.data
|
||||
if ($Ssid) {
|
||||
$wlans = $wlans | Where-Object { $_.name -eq $Ssid }
|
||||
if (-not $wlans) {
|
||||
Write-Warning "No WLAN named '$Ssid' found on site '$siteId'."
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
$props = [System.Collections.Generic.List[object]]@(
|
||||
@{ N = 'SSID'; E = { $_.name } }
|
||||
@{ N = 'Enabled'; E = { $_.enabled } }
|
||||
@{ N = 'Security'; E = { $_.security } }
|
||||
@{ N = 'Band'; E = { switch ($_.wlan_band) { '2g' {'2.4 GHz'} '5g' {'5 GHz'} 'both' {'Both'} default { $_.wlan_band } } } }
|
||||
)
|
||||
|
||||
if ($ShowPassword) {
|
||||
$props.Add(@{ N = 'Password'; E = { $_.x_passphrase } })
|
||||
}
|
||||
|
||||
$props.Add(@{ N = 'ID'; E = { $_._id } })
|
||||
|
||||
$wlans | Select-Object $props
|
||||
}
|
||||
118
Public/Invoke-UnifiCli.ps1
Normal file
118
Public/Invoke-UnifiCli.ps1
Normal file
@@ -0,0 +1,118 @@
|
||||
function Invoke-UnifiCli {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Position = 0)]
|
||||
[string]$Command,
|
||||
|
||||
[Parameter(Position = 1, ValueFromRemainingArguments)]
|
||||
[string[]]$Arguments
|
||||
)
|
||||
|
||||
switch ($Command.ToLower()) {
|
||||
|
||||
'connect' {
|
||||
if (-not $Arguments -or $Arguments.Count -eq 0) {
|
||||
Write-Error "Usage: unifi-cli connect <host> [--port <n>] [--insecure] [--save]"
|
||||
return
|
||||
}
|
||||
$p = @{ Controller = $Arguments[0] }
|
||||
for ($i = 1; $i -lt $Arguments.Count; $i++) {
|
||||
switch ($Arguments[$i]) {
|
||||
'--insecure' { $p.SkipCertificateCheck = $true }
|
||||
'--save' { $p.Save = $true }
|
||||
'--port' { $p.Port = [int]$Arguments[++$i] }
|
||||
'--totp' { $p.Token = $Arguments[++$i] }
|
||||
}
|
||||
}
|
||||
Connect-UnifiController @p
|
||||
}
|
||||
|
||||
'disconnect' {
|
||||
Disconnect-UnifiController
|
||||
}
|
||||
|
||||
'status' {
|
||||
Get-UnifiConnectionStatus
|
||||
}
|
||||
|
||||
'use' {
|
||||
$noun = if ($Arguments.Count -gt 0) { $Arguments[0].ToLower() } else { '' }
|
||||
switch ($noun) {
|
||||
'site' {
|
||||
if ($Arguments.Count -lt 2) {
|
||||
Write-Error "Usage: unifi-cli use site <id> (get IDs from: unifi-cli list sites)"
|
||||
return
|
||||
}
|
||||
$p = @{ Site = $Arguments[1] }
|
||||
if ($Arguments -contains '--save') { $p.Save = $true }
|
||||
Set-UnifiDefaultSite @p
|
||||
}
|
||||
default { Write-Warning "Unknown: unifi-cli use $noun. Try: unifi-cli use site <id>" }
|
||||
}
|
||||
}
|
||||
|
||||
'set' {
|
||||
$noun = if ($Arguments.Count -gt 0) { $Arguments[0].ToLower() } else { '' }
|
||||
switch ($noun) {
|
||||
'wlan-password' {
|
||||
$p = @{}
|
||||
for ($i = 1; $i -lt $Arguments.Count; $i++) {
|
||||
switch ($Arguments[$i]) {
|
||||
'--ssid' { $p.Ssid = $Arguments[++$i] }
|
||||
'--site' { $p.Site = $Arguments[++$i] }
|
||||
}
|
||||
}
|
||||
if (-not $p.Ssid) {
|
||||
Write-Error "Usage: unifi-cli set wlan-password --ssid <name> [--site <id>]"
|
||||
return
|
||||
}
|
||||
Set-UnifiWlanPassword @p
|
||||
}
|
||||
default { Write-Warning "Unknown: unifi-cli set $noun. Try: unifi-cli set wlan-password --ssid <name>" }
|
||||
}
|
||||
}
|
||||
|
||||
{ $_ -in 'list', 'get', 'show' } {
|
||||
$target = if ($Arguments.Count -gt 0) { $Arguments[0].ToLower() } else { '' }
|
||||
|
||||
# Parse shared flags: --site <id>, --active, --show-password
|
||||
$p = @{}
|
||||
for ($i = 1; $i -lt $Arguments.Count; $i++) {
|
||||
switch ($Arguments[$i]) {
|
||||
'--site' { $p.Site = $Arguments[++$i] }
|
||||
'--active' { $p.Active = $true }
|
||||
'--show-password' { $p.ShowPassword = $true }
|
||||
'--ssid' { $p.Ssid = $Arguments[++$i] }
|
||||
}
|
||||
}
|
||||
|
||||
switch ($target) {
|
||||
'sites' { Get-UnifiSite }
|
||||
'devices' { Get-UnifiDevice @p }
|
||||
'clients' { Get-UnifiClient @p }
|
||||
'wlans' { Get-UnifiWlan @p }
|
||||
default {
|
||||
Write-Warning "Unknown target '$target'. Available: sites, devices, clients, wlans"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default {
|
||||
Write-Host ""
|
||||
Write-Host "UnifiCLI — Unifi Network Controller CLI" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host " unifi-cli connect <host> [--port <n>] [--insecure] [--save] [--totp <code>]"
|
||||
Write-Host " unifi-cli disconnect"
|
||||
Write-Host " unifi-cli status"
|
||||
Write-Host " unifi-cli use site <id> [--save]"
|
||||
Write-Host " unifi-cli list sites"
|
||||
Write-Host " unifi-cli list devices [--site <id>]"
|
||||
Write-Host " unifi-cli list clients [--site <id>] [--active]"
|
||||
Write-Host " unifi-cli list wlans [--site <id>] [--ssid <name>] [--show-password]"
|
||||
Write-Host " unifi-cli set wlan-password --ssid <name> [--site <id>]"
|
||||
Write-Host ""
|
||||
Write-Host "Native PS functions: Connect-UnifiController, Get-UnifiSite, etc." -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Public/Set-UnifiDefaultSite.ps1
Normal file
30
Public/Set-UnifiDefaultSite.ps1
Normal file
@@ -0,0 +1,30 @@
|
||||
function Set-UnifiDefaultSite {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory, Position = 0)]
|
||||
[string]$Site,
|
||||
|
||||
# Persist the selection to disk so it survives reconnects
|
||||
[switch]$Save
|
||||
)
|
||||
|
||||
if (-not $script:UnifiSession -or -not $script:UnifiConfig) {
|
||||
Write-Error "Not connected. Run Connect-UnifiController first."
|
||||
return
|
||||
}
|
||||
|
||||
$previous = $script:UnifiConfig.DefaultSite
|
||||
$script:UnifiConfig.DefaultSite = $Site
|
||||
|
||||
if ($Save) {
|
||||
Write-UnifiConfig -Config $script:UnifiConfig
|
||||
Write-Verbose "Site selection saved to $(Get-UnifiConfigPath)"
|
||||
}
|
||||
|
||||
Write-Host "Default site: " -NoNewline -ForegroundColor White
|
||||
if ($previous -ne $Site) {
|
||||
Write-Host "$previous" -NoNewline -ForegroundColor DarkGray
|
||||
Write-Host " -> " -NoNewline -ForegroundColor DarkGray
|
||||
}
|
||||
Write-Host $Site -ForegroundColor Cyan
|
||||
}
|
||||
38
Public/Set-UnifiWlanPassword.ps1
Normal file
38
Public/Set-UnifiWlanPassword.ps1
Normal file
@@ -0,0 +1,38 @@
|
||||
function Set-UnifiWlanPassword {
|
||||
[CmdletBinding(SupportsShouldProcess)]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$Ssid,
|
||||
|
||||
[string]$Site,
|
||||
|
||||
# If omitted, a SecureString prompt will appear
|
||||
[SecureString]$NewPassword
|
||||
)
|
||||
|
||||
if (-not $script:UnifiSession) {
|
||||
Write-Error "Not connected. Run Connect-UnifiController first."
|
||||
return
|
||||
}
|
||||
|
||||
$siteId = Resolve-UnifiSite $Site
|
||||
|
||||
$result = Invoke-UnifiRequest -Endpoint '/rest/wlanconf' -Site $siteId
|
||||
$wlan = $result.data | Where-Object { $_.name -eq $Ssid } | Select-Object -First 1
|
||||
|
||||
if (-not $wlan) {
|
||||
Write-Warning "No WLAN named '$Ssid' found on site '$siteId'."
|
||||
return
|
||||
}
|
||||
|
||||
if (-not $NewPassword) {
|
||||
$NewPassword = Read-Host -Prompt "New password for '$Ssid'" -AsSecureString
|
||||
}
|
||||
|
||||
$plainText = ConvertFrom-SecureString -SecureString $NewPassword -AsPlainText
|
||||
|
||||
if ($PSCmdlet.ShouldProcess("WLAN '$Ssid' on site '$siteId'", 'Set password')) {
|
||||
Invoke-UnifiRequest -Method PUT -Endpoint "/rest/wlanconf/$($wlan._id)" -Site $siteId -Body @{ x_passphrase = $plainText }
|
||||
Write-Host "Password updated for '$Ssid' on site '$siteId'." -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user