Monday, 31 August 2015

Seeding an ONTAP 8.3 to 8.2.3 SnapVault using SMTAPE

Note: This does not work but the post is still useful to show why it doesn’t work (“backup image header version is not supported”).

Carrying on from the previous post....

In this post we go:

NACLU7:NASV1 -> SMTAPE BACKUP -> Staging disk
8.3          -> SMTAPE BACKUP

Staging disk -> SMTAPE RESTORE -> NACLU5:CIFSV1
             -> SMTAPE RESTORE -> 8.2.3

NACLU7:NASV1 -> SV (RESYNC) -> NACLU5:CIFSV1
8.3          -> SV (RESYNC) -> 8.2.3

Seeding and ONTAP 8.3 to 8.2.3 SnapVault using SMTAPE - Step-by-Step Walkthrough

N.B.: Cluster and Vserver peers are already created from previous posts.

NACLU7::> version
NetApp Release 8.3P2: Tue May 19 07:34:02 PDT 2015

NACLU5::> version
NetApp Release 8.2.3P2 Cluster-Mode: Thu Mar 05 00:47:29 PST 2015

NACLU7::>

vol create -vserver NASV1 -vol SVVOL01 -junction-path /SVVOL01 -aggregate NACLU7N2_AGGR1 -size 2468m -space-guarantee none -language en

vol create -vserver NASV1 -vol SVVOL01_STAGING -junction-path /SVVOL01_STAGING -aggregate NACLU7N2_AGGR1 -size 3000m -space-guarantee none -language en

cifs share create -vserver NASV1 -share-name SVVOL01_STAGING$ -path /SVVOL01_STAGING

volume snapshot create -volume SVVOL01 -snapshot SMTBACKUP -vserver NASV1

set d
system smtape backup -vserver NASV1 -volume SVVOL01 -backup-snapshot SMTBACKUP -file-path /NASV1/SVVOL01_STAGING/image
system smtape status show

NACLU5::>

vol create -vserver CIFSV1 -vol SVVOL01_STAGING -junction-path /SVVOL01_STAGING -aggregate N2_aggr1 -size 3000m -space-guarantee none -language en

cifs share create -vserver CIFSV1 -share-name SVVOL01_STAGING$ -path /SVVOL01_STAGING

vol create -vserver CIFSV1 -vol SVVOL01 -aggregate N2_aggr1 -size 2468m -state restricted -type DP -space-guarantee none -language en

Now we copy the image from NACLU7’s \\NASV1\SVVOL01_STAGING$ to NACLU5’s \\CIFSV1\SVVOL01_STAGING$ staging volume (perhaps via a generic and transportable disk storage device.)

NACLU5::>

set d
system smtape restore -vserver CIFSV1 -vol SVVOL01 -file-path /CIFSV1/SVVOL01_STAGING/image
system smtape status show

Session Type    Status Progress Path         Device             Node
------- ------- ------ -------- ------------ ------------------ ----------
      3 restore FAILED      8KB /CIFSV1/SVVOL01
                                             /CIFSV1/SVVOL01_STAGING/image
                                                                NACLU5N2

event log show

Time                Node             Severity      Event
------------------- ---------------- ------------- ---------------------------
8/31/2015 22:26:03  NACLU5N2         ERROR         smtape.rst.fail: SMTape restore session 3 to /CIFSV1/SVVOL01 from /CIFSV1/SVVOL01_STAGING/image failed with error backup image header version is not supported (expected_image_header_version =131072 real_image_header_version =-4294901757) 14:1712.

Failed!

Seeding an ONTAP 8.3 to 8.2.3 SnapVault via Swing Kit

Carrying on from the previous post....

In the post we go:

NACLU7:NASV1 -> SV -> SWINGCLU:SWINGCLUV1
8.3          -> SV -> 8.2.3

SWINGCLU:SWINGCLUV1 -> SM -> NACLU5:CIFSV1
8.2.3               -> SM -> 8.2.3

NACLU7:NASV1 -> SV (RESYNC) -> NACLU5:CIFSV1
8.3          -> SV (RESYNC) -> 8.2.3

Seeding an ONTAP 8.3 to 8.2.3 SnapVault via Swing Kit - Step-by-Step Walkthrough

NACLU7::> version
NetApp Release 8.3P2: Tue May 19 07:34:02 PDT 2015

SWINGCLU::> version
NetApp Release 8.2.3P2 Cluster-Mode: Thu Mar 05 00:47:29 PST 2015

NACLU5::> version
NetApp Release 8.2.3P2 Cluster-Mode: Thu Mar 05 00:47:29 PST 2015

NACLU7::> cluster peer create -peer-addrs 10.10.10.227 -no-authentication true

SWINGCLU::> cluster peer create -peer-addrs 10.10.10.223

NACLU7::> vserver peer create -vserver NASV1 -peer-cluster SWINGCLU -peer-vserver SWINGCLUV1 -applications snapmirror

SWINGCLU::> vserver peer accept -vserver SWINGCLUV1 -peer-vserver NASV1

NACLU7::> vol create -vserver NASV1 -volume SVSEEDTEST -aggregate NACLU7N1_AGGR1 -size 1g -junction-path /SVSEEDTEST -space-guarantee none -snapshot-policy none

SWINGCLU::>
vol create -vserver SWINGCLUV1 -volume SVSEEDTEST -aggregate AGGR1 -size 1g -space-guarantee none -type DP -language en.UTF-8
snapmirror create -source-path NASV1:SVSEEDTEST -destination-path SWINGCLUV1:SVSEEDTEST -type XDP -policy XDPDefault
snapmirror initialize -destination-path SWINGCLUV1:SVSEEDTEST

To make the snapshots more like what’s on a normal vault...

NACLU7::> snapshot create -vserver NASV1 -volume SVSEEDTEST -snapshot daily.01 -snapmirror-label daily

SWINGCLU::>
snapmirror update -destination-path SWINGCLUV1:SVSEEDTEST
snapshot show -volume SVSEEDTEST -fields snapshot

vserver    volume     snapshot
---------- ---------- --------
SWINGCLUV1 SVSEEDTEST daily.01

SWINGCLU::> cluster peer create -peer-addrs 10.10.10.243

NACLU5::> cluster peer create -peer-addrs 10.10.10.227

SWINGCLU::> vserver peer create -vserver SWINGCLUV1 -peer-cluster NACLU5 -peer-vserver CIFSV1 -applications snapmirror

NACLU5::>
vserver peer accept -vserver CIFSV1 -peer-vserver SWINGCLUV
vol create -vserver CIFSV1 -volume SVSEEDTEST -aggregate N1_aggr1 -size 1g -space-guarantee none -type DP -language en.UTF-8
snapmirror create -source-path SWINGCLUV1:SVSEEDTEST -destination-path CIFSV1:SVSEEDTEST
snapmirror initialize -destination-path CIFSV1:SVSEEDTEST
snapmirror show -destination-path CIFSV1:SVSEEDTEST -fields state,status,healthy

