~/2014/01/21/powershell-gather-netapp-snapshot-details.md

PowerShell: NetApp – Gather Snapshot Details

---
author: 
date: 
updated: 
read: 1 min
in:   [dotps, ps, scripts]
tags: [netapp, powershell, snapshots]
---

$ grep -n '^#' post.md

Gathering snapshot statistics is a tedious task when looking at autosupports and cli output. I needed to gather information about oldest snapshot, average number of snaps per day, total snapshots, and other various information.

This PowerShell script uses the Data ONTAP PowerShell Toolkit to collect that information for every online volume on each 7-Mode controller and writes a summary CSV and a detail CSV.

Requirements

  • Data ONTAP PowerShell Toolkit 1.2 or higher (the DataONTAP module)
  • NetApp 7-Mode controllers reachable over HTTPS
  • An account that can list volumes and snapshots on each controller
  • Write access to the folder the script runs from (the CSV files are saved next to the script)

Parameters

NameTypeRequiredDescription
nodesArrayYesControllers to query (host names or IPs). If omitted, PowerShell prompts for one per line; press Enter on an empty line to continue.
usernameStringYesAccount used to connect, for example domain\username.
passwordSecureStringYesPassword for the account. Prompted for if omitted.
IsVerboseSwitchNoAlso prints a detail table of every snapshot per volume.

Usage

Pass the controllers and account on the command line. The password is prompted for:

powershell
& '.\NetApp-Gather Snapshot Information v1.1.ps1' -nodes filer1.company.biz,filer2.company.biz -username "domain\username" -IsVerbose

Or run it with no parameters and answer the prompts:

powershell
& '.\NetApp-Gather Snapshot Information v1.1.ps1'
text
cmdlet NetApp-Gather Snapshot Information v1.1.ps1 at command pipeline position 1
Supply values for the following parameters:
nodes[0]: filer1.company.biz
nodes[1]: filer2.company.biz
nodes[2]:
username: domain\username
password: *************

Script

Original (v1.1)

powershell
<#
.SYNOPSIS
    NetApp-Gather Snapshot Information
.DESCRIPTION
    Collects snapshot information, summary and detail, for every online volume
    on each 7-Mode controller, including created time, total snapshots, total
    days, average per day and oldest snapshot. Saves a Summary and a Detail CSV
    next to the script.
.PARAMETER nodes
    Controllers to query, one per entry.
.PARAMETER username
    Account used to connect, for example domain\username.
.PARAMETER password
    Password for the account, as a SecureString.
.PARAMETER IsVerbose
    Display snapshot detail for each volume.
.EXAMPLE
    & '.\NetApp-Gather Snapshot Information v1.1.ps1' -nodes filer1.company.biz,filer2.company.biz -username "domain\username" -IsVerbose
.EXAMPLE
    & '.\NetApp-Gather Snapshot Information v1.1.ps1'
