Guide technique ANYSYS · vérifié le 26 octobre 2026

Exploiter UniFi : sauvegardes, mises à jour, SIEM et inventaire par API

Cadenas ancien, mécanisme de fermeture
Cadenas ancien, mécanisme de fermetureThe Metropolitan Museum of Art, The Met via Wikimedia Commons, CC0 · Crédits

Sauvegarder et migrer une console, encadrer les mises à jour, envoyer les journaux vers un SIEM en CEF et inventorier sites, équipements et clients avec l’API officielle UniFi Network, grâce à un script PowerShell en lecture seule.

01 / Cadre

Périmètre

Ce dernier guide de la série traite du maintien en condition opérationnelle : sauvegardes, migration de console, mises à jour, journalisation et automatisation en lecture seule. Il complète la conception, le pare-feu, le Wi-Fi et l’accès distant.

La partie API s’appuie sur la référence officielle UniFi Network API 10.6.106 publiée sur developer.ui.com. Ubiquiti indique que la documentation locale de l’API, propre à votre version, est consultable dans UniFi Network > Integrations : c’est elle qui fait foi pour votre console.

02 / Procédure

Sauvegardes, restauration et migration

UniFi distingue deux familles de sauvegardes :

Sauvegardes UniFi
TypeContenuOùUsage
Sauvegarde système (System Config Backup)OS, applications et configuration des équipementsSettings > Control Plane > BackupsCas général sur Cloud Gateway et CloudKey ; seule méthode pour transférer la gestion vers une autre console sans réinitialiser les équipements.
Sauvegarde automatique cloudIdem, stockée sur le compte UI du propriétaireOption « System Backups »Chaque semaine et avant chaque mise à jour majeure ; gérée par le seul propriétaire.
Sauvegarde locale hors ligneFichier téléchargéSettings > Control Plane > BackupsCopie externe, conservée hors de la console et hors du compte UI.
Sauvegarde Network seule (.unf)Réglages Network et configuration des équipementsApplication NetworkServeurs auto-hébergés et anciennes installations, migration vers UniFi OS Server.

Politique recommandée

  • Activez la sauvegarde cloud automatique et téléchargez une sauvegarde locale après chaque changement significatif. Rangez-la dans votre stockage de sauvegarde, chiffrée, avec une rétention d’au moins 90 jours.
  • Les sauvegardes cloud dépendent du compte propriétaire : un compte d’entreprise sous MFA, jamais le compte personnel d’un technicien.
  • Testez une restauration une fois par an sur une console de laboratoire, en restauration partielle si besoin (option « Restore All Applications and Settings » décochée).

Migrer vers une nouvelle console

  1. 01

    Sur l’ancienne console : sauvegarde locale téléchargée (Settings > Control Plane > Backups).

  2. 02

    Remplacez la console en laissant le réseau en aval inchangé. Pour un autre modèle, la nouvelle console doit être en UniFi OS 3.1 ou plus récent.

  3. 03

    Restaurez la sauvegarde pendant l’assistant d’installation ou ensuite depuis Backups.

  4. 04

    Depuis un ancien Network Server : sauvegarde .unf, installation de UniFi OS Server (l’installeur Windows et macOS propose la migration automatique), restauration, puis « Forget » des équipements sur l’ancienne instance une fois tout en ligne. Si les équipements avaient été adoptés en couche 3 (DNS, option DHCP 43), activez « Override Inform Host » sur l’ancienne application avec l’IP de la nouvelle.

03 / Exploitation

Politique de mise à jour

Les mises à jour se pilotent depuis l’Update Manager de Site Manager ou, console par console, dans Settings > Control Plane > Updates : canal (Official ou Early Access) et mises à jour automatiques. Les firmwares des équipements se règlent dans l’application Network. Ubiquiti recommande d’activer les mises à jour automatiques ; en environnement professionnel, nous les encadrons ainsi :