source-path           destination-path  state        status healthy
--------------------- ----------------- ------------ ------ -------
SWINGCLUV1:SVSEEDTEST CIFSV1:SVSEEDTEST Snapmirrored Idle   true

snapmirror break -destination-path CIFSV1:SVSEEDTEST
snapmirror delete -destination-path CIFSV1:SVSEEDTEST
snapmirror create -source-path NASV1:SVSEEDTEST -destination-path CIFSV1:SVSEEDTEST -type XDP -policy XDPDefault
snapmirror resync -destination-path CIFSV1:SVSEEDTEST

Warning: All data newer than Snapshot copy daily.01 on volume CIFSV1:SVSEEDTEST will be deleted.
Do you want to continue? {y|n}: y

snapmirror show -destination-path CIFSV1:SVSEEDTEST -fields state,status,healthy,type

source-path      destination-path  type state        status healthy
---------------- ----------------- ---- ------------ ------ -------
NASV1:SVSEEDTEST CIFSV1:SVSEEDTEST XDP  Snapmirrored Idle   true

snapshot show -vserver CIFSV1 -volume SVSEEDTEST -fields snapshot

vserver volume     snapshot
------- ---------- --------
CIFSV1  SVSEEDTEST daily.01

Done!

Proof of SnapVault from ONTAP 8.3 to 8.2.3

It might be a little known fact, but you can SnapVault from ONTAP 8.3 to 8.2.3 (I’m not certain it is 100% supported). The usefulness of this is if you want to run your production systems on 8.3 (or better), but still have 32-bit snapshots to keep on your SnapVault cluster, so want the SnapVault cluster to remain sub 8.3 (and when the snapshots have finally aged out you’ll upgrade it to 8.3+).

N.B. 1: Version flexible SnapMirror comes in in ONTAP 8.3, but only allows going back to 8.3 (e.g. 8.4 source back to 8.3 destination); SnapVault (XDP mirror) is a different case.

N.B. 2: Here my 8.3P2 cluster is NACLU7, and 8.2.3P2 is NACLU5. The SVMs are respectively NASV1 and CIFSV1.

NACLU7::> version
NetApp Release 8.3P2: Tue May 19 07:34:02 PDT 2015

NACLU5::> version
NetApp Release 8.2.3P2 Cluster-Mode: Thu Mar 05 00:47:29 PST 2015

NACLU7::>
cluster peer policy modify -is-unauthenticated-access-permitted true
cluster peer create -peer-addrs 10.10.10.243 -no-authentication

NACLU5::>
cluster peer create -peer-addrs 10.10.10.223
cluster peer show

Peer Cluster Name Cluster Serial Number Availability
----------------- --------------------- ---------------
NACLU7            1-80-000008           Available

NACLU7::>
cluster peer show

Peer Cluster Name Cluster Serial Number Availability   Authentication
----------------- --------------------- -------------- --------------
NACLU5            1-80-000099           Available      absent

NACLU7::>
vserver peer create -vserver NASV1 -peer-vserver CIFSV1 -peer-cluster NACLU5 -applications snapmirror

NACLU5::> vserver peer accept -vserver CIFSV1 -peer-vserver NASV1

NACLU7::> vol create -vserver NASV1 -volume SVTEST -aggregate NACLU7N1_AGGR1 -size 1g -junction-path /SVTEST -space-guarantee none -snapshot-policy none

NACLU5::>
vol create -vserver CIFSV1 -volume SVTEST_SV -aggregate N1_aggr1 -size 1g -space-guarantee none -type DP -language en.UTF-8
snapmirror create -source-path NASV1:SVTEST -destination-path CIFSV1:SVTEST_SV -type XDP -policy XDPDefault
snapmirror initialize -destination-path CIFSV1:SVTEST_SV
snapmirror show -destination-path CIFSV1:SVTEST_SV -fields state,status,healthy

source-path  destination-path state        status healthy
------------ ---------------- ------------ ------ -------
NASV1:SVTEST CIFSV1:SVTEST_SV Snapmirrored Idle   true

NACLU5::>
snapshot show -volume SVTEST_SV  -fields snapshot,snapmirror-label

vserver volume    snapshot                       snapmirror-label
------- --------- ------------------------------ ----------------
CIFSV1  SVTEST_SV snapmirror...2015-08-31_125004 -

The XDPDefault SnapMirror (SnapVault) Policy keeps 7 daily and 52 weekly snapshots, so if we create a snapshot on NACLU7 with the label daily or weekly, it will get transferred on the next update, any other snapshots won’t.

NACLU7::>
snapshot create -vserver NASV1 -volume SVTEST -snapshot daily.01 -snapmirror-label daily
snapshot create -vserver NASV1 -volume SVTEST -snapshot random.01 -snapmirror-label random

NACLU5::>
snapmirror update -destination-path CIFSV1:SVTEST_SV
snapshot show -volume SVTEST_SV  -fields snapshot,snapmirror-label

vserver volume    snapshot snapmirror-label
------- --------- -------- ----------------
CIFSV1  SVTEST_SV daily.01 daily

Proof we have a working SnapVault relation from 8.3 to 8.2.3!

Creating a NAS (CIFS and NFS) Vserver using Clustershell in cDOT 8.3

Introduction

With cDOT 8.3+ the Clustershell command "vserver setup" no longer exists:

::> set d
::*> version
NetApp Release 8.3P2: Tue May 19 07:34:02 PDT 2015

::*> vserver setup
Error: "setup" is not a recognized command

Some alternatives to “vserver setup”:

- Using the On-Box System Manager (see here)
- A WFA workflow
- Clustershell ("vserver create") or PowerShell commands

The below is a walkthrough to construct a basic NAS (CIFS and NFS) vserver in cDOT 8.3P2 using ClusterShell commands (borrowing in part from these 2013 posts here for CIFS and here for NFS but updated for 8.3+). It really is pretty simple!

Step-by-Step Walkthrough

N.B.: We have a cluster here called NACLU7 with two nodes - NACLU7N1 & NACLU7N2.

system license add -license-code XXX # CIFS License Code (Repeat per node)
system license add -license-code XXX # NFS License Code (Repeat per node)
system license show -package CIFS
system license show -package NFS

vserver create -vserver NASV1 -subtype default -rootvolume rootvol -aggregate NACLU7N1_AGGR1 -rootvolume-security-style ntfs -language en.UTF-8 -snapshot-policy default -ipspace Default

vserver show -vserver NASV1 -fields allowed-protocols
vserver remove-protocols -vserver NASV1 -protocols fcp,iscsi,ndmp
vserver show -vserver NASV1 -fields allowed-protocols

network interface create -vserver NASV1 -lif NASV1_LIF1 -role data -data-protocol cifs,nfs -home-node NACLU7N1 -home-port e0d -address 10.10.10.225 -netmask 255.255.255.0

network interface create -vserver NASV1 -lif NASV1_LIF2 -role data -data-protocol cifs,nfs -home-node NACLU7N2 -home-port e0d -address 10.10.10.226 -netmask 255.255.255.0