.NOTES
    Author  : Thomas Lasswell (https://www.techcolumnist.com)
    Version : 1.2 (2026-09-20)
    Requires: Data ONTAP PowerShell Toolkit 1.2 or higher
    History : 1.0 2014-01-17 Initial version.
              1.1 2014-01-21 Added CSV export, days old column fixed.
              1.2 2026-09-20 Fixed the timestamp in the CSV file names (it used minutes
                  where the month belongs).
#>
# Parameters: nodes is each node; when finished, press Enter and it will continue.
param(
    [Parameter(Mandatory = $true)]
    [Array]$nodes,
    [Parameter(Mandatory = $true)]
    [String]$username,
    [Parameter(Mandatory = $true, ParameterSetName = 'Secret')]
    [Security.SecureString]$password,
    [switch]$IsVerbose
)

# Create output file names
$exedir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$currentDate = (Get-Date -Format yyyyMMdd.HHmmss)
$outsummary = ($exedir + "\" + "GatherSnapshotInformation_" + $currentDate + "_Summary.csv")
$outdetails = ($exedir + "\" + "GatherSnapshotInformation_" + $currentDate + "_Detail.csv")

# Load the ONTAP PowerShell Toolkit
$module = Get-Module DataONTAP
if ($module -eq $null) {
    Import-Module DataONTAP
}

try {
    $requiredVersion = New-Object System.Version(1.2)
    if ((Get-NaToolkitVersion).CompareTo($requiredVersion) -lt 0) { throw }
} catch [Exception] {
    Write-Host "`nThis script requires Data ONTAP PowerShell Toolkit 1.2 or higher`n" -ForegroundColor Red
    return
}
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $username, $password

# Calculate the difference between two dates
function Get-DateDiff {
    param (
        [CmdletBinding()]
        [parameter(Mandatory = $true)]
        [datetime]$date1,
        [parameter(Mandatory = $true)]
        [datetime]$date2
    )
    if ($date2 -gt $date1) {
        $diff = $date2 - $date1
    } else {
        $diff = $date1 - $date2
    }
    $diff
}

# Declare object arrays
$objDetail = @()
$objSummary = @()

# Connect to each node individually
foreach ($node in $nodes) {
    Write-Host "connecting to node $node..."
    $conn = Connect-NaController -Name $node -HTTPS -Credential $cred

    if ($conn -ne $null) {
        Write-Host "node connected, continuing on to snapshot calculations..."
        Write-Host "gathering node volumes..."
        $vols = Get-NaVol | Where-Object { $_.state -eq "online" -and $_.raidstatus -notmatch "read-only" }
        if ($vols -ne $null) {
            foreach ($vol in $vols) {
                Write-Host "gathering snapshots for volume $vol..."
                # Get snapshots
                $snaps = Get-NaSnapshot -TargetName $vol

                # Group to count snapshots per day
                $snapsdatecount = $snaps | Group-Object { ((Get-Date) - $_.Created).Days } -NoElement | Sort-Object Name -Descending
                # Measure to get count and average; the average is snapshots per day
                $totalavgdays = $snapsdatecount | Measure-Object Count -Average | Select-Object Count, Average
                $avgdays = $totalavgdays.Average
                $totaldays = $totalavgdays.Count

                # Sum snapshots for total snapshot consumption
                $totalsize = $snaps | Measure-Object Total -Sum | Select-Object Count, Sum
                $ftotalsize = ConvertTo-FormattedNumber $totalsize.sum DataSize "0.0"

                # Get oldest snapshot
                $foldestsnap = $snaps | Sort-Object Created -Descending | Select-Object -Last 1

                # Build array for summary
                if ($avgdays -ne $null) {
                    $summaryprop = @{
                        'Node'       = $node
                        'Volume'     = $vol
                        'TotalSnaps' = $snaps.length
                        'TotalDays'  = $totaldays
                        'AvgPerDay'  = $avgdays
                        'TotalSize'  = $ftotalsize
                        'Oldest'     = $foldestsnap.created
                    }
                    $objectS = New-Object -TypeName PSObject -Property $summaryprop
                    $objSummary += $objectS
                    $objectS

                    foreach ($snap in $snaps) {
                        $daysold = Get-DateDiff (Get-Date) $snap.created
                        $ftotal = ConvertTo-FormattedNumber $snap.total DataSize "0.0"
                        $fcumulative = ConvertTo-FormattedNumber $snap.CumulativeTotal DataSize "0.0"

                        # Build array for details
                        $detailprop = @{
                            'Node'            = $node
                            'Volume'          = $vol
                            'Name'            = $snap.name
                            'Created'         = $snap.created
                            'DaysOld'         = $daysold.days
                            'TotalSize'       = $ftotal
                            'CumulativeTotal' = $fcumulative
                        }
                        $objectD = New-Object -TypeName PSObject -Property $detailprop
                        $objDetail += $objectD
                    }
                    if ($IsVerbose) {
                        $snaps | Format-Table `
                            @{Expression = {$node}; Label = "Node name"; Width = 20},`
                            @{Expression = {$vol}; Label = "Volume"; Width = 40},`
                            @{Expression = {$_.Name}; Label = "Name"; Width = 150},`
                            @{Expression = {$_.Created.ToShortDateString()}; Label = "Created"; Width = 12},`
                            @{Expression = {'{0} Days' -f (Get-DateDiff (Get-Date) $_.created).days}; Label = "Days Old"; Width = 20},`
                            @{Expression = {ConvertTo-FormattedNumber $_.Total DataSize "0.0"}; Label = "Total"; Width = 15},`
                            @{Expression = {ConvertTo-FormattedNumber $_.CumulativeTotal DataSize "0.0"}; Label = "Cumulative"; Width = 10} -AutoSize `
                            | Out-String -Width 1000 | Write-Host
                    }
                }
                if ($avgdays -eq $null) { Write-Host "`tNo Snapshots Exist on $vol..." }
            }
        }
    }
}

# Save files
$objSummary | Select-Object Node, Volume, TotalSnaps, TotalDays, AvgPerDay, TotalSize, Oldest | Export-Csv -Path $outsummary -NoTypeInformation
$objDetail | Select-Object Node, Volume, Name, Created, DaysOld, TotalSize, CumulativeTotal | Export-Csv -Path $outdetails -NoTypeInformation

ChatGPT rewrite

I was curious to see what ChatGPT code interpreter would do with this code and this is its output.

powershell
<#
.SYNOPSIS
    NetApp-Gather Snapshot Information
.DESCRIPTION
    Collects snapshot information, summary and detail, including created time,
    total snapshots, total days, average per day and oldest snapshot.
.PARAMETER nodes
    Controllers to query, one per entry.
.PARAMETER username
    Account used to connect, for example domain\username.
.PARAMETER password
    Password for the account, as a SecureString.
.PARAMETER IsVerbose
    Display snapshot detail for each volume.
.NOTES
    Author  : Thomas Lasswell (https://www.techcolumnist.com)
    Version : 1.2 (2026-09-20), rewritten by ChatGPT
    Requires: Data ONTAP PowerShell Toolkit 1.2 or higher
#>
param(
    [Parameter(Mandatory = $true)]
    [Array]$nodes,
    [Parameter(Mandatory = $true)]
    [String]$username,
    [Parameter(Mandatory = $true, ParameterSetName = 'Secret')]
    [Security.SecureString]$password,
    [switch]$IsVerbose
)

$exedir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$currentDate = (Get-Date -Format "yyyyMMdd.HHmmss")
$outsummary = -join ($exedir, "\GatherSnapshotInformation_", $currentDate, "_Summary.csv")
$outdetails = -join ($exedir, "\GatherSnapshotInformation_", $currentDate, "_Detail.csv")

Import-Module DataONTAP -ErrorAction SilentlyContinue

try {
    $requiredVersion = New-Object System.Version(1.2)
    if ((Get-NaToolkitVersion).CompareTo($requiredVersion) -lt 0) { throw "This script requires Data ONTAP PowerShell Toolkit 1.2 or higher." }
} catch {
    Write-Host "`nThis script requires Data ONTAP PowerShell Toolkit 1.2 or higher`n" -ForegroundColor Red
    return
}

$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $username, $password

function Get-DateDiff {
    param (
        [parameter(Mandatory = $true)]
        [datetime]$date1,
        [parameter(Mandatory = $true)]
        [datetime]$date2
    )
    return [Math]::Abs(($date2 - $date1).Days)
}

$objDetail = @()
$objSummary = @()

foreach ($node in $nodes) {
    Write-Host "Connecting to node $node..."
    $conn = Connect-NaController -Name $node -HTTPS -Credential $cred

    if ($conn) {
        Write-Host "Node connected, continuing on to snapshot calculations..."
        $vols = Get-NaVol | Where-Object { $_.state -eq "online" -and $_.raidstatus -notmatch "read-only" }

        foreach ($vol in $vols) {
            $snaps = Get-NaSnapshot -TargetName $vol
            $snapsdatecount = $snaps | Group-Object { ((Get-Date) - $_.Created).Days } -NoElement | Sort-Object Name -Descending
            $totalavgdays = $snapsdatecount | Measure-Object Count -Average | Select-Object Count, Average
            $totalsize = $snaps | Measure-Object Total -Sum | Select-Object Count, Sum
            $foldestsnap = $snaps | Sort-Object Created -Descending | Select-Object -Last 1

            if ($totalavgdays.Average) {
                $summaryprop = @{
                    'Node'       = $node
                    'Volume'     = $vol
                    'TotalSnaps' = $snaps.length
                    'TotalDays'  = $totalavgdays.Count
                    'AvgPerDay'  = $totalavgdays.Average
                    'TotalSize'  = [System.Math]::Round($totalsize.Sum, 2)
                    'Oldest'     = $foldestsnap.created
                }
                $objSummary += New-Object -TypeName PSObject -Property $summaryprop

                foreach ($snap in $snaps) {
                    $daysold = Get-DateDiff (Get-Date) $snap.created
                    $detailprop = @{
                        'Node'            = $node
                        'Volume'          = $vol
                        'Name'            = $snap.name
                        'Created'         = $snap.created
                        'DaysOld'         = $daysold
                        'TotalSize'       = [System.Math]::Round($snap.total, 2)
                        'CumulativeTotal' = [System.Math]::Round($snap.CumulativeTotal, 2)
                    }
                    $objDetail += New-Object -TypeName PSObject -Property $detailprop
                }
            } else {
                Write-Host "`tNo Snapshots Exist on $vol..."
            }
        }
    }
}

$objSummary | Export-Csv -Path $outsummary -NoTypeInformation
$objDetail | Export-Csv -Path $outdetails -NoTypeInformation

Notes

  • Output is two CSV files next to the script: GatherSnapshotInformation_<date>_Summary.csv (one row per volume) and GatherSnapshotInformation_<date>_Detail.csv (one row per snapshot).
  • The ChatGPT version is a straight rewrite of the original and has not been run against a filer. It is not a drop-in replacement: it drops the -IsVerbose table output and formats sizes with [Math]::Round instead of ConvertTo-FormattedNumber.
  • Update 2026-09-20 (v1.2): the CSV file name timestamp was yyyymmdd.Hm.s, where mm is minutes, so it never contained the month. It is now yyyyMMdd.HHmmss. The ChatGPT rewrite also grouped snapshots by $_.Created.Days, which does not exist on a date, so every snapshot landed in one group; it now groups by age in days like the original.