~/2025/08/13/powershell-netapp-discover-every-volume-over-80-percent-capacity.md
PowerShell: NetApp – Discover Every Volume Over 80 Percent Capacity
--- author: Tom Lasswell 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 Basicand-SkipCertificateCheckonInvoke-RestMethod). - ONTAP 9.9 or later on every cluster; that's the release where the volume
space.percent_usedfield used here appears in the REST API. - HTTPS reachability from wherever the script runs to each cluster management LIF.
- A cluster login with the
httpapplication and the built-inreadonlyrole. Create it once per cluster:
cluster1::> security login create -vserver <cluster-admin-svm> -user-or-group-name capacity-report -application http -authentication-method password -role readonly
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
Clusters | String | Yes | Cluster management hostnames or IPs to sweep. |
Credential | PSCredential | No | Credential used against every cluster. Prompts if omitted. |
ThresholdPercent | Int | No | Minimum space.percent_used to report a volume. Defaults to 80. |
SkipCertificateCheck | Switch | No | Accepts self-signed cluster certificates. Use only on a management network you trust. |
OutputPath | String | No | If set, writes the results to this CSV path in addition to the console. |
Usage
Sweep two clusters with the default 80 percent threshold:
.\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:
$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:
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):
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
<#
.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_usedas the percentage of the volume size that is used,space.usedas the virtual space used including volume reserves, before storage efficiency, andspace.sizeas the total provisioned size. The reference also definesspace.afs_totalas the active file system size excluding snapshot reserve, so a volume with a largeSnapReservePcthas less room for data thanSizeGBsuggests; 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), sospace.percent_used=>=80makes each cluster return only the volumes that matter instead of every volume in the fleet. Requesting explicitfieldsalso 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=500keeps each page modest. - FlexGroups.
GET /api/storage/volumesdefaults tois_constituent=false, so a FlexGroup is reported once, with the aggregates of its constituents in theAggregatecolumn, rather than as dozens of constituent rows. - Offline and restricted volumes are skipped by the
state=onlinefilter. A volume that's offline for a migration shouldn't show up as a capacity problem. - Certificates. Clusters ship with self-signed certificates.
-SkipCertificateCheckgets 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
$Credentialwith a hashtable of credentials keyed by cluster name. - Prefer the Toolkit? NetApp's PowerShell Toolkit is now the
NetApp.ONTAPmodule 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-ONTAPIparameter forces ONTAPI. It also warns that some properties are missing when a cmdlet runs over REST, so property paths from oldGet-NcVolscripts (VolumeSpaceAttributes.PercentageSizeUsedand 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/aggregatesthe same way if you want the full picture.
Source
- ONTAP REST API reference: Retrieve volumes (GET /api/storage/volumes)
- Input variables for an ONTAP REST API request (filters, fields, max_records, paging)
- ONTAP CLI: volume modify (nearly-full and full threshold defaults)
- ONTAP CLI: security login create
- Deferral of ONTAPI (ZAPI) end of availability (NetApp KB)
- NetApp.ONTAP on the PowerShell Gallery
- Learn about the NetApp ONTAP PowerShell Toolkit