Using PowerShell to Generate Reports for 7-Mode Systems (Get-NaVolumeReport)

I wanted a report - in one worksheet - containing specific bits of information regards volumes on NetApp 7-Mode controllers, to aid with planning some 7 to C migrations. The report only concerns itself with volumes that are read/write (ignores readonly snapmirror destination volumes) and has the following columns:

- Root (Is this volume the root vol?)
- Source Controller
- Source Volume (Any read/write volume, whether it has a snapmirror destination or not)
- Destinations Count
- Destination Controller(s)
- Destination Volume(s)
- SV Primary Count (Count of SnapVaults where this volume is Primary/Source)
- SV Secondary Count (Count of SnapVaults where this volume is Secondary/Destination)
- Files Used (Files in the volume)
- Size Total (GB)
- Size Used (GB)
- Qtree Count
- Vol Security Style
- Shares Count (Shares in the volume)
- Exports Count (Exports in the volume)
- LUNs Count (LUNs in the volume)
- Vfilers*

*This just detects if the controller has vFilers or not. The script as written does not handle vFilers (for my requirement I had no need.)

There are two modes: -Collect and -Report. The first collects the data and saves as XML. The second reports the data using the saved XMLs for input, and exports to CSV.


.\Get-NaVolumeReport.ps1 -Collect
.\Get-NaVolumeReport.ps1 -Report


It requires a file (default is “Controllers.txt”) with 7-mode controller names/IPs in. And you’ll want to use Add-NaCredential to add 7-Mode controller credentials into your PowerShell credentials cache first.

Image: An example of the CSV formatted in Excel (the data is intentionally blurred)
The Script


########################
## Get-NaVolumeReport ##
########################

## TIP: Add controller credentials with Add-NaCredential ##
Param(
  [Switch]$Collect, # Use this switch to collect data
  [Switch]$Report,  # Use this switch to generate CSVs
  [String]$Controllers_Text_File = "Controllers.txt"
)

If(!(Test-Path $Controllers_Text_File)){"ERROR: Failed to load $Controllers_Text_File!";EXIT}
[System.Array]$Controllers = Get-Content $Controllers_Text_File

## INPUTS ##
[System.Array]$Gets = ,
"Get-NaVol",
"Get-NaSnapMirror",
"Get-NaSnapmirrorDestination",
"Get-NaSnapvaultPriDestinations",
"Get-NaSnapVault",
"Get-NaQtree",
"Get-NaCifsShare",
"Get-NaNfsExport",
"Get-NaVfiler",
"Get-NaLun"

## COLLECT FUNCTION ##
Function Collect{
  "INFO: Finding the root vol"
  $VolRoot = (Get-NaVolRoot).Name
  If($VolRoot){$VolRoot > "$Ctr\$Ctr.volroot.txt"}
  Foreach($Get in $Gets){
    "INFO: Collecting $Get"
    [System.Array]$NaData = Invoke-Expression ([String]$Get)
    If($NaData){$NaData | Export-CLIXML "$Ctr\$Ctr.$Get.XML"}
  }
}

## GENERIC ADD MEMBER FUNCTION ##
Function AddM{
  Param([System.Object]$O,[String]$N,[String]$V)
  Add-Member -InputObject $O -MemberType NoteProperty -Name $N -Value $V
}