network route create -vserver NASV1 -destination 0.0.0.0/0 -gateway 10.10.10.1

vserver services dns create -vserver NASV1 -domains lab.priv -name-servers 10.10.10.10

vserver cifs create -vserver NASV1 -cifs-server NASV1 -domain lab.priv
vserver cifs show -vserver NASV1

vserver nfs create -vserver NASV1

Sunday, 30 August 2015

Ping All LIFs on My Cluster Tool

Ping All LIFs on My NetApp Clustered Data ONTAP Cluster Tool

Back in April I wrote Multiple Pings Testing Tool. It occurred to me that with a little bit of work it could be used as a “Ping All LIFs on My Cluster!” tool. Of course, we won’t ping cluster role LIFs (on the private and un-routable cluster backbone network), and all the LIFs would need to be routable to the client doing the ping test (if you’re using IPSpaces beyond just the default, this may be impossible.)

Image: Example output from test cluster

A good idea to expand the tool would be to include service-processors also.

Copy and paste the below script into a text file and save as say PingAllLifs.ps1. Then run as:

PS C:\ > .\PingAllLifs.ps1 -Cluster ClusterName -Username UserName

You will be prompted for the cluster password.

The Script

###################################################
# PingAllLifs.ps1 or Ping All LIFs On My Cluster! #
###################################################

Param(
  [Parameter(Mandatory=$true)][String]$Cluster,
  [Parameter(Mandatory=$true)][String]$Username,
  [Int]$w = 50
)

FUNCTION Wr {
  Param([String]$ToDisplay,[String]$ForegroundColor,[String]$BackgroundColor)
  If(!$ToDisplay){ Write-Host; RETURN    }
  If($BackgroundColor){ Write-Host $ToDisplay -ForegroundColor $ForegroundColor -BackgroundColor $BackgroundColor -NoNewLine; RETURN }
  If($ForegroundColor){ Write-Host $ToDisplay -ForegroundColor $ForegroundColor -NoNewLine; RETURN }
  Write-Host $ToDisplay -ForegroundColor White 
}

## LOAD THE DATA ONTAP POWERSHELL TOOLKIT ##

[Void](Import-Module DataONTAP -ErrorAction SilentlyContinue)
If(!(Get-Module DataONTAP)){ Wr "Unable to load the DataONTAP PowerShell Toolkit - exiting!" Red; Wr; EXIT }
Wr "Loaded the Data ONTAP PowerShell Toolkit!" Green; Wr

## GET CREDENTIALS ##

Wr "Password: " Cyan; $Password = Read-Host -AsSecureString
$SecureString = $Password | ConvertFrom-SecureString
$Credential = New-Object System.Management.Automation.PsCredential($Username,$Password)

## TEST CREDENTIALS AND ACQUIRE LIFS ##

Wr "Checking connection to " Cyan; Wr $Cluster Yellow; Wr " ..." Cyan; Wr
If ( !(Connect-NcController -Name $Cluster -Credential $Credential -Timeout 20000 -ErrorAction SilentlyContinue) ){
  Wr "Unable to connect to " Red; Wr $Cluster Yellow; Wr " with provided credentials - exiting!" Red; Wr; EXIT
} else { Wr "Successfully connected to " Green; Wr $Cluster Yellow; Wr }
      
$Attrs = Get-NcNetInterface -Template
$Attrs.role = ""
$Attrs.address = ""
$Attrs.interfacename = ""
$LIFs = Get-NcNetInterface -Attributes $Attrs

[System.Object]$targets = @{}
[Int]$count = 0
$LIFs | Foreach{
  If ($_.role -ne "cluster"){
    $count++
    [System.Object]$targets.$count = @{}
    [String]$targets.$count.address = $_.address
    [String]$targets.$count.interfacename = $_.interfacename
  }
}     

If($count -eq 0){ Wr "No targets to ping - exiting!" Red; Wr; EXIT }

## PING ALL NON-CLUSTER LIFS ##

[System.Array]$status = "" # $status[0] is unused
For($i = 1; $i -le $count; $i++){ $status += "Unitialized" }

Function PrintStatus{
  Param([Int16]$pointer)
  If ($pointer -eq $count){ $pointer = 1}
  else { $pointer ++ }    
  cls
  For($j = 1; $j -le $count; $j++){
    If ($pointer -eq $j){ [String]$Display = " * " } else { [String]$Display = "   " }
    $Display += $Cluster + " : " + $targets.$j.interfacename + " : " + $targets.$j.address
    If ($status[$j] -eq "UP"){ Wr ($Display.PadRight($w).Substring(0,$w)) BLACK GREEN; Wr }
    elseif ($status[$j] -eq "DOWN"){ Wr ($Display.PadRight($w).Substring(0,$w)) WHITE RED; Wr }
    else { Wr ($Display.PadRight($w).Substring(0,$w)) BLACK GRAY ; Wr }
  }
}

Function GetResult{
  For($i = 1; $i -le $count; $i++){
    [Boolean]$Result = Test-Connection -ComputerName $targets.$i.address -Count 1 -Quiet
    If ($Result){ $status[$i] = "UP" }
    If(!$Result){ $status[$i] = "DOWN" }
    PrintStatus $i
  }
}

While ($true){ GetResult }

Tuesday, 11 August 2015

Crib Sheet: Seeding to Disk a SnapMirror Relationship using SmTape

These commands were run under cDOT 8.2.3:

::> version

NetApp Release 8.2.3 Cluster-Mode: Fri Jan 16 01:07:00 PST 2015

Preliminary Work

Firstly, verify that the primary and secondary Clusters and SVMs are peered.

::> cluster peer show
::> vserver peer show

On the Primary (Source) Cluster

Create a staging volume on an aggregate that belongs to the same node as the volume to be SnapMirrored:

::> vol create -vserver SVM -vol vol01_smtape_staging_src -aggregate N1_aggr1 -size 1050g -state online -type RW -junction-path /vol01_smtape_staging_src

N.B.: You may need to make the volume slightly larger than the source volume.

Create a backup snapshot:

::> volume snapshot create -volume vol01 -snapshot smt_1 -vserver SVM

Create an SMTAPE backup:

::> set diag
::*> system smtape backup -vserver SVM -volume vol01 -backup-snapshot smt_1 -file-path /SVM/vol01_smtape_staging_src/image
::*> system smtape status show

Wait for the SMTAPE backup to complete. Create a CIFS share (or could do an NFS export):

::*> cifs share create -vserver SVM -share-name vol01_smtape_staging_src$ -path /vol01_smtape_staging_src

And copy the image off onto disk.

N.B.: What the image is copied to is beyond the scope of this post.

On the Secondary (DR) Cluster

Create a staging volume on the DR SVM:

::*> volume create -vserver SVMDR -volume vol01_smtape_staging_dst -aggregate N1_aggr1 -size 1050g -state online -type RW -junction-path /vol01_smtape_staging_dst