Politique de mise à jour proposée (recommandation ANYSYS)
ComposantCanalModeFenêtre
UniFi OS (console)OfficialManuel, après validation sur un site piloteMensuelle, hors heures ouvrées
Application NetworkOfficialManuel, décalé de 2 à 4 semaines après publicationIdem, après lecture de la note de version
Firmwares AP et switchsOfficialAutomatique planifié la nuit, ou manuel par lotsHebdomadaire ou mensuelle
Site pilote ou laboOfficial (Early Access réservé au labo)ManuelDès publication
  • Lisez la note de version avant chaque mise à jour majeure. Exemple concret : Network 11.0.97 exige UniFi OS 6.0, retire les USG et Network Server, migre de force vers le pare-feu par zones et efface l’historique des statistiques. Selon l’équipe UI sur la note de version, UniFi OS 6.0 est déjà en canal stable sur les Cloud Keys et suivra sur les autres consoles.
  • Early Access s’active d’abord sur le compte UI, puis par site : à réserver au laboratoire, jamais en production.
  • Mise à jour par SSH : Ubiquiti précise que ce n’est pas un processus officiellement supporté et qu’il ne doit être utilisé qu’à la demande du support.
  • Échec de mise à jour : vérifiez l’accès Internet de la console, le DNS et les ports UDP 53 et 123.
  • Network Server auto-hébergé : ses mises à jour se lancent toujours à la main, raison de plus pour migrer vers UniFi OS Server.

04 / Supervision

Journaux, SIEM et CEF

UniFi produit deux types de traces : les journaux système (connexions de clients, WAN, PoE, sécurité, actions d’administration) et les Traffic Flows (sessions passées par la passerelle).

Journaux système vers le SIEM

  1. 01

    Dans Integration > System Logging / SIEM, choisissez SIEM Server comme destination.

  2. 02

    Sélectionnez les catégories : Security (pare-feu, honeypot, IPS) et System (activité des administrateurs, équipements, VPN) au minimum ; Monitoring si vous suivez les connexions des clients.

  3. 03

    Saisissez l’adresse IP et le port du collecteur syslog ou du SIEM. Tous les journaux sont exportés au format CEF.

  4. 04

    Activez en parallèle l’Alarm Manager (push, e-mail, webhook GET ou POST) pour les événements qui exigent une réaction immédiate : WAN en panne, équipement hors ligne, détection IPS.

Format documenté par Ubiquiti : CEF:Version|Device Vendor|Device Product|Device Version|Device Event Class ID|Name|Severity|[Extension]. Exemple tiré de la documentation (événement d’accès administrateur) :

Exemple CEF publié par Ubiquiti
CEF:0|Ubiquiti|UniFi Network|9.3.33|544|Admin Accessed UniFi Network|1|UNIFIcategory=System UNIFIsubCategory=Admin UNIFIhost=Office UDM Pro UNIFIaccessMethod=web UNIFIadmin=Craig src=105.5.138.59 msg=Craig accessed UniFi Network using the web. Source IP: 105.5.138.59

Côté SIEM, les champs UNIFIcategory, UNIFIadmin et src suffisent pour une première règle de corrélation : connexion administrateur hors heures ouvrées ou depuis une IP inconnue.

Traffic Flows

Les Traffic Flows s’exportent en CSV (Insights > Flows) ou en NetFlow (IPFIX) échantillonné vers un collecteur (Settings > CyberSecure > Traffic Logging). Ubiquiti indique que l’export des journaux de trafic vers un SIEM arrivera dans une version future : à ce jour, seuls les journaux système partent en CEF. La rétention locale est limitée (de l’ordre de 10 000 flux sur un UDM Pro ou un UCG Max sans stockage ajouté, 500 000 sur un UDM SE, 10 millions sur un EFG) : pour une exigence de conservation, l’export est indispensable. Network 11.0 ajoute une option « Flows Logging » directement dans les politiques de pare-feu.

05 / Automatisation

L’API UniFi Network

L’API UniFi Network officielle (dite « Integration API ») est servie par l’application Network elle-même. La référence 10.6.106 publie deux URL de base :