## REPORT FUNCTION ##
Function Report{
  ## ACQUIRE ROOT VOL INFO ##
  [String]$VolRoot = ""
  If(Test-Path "$Ctr\$Ctr.volroot.txt"){[String]$VolRoot = Get-Content "$Ctr\$Ctr.volroot.txt"}
 
  ## ACQUIRE XMLs ##
  [System.Object]$CtrInfo = @{}
  Foreach($Get in $Gets){
    If(Test-Path "$Ctr\$Ctr.$Get.xml"){$CtrInfo."$Get" = Import-CLIXML "$Ctr\$Ctr.$Get.XML" }
  }
 
  ## ACQUIRE SNAPMIRRORED VOLS ##
  [System.Array]$SnapMirroredVols = @()
  If($CtrInfo."Get-NaSnapMirror"){
    [System.Array]$SnapMirroredVols = ($CtrInfo."Get-NaSnapMirror" | Where{$_.state -eq "snapmirrored"}).Destination
    If($SnapMirroredVols){$SnapMirroredVols = $SnapMirroredVols | Foreach{$_.Split(":")[1]}}
  }
 
  ## CYCLE THROUGH VOLUMES ##
  Foreach($Vol in $CtrInfo."Get-NaVol"){
    [String]$VolName = $Vol.Name
    "INFO: Processing $VolName"
   
    ## ONLY REPORT ON SOURCE VOLUMES ##
    If($SnapMirroredVols -contains $VolName){"INFO: ... $VolName is a SnapMirror DR volume"}
    else {
     
      ## START POPULATING REPORT ##
      $VolObj = New-Object PSObject
      If($VolName -eq $VolRoot){$Root = "YES"}else{$Root = "NO"}
      AddM $VolObj "Root"              $Root
      AddM $VolObj "Source Controller" $Ctr
      AddM $VolObj "Source Volume"     $VolName
     
      ## SNAPMIRROR DESTINATIONS ##
      [System.Array]$Destination = $CtrInfo."Get-NaSnapmirrorDestination" | Where{$_.SourceLocation -eq ($Ctr + ":" + $VolName)}
      $i = 1;[String]$DestinationCtr = "";[String]$DestinationVol = ""
      If($Destination){
        $Destination | Foreach{
          If($Destination.Count -gt 1){$DestinationCtr += "$i) ";$DestinationVol += "$i) "}
          $DestinationCtr += $_.DestinationLocation.Split(":")[0]
          $DestinationVol += $_.DestinationLocation.Split(":")[1]
          If($i -lt $Destination.Count){$DestinationCtr += " / ";$DestinationVol += " / "}
          $i++
        }
      }
      AddM $VolObj "Destinations Count"        $Destination.Count
      AddM $VolObj "Destination Controller(s)" $DestinationCtr
      AddM $VolObj "Destination Volume(s)"     $DestinationVol
     
      ## VOLUME MOUNT POINT INFO ##
      $MountPoint = "/vol/$Volname"
      $MPL = $MountPoint.length                
     
      ## SNAPVAULT PRIMARY AND SECONDARY QT COUNT ##
      $SVprimaries = @()
      $SVsecondaries = @()
      $CtrInfo."Get-NaSnapvaultPriDestinations" | Foreach{
        If($_.SourcePath.Length -ge $MPL){
          If($_.SourcePath.substring(0,$MPL) -eq $MountPoint){$SVprimaries += $_}
        }
      }
      $CtrInfo."Get-NaSnapVault" | Foreach{
        If($_.SecondaryPath.Length -ge $MPL){
          If($_.SecondaryPath.substring(0,$MPL) -eq $MountPoint){$SVsecondaries += $_}
        }
      }
      AddM $VolObj "SV Primary Count"   ($SVprimaries.Count)
      AddM $VolObj "SV Secondary Count" ($SVsecondaries.Count)
     
      ## OTHER VOLUME PROPERTIEs ##
      AddM $VolObj "Files Used"      $Vol.FilesUsed
      AddM $VolObj "Size Total (GB)" ([math]::Round((($Vol.sizeTotal) / 1024 / 1024 / 1024),2))
      AddM $VolObj "Size Used (GB)"  ([math]::Round((($Vol.sizeUsed) / 1024 / 1024 / 1024),2))
     
      ## QTREES COUNT AND VOLUME SECURITY STYLE ##
      [System.Array]$Qtrees = $CtrInfo."Get-NaQtree" | Where{$_.Volume -eq $VolName}
      [String]$Security = ($CtrInfo."Get-NaQtree" | Where{($_.Volume -eq $VolName) -and ($_.Qtree -eq "")}).Security
      AddM $VolObj "Qtree Count"        $Qtrees.count
      AddM $VolObj "Vol Security Style" $Security
     
      ## CIFS SHARES ##
      $TempShares = @()
      $CtrInfo."Get-NaCifsShare" | Foreach{
        If($_.MountPoint.Length -ge $MPL){
          If($_.MountPoint.substring(0,$MPL) -eq $MountPoint){$TempShares += $_}
        }
      }
      AddM $VolObj "Shares Count" -Value $TempShares.Count
     
      ## Exports ##
      $TempExports = @()         
      $CtrInfo."Get-NaNfsExport" | Foreach{
        If($_.Pathname.Length -ge $MPL){
          If($_.Pathname.substring(0,$MPL) -eq $MountPoint){$TempExports += $_}
        }
      }
      AddM $VolObj "Exports Count" $TempExports.Count
     
      ## LUNs ##
      $TempLUNs = @()            
      $CtrInfo."Get-NaLun" | Foreach{
        If($_.Path.Length -ge $MPL){
          If($_.Path.substring(0,$MPL) -eq $MountPoint){$TempLUNs += $_}
        }    
      }
      AddM $VolObj "LUNs Count" $TempLUNs.Count
     
      ## VFILER CHECK (NOT CURRENTLY PROGRAMMED TO HANDLE VFILERS) ##
      [String]$HasVfilers = "NO"
      If($CtrInfo."Get-NaVfiler"){
        If($CtrInfo."Get-NaVfiler".count -gt 1){ $HasVfilers = "YES" }
      }
      AddM $VolObj "Vfilers" $HasVfilers
     
      ## ADD DATA TO THE REPORT ##
      $Global:ReportCSV += $VolObj
    }
  }   
}

## MAIN PROGRAM with -Collect ##
If($Collect){
  If(!(Get-Module DataONTAP)){Import-Module DataONTAP}
  If(!(Get-Module DataONTAP)){"ERROR: No DataONTAP PSTK!";EXIT}
  Foreach($Ctr in $Controllers){
    [Void](New-Item -ItemType Directory -Force -Path $Ctr)   
    If(Test-Connection $Ctr){
      $global:currentnacontroller = $NULL
      "INFO: Connecting to $Ctr"
      [Void](Connect-NaController -Name $Ctr)
      If($global:currentnacontroller){"INFO: Connected to $Ctr";Collect}
      else{"ERROR: Unable to connect to $Ctr"}
    }else{"ERROR: Unable to ping $Ctr"}
  }
}

## MAIN PROGRAM with -Report ##
If($Report){
  [System.Array]$Global:ReportCSV = @()
  Foreach($Ctr in $Controllers){"INFO: Processing $Ctr";Report}
  $Global:ReportCSV | Export-CSV "NaVolumeReport.CSV" -NoTypeInformation
}


Comments