~/2025/08/13/powershell-netapp-discover-every-volume-over-80-percent-capacity.md

PowerShell: NetApp – Discover Every Volume Over 80 Percent Capacity

---
author: 
date: 
read: 4 min
in:   [ps, scripts, dotps]
tags: [powershell, netapp, ontap, discovery]
---

$ grep -n '^#' post.md

Capacity alerts from Active IQ Unified Manager are useful, but they're per-cluster and per-threshold-policy, which means the thresholds drift over time as different admins tune different systems. ONTAP itself only raises EMS events at each volume's own nearly-full and full thresholds (95% and 98% by default). When I want one honest answer to "which volumes across the whole fleet are getting tight," I skip the monitoring stack and ask every cluster directly, with the same bar applied everywhere.

The first version of this script used the Data ONTAP PowerShell Toolkit (Connect-NcController, Get-NcVol), which spoke ONTAPI (ZAPI) under the hood. NetApp has since replaced ZAPI with the ONTAP REST API as the primary automation interface: from ONTAP 9.14, ONTAPI is disabled automatically if no ONTAPI calls are seen for 30 days after an upgrade. So this version calls GET /api/storage/volumes directly with Invoke-RestMethod. No module to install, and the filtering happens on the cluster.

Requirements

  • PowerShell 7 (for -Authentication Basic and -SkipCertificateCheck on Invoke-RestMethod).
  • ONTAP 9.9 or later on every cluster; that's the release where the volume space.percent_used field used here appears in the REST API.
  • HTTPS reachability from wherever the script runs to each cluster management LIF.
  • A cluster login with the http application and the built-in readonly role. Create it once per cluster:
text
cluster1::> security login create -vserver <cluster-admin-svm> -user-or-group-name capacity-report -application http -authentication-method password -role readonly

Parameters

NameTypeRequiredDescription
ClustersStringYesCluster management hostnames or IPs to sweep.
CredentialPSCredentialNoCredential used against every cluster. Prompts if omitted.
ThresholdPercentIntNoMinimum space.percent_used to report a volume. Defaults to 80.
SkipCertificateCheckSwitchNoAccepts self-signed cluster certificates. Use only on a management network you trust.
OutputPathStringNoIf set, writes the results to this CSV path in addition to the console.

Usage

Sweep two clusters with the default 80 percent threshold:

powershell
.\Get-NetAppVolumesOverThreshold.ps1 -Clusters "cluster1-mgmt.example.com", "cluster2-mgmt.example.com"

Sweep every cluster in a list, lower the threshold to catch volumes trending toward full, and export a CSV for the storage review:

powershell
$cred = Get-Credential -UserName "capacity-report" -Message "ONTAP read-only login"
$clusterList = Get-Content -Path "C:\Scripts\clusters.txt"
.\Get-NetAppVolumesOverThreshold.ps1 -Clusters $clusterList -Credential $cred -ThresholdPercent 75 -OutputPath "C:\Reports\volumes-over-threshold.csv"

Sample output:

text
Querying cluster1-mgmt.example.com ...
Querying cluster2-mgmt.example.com ...

Cluster                   Svm          Volume           Style    SizeGB UsedGB AvailGB PercentUsed NearlyFull Aggregate
-------                   ---          ------           -----    ------ ------ ------- ----------- ---------- ---------
cluster1-mgmt.example.com svm_prod01   vol_sql_logs     flexvol     512    492      20          96       True aggr1_n1
cluster1-mgmt.example.com svm_prod01   vol_app_data     flexvol    2048   1782     266          87      False aggr1_n2
cluster2-mgmt.example.com svm_files02  vol_home_shares  flexgroup  4096   3379     717          82      False aggr1_n1,aggr1_n2

3 volume(s) at or above 80% used across 2 cluster(s).
Results written to C:\Reports\volumes-over-threshold.csv