Create a share to the volume and copy on the backup image:

::> cifs share create -vserver SVMDR -share-name vol01_smtape_staging_dst$ -path /vol01_smtape_staging_dst

Create a restricted volume to restore the image to:

::> volume create -vserver SVMDR -volume vol01_dr -aggregate N1_aggr1 -size 1000g -state restricted -type DP

Restore the volume:

::> set diag
::*> system smtape restore -vserver SVMDR -vol vol01_dr -file-path /SVMDR/vol01_smtape_staging_dst/image
::*> system smtape status show

When the SMTAPE restore is complete, resync primary and secondary volumes:

::*> snapmirror resync -source-path SVM:vol01 -destination-path SVMDR:vol01_dr -source-snapshot smt_1
::*> snapmirror show

Sunday, 9 August 2015

How to Convert 7-Mode NFS Exports into cDOT Export Policies and Rules

Introduction

If you want to manually convert your 7-Mode exports file into the equivalent Clustered Data ONTAP Export Polices and Export Policy Rules - as close to equivalent as reasonably possible anyway - the following PowerShell script might help you to do this.

It only handles -sec=sys exports (which is probably 99.9% of exports anyway).
It doesn’t handle -actual= which isn’t supported in cDOT (check out NetApp TR-4067.)

Because you can only export at the volume level or qtree level in cDOT (8.2.1+), the tool consolidates volume and sub-volume (without a Qtree in the path) exports into volume specific Export Policies, and qtree and sub-qtree exports into qtree specific Export Policies.

The r/o hosts and r/w hosts need to be split out into separate rules. The r/o rules are always placed after the r/w rules.

The script is easy to run. Name it as say 7CET.ps1, and run as:

.\7CET.ps1 -FilePath YOUR_7M_EXPORTS_FILE

N.B. 7CET = 7 to C Exports Transitioner

The Script (formatted for blogger)

############################################
## 7 to C Exports Transitioner (7CET.ps1) ##
############################################

Param(
  # 7-Mode Exports File
  [Parameter(Mandatory=$true)][String]$FilePath,
  # cDOT: Vserver Name
  [String]$VserverName = "SVM1",
  # Specify a list as - "vol1.folder1","vol2.folder2",...
  # ... where ??? in the path /vol/VOLNAME/??? is not a Qtree
  [System.Array]$OverrideQtrees
)