Points d’entrée de l’API Network 10.6.106
ModeURL de baseClé d’API
Localhttps://<console>/proxy/network/integrationCréée sur la console : section Integrations, « Create New API Key »
Distant (Cloud Connector)https://api.ui.com/v1/connector/consoles/<consoleId>/proxy/network/integrationCréée dans Site Manager : Settings > API Keys
  • Authentification : la clé passe dans l’en-tête X-API-KEY (l’exemple officiel l’écrit X-API-Key, les noms d’en-tête HTTP ne tenant pas compte de la casse). La clé n’est affichée qu’une fois et reste liée au compte ou à l’organisation qui l’a créée.
  • Lecture : GET /v1/info (version de l’application), GET /v1/sites (sites, dont l’ID sert à tous les autres appels), GET /v1/sites/{siteId}/devices, /clients (appareils et connexions VPN actives), /networks, /firewall/zones, /firewall/policies.
  • Pagination : offset (0 par défaut) et limit (25 par défaut, 200 au maximum). La réponse porte count et totalCount.
  • Filtrage : paramètre filter avec une syntaxe structurée, par exemple name.like('guest*').
  • Erreurs : objet JSON avec statusCode, statusName, code (par exemple api.authentication.missing-credentials) et message.

Premier test en lecture seule, sous PowerShell 7 (console à certificat auto-signé) :

Test de l’API en lecture seule
# Read-only: returns the UniFi Network application version
$key = Read-Host -AsSecureString -Prompt 'UniFi API key'
$plain = [System.Net.NetworkCredential]::new('', $key).Password
Invoke-RestMethod -Uri 'https://192.168.1.1/proxy/network/integration/v1/info' `
    -Headers @{ 'X-API-Key' = $plain; 'Accept' = 'application/json' } `
    -SkipCertificateCheck
Remove-Variable plain

Microsoft rappelle que -SkipCertificateCheck (disponible depuis PowerShell 6.0.0) désactive toute validation de certificat et n’est destiné qu’aux hôtes connus à certificat auto-signé. La bonne pratique reste d’installer sur la console un certificat émis par votre PKI interne ; Ubiquiti permet d’importer un certificat dans les réglages de la console (Control Plane > Console > Certificates sur UniFi OS Server).

06 / Script

Inventaire en lecture seule : Get-UniFiInventory.ps1

Le script Get-UniFiInventory.ps1 produit un inventaire CSV des sites, équipements (modèle, état, IP, firmware, mise à jour disponible), clients connectés, réseaux et, en option, zones et politiques de pare-feu. Il n’envoie que des requêtes GET vers les points d’accès documentés, suit la pagination jusqu’au totalCount, réessaie sur une réponse HTTP 429 et fonctionne en local comme via le Cloud Connector.

  • Compatible PowerShell 7 et Windows PowerShell 5.1. Sous PowerShell 7, -SkipCertificateCheck est passé à Invoke-RestMethod ; sous 5.1, le script installe temporairement un rappel de validation de certificat .NET et restaure l’ancien à la fin.
  • La clé est lue en SecureString (paramètre, variable d’environnement UNIFI_API_KEY ou saisie), jamais écrite sur disque ni affichée.
  • Un fichier CSV par type d’objet, horodaté, en UTF-8.
Get-UniFiInventory.ps1PowerShell · 388 lignesTélécharger
<#
.SYNOPSIS
    Read-only inventory of a UniFi Network application through the official
    UniFi Network Integration API (sites, devices, clients, networks and,
    optionally, firewall zones and policies). Exports one CSV per object type.