The same question as a single REST call, useful for checking one cluster by hand (the >= operator is URL-encoded as %3E%3D):

powershell
Invoke-RestMethod -Uri "https://cluster1-mgmt.example.com/api/storage/volumes?state=online&space.percent_used=%3E%3D80&fields=name,svm.name,space.percent_used" -Authentication Basic -Credential $cred |
    Select-Object -ExpandProperty records |
    Select-Object -Property name, @{ Name = 'svm'; Expression = { $_.svm.name } }, @{ Name = 'percent_used'; Expression = { $_.space.percent_used } }

Script

powershell
<#
.SYNOPSIS
    Reports every volume across a set of NetApp ONTAP clusters above a capacity threshold.
.DESCRIPTION
    Calls GET /api/storage/volumes on each cluster through the ONTAP REST API, asking the cluster to
    return only online volumes whose space.percent_used is at or above the threshold, with just the
    fields the report needs. Follows _links.next for large result sets. Reports FlexVol and FlexGroup
    volumes (not FlexGroup constituents), flags volumes past their own nearly-full threshold, and
    applies the same bar to every cluster regardless of how each cluster's alerting is configured.
.PARAMETER Clusters
    Cluster management hostnames or IPs to sweep.
.PARAMETER Credential
    Credential used against every cluster. Prompts if omitted.
.PARAMETER ThresholdPercent
    Minimum space.percent_used to report a volume. Defaults to 80.
.PARAMETER SkipCertificateCheck
    Accepts self-signed cluster certificates. Use only on a management network you trust.
.PARAMETER OutputPath
    If set, writes the results to this CSV path in addition to the console.
.EXAMPLE
    .\Get-NetAppVolumesOverThreshold.ps1 -Clusters "cluster1-mgmt.example.com", "cluster2-mgmt.example.com" -ThresholdPercent 75