# We use the FileName in some outputs
[String]$FileName = $FilePath.Split("\")[0]

# Records lines of Screen Output (flushed to SOut on Wr)
[String]$Global:LOut = ""

# Used to record the ScreenOutput
[System.Array]$Global:SOut = @()

Function Wr  {      
  Param([String]$DisplayThis,[String]$ForegroundColor)
  If(!$DisplayThis){
    Write-Host
    $Global:SOut += $Global:LOut
    $Global:LOut = ""
    RETURN
  } elseif(!$ForegroundColor) {
    Write-Host $DisplayThis -ForegroundColor White -NoNewLine
  } else {
    Write-Host $DisplayThis -ForegroundColor $ForegroundColor -NoNewLine
  }
  $Global:LOut += $DisplayThis
}; Wr

Wr "<<<<< NFS EXPORTS 7 to C v1.0 >>>>>" MAGENTA; Wr; Wr

######################################
## GET AND PROCESS THE EXPORTS FILE ##
######################################

[System.Array]$ExportsFile = Get-Content $FilePath
Wr "Loaded file from: $FilePath" GREEN; Wr

Function Process-Array {
  Param([System.Array]$ArrayToProcess)
  [System.Array]$ProcessedArray = @()
  Foreach ($Line in $ArrayToProcess){          
    # REMOVE BLANK LINES "" AND LINES BEGINNING WITH "#"
    If( ($Line -ne "") -and ($Line.substring(0,1) -ne "#") ){
      $ProcessedArray += $Line
    }
  }
  # RETURN THE PROCESSED ARRAY
  , $ProcessedArray
}

$ExportsFile = Process-Array $ExportsFile; Wr

######################################################
## STEP 1: PROCESS 7-MODE EXPORTS FILE LINE BY LINE ##
######################################################

Wr ">>> STEP 1: PLEASE VERIFY THAT THE EXPORTS FILE HAS BEEN READ CORRECTLY! <<<" MAGENTA; Wr; Wr

Function Process-ExportsLine {
 
  Param([String]$Line)
      
  ##### SPLIT ON -actual= OR -sec= TO GET PATH & OPTIONS BITS #####
 
  # Expect (1)PATH_BIT[0]/(2)OPTIONS_BIT[1]
  [System.Array]$SplitOnDashSec    = $Line.Replace("-sec=","#").Split("#")
  [System.Array]$SplitOnDashActual = $Line.Replace("-actual=","#").Split("#")
 
  If( !( ($SplitOnDashSec.Count -eq 2) -or ($SplitOnDashActual.Count -eq 2) ) ){
    # NOT A VALID LINE ( .LineIsValid remains FALSE)
    RETURN
  }
 
  If( $SplitOnDashSec.Count -eq 2 ){
    ## HAS -sec=
    [String]$PathBit    = $SplitOnDashSec[0].Trim(" ","`t")
    [String]$OptionsBit = $SplitOnDashSec[1].Trim(" ","`t")
  } else {
    ## HAS -actual=
    $Exports.$Index.ActualSpecified = $TRUE
    [String]$PathBit    = $SplitOnDashActual[0].Trim(" ","`t")
    [String]$OptionsBit = $SplitOnDashActual[1].Trim(" ","`t")
  }
 
  # Expect (1)[0]/(2)vol[1]/(3)VOLNAME[2]/(4){QTREE}[3]/{SUBQTREE_BIT}
  $SplitOnSlash = $PathBit.Split("/")
 
  If($SplitOnSlash.Count -lt 3){
    # NOT A VALID LINE ( .LineIsValid remains FALSE)
    RETURN
  }
 
  [String]$VolNameFromExport = $SplitOnSlash[2]
  $Exports.$Index.VolName    = $VolNameFromExport
 
  If($SplitOnSlash.Count -ge 4){
    $PossibleQtree = $SplitOnSlash[3].Replace("\"," ")
    # N.B. Qtrees names can have " " in (which is \ in exports)
    If( !($OverrideQtrees -contains ($VolNameFromExport + "." + $PossibleQtree)) ){
      $Exports.$Index.QtreeName = $PossibleQtree
    }
  }
  If($SplitOnSlash.Count -ge 5){
    [String]$PathUpToQtree = ("/vol/" + $Exports.$Index.VolName + "/" + $Exports.$Index.QtreeName + "/")
    $Exports.$Index.SubQtreeBit = $PathBit.Substring($PathUpToQtree.Length).Replace("\"," ")
    # N.B. Folder names can have " " in (which is \ in exports)
  }
 
  ##### HANDLE THE OPTIONS #####
 
  ## -actual= COMES 1ST ##
  If($Exports.$Index.ActualSpecified){
    $Exports.$Index.ActualEquals = $OptionsBit.Replace(",sec=","#").Split("#")[0]
    $OptionsBit                  = $OptionsBit.Replace(",sec=","#").Split("#")[1]
  }
 
  ## -sec= COMES 2ND ##
  $Exports.$Index.SecEquals = $OptionsBit.Split(",")[0]
  $OptionsBit = $OptionsBit.TrimStart($Exports.$Index.SecEquals)
 
  ## THESE REST (all separated by ,) ##
  $OptionsSplitComma = $OptionsBit.Split(",")
  # N.B. We left a comma after the sec= bit
 
  For( [Int]$i=1; $i -lt ($OptionsSplitComma.Count); $i++ ){
   
    [String]$CheckThis = [String]($OptionsSplitComma[$i])
    $CheckThis         = $CheckThis.Trim(" ","`t","`n")
   
    If($CheckThis.StartsWith("anon=")){
      $Exports.$Index.AnonSpecified = $TRUE
      $Exports.$Index.AnonEquals = $CheckThis.Substring(5)
    }
    If($CheckThis -eq "nosuid"){
      $Exports.$Index.NoSuidSpecified = $TRUE
    }
    If($CheckThis -eq "ro"){
      $Exports.$Index.RoSpecified = $TRUE
    }
    If($CheckThis.StartsWith("ro=")){
      $Exports.$Index.RoEquals = $CheckThis.Substring(3)
    }
    If($CheckThis -eq "rw"){
      $Exports.$Index.RwSpecified = $TRUE
    }
    If($CheckThis.StartsWith("rw=")){
      $Exports.$Index.RwEquals = $CheckThis.Substring(3)
    }
    If($CheckThis.StartsWith("root=")){
      $Exports.$Index.RootEquals = $CheckThis.Substring(5)
    }
  }
 
  $Exports.$Index.LineIsValid = $TRUE
  # N.B. Could do with adding some validity tests!

}

[Int]$Index             = 0   # INITIALIZE THE INDEX AS 0
[System.Object]$Exports = @{} # INITIALIZE THE $Exports OBJECT

Foreach($Line in $ExportsFile){
 
  $Index++
 
  ## INITIALIZE THE OBJECT TO HOLD THE 7-MODE EXPORT LINE'S DATA ##
 
  # DATA STRUCTURE TO CONTAIN 7-MODE EXPORTS
  [System.Object]$Exports.$Index = @{}
 
  # /vol/VOLNAME   
  [String]$Exports.$Index.VolName = ""
 
  # /vol/VOLNAME/QTREENAME
  [String]$Exports.$Index.QtreeName = ""
 
  # /vol/VOLNAME/QTREENAME/SUBQTREEBIT
  [String]$Exports.$Index.SubQtreeBit = ""
 
  # actual= is specified
  [Boolean]$Exports.$Index.ActualSpecified = $FALSE
 
  # actual={path}
  [String]$Exports.$Index.ActualEquals = ""
 
  # anon= is specified
  [Boolean]$Exports.$Index.AnonSpecified = $FALSE
 
  # anon={uid|name}
  [String]$Exports.$Index.AnonEquals = ""
 
  # nosuid is specified
  [Boolean]$Exports.$Index.NoSuidSpecified = $FALSE
 
  # ro is specified (with no equals)
  [Boolean]$Exports.$Index.RoSpecified = $FALSE
 
  # ro={clientid[:clientid...]}
  [String]$Exports.$Index.RoEquals = ""
 
  # rw is specified (with no equals)
  [Boolean]$Exports.$Index.RwSpecified = $FALSE
 
  # rw={clientid[:clientid...]}
  [String]$Exports.$Index.RwEquals = ""
 
  # root={clientid[:clientid...]}
  [String]$Exports.$Index.RootEquals = ""
 
  # sec={sectype[:sectype...]}
  [String]$Exports.$Index.SecEquals = ""
 
  # (... check exports line is valid ...)
  [Boolean]$Exports.$Index.LineIsValid = $FALSE
 
  # (... record the line sent for processing ...)
  [String]$Exports.$Index.OriginalLine = ""
 
  # See: https://library.netapp.com/ecmdocs/ECMP1196979/html/man5/na_exports.5.html
 
  ## GET RID OF ANY COMMENTS AT THE END OF THE LINE AND TRIM SPACES AND TABS ##
  $Exports.$Index.OriginalLine = $Line.Split("#")[0].Trim(" ","`t")
 
  ## INVOKE THE PROCESS-EXPORTS FUNCTION TO PROCESS THE EXPORTS LINE ##
  Process-ExportsLine ($Exports.$Index.OriginalLine)
 
  ## DISPLAY THE PROCESSED LINE (TO MAKE SURE WE'VE READ IT RIGHT!) ##
  Wr "ORIGINAL LINE: "; Wr $Exports.$Index.OriginalLine GREEN; Wr
 
  If(!$Exports.$Index.LineIsValid){
    Wr "NOT A VALID LINE!" RED; Wr
  } else {
 
    Wr "Volume         : "
    Wr $Exports.$Index.VolName CYAN; Wr
      
    If($Exports.$Index.QtreeName){
      If($OverrideQtrees){
        Wr "Qtree          : "
        Wr $Exports.$Index.QtreeName CYAN; Wr
      } else {
        Wr "Qtree "
        Wr "(Assumed)" YELLOW; Wr ": "
        Wr $Exports.$Index.QtreeName CYAN; Wr
      }
    }
   
    If($Exports.$Index.SubQtreeBit){
      Wr "SUBQTREE BIT   : ";
      Wr $Exports.$Index.SubQtreeBit CYAN; Wr
    }
   
    If($Exports.$Index.ActualSpecified){
      Wr "actual=        : ";
      Wr $Exports.$Index.ActualEquals CYAN; Wr
    }
   
    Wr "sec=           : ";
    Wr $Exports.$Index.SecEquals CYAN; Wr
   
    If($Exports.$Index.RoSpecified){
      Wr "RO             : "
      Wr "Specified as ro!" YELLOW; Wr
    }
   
    If($Exports.$Index.RoEquals){
      Wr "RO HOSTS=      : "
      Wr $Exports.$Index.RoEquals CYAN; Wr
    }
   
    If($Exports.$Index.RwSpecified){
      Wr "RW             : "
      Wr "Specified as rw!" YELLOW; Wr
    }
   
    If($Exports.$Index.RwEquals){
      Wr "RW HOSTS=      : "
      Wr $Exports.$Index.RwEquals CYAN; Wr
    }
   
    If($Exports.$Index.RootEquals){
      Wr "ROOT HOSTS=    : "
      Wr $Exports.$Index.RootEquals CYAN; Wr
    }
   
    If($Exports.$Index.AnonSpecified){
      Wr "ANON =         : "
      Wr $Exports.$Index.AnonEquals YELLOW; Wr
    }
   
    If($Exports.$Index.NoSuidSpecified){
      Wr "NOSUID         : "
      Wr "Nosuid is specified." GREEN; Wr
    }
  }
  Wr
}

#########################################################################
## STEP 2: PROCESSING THE 7-MODE EXPORTS TO CDOT CLUSTERSHELL COMMANDS ##
#########################################################################

Wr ">>> STEP 2: PROCESSING THE 7-MODE EXPORTS TO CDOT CLUSTERSHELL COMMANDS <<<" MAGENTA; Wr; Wr

## CHECK FOR LINES WE CAN HANDLE ##
## ============================= ##

# Array for volume exports only - /vol/VOLNAME
[System.Array]$VolExportsOnly = @()

# Array for sub-volume exports only - /vol/VOLNAME/SUB-FOLDER
[System.Array]$SubVolExportsOnly = @()

# Array for qtree exports only - /vol/VOLNAME/QTREENAME
[System.Array]$QtreeExportsOnly = @()

# Array for sub-qtree exports only - /vol/VOLNAME/QTREENAME/SUB-QTREE-FOLDER
[System.Array]$SubQtreeExportsOnly = @()

# Initialize cExports object
[System.Object]$cExports = @{}

# Create an array for the ExportedVolumeList
[System.Array]$cExports.ExportedVolList = @()

# Create an object for volumes
[System.Object]$cExports.Volume = @{}

## CYCLE THROUGH THE 7-MODE EXPORTS ##
For($i=1; $i -le $Index; $i++){
 
  ## CHECK FOR VALID EXPORTS LINE ##
  If($Exports.$Index.LineIsValid){
    
    ## THINGS THIS PROGRAM DOESN'T HANDLE (YET) ##    
    If( $Exports.$i.ActualSpecified ){
      Wr "-actual= IS NOT SUPPORTED IN cDOT (see TR-4067)!" RED; Wr
      Wr "WON'T HANDLE: " RED; Wr $Exports.$i.OriginalLine YELLOW; Wr; Wr
    } elseif( $Exports.$i.SecEquals -ne "sys" ){
      Wr "ONLY HANDLING SEC=SYS EXPORTS!" RED; Wr
      Wr "WON'T HANDLE: " RED; Wr $Exports.$i.OriginalLine YELLOW; Wr; Wr
    } else {
      $VolName   = $Exports.$i.VolName
      $QtreeName = $Exports.$i.QtreeName
     
      ## THINGS WE CAN PROCESS ##
      If(!$QtreeName -and !$Exports.$i.SubQtreeBit){
        # 7M Exports that have NO Qtree NOR a Sub-Qtree bit
        $VolExportsOnly += $i
      } elseif (!$QtreeName -and  $Exports.$i.SubQtreeBit){
        # 7M Exports that have NO Qtree BUT a Sub-Qtree (Sub-Volume) bit
        $SubVolExportsOnly += $i
      }
      elseif ($QtreeName -and !$Exports.$i.SubQtreeBit){
        # 7M Exports that have a Qtree and NO Sub-Qtree bit
        $QtreeExportsOnly += $i
      }
      elseif ($QtreeName -and  $Exports.$i.SubQtreeBit){
        # 7M Exports that have a Qtree and a Sub-Qtree bit
        $SubQtreeExportsOnly += $i
      }
     
      ## WARN ABOUT SUB QTREE/SUB VOLUME EXPORTS ##
      If( $Exports.$i.SubQtreeBit ){
        Wr "WARNING! THE FOLLOWING LINE IS A SUB QTREE/SUB VOLUME (without a QTREE) EXPORT. " YELLOW
        If( $QtreeName ){
          Wr "WILL DEFINE RULES IN AN EXPORT POLICY ON THE QTREE." CYAN; Wr
        } else {
          Wr "WILL DEFINE RULES IN AN EXPORT POLICY ON THE VOLUME." CYAN; Wr
        }
        Wr "LINE: " CYAN; Wr $Exports.$i.OriginalLine GREEN; Wr; Wr
      }
     
      ## POPULATE EXPORTED VOLS LIST ##
      If( !($cExports.ExportedVolList -Contains $VolName) ){
       
        # Accumulate the Exported Vols List
        $cExports.ExportedVolList += $VolName
       
        # Create an object for this volume
        [System.Object]$cExports.Volume.$VolName = @{}
       
        # Initialize as FALSE for has Qtree Exports
        [Boolean]$cExports.Volume.$VolName.HasQtreeExports = $FALSE
       
        # Create an array for exported qtrees for $VolName
        [System.Array]$cExports.Volume.$VolName.ExportedQtreeList = @()
      }
     
      ## POPULATE EXPORTED QTREES LIST ##
      If($QtreeName){
        If( !($cExports.Volume.$VolName.ExportedQtreeList -Contains $QtreeName) ){
          $cExports.Volume.$VolName.ExportedQtreeList += $QtreeName
          $cExports.Volume.$VolName.HasQtreeExports    = $TRUE
        }
      }                   
    }
  }
}

## INITIALIZE CLUSTERSHELL OUTPUT ##
## ============================== ##

[System.Array]$Output = @()

$Output += ("### 7 TO C EXPORTS TRANSLATIONS for $FileName ###")
$Output += (""),("## VOLUME MOUNT FOR EXPORTED VOLUMES ##"),("")

# Mount Volumes
$cExports.ExportedVolList | Foreach {
  $Output += ("volume mount -vserver $VserverName -junction-path /vol/$_")
}

$Output += (""),("## CREATE NO ACCESS AND READONLY EXPORT POLICIES (8.2+) ##"),("")

# No Access Export Policy
$Output += ("vserver export-policy create -vserver $VserverName -policyname expol_noaccess")

# Read Only Export Policy
$Output += ("vserver export-policy create -vserver $VserverName -policyname expol_readonly")
$Output += ("vserver export-policy rule create -vserver $VserverName -policyname expol_readonly -ruleindex 1 -protocol any -clientmatch 0.0.0.0/0 -rorule any -rwrule never -anon 65534 -superuser none -allow-suid true -allow-dev true")

$Output += (""),("## INITIALLY APPLY READ ONLY TO ALL EXPORTED VOLUMES ##"),("")

# Initially apply readonly to all volumes
$cExports.ExportedVolList | Foreach {
  $Output += "volume modify -vserver $VserverName -volume $_ -policy expol_readonly"
}

$Output += (""),("## INITIALLY APPLY NO ACCESS TO QTREES ON EXPORTED VOLUMES ##"),("")

# Initially apply no access to all qtrees
$cExports.ExportedVolList | Foreach {
  $Output += ("qtree modify -vserver $VserverName -volume $_ -qtree !" + '"' + '"' + " -export-policy expol_noaccess")
}

$Output += ("")

## FUNCTION TO PROCESS ROOT HOSTS, RW HOSTS, AND RO HOSTS ##
## ====================================================== ##

# Initialize array for Read/Write Rules (must come before ro rules)
[System.Array]$Global:RwRules   = @()

# Initialize array for Read/Only Rules
[System.Array]$Global:RoRules   = @()

# Initialize array for RW Clients (used to prevent duplicates)
[System.Array]$Global:RwClients = @()

# Initialize array for RO Clients (used to prevent duplicates)
[System.Array]$Global:RoClients = @()

Function Process-AllHosts {
  Param([System.Object]$7mExport)
  [System.Array]$RootHosts = ($7mExport.RootEquals).Split(":")
  [System.Array]$RwHosts   = ($7mExport.RwEquals).Split(":")
  [System.Array]$RoHosts   = ($7mExport.RoEquals).Split(":")                            
  If($RwHosts){
    Foreach ($RwHost in $RwHosts){
      If( !($Global:RwClients -Contains $RwHost) ){
        $Global:RwClients += $RwHost
        If($RootHosts -Contains $RwHost){
          $Global:RwRules += ("$DefaultProtocol -clientmatch $RwHost -rorule sys -rwrule sys $Anon -superuser sys $Suid $DefaultDev")
        } else {
          $Global:RwRules += ("$DefaultProtocol -clientmatch $RwHost -rorule sys -rwrule sys $Anon -superuser none $Suid $DefaultDev")
        }
      }
    }
  }
  If($RoHosts){
    Foreach ($RoHost in $RoHosts){
      If( !($Global:RoClients -Contains $RoHost) ){
        $Global:RoClients += $RoHost
        If($RootHosts -Contains $RoHost){
          $Global:RoRules += ("$DefaultProtocol -clientmatch $RoHost -rorule sys -rwrule never $Anon -superuser sys $Suid $DefaultDev")
        } else {
          $Global:RoRules += ("$DefaultProtocol -clientmatch $RoHost -rorule sys -rwrule never $Anon -superuser none $Suid $DefaultDev")
        }
      }
    }
  }
  If($RootHosts){
    Foreach ($RootHost in $RootHosts){
      If( !($Global:RwClients -Contains $RootHost) ){
        If( !($RwHosts -contains $RootHost) -and !($RoHosts -contains $RootHost) ){
          $Global:RwClients += $RootHost
          $Global:RwRules += ("$DefaultProtocol -clientmatch $RootHost -rorule sys -rwrule sys $Anon -superuser sys $Suid $DefaultDev")
        }
      }
    }
  }
  Return $TRUE
}

## CREATE AND APPLY PER VOLUME EXPORT POLICIES (INCLUDES SUB-VOLUME EXPORTS) ##
## ========================================================================= ##

# Not specified in 7-Mode
[String]$DefaultProtocol = "-protocol any"

# Same default in 7-Mode (so, if not specified is this)
[String]$DefaultAnon = "-anon 65534"

# Same default in 7-Mode (so, if not specified is this)
[String]$DefaultSuid = "-allow-suid true"

# Same default in 7-Mode (so, if not specified is this)
[String]$DefaultDev = "-allow-dev true"

$Output += ("## PER VOLUME EXPORT POLICIES (INCLUDES SUB-VOLUME EXPORTS ##"),("")

### CYCLE THROUGH THE EXPORTED VOLUMES LIST ##
Foreach($VolName in $cExports.ExportedVolList){

  [String]$ExPolicy = ("expol_v_" + $VolName)
 
  # N.B. We add rule indexes later
  [String]$ExPolRuleStart = "vserver export-policy rule create -vserver $VserverName -policyname $ExPolicy -ruleindex "
 
  # Initialize "Has a Vol or Sub Vol Export (for $VolName) as FALSE
  [Boolean]$HasAVolOrSubVolExport = $FALSE
 
  # Reset R/W Rules
  $Global:RwRules = @()
 
  # Reset R/O Rules
  $Global:RoRules = @()
 
  # Initialize as NULL general Read/Write rule
  [String]$RwRule = ""
 
  # Initialize as NULL general Read/Only rule
  [String]$RoRule = ""
 
  # Reset R/W Clients
  $Global:RwClients = @()
 
  # Reset R/O Clients
  $Global:RoClients = @()

  ## VOLUME EXPORTS ##
  $VolExportsOnly | Foreach {
    If($Exports.$_.AnonSpecified){
      [String]$Anon = ("-anon " + $Exports.$_.AnonEquals)
    } else {
      [String]$Anon = $DefaultAnon
    }
    If($Exports.$_.NoSuidSpecified){
      [String]$Suid = ("-allow-suid false")
    } else {
      [String]$Suid = $DefaultSuid
    } 
    If($Exports.$_.VolName -eq $VolName){
      # $VolName has a volume export (Process-AllHosts always returns TRUE)
      $HasAVolOrSubVolExport = Process-AllHosts $Exports.$_
      If($Exports.$_.RwSpecified){
        $RwRule = ("$DefaultProtocol -clientmatch 0.0.0.0/0 -rorule sys -rwrule sys $Anon -superuser none $Suid $DefaultDev")
      }
      If($Exports.$_.RoSpecified){
        $RoRule = ("$DefaultProtocol -clientmatch 0.0.0.0/0 -rorule sys -rwrule never $Anon -superuser none $Suid $DefaultDev")
      }
    }
  }

  ## SUB VOLUME EXPORTS ##
  $SubVolExportsOnly | Foreach {
    If($Exports.$_.AnonSpecified){
      [String]$Anon = ("-anon " + $Exports.$_.AnonEquals)
    } else {
      [String]$Anon = $DefaultAnon
    }
    If($Exports.$_.NoSuidSpecified){
      [String]$Suid = ("-allow-suid false")
    } else {
      [String]$Suid = $DefaultSuid
    }
    If($Exports.$_.VolName -eq $VolName){
      # $VolName has a sub-volume export (Process-AllHosts always returns TRUE)
      $HasAVolOrSubVolExport = Process-AllHosts $Exports.$_
      If($Exports.$_.RwSpecified -and !$RwRule){
        $RwRule = ("$DefaultProtocol -clientmatch 0.0.0.0/0 -rorule sys -rwrule sys $Anon -superuser none $Suid $DefaultDev")
      }
      If($Exports.$_.RoSpecified -and !$RoRule){
        $RoRule = ("$DefaultProtocol -clientmatch 0.0.0.0/0 -rorule sys -rwrule never $Anon -superuser none $Suid $DefaultDev")
      }
    }
  }
      
  ## IF THE VOLUME HAS AN EXPORTED QTREE, ADD THE STANDARD READONLY RULE ##
  If( $cExports.Volume.$VolName.HasQtreeExports -and !$RwRule -and !$RoRule){
    $RoRule = ("$DefaultProtocol -clientmatch 0.0.0.0/0 -rorule sys -rwrule never $DefaultAnon -superuser none $DefaultSuid $DefaultDev")
  }

  ## IF WE HAVE A VOLUME OR SUB VOLUME EXPORT, OUTPUT IT ##
  If($HasAVolOrSubVolExport){
    $RuleIndex = 1
    $Output += ("vserver export-policy create -vserver $VserverName -policyname $ExPolicy"      )
    $Global:RwRules | Foreach {
      $Output += ($ExPolRuleStart + " " + [String]$RuleIndex + " " + $_)
      $RuleIndex++
    }
    If($RwRule){
      $Output += ($ExPolRuleStart + " " + [String]$RuleIndex + " " + $RwRule)
      $RuleIndex++
    }
    If(!$RwRule){
      $Global:RoRules | Foreach {
        [Boolean]$NotRwClient = $TRUE
        Foreach ($RwClient in $Global:RwClients){
          If($_ -match $RwClient){
            $NotRwClient = $FALSE
          }
        }
        If($NotRwClient){
          $Output += ($ExPolRuleStart + " " + [String]$RuleIndex + " " + $_)
          $RuleIndex++
        }
      }
    }
    If(!$RwRule -and $RoRule){
      $Output += ($ExPolRuleStart + " " + [String]$RuleIndex + " " + $RoRule)
      $RuleIndex++
    }
    $Output += ("volume modify -vserver $VserverName -volume $VolName -policy $ExPolicy"),("")
  }
}

## CREATE AND APPLY PER QTREE EXPORT POLICIES (INCLUDES SUB-QTREE EXPORTS) ##
## ======================================================================= ##

$Output += ("## PER VOLUME QTREE POLICIES (INCLUDES SUB-QTREE EXPORTS ##"),("")

## CYCLE THROUGH THE EXPORTED VOLUMES LIST ##
Foreach($VolName in $cExports.ExportedVolList){
 
  ## CYCLE THROUGH THE EXPORTED QTREES FOR $VOLNAME ##
  Foreach($QtreeName in $cExports.Volume.$VolName.ExportedQtreeList){
   
    # Qtree Names can have spaces in!
    [String]$ExPolicy = ("expol_q_" + $VolName + "_" + $QtreeName.Replace(" ","_"))
   
    # Add rule indexes later
    [String]$ExPolRuleStart = "vserver export-policy rule create -vserver $VserverName -policyname $ExPolicy -ruleindex "
    
    # Initialize "Has a Qtree or Sub Qtree Export (for $VolName.$QtreeName) as FALSE
    [Boolean]$HasAQtreeOrSubQtreeExport = $FALSE
   
    # Reset R/W Rules
    $Global:RwRules = @()
   
    # Reset R/O Rules
    $Global:RoRules = @()
   
    # Initialize as NULL general Read/Write rule
    [String]$RwRule = ""
   
    # Initialize as NULL general Read/Only rule
    [String]$RoRule = ""
   
    # Reset R/W Clients
    $Global:RwClients = @()
   
    # Reset R/O Clients
    $Global:RoClients = @()
   
    ## QTREE EXPORTS ##
    $QtreeExportsOnly | Foreach {
      If($Exports.$_.AnonSpecified){
        [String]$Anon = ("-anon " + $Exports.$_.AnonEquals)
      } else {
        [String]$Anon = $DefaultAnon
      }
      If($Exports.$_.NoSuidSpecified){
        [String]$Suid = ("-allow-suid false")
      } else {
        [String]$Suid = $DefaultSuid
      }
      If($Exports.$_.VolName -eq $VolName){
        If($Exports.$_.QtreeName -eq $QtreeName){
          # $QtreeName has a Qtree export (Process-AllHosts always returns TRUE)
          $HasAQtreeOrSubQtreeExport = Process-AllHosts $Exports.$_
          If($Exports.$_.RwSpecified){
            $RwRule = ("$DefaultProtocol -clientmatch 0.0.0.0/0 -rorule sys -rwrule sys $Anon -superuser none $Suid $DefaultDev")
          }
          If($Exports.$_.RoSpecified){
            $RoRule = ("$DefaultProtocol -clientmatch 0.0.0.0/0 -rorule sys -rwrule never $Anon -superuser none $Suid $DefaultDev")
          }
        }
      }
    }
             
    ## SUB QTREE EXPORTS ##
    $SubQtreeExportsOnly | Foreach {           
      If($Exports.$_.AnonSpecified){
        [String]$Anon = ("-anon " + $Exports.$_.AnonEquals) } else { [String]$Anon = $DefaultAnon
      }
      If($Exports.$_.NoSuidSpecified){
        [String]$Suid = ("-allow-suid false")
      } else {
        [String]$Suid = $DefaultSuid
      }
      If($Exports.$_.VolName -eq $VolName){
        If($Exports.$_.QtreeName -eq $QtreeName){                   
          # $QtreeName has a Sub-Qtree export (Process-AllHosts always returns TRUE)
          $HasAQtreeOrSubQtreeExport = Process-AllHosts $Exports.$_
          If($Exports.$_.RwSpecified -and !$RwRule){
            $RwRule = ("$DefaultProtocol -clientmatch 0.0.0.0/0 -rorule sys -rwrule sys $Anon -superuser none $Suid $DefaultDev")
          }
          If($Exports.$_.RoSpecified -and !$RoRule){
            $RoRule = ("$DefaultProtocol -clientmatch 0.0.0.0/0 -rorule sys -rwrule never $Anon -superuser none $Suid $DefaultDev")
          }
        }
      }                   
    }
      
    ## IF WE HAVE A QTREE OR SUB QTREE EXPORT, OUTPUT IT ##
    If($HasAQtreeOrSubQtreeExport){
      $RuleIndex = 1
      $Output += ("vserver export-policy create -vserver $VserverName -policyname $ExPolicy"    )
      $Global:RwRules | Foreach {
        $Output += ($ExPolRuleStart + " " + [String]$RuleIndex + " " + $_)
        $RuleIndex++
      }
      If($RwRule){
        $Output += ($ExPolRuleStart + " " + [String]$RuleIndex + " " + $RwRule)
        $RuleIndex++
      }
      If(!$RwRule){
        $Global:RoRules | Foreach {
          $Output += ($ExPolRuleStart + " " + [String]$RuleIndex + " " + $_)
          $RuleIndex++
        }
      }
      If(!$RwRule -and $RoRule){
        $Output += ($ExPolRuleStart + " " + [String]$RuleIndex + " " + $RoRule)
        $RuleIndex++
      }
      $Output += ("qtree modify -vserver $VserverName -volume $VolName -qtree " + '"' + $QtreeName + '"' + " -export-policy $ExPolicy"),("")
    }
  }
}

##################################
## CLUSTERSHELL COMMANDS OUTPUT ##
##################################

Wr ">>> STEP 3: CLUSTERSHELL COMMANDS OUTPUT <<<" MAGENTA; Wr; Wr
Wr ("For Clustershell commands see text file: " + ($FileName + "-CONVERTED_TO_CSHELL.txt")); Wr
Wr ("For log of screen output see: " + ($FileName + "-7toC_SCREEN_OUTPUT")); Wr; Wr
$Output | Out-File ($FileName + "-CONVERTED_TO_CSHELL.txt")
$Global:SOut | Out-File ($FileName + "-7toC_SCREEN_OUTPUT.txt")

NotePad ($FileName + "-CONVERTED_TO_CSHELL.txt")