.DESCRIPTION
    The script only sends HTTP GET requests to the documented endpoints of the
    UniFi Network API (developer.ui.com, Network API v10.6.106):
        GET /v1/info
        GET /v1/sites
        GET /v1/sites/{siteId}/devices
        GET /v1/sites/{siteId}/clients
        GET /v1/sites/{siteId}/networks
        GET /v1/sites/{siteId}/firewall/zones      (with -IncludeFirewall)
        GET /v1/sites/{siteId}/firewall/policies   (with -IncludeFirewall)
    It never creates, updates or deletes anything on the console.

    Two connection modes are supported, matching the two server URLs published
    in the official OpenAPI specification:
      - Local   : https://<ConsoleHost>/proxy/network/integration
      - Remote  : https://api.ui.com/v1/connector/consoles/<ConsoleId>/proxy/network/integration
    Authentication uses an API key sent in the X-API-Key header. For the local
    mode, create the key in the Integrations section of the local UniFi Network
    application; for the remote mode, use a key created in UniFi Site Manager.

    Results are paginated by the API (offset/limit, limit max 200); the script
    follows totalCount until every item has been read.

.PARAMETER ConsoleHost
    IP address or FQDN of the UniFi console for the local mode, optionally with
    a port (for example 192.168.1.1 or unifi.example.local:443).

.PARAMETER ConsoleId
    Console ID for the remote mode through the Site Manager Cloud Connector.

.PARAMETER ApiKey
    API key as a SecureString. If omitted, the script reads the environment
    variable UNIFI_API_KEY, then prompts for the key.

.PARAMETER OutputDirectory
    Folder for the CSV files. Created if missing. Defaults to the current folder.

.PARAMETER PageSize
    Number of items requested per page (1 to 200). Defaults to 200.

.PARAMETER IncludeFirewall
    Also exports firewall zones and firewall policies (read-only GET calls).

.PARAMETER SkipCertificateCheck
    Accepts the self-signed certificate of a local console. Use only on a
    trusted management network. On PowerShell 7 this maps to the
    -SkipCertificateCheck parameter of Invoke-RestMethod; on Windows
    PowerShell 5.1 a temporary certificate validation callback is used and the
    previous callback is restored when the script ends.

.EXAMPLE
    $key = Read-Host -AsSecureString -Prompt 'UniFi API key'
    .\Get-UniFiInventory.ps1 -ConsoleHost 192.168.1.1 -ApiKey $key -SkipCertificateCheck -OutputDirectory C:\Audit\UniFi

.EXAMPLE
    $env:UNIFI_API_KEY = '<key>'   # session only, do not store in scripts
    .\Get-UniFiInventory.ps1 -ConsoleId '<console-id>' -IncludeFirewall

.NOTES
    Version : 1.0 (2026-10-26)
    Target  : UniFi Network API as documented for UniFi Network 10.6.106 on developer.ui.com
    Runtime : Windows PowerShell 5.1 and PowerShell 7.x
    Status  : parsed and checked with PSScriptAnalyzer; not executed against a
              UniFi console by the author. Test it on a lab site first.
    Read-only: GET requests only.
    Source: ANYSYS ICT, https://www.anysys.be/articles/
#>
[CmdletBinding(DefaultParameterSetName = 'Local')]
param(
    [Parameter(Mandatory = $true, ParameterSetName = 'Local')]
    [ValidateNotNullOrEmpty()]
    [string]$ConsoleHost,

    [Parameter(Mandatory = $true, ParameterSetName = 'Remote')]
    [ValidateNotNullOrEmpty()]
    [string]$ConsoleId,

    [System.Security.SecureString]$ApiKey,

    [string]$OutputDirectory = (Get-Location).Path,

    [ValidateRange(1, 200)]
    [int]$PageSize = 200,

    [switch]$IncludeFirewall,

    [switch]$SkipCertificateCheck
)

Set-StrictMode -Version 2.0
$ErrorActionPreference = 'Stop'

# ---------------------------------------------------------------------------
# Resolve the API key (parameter, then environment variable, then prompt)
# ---------------------------------------------------------------------------
if (-not $ApiKey) {
    if ($env:UNIFI_API_KEY) {
        $ApiKey = New-Object System.Security.SecureString
        foreach ($ch in $env:UNIFI_API_KEY.ToCharArray()) { $ApiKey.AppendChar($ch) }
        $ApiKey.MakeReadOnly()
    }
    else {
        $ApiKey = Read-Host -AsSecureString -Prompt 'UniFi API key'
    }
}