.NOTES
    Author  : Thomas Lasswell (https://www.techcolumnist.com)
    Version : 1.0 (2025-08-13)
    Requires: PowerShell 7, ONTAP 9.9 or later, a login with the http application and readonly role
#>
#Requires -Version 7.0
[CmdletBinding()]
param (
    [Parameter(Mandatory = $true)]
    [string[]]$Clusters,

    [Parameter(Mandatory = $false)]
    [System.Management.Automation.PSCredential]$Credential,

    [Parameter(Mandatory = $false)]
    [ValidateRange(1, 100)]
    [int]$ThresholdPercent = 80,

    [Parameter(Mandatory = $false)]
    [switch]$SkipCertificateCheck,

    [Parameter(Mandatory = $false)]
    [string]$OutputPath
)

if (-not $Credential) {
    $Credential = Get-Credential -Message "ONTAP read-only login used for every cluster"
}

$fields = @(
    'name', 'svm.name', 'state', 'style', 'aggregates.name',
    'space.size', 'space.used', 'space.available', 'space.percent_used',
    'space.snapshot.reserve_percent', 'space.nearly_full_threshold_percent'
) -join ','

$restParams = @{
    Method         = 'Get'
    Authentication = 'Basic'
    Credential     = $Credential
    ErrorAction    = 'Stop'
}
if ($SkipCertificateCheck) {
    $restParams['SkipCertificateCheck'] = $true
}

$results = foreach ($cluster in $Clusters) {
    Write-Host "Querying $cluster ..."

    # Filter on the cluster: online volumes at or above the threshold. ">=" is URL-encoded.
    $nextPath = "/api/storage/volumes?state=online&space.percent_used=%3E%3D$ThresholdPercent&max_records=500&fields=$fields"

    while ($nextPath) {
        try {
            $response = Invoke-RestMethod -Uri "https://$cluster$nextPath" @restParams
        } catch {
            Write-Warning "Could not query ${cluster}: $($_.Exception.Message)"
            break
        }

        foreach ($volume in $response.records) {
            $space = $volume.space
            $nearlyFullAt = if ($space.nearly_full_threshold_percent) { $space.nearly_full_threshold_percent } else { 95 }

            [PSCustomObject]@{
                Cluster        = $cluster
                Svm            = $volume.svm.name
                Volume         = $volume.name
                Style          = $volume.style
                SizeGB         = [math]::Round($space.size / 1GB, 0)
                UsedGB         = [math]::Round($space.used / 1GB, 0)
                AvailGB        = [math]::Round($space.available / 1GB, 0)
                PercentUsed    = $space.percent_used
                NearlyFull     = $space.percent_used -ge $nearlyFullAt
                SnapReservePct = $space.snapshot.reserve_percent
                Aggregate      = ($volume.aggregates.name | Sort-Object -Unique) -join ','
            }
        }

        # ONTAP returns a relative next link while more records remain.
        $nextPath = $response._links.next.href
    }
}

$results = @($results | Sort-Object -Property PercentUsed -Descending)

$results | Format-Table -Property Cluster, Svm, Volume, Style, SizeGB, UsedGB, AvailGB, PercentUsed, NearlyFull, Aggregate -AutoSize

Write-Host ""
Write-Host "$($results.Count) volume(s) at or above $ThresholdPercent% used across $($Clusters.Count) cluster(s)."

if ($OutputPath) {
    $results | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
    Write-Host "Results written to $OutputPath"
}

Notes

  • What the numbers mean. NetApp's REST reference defines space.percent_used as the percentage of the volume size that is used, space.used as the virtual space used including volume reserves, before storage efficiency, and space.size as the total provisioned size. The reference also defines space.afs_total as the active file system size excluding snapshot reserve, so a volume with a large SnapReservePct has less room for data than SizeGB suggests; the column is in the CSV for that reason.
  • Why filter on the cluster. The ONTAP REST API accepts operators after the equals sign (<field>=>=<value>, plus <, >, <=, ! and * wildcards), so space.percent_used=>=80 makes each cluster return only the volumes that matter instead of every volume in the fleet. Requesting explicit fields also keeps the response small. By default a GET returns only identifying fields.
  • Paging. ONTAP returns up to 10,000 records per GET by default and stops early if it hits the 15-second return_timeout. When more records remain, the response carries _links.next.href, and the loop follows it. max_records=500 keeps each page modest.
  • FlexGroups. GET /api/storage/volumes defaults to is_constituent=false, so a FlexGroup is reported once, with the aggregates of its constituents in the Aggregate column, rather than as dozens of constituent rows.
  • Offline and restricted volumes are skipped by the state=online filter. A volume that's offline for a migration shouldn't show up as a capacity problem.
  • Certificates. Clusters ship with self-signed certificates. -SkipCertificateCheck gets you running, but installing a CA-signed certificate on each cluster management LIF is the better fix, because the script sends a password with every request.
  • Different logins per cluster. The script uses one credential everywhere, which is why a dedicated read-only login created with the same name on each cluster is worth the minute it takes. If your clusters use different accounts, replace $Credential with a hashtable of credentials keyed by cluster name.
  • Prefer the Toolkit? NetApp's PowerShell Toolkit is now the NetApp.ONTAP module on the PowerShell Gallery (Install-Module NetApp.ONTAP, Windows PowerShell 5.1 or PowerShell 7.3.4 and later). Its installation guide says that on ONTAP 9.10 or later a cmdlet uses the REST API by default when a REST equivalent exists, and falls back to ONTAPI otherwise; the -ONTAPI parameter forces ONTAPI. It also warns that some properties are missing when a cmdlet runs over REST, so property paths from old Get-NcVol scripts (VolumeSpaceAttributes.PercentageSizeUsed and friends) are worth re-testing before you trust them.
  • This reports volume capacity, not aggregate capacity. A volume can look fine while its aggregate is nearly full, especially with thin provisioning; sweep /api/storage/aggregates the same way if you want the full picture.

Source