function ConvertTo-PlainText {
    param([Parameter(Mandatory = $true)][System.Security.SecureString]$Secure)
    $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Secure)
    try { return [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) }
    finally { [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) }
}

# ---------------------------------------------------------------------------
# Base URL (both forms come from the "servers" section of the OpenAPI spec)
# ---------------------------------------------------------------------------
if ($PSCmdlet.ParameterSetName -eq 'Local') {
    $baseUrl = 'https://{0}/proxy/network/integration' -f $ConsoleHost.TrimEnd('/')
    $target = $ConsoleHost
}
else {
    $baseUrl = 'https://api.ui.com/v1/connector/consoles/{0}/proxy/network/integration' -f $ConsoleId
    $target = "console $ConsoleId (Cloud Connector)"
}

$isCore = $PSVersionTable.PSEdition -eq 'Core'
$pageLimit = $PageSize
$previousCallback = $null
$previousProtocol = $null

if (-not $isCore) {
    # Windows PowerShell 5.1: make sure TLS 1.2 is offered
    $previousProtocol = [System.Net.ServicePointManager]::SecurityProtocol
    [System.Net.ServicePointManager]::SecurityProtocol = $previousProtocol -bor [System.Net.SecurityProtocolType]::Tls12
    if ($SkipCertificateCheck) {
        $previousCallback = [System.Net.ServicePointManager]::ServerCertificateValidationCallback
        [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
    }
}

function Invoke-UniFiGet {
    <# Sends one GET request with the API key header; retries on HTTP 429. #>
    param(
        [Parameter(Mandatory = $true)][string]$Uri,
        [Parameter(Mandatory = $true)][hashtable]$Headers
    )
    $params = @{ Uri = $Uri; Method = 'Get'; Headers = $Headers; ErrorAction = 'Stop' }
    if ($isCore -and $SkipCertificateCheck) { $params['SkipCertificateCheck'] = $true }

    for ($attempt = 1; $attempt -le 3; $attempt++) {
        try {
            return Invoke-RestMethod @params
        }
        catch {
            $status = $null
            $retryAfter = 5
            $response = Get-PropertyValue $_.Exception 'Response'
            if ($response) {
                $status = [int]$response.StatusCode
                try {
                    if ($isCore) {
                        # HttpResponseMessage (PowerShell 7)
                        $delta = $response.Headers.RetryAfter.Delta
                        if ($delta) { $retryAfter = [int][Math]::Ceiling($delta.TotalSeconds) }
                    }
                    else {
                        # HttpWebResponse (Windows PowerShell 5.1)
                        $ra = $response.Headers['Retry-After']
                        if ($ra) { $retryAfter = [int]("$ra".Trim()) }
                    }
                }
                catch { Write-Verbose 'No usable Retry-After header.' }
            }
            if ($status -eq 429 -and $attempt -lt 3) {
                Write-Warning "HTTP 429 on $Uri, waiting $retryAfter s (attempt $attempt of 3)."
                Start-Sleep -Seconds $retryAfter
                continue
            }
            $hint = switch ($status) {
                401 { 'API key missing or invalid.' }
                403 { 'API key valid but not allowed for this resource.' }
                404 { 'Endpoint not found: check the console address, the connection mode and the Network version.' }
                default { 'See the error message below.' }
            }
            throw "GET $Uri failed (HTTP $status). $hint $($_.Exception.Message)"
        }
    }
}

function Get-UniFiPagedItem {
    <# Reads every page of a list endpoint (offset/limit/totalCount). #>
    param(
        [Parameter(Mandatory = $true)][string]$Path,
        [Parameter(Mandatory = $true)][hashtable]$Headers
    )
    $items = New-Object System.Collections.Generic.List[object]
    $offset = 0
    do {
        $uri = '{0}{1}?offset={2}&limit={3}' -f $baseUrl, $Path, $offset, $pageLimit
        $page = Invoke-UniFiGet -Uri $uri -Headers $Headers
        foreach ($item in @($page.data)) { $items.Add($item) }
        $count = [int]$page.count
        $total = [int64]$page.totalCount
        $offset += $count
        Write-Verbose ("{0}: {1}/{2}" -f $Path, $offset, $total)
    } while ($count -gt 0 -and $offset -lt $total)
    return , $items.ToArray()
}

function Get-PropertyValue {
    <# Returns a property value or $null when the property is absent (StrictMode safe). #>
    param($InputObject, [Parameter(Mandatory = $true)][string]$Name)
    if ($null -eq $InputObject) { return $null }
    $prop = $InputObject.PSObject.Properties[$Name]
    if ($prop) { return $prop.Value }
    return $null
}

function ConvertTo-IsoText {
    <# PowerShell 7 turns ISO 8601 strings into DateTime; keep a stable ISO text in the CSV. #>
    param($Value)
    if ($Value -is [datetime]) { return $Value.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') }
    return $Value
}

# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
$plainKey = ConvertTo-PlainText -Secure $ApiKey
$headers = @{ 'X-API-Key' = $plainKey; 'Accept' = 'application/json' }

try {
    if (-not (Test-Path -LiteralPath $OutputDirectory)) {
        New-Item -ItemType Directory -Path $OutputDirectory -Force | Out-Null
    }
    $stamp = Get-Date -Format 'yyyyMMdd-HHmmss'

    $info = Invoke-UniFiGet -Uri ($baseUrl + '/v1/info') -Headers $headers
    $appVersion = Get-PropertyValue $info 'applicationVersion'
    Write-Host ("UniFi Network application {0} on {1}" -f $appVersion, $target)

    $sites = Get-UniFiPagedItem -Path '/v1/sites' -Headers $headers
    $siteRows = New-Object System.Collections.Generic.List[object]
    $deviceRows = New-Object System.Collections.Generic.List[object]
    $clientRows = New-Object System.Collections.Generic.List[object]
    $networkRows = New-Object System.Collections.Generic.List[object]
    $zoneRows = New-Object System.Collections.Generic.List[object]
    $policyRows = New-Object System.Collections.Generic.List[object]

    foreach ($site in $sites) {
        $siteId = $site.id
        $siteName = $site.name
        Write-Host "Site: $siteName ($siteId)"

        $devices = Get-UniFiPagedItem -Path "/v1/sites/$siteId/devices" -Headers $headers
        $deviceNames = @{}
        foreach ($d in $devices) {
            $deviceNames[[string]$d.id] = $d.name
            $deviceRows.Add([pscustomobject]@{
                    Site              = $siteName
                    Name              = $d.name
                    Model             = $d.model
                    State             = $d.state
                    IPAddress         = Get-PropertyValue $d 'ipAddress'
                    MACAddress        = Get-PropertyValue $d 'macAddress'
                    FirmwareVersion   = Get-PropertyValue $d 'firmwareVersion'
                    FirmwareUpdatable = Get-PropertyValue $d 'firmwareUpdatable'
                    Features          = (@(Get-PropertyValue $d 'features') -join ';')
                    Supported         = Get-PropertyValue $d 'supported'
                    DeviceId          = $d.id
                })
        }

        $clients = Get-UniFiPagedItem -Path "/v1/sites/$siteId/clients" -Headers $headers
        foreach ($c in $clients) {
            $uplinkId = [string](Get-PropertyValue $c 'uplinkDeviceId')
            $uplinkName = $null
            if ($uplinkId -and $deviceNames.ContainsKey($uplinkId)) { $uplinkName = $deviceNames[$uplinkId] }
            $clientRows.Add([pscustomobject]@{
                    Site        = $siteName
                    Name        = Get-PropertyValue $c 'name'
                    Type        = Get-PropertyValue $c 'type'
                    IPAddress   = Get-PropertyValue $c 'ipAddress'
                    MACAddress  = Get-PropertyValue $c 'macAddress'
                    ConnectedAt = ConvertTo-IsoText (Get-PropertyValue $c 'connectedAt')
                    UplinkName  = $uplinkName
                    UplinkId    = $uplinkId
                    ClientId    = $c.id
                })
        }

        $networks = Get-UniFiPagedItem -Path "/v1/sites/$siteId/networks" -Headers $headers
        foreach ($n in $networks) {
            $networkRows.Add([pscustomobject]@{
                    Site       = $siteName
                    Name       = $n.name
                    VlanId     = Get-PropertyValue $n 'vlanId'
                    Management = Get-PropertyValue $n 'management'
                    Enabled    = Get-PropertyValue $n 'enabled'
                    Default    = Get-PropertyValue $n 'default'
                    ZoneId     = Get-PropertyValue $n 'zoneId'
                    NetworkId  = $n.id
                })
        }

        if ($IncludeFirewall) {
            $zones = Get-UniFiPagedItem -Path "/v1/sites/$siteId/firewall/zones" -Headers $headers
            $zoneNames = @{}
            foreach ($z in $zones) {
                $zoneNames[[string]$z.id] = $z.name
                $zoneRows.Add([pscustomobject]@{
                        Site       = $siteName
                        Zone       = $z.name
                        Origin     = Get-PropertyValue (Get-PropertyValue $z 'metadata') 'origin'
                        NetworkIds = (@(Get-PropertyValue $z 'networkIds') -join ';')
                        ZoneId     = $z.id
                    })
            }
            $policies = Get-UniFiPagedItem -Path "/v1/sites/$siteId/firewall/policies" -Headers $headers
            foreach ($p in $policies) {
                $srcZone = [string](Get-PropertyValue (Get-PropertyValue $p 'source') 'zoneId')
                $dstZone = [string](Get-PropertyValue (Get-PropertyValue $p 'destination') 'zoneId')
                $policyRows.Add([pscustomobject]@{
                        Site            = $siteName
                        Index           = Get-PropertyValue $p 'index'
                        Name            = Get-PropertyValue $p 'name'
                        Enabled         = Get-PropertyValue $p 'enabled'
                        Action          = Get-PropertyValue (Get-PropertyValue $p 'action') 'type'
                        SourceZone      = $(if ($zoneNames.ContainsKey($srcZone)) { $zoneNames[$srcZone] } else { $srcZone })
                        DestinationZone = $(if ($zoneNames.ContainsKey($dstZone)) { $zoneNames[$dstZone] } else { $dstZone })
                        LoggingEnabled  = Get-PropertyValue $p 'loggingEnabled'
                        Origin          = Get-PropertyValue (Get-PropertyValue $p 'metadata') 'origin'
                        PolicyId        = $p.id
                    })
            }
        }

        $siteRows.Add([pscustomobject]@{
                Site               = $siteName
                InternalReference  = Get-PropertyValue $site 'internalReference'
                Devices            = @($devices).Count
                DevicesOffline     = @($devices | Where-Object { $_.state -ne 'ONLINE' }).Count
                FirmwareUpdatable  = @($devices | Where-Object { (Get-PropertyValue $_ 'firmwareUpdatable') -eq $true }).Count
                ConnectedClients   = @($clients).Count
                Networks           = @($networks).Count
                ApplicationVersion = $appVersion
                SiteId             = $siteId
            })
    }

    $exports = [ordered]@{
        'sites'    = $siteRows
        'devices'  = $deviceRows
        'clients'  = $clientRows
        'networks' = $networkRows
    }
    if ($IncludeFirewall) {
        $exports['firewall-zones'] = $zoneRows
        $exports['firewall-policies'] = $policyRows
    }

    foreach ($name in $exports.Keys) {
        $file = Join-Path $OutputDirectory ("unifi-{0}-{1}.csv" -f $name, $stamp)
        $rows = $exports[$name].ToArray()
        $rows | Export-Csv -LiteralPath $file -NoTypeInformation -Encoding UTF8
        Write-Host ("{0,-18} {1,6} rows -> {2}" -f $name, $rows.Length, $file)
    }

    $siteRows | Format-Table Site, Devices, DevicesOffline, FirmwareUpdatable, ConnectedClients, Networks -AutoSize
}
finally {
    $plainKey = $null
    $headers = $null
    if (-not $isCore) {
        if ($SkipCertificateCheck) {
            [System.Net.ServicePointManager]::ServerCertificateValidationCallback = $previousCallback
        }
        if ($null -ne $previousProtocol) {
            [System.Net.ServicePointManager]::SecurityProtocol = $previousProtocol
        }
    }
}
Exécution du script
# Read-only inventory of a local console with a self-signed certificate
$key = Read-Host -AsSecureString -Prompt 'UniFi API key'
.\Get-UniFiInventory.ps1 -ConsoleHost 192.168.1.1 -ApiKey $key -SkipCertificateCheck `
    -IncludeFirewall -OutputDirectory 'D:\Audit\UniFi'

# Same through the Site Manager Cloud Connector (key created in Site Manager)
.\Get-UniFiInventory.ps1 -ConsoleId '<console-id>' -ApiKey $key -OutputDirectory 'D:\Audit\UniFi'

Exemple de sortie (illustratif) :

Exemple de sortie (illustratif)
UniFi Network application 10.6.109 on 192.168.1.1
Site: Siege (5f1c...)
sites                   1 rows -> D:\Audit\UniFi\unifi-sites-20261026-101500.csv
devices                24 rows -> D:\Audit\UniFi\unifi-devices-20261026-101500.csv
clients               187 rows -> D:\Audit\UniFi\unifi-clients-20261026-101500.csv
networks                7 rows -> D:\Audit\UniFi\unifi-networks-20261026-101500.csv

Usages typiques : rapport mensuel des équipements hors ligne ou en attente de firmware, inventaire pour l’assurance cyber, comparaison des réseaux et des zones avec la documentation de conception.

07 / Pièges

Pièges

  • Sauvegarde jamais restaurée : une sauvegarde non testée n’est qu’une hypothèse. Planifiez un test annuel.
  • Compte propriétaire unique : il porte les sauvegardes cloud, le SD-WAN et les abonnements. Sa perte bloque tout : compte d’entreprise, MFA, procédure de récupération documentée.
  • Mise à jour majeure en production le jour de sa sortie : la 11.0.97 a été publiée en déploiement progressif et exige un UniFi OS 6.0 pas encore stable sur toutes les consoles. Attendez, testez, puis déployez.
  • Clé d’API dans un script ou une tâche planifiée en clair : utilisez un coffre (SecretManagement, Credential Manager, coffre de mots de passe) et un compte de service dédié.
  • Port de l’API sur UniFi OS Server : l’URL locale documentée ne précise pas de port ; si votre instance écoute sur un port non standard, indiquez-le dans -ConsoleHost (hôte:port). À vérifier dans la documentation locale de votre version (Integrations).
  • SIEM incomplet : les journaux de trafic ne partent pas encore en CEF ; si vous en avez besoin, NetFlow/IPFIX ou l’export CSV sont les seules voies documentées.

08 / Contrôle

Vérification

  1. 01

    Sauvegarde cloud automatique active et dernière sauvegarde datée de moins de 7 jours ; sauvegarde locale présente dans le stockage de sauvegarde.

  2. 02

    Restauration testée sur une console de laboratoire, avec procès-verbal.

  3. 03

    Canaux de mise à jour sur Official, mises à jour automatiques conformes à la politique, site pilote identifié.

  4. 04

    SIEM : réception d’un événement CEF de test (connexion administrateur), règle d’alerte déclenchée.

  5. 05

    Script d’inventaire : exécuté avec succès, CSV archivés, écarts avec la documentation traités.

09 / Références

Sources