NexentaStor.psm1

Function Connect-NsController {
<#
.SYNOPSIS
    Connects to the api of a NexentaStor controller.
.DESCRIPTION
    Uses the parameters given to perform an authentication against the NexetaStor API and sets up an auth token that can be used by other functions.
    The Save option writes the username and password to a file. This allows the Connect-NsController to be called again without requiring credentials.
.OUTPUTS
    Nothing.
.PARAMETER Name <String>
    The FQDN or IP for the controller to connect to.
.PARAMETER Port <Int>
    The port number for the api to connect to. Defaults to 8443
.PARAMETER Credential <PSCredential>
    Credentials used to authenticate to the api in order to retrive a token
.PARAMETER Save
    Invoke to enable the saving of the given credentials to a file for the controller specified.
.EXAMPLE
    Connect-NsController -Name nexentastor-controller.local -Port 443 -Credential (Get-Credential) -Save
    Connect to the controller on for 443 and save the credentials to file
.EXAMPLE
    Connect-NsController -Name nexentastor-controller.local -Credential $cred
    Connect to the controller on the deafult port
.EXAMPLE
    Connect-NsController -Name nexentastor-controller.local
    Connect to the controller on the default port using credentials from the saved file
.NOTES
    Credentials are saved per controller and the password is written out as a secure string.
    Parameters can be passed as an object.
 
#>

    Param (
        [Parameter(Mandatory=$true,
                    Position=0,
                    ParameterSetName='Default',
                    ValueFromPipelineByPropertyName=$true,
                    HelpMessage="Name of the NexentaStor controller to connect to")]
            [Parameter(ParameterSetName='Cred')]
            [ValidateNotNullOrEmpty()]
            [string]$Name,
        [Parameter(Mandatory=$false,
                    Position=1,
                    ParameterSetName='Default',
                    ValueFromPipelineByPropertyName=$true,
                    HelpMessage="API Port of the NexentaStor controller to connect to - Defaults to 8443")]
            [ValidateNotNullOrEmpty()]
            [int]$Port = 8443,
        [Parameter(Mandatory=$true,
                    Position=3,
                    ParameterSetName='Cred',
                    ValueFromPipelineByPropertyName=$true,
                    HelpMessage="A credential opbject containg the username and password used to connecto to the NexentaStor API")]
            [ValidateNotNull()]
            [System.Management.Automation.PSCredential]
            [System.Management.Automation.Credential()]
            $Credential,
        [Parameter(Mandatory=$false,
                    Position=4,
                    ParameterSetName='Cred',
                    ValueFromPipelineByPropertyName=$true,
                    HelpMessage="Include if you wish to save the credentials as a secure string for later recall")]
        [switch]$Save
        )

        #Read in or save credentials
        $secrets = "$($Env:APPDATA)\NexentaStor"
        if ($PSCmdlet.ParameterSetName -eq 'Cred') {
            if ($Save) {
                if (-Not (Test-Path $secrets)) {New-Item -ItemType Directory -Path $secrets | out-null}
                $detail = @{
                    user = $Credential.UserName
                    pass = $Credential.Password | ConvertFrom-SecureString
                }
                $detail | ConvertTo-Json | Out-File "$secrets\$($Name.ToLower()).sec"
            }
        } else {
            if (Test-Path "$Secrets\$($Name.ToLower()).sec") {
                $detail = Get-Content "$secrets\$($Name.ToLower()).sec" | ConvertFrom-Json
                $Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $detail.user, ($detail.pass | ConvertTo-SecureString)
            } else {
                throw "Could not read stored Credential from $Sscrets\$($Name.ToLower()).sec"
            }
        }
#Set type for ingnoring ssl cert errors
add-type @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
     
    public class IDontCarePolicy : ICertificatePolicy {
        public IDontCarePolicy() {}
        public bool CheckValidationResult(
            ServicePoint sPoint, X509Certificate cert,
            WebRequest wRequest, int certProb) {
            return true;
        }
    }
"@


   # Connect to NexentaStore and save script wide variable
   try {
        # Clean out any old variables
        Remove-Variable nsController -Scope script -ErrorAction SilentlyContinue
        # Disable SSL checks
        $CertificatePolicy = [System.Net.ServicePointManager]::CertificatePolicy
        [System.Net.ServicePointManager]::CertificatePolicy = new-object IDontCarePolicy 
        #Set tls
        $tlsBackup = [Net.ServicePointManager]::SecurityProtocol
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        #Build base url
        $baseUrl = "https://$($Name):$Port"

        #Get Token
        $requestUrl = "$baseUrl/auth/login"
        $login = @{
          "password" = $Credential.GetNetworkCredential().password
          "username" = $Credential.UserName
        }
        $responce = Invoke-RestMethod -Uri $requestUrl -Method Post -Body ($login | ConvertTo-Json) -ContentType "application/json" -Verbose:$false
        #Write connection details to script wide variable fo ruse by other functions
        $script:nsController = [pscustomobject]@{name=$Name;port=$Port;baseUrl=$baseUrl;user=$Credential.UserName;token=$responce.token}
        $script:nsController.psobject.TypeNames.Insert(0,'Ns.Controller')
        Write-Verbose ($script:nsController | fl | Out-String)
        Return $script:nsController
    } catch {
        if ($_.responce.message -eq 'Bad credentials') {
            throw "Invalid Nexenta Credentials"
        } else {
            throw $_
        }
    } finally {
        [System.Net.ServicePointManager]::CertificatePolicy = $CertificatePolicy
        [Net.ServicePointManager]::SecurityProtocol = $tlsBackup
        Remove-Variable login
    }
}

Function Invoke-NsApi {
    Param (
        [Parameter(Mandatory=$true,ParameterSetName='POST')]
            [ValidateNotNullOrEmpty()]
            [string]$Request,
        [Parameter(Mandatory=$true,ParameterSetName='Default')]
        [Parameter(ParameterSetName='POST')]
            [ValidateNotNullOrEmpty()]
            [string]$Operatation,
        [Parameter()]
            [ValidateSet('post','get','delete','put')]
            [string]$Method
            
        )

        #Define a hash of valid operations and their assosiated default action
        $operatationSet = @{}
        $operatationSet.add('auth/status','get')
        $operatationSet.add('storage/pools','get')
        $operatationSet.add('storage/filesystems','get')
        $operatationSet.add('jobStatus','get')
        $operatationSet.add('nas/smb','get')
        $operatationSet.add('nas/nfs','get')
        
        Write-Verbose $PSCmdlet.ParameterSetName
        #Validate Operation
        $OperatationArray = ($Operatation -split '\?')[0] -split '/'
        for ($x = $OperatationArray.Count; $x -ge 1 ; $x -= 1) { #hunt map hash for closest match chomping the last operation each time
            $key = ($OperatationArray | select -First $x) -join "/"
            if ($operatationSet.ContainsKey($key.ToLower())) {
                if (-Not $Method) {
                    $Method = $operatationSet[$key]
                }
                break
            }
            if ($x -eq 1) {
                throw 'Operation could not be found in operatationSet'
            }
        }        

        #Override default OpSet if a request is included
        if ($PSCmdlet.ParameterSetName -eq 'POST') {
            if ($Method -eq 'get') {
                $Method = 'post'
            }
            #Validate Json
            try {
                $Request | ConvertFrom-Json -ErrorAction stop | Out-Null
            } catch {
                throw 'Request is not a valid Json string'
            }

        }

        #Check we are connected to a controller
        If (-Not $Script:nsController) {
            throw 'Please connect to NexentaStor controller'
        }

#Set type for ingnoring ssl cert errors
add-type @'
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
     
    public class IDontCarePolicy : ICertificatePolicy {
        public IDontCarePolicy() {}
        public bool CheckValidationResult(
            ServicePoint sPoint, X509Certificate cert,
            WebRequest wRequest, int certProb) {
            return true;
        }
    }
'@

    try {
        # Disable SSL checks
        $CertificatePolicy = [System.Net.ServicePointManager]::CertificatePolicy
        [System.Net.ServicePointManager]::CertificatePolicy = new-object IDontCarePolicy 
        #Set tls
        $tlsBackup = [Net.ServicePointManager]::SecurityProtocol
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        #Build request
        $baseUrl = $Script:nsController.baseUrl
        $token = $Script:nsController.token
        $requestUrl = "$baseUrl/$Operatation"
        Write-Verbose $requestUrl
        Write-Verbose $Method
        $Header = @{'Authorization' = "Bearer $token";'Content-Type' = 'application/json'}
        # Make the request
        switch ($method) {
            {$_ -eq 'post' -or $_ -eq 'put'} {
                Write-Verbose ($Request | Out-String)
                $responce = Invoke-RestMethod -Uri $requestUrl -Method $Method -Body $Request -Headers $Header -Verbose:$false
            }
            default {
                $responce = Invoke-RestMethod -Uri $requestUrl -Method $Method -Headers $Header -Verbose:$false
            }

        }
        # Return the responce
        Return $responce
    } catch {
        Write-Verbose $_.Exception.Response.StatusCode
        Write-Verbose $_.Exception.Response.StatusCode.value__
        Switch ($_.Exception.Response.StatusCode.value__) {
            401 { # Unauthorized
                # Token timeout, try reconnecting.
                Write-Verbose "We are not connected to a NexentaStor controller..trying to reconnect"
                try {
                    # Try reconnecting and resubmitting the request.
                    Write-Verbose "Trying to reconnect to $($Script:nsController.name):$($Script:nsController.port)"
                    Connect-NsController -Name $Script:nsController.name -Port $Script:nsController.port | Out-Null
                    Write-Verbose "Reconnected....Retrying request."
                    Write-Verbose "Invoke-NsApi -Operatation $Operatation -Method $Method"
                    $responce = $null
                    switch ($method) {
                        {$_ -eq 'post' -or $_ -eq 'put'} {
                            $responce = Invoke-NsApi -Operatation $Operatation -Request $Request -Method $Method
                        }
                        default {
                            $responce = Invoke-NsApi -Operatation $Operatation -Method $Method
                        }
                    }
                    Return $responce
                } catch {
                    throw "Could not re-connect to $($Script:nsController.name) : ($_.Exception.Message)"
                }
            }
            404 { #NotFound
                # No results were found, return nothing
            }
            400 {
                throw "Bad request"
            }
            default {
                # Not an error we know how to handle, pass it up the stack
                throw $_
            }
        }
    } finally {
        [System.Net.ServicePointManager]::CertificatePolicy = $CertificatePolicy
        [Net.ServicePointManager]::SecurityProtocol = $tlsBackup
    }
}    

Function Get-NsPool {
<#
.SYNOPSIS
    Get pools for the current controller.
.DESCRIPTION
    Retrieves one or more pools from the connected NexenaStor controller.
    If no name is specified, all pools are retrived with the exception of the pool hosting the controller's OS.
    If no name is specified but the All switch is invoked, all pools are retrived including the pool hosting the controller's OS.
    If a name is specified, only the named pool is retived.
.OUTPUTS
    Nothing if no mathcing pools are found.
    Otherwise, matching Pool objects
.PARAMETER Name <String>
    The name of the pool to retrive
.PARAMETER All
    Invoke to retrive all pools
.EXAMPLE
    PS C:\> Get-NsPool
 
    Name Health TotalSize Used %Used
    ---- ------ --------- ---- -----
    p1 ONLINE 28.88 GB 100.30 MB 0
    p2 ONLINE 28.88 GB 100.31 MB 0
 
    Retrive pools from current controller
.EXAMPLE
    PS C:\> Get-NsPool -all
 
    Name Health TotalSize Used %Used
    ---- ------ --------- ---- -----
    p1 ONLINE 28.88 GB 100.30 MB 0
    p2 ONLINE 28.88 GB 100.31 MB 0
    rpool ONLINE 9.63 GB 6.18 GB 41
 
    Retrive All pools from current controller
.EXAMPLE
    PS C:\> Get-NsPool -Name p2
 
    Name Health TotalSize Used %Used
    ---- ------ --------- ---- -----
    p2 ONLINE 28.88 GB 100.31 MB 0
 
    Retrive a named pool
 
.NOTES
    Format-Table defaults to converting bytes to Mb/Gb/Tb
 
#>

    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipeline,
                    ParameterSetName='Name',
                    HelpMessage="Name of the pool to retirve")]
            [ValidatePattern("^\w+$")]
            [string]$Name,
        [Parameter(ParameterSetName='All',
                HelpMessage="Invoke to retrive all pools, by default the os pool is hidden")]
            [switch]$All
    )
    try {
        #Are we after all pools or a named one?
        if ($Name) {
            $pools = Invoke-NsApi -Operatation "storage/pools?poolName=$([uri]::EscapeDataString($Name))"
            $returnValue = $pools.data
        } else {
            $pools = Invoke-NsApi -Operatation "storage/pools"
            # Are we returning a filter view or all the pools?
            if ($All) {
                $returnValue = $pools.data
            } else {
                $returnValue = $pools.data | ? {$_.poolName -ne 'rpool'}
            }
        }
        $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.Pool')}
        Return $returnValue
    } catch {
        throw $_
    }
}

Function Get-NsFilesystem {
<#
.SYNOPSIS
    Get filesystems for the current controller
.DESCRIPTION
    Retrieves one or more filesystems from the connected NexenaStor controller.
    If nothing is specified, all filesystems are returned.
    If a path is specified, only that filesystem is retrived.
    If Recurse is invoked, all child filsystems from the pool or path are retrived.
    If Detailed is invoked, exteneded attributes are retrived for all filesystems matching the request.
.OUTPUTS
    Nothing if no mathcing filesystems are found.
    Otherwise, matching Filesystem objects
.PARAMETER Path <String>
    The path to the filesystem to retrive
.PARAMETER Recurse
    Invoke to request child filesystems are retrived
.PARAMETER Detailed
    Invoke to request extended attributes of the filesystems are retrived
.EXAMPLE
    PS C:\> Get-NsPool | Get-NsFilesystem -Recurse
 
    name path Available LogicalUsed ActualUsed UsedBySnapshots quotaSize compressionRatio
    ---- ---- --------- ----------- ---------- --------------- --------- ----------------
    p1 p1 28.78 Gb 187.50 Kb 100.45 Mb 0 B 0 B 1
    top1 p1/top1 28.78 Gb 12.00 Kb 24.00 Kb 0 B 0 B 1
    top2 p1/top2 28.78 Gb 24.00 Kb 48.00 Kb 0 B 0 B 1
    top3 p1/top3 28.78 Gb 12.00 Kb 24.00 Kb 0 B 0 B 1
    p2 p2 28.78 Gb 124.00 Kb 100.31 Mb 0 B 0 B 1
 
    Get filestsystems for all pools
.EXAMPLE
    PS C:\> Get-NsFilesystem p1/top2 -Recurse
 
    name path Available LogicalUsed ActualUsed UsedBySnapshots quotaSize compressionRatio
    ---- ---- --------- ----------- ---------- --------------- --------- ----------------
    top2 p1/top2 28.78 Gb 24.00 Kb 48.00 Kb 0 B 0 B 1
    top2/sub1 p1/top2/sub1 28.78 Gb 12.00 Kb 24.00 Kb 0 B 0 B 1
 
    Get named filesystem and all desendants.
.NOTES
    Format-Table defaults to converting bytes to Mb/Gb/Tb
 
#>

    [cmdletbinding(DefaultParameterSetName='none')]
    Param (
        [Parameter(Mandatory=$true,
                    Position=0,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    ParameterSetName='ByPath',
                    HelpMessage='The path to the filesystem to retrive')]
            [ValidatePattern("^\w+(|(\/\w+)){1,}$")]
            [alias("poolName")]
            [string]$Path,
        [switch]$Recurse,
        [switch]$Detailed
     )
     Process {
        try {
            # Set operation based on input
            switch ($PSCmdlet.ParameterSetName) {
                'ByPath' {
                    if (-Not $Recurse) {
                        $operation = "storage/filesystems?path=$([uri]::EscapeDataString($Path))"
                    } else {
                        # using parent causes all sub filesystems to be returned.
                        $operation = "storage/filesystems?parent=$([uri]::EscapeDataString($Path))&recursive=$Recurse"
                    }
                }
                'none' {
                    $operation = "storage/filesystems"
                }
            }
            Write-Verbose $operation
            # Get the required filesystems
            $filesystems = Invoke-NsApi -Operatation $operation
            # Do we want detailed output? if so we are going to have to go and get each filesystem seperatly
            if ($Detailed) {
                $newFilesystems = @()
                Foreach ($filesystem in $filesystems.data) {
                    $operation = "storage/filesystems/$([uri]::EscapeDataString($filesystem.path))"
                    Write-Verbose $operation
                    $newFilesystems += Invoke-NsApi -Operatation $operation
                }
                $returnValue = $newFilesystems
            } else {
                $returnValue = $filesystems.data
            }
            $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.Filesystem')}
            Return $returnValue
        } catch {
            throw $_
        }
    }
}

Function Get-NsJobs {
    Param (
        [Parameter(ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    Position = 0)]
            [ValidateNotNullOrEmpty()]
            [ValidatePattern("^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$")]
            [string]$JobId
     )
    Process {
        try {
            # If JobId is not given, we get all jobs
            if ($JobId) {$jobQuery="?jobId=$JobId"}
            $jobs = Invoke-NsApi -Operatation "jobStatus$jobQuery"
            $jobs.data
        } catch {
            throw $_
        }
    }
}

Function New-NsFilesystem {
<#
.SYNOPSIS
    Create a new filesystem
.DESCRIPTION
    Creates a new filesystem with the given attributes
.OUTPUTS
    The cmdlet will wait (30sec) for the task to complete and return a Filesystem object for the created filesystem
    If -NoWait was invoked it will return the GUID of the creation task
    If the cmdlet waits for more then 30sec it will return the GUID of the task.
.PARAMETER Path <String>
    The path for the filesystem to create
.PARAMETER ReferencedQuota <String>
    The data quota (in [int]Mb/[int]Gb/[int]Tb) for the filesystem,
.PARAMETER Quota <String>
    The hard (data+desendents) quota (in [int]Mb/[int]Gb/[int]Tb) for the filesystem,
.PARAMETER CustomProperties <[String[]]>
    An Attray of strings to set as custom attributes for the filesystem in the format AttributeName:AttributeValue
    Eg "custatt1:yes","custatt2:Tuesday"
.PARAMETER NoWait
    Invoke to make the cmdlet start the job and return the GUID of the JobID
.EXAMPLE
    PS C:\> New-NsFileSystem -Path p2/top1 -NoWait
    c36fea60-faac-11e7-a2ef-2b309711a489
 
    Create a new filesystem
.EXMAPLE
    PS C:\> New-NsFileSystem -Path p2/top1/sub1
 
    name path Available LogicalUsed ActualUsed UsedBySnapshots quotaSize compressionRatio
    ---- ---- --------- ----------- ---------- --------------- --------- ----------------
    top1/sub1 p2/top1/sub1 28.78 Gb 12.00 Kb 24.00 Kb 0 B 0 B 1
 
    Create a new filesystem and wait for it to be created
.EXAMPLE
    PS C:\> New-NsFileSystem -Path p2/top2 -ReferencedQuota 10Gb -Quota 15Gb -CustomProperties "example:attribute" -Wait
 
    name path Available LogicalUsed ActualUsed UsedBySnapshots quotaSize compressionRatio
    ---- ---- --------- ----------- ---------- --------------- --------- ----------------
    top2 p2/top2 10.00 Gb 12.00 Kb 24.00 Kb 0 B 15.00 Gb 1
 
    PS C:\> (Get-NsFilesystem -Path p2/top2 -Detailed)[0] | select referencedQuotaSize,example:
 
    referencedQuotaSize example:
    ------------------- --------
            10737418240 attribut
 
    Create new filesystem with quotas and custom attrbutes.
#>

    [cmdletbinding(   
        ConfirmImpact = 'low',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The path for the filesystem to create')]
            [ValidatePattern("^\w+(|(\/\w+)){1,}$")]
            [string]$Path, #path
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='The data quota (in [int]Mb/[int]Gb/[int]Tb) for the filesystem')]
            [ValidatePattern("^\d+[MGTmgt][Bb]$")]
            [string]$ReferencedQuota, #referencedQuotaSize
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='The hard (data+desendents) quota (in [int]Mb/[int]Gb/[int]Tb) for the filesystem,')]
            [ValidatePattern("^\d+[MGTmgt][Bb]$")]
            [string]$Quota, #quotaSize
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='An Attray of strings to set as custom attributes for the filesystem in the format AttributeName:AttributeValue')]
            [ValidateNotNullOrEmpty()]
            [string[]]$CustomProperties,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Makes the cmdlet wait for the creation to complete before returning a Filesystem object.')]
            [switch]$NoWait
    )

    #Test fileststem already exists
    if (($Path | Get-NsFilesystem) -ne $null) {
        throw 'Filesystem already exists'
    }

    #Test parent Exists
    $parentPath = ($Path | Split-Path) -replace '\\','/'
    if (($parentPath | Get-NsFilesystem) -eq $null) {
        throw 'Parent filesystem does not exist'
    }

    #Check quota sizes
    if ($ReferencedQuota -and $Quota) {
        if (($ReferencedQuota/1) -gt ($Quota/1)) {
            throw 'ReferencedQuota cannot be smaller then Quota'
        }
    }

    # Test Custom Properties
    if ($CustomProperties) {
        $CustomProperties | % {if ($_ -notmatch "^([._a-z0-9][-._a-z0-9]*)?:[-:._a-z0-9]*$") {throw "$_ is an invalid custom property"}}
    }

    #Build request
    $request = @{}
    $request.add('path',$Path)
    if ($ReferencedQuota) {$request.add('referencedQuotaSize',$ReferencedQuota /1)}
    if ($Quota) {$request.add('quotaSize',$Quota /1)}
    if ($CustomProperties) {$CustomProperties | % {$properties = $_ -split ':',2;$request.add("$($properties[0]):",$properties[1])}}
    try {
        if ($pscmdlet.ShouldProcess($Path,"Create Filesystem")) {
            # Kick off the creation request
            $responce = Invoke-NsApi -Operatation "storage/filesystems" -Request ($request | ConvertTo-Json)
            # Grap the id from the result
            $JobId = $responce.links.href | Split-Path -Leaf
            # If we've been told to wait for compleation...
            if (-Not $NoWait) {
                # Limit ourselves to 30 sec before returning filesystem
                $count = 10
                #Get job and check status
                while ((Get-NsJobs -JobId $JobId).done -ne $true) {
                    Write-Verbose "Filesystem not ready yet - sleeping"
                    sleep 5
                    $count -= 1
                    # If we've waited too long, just return the jobid
                    if ($count -eq 0) {Return $JobId}
                }
                # Filesystem has been created so go get the details.
                Return Get-NsFilesystem -Path $Path -Detailed
            } else {
                # Not been asked to wait so just return the jobid
                Return $JobId
            }
        }
    } catch {
        throw $_
    }
}

Function Remove-NsFilesystem {
<#
.SYNOPSIS
    Deletes a filesystem
.DESCRIPTION
    Deletes the named filesystem
.OUTPUTS
    The cmdlet will wait (30sec) for the task to complete and return true
    If -NoWait was invoked it will return the GUID of the creation task
    If the cmdlet waits for more then 30sec it will return the GUID of the task.
.PARAMETER Path <String>
    The path for the filesystem to create
.PARAMETER NoForce
    Prevent the deletetion of the filesystem if there are open files
.PARAMETER NoSnapshots
    Prevent the deletion of snapshots for the filesystem
.PARAMETER NoWait
    Invoke to make the cmdlet wait for the creation to complete before returning a Filesystem object.
.EXAMPLE
    PS C:\> Remove-NsFilesystem -Path p1/top3 -Confirm:$false -NoForce -NoWait
    ed752d30-faba-11e7-a2ef-2b309711a489
 
    Remove the filesystem and snapshots as long as there are no open files, wait for the task to complete before returning
.EXMAPLE
    PS C:\> Remove-NsFilesystem -Path p1/top3 -Confirm:$false -NoSnapshots
    True
 
    Remove the filesystem but not the snapshots
 
#>

    [cmdletbinding(   
        ConfirmImpact = 'high',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The path for the filesystem to delete')]
            [ValidatePattern("^\w+(|(\/\w+)){1,}$")]
            [string]$Path, #path
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Prevent the deletetion of the filesystem if there are open files')]
            [switch]$NoForce,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Prevent the deletion of snapshots for the filesystem')]
            [switch]$NoSnapshots,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Makes the cmdlet wait for the deletion to complete before returning')]
            [switch]$NoWait

    )

    #Test filesystem exists
    if (($Path | Get-NsFilesystem) -eq $null) {
        throw 'Filesystem does not exists'
    }

    try {
        if ($pscmdlet.ShouldProcess($Path,"Remove Filesystem")) {
            $Operation = "storage/filesystems/$([uri]::EscapeDataString($Path))?force=$(-Not $NoForce)&snapshots=$(-Not $NoSnapshots)"
            Write-Verbose $Operation
            # Kick off the deletion request
            $responce = Invoke-NsApi -Operatation $Operation -Method 'delete'
            # Grap the id from the result
            $JobId = $responce.links.href | Split-Path -Leaf
            # If we've been told to wait for compleation...
            if (-Not $NoWait) {
                # Limit ourselves to 30 sec before returning filesystem
                $count = 10
                #Get job and check status
                while ((Get-NsJobs -JobId $JobId).done -ne $true) {
                    Write-Verbose "Filesystem not deleted yet - sleeping"
                    sleep 5
                    $count -= 1
                    # If we've waited too long, just return the jobid
                    if ($count -eq 0) {Return $JobId}
                }
                # Filesystem has been deleted so go get the details.
                Return $true
            } else {
                # Not been asked to wait so just return the jobid
                Return $JobId
            }
        }
    }catch {
        throw $_
    }
}

Function Get-NsSmbShare {
<#
.SYNOPSIS
    Get information about one ot more SMB Shares
.DESCRIPTION
    Get information about one ot more SMB Shares
.OUTPUTS
    If nothing is specified, all sMB shares are returned.
    If a Name is specified, the SMB share with that name is retrived.
    If a path is specified, the SMB share with that filesystem is retrived.
.PARAMETER ShareName <String>
    Name of the share to retirve
.PARAMETER Path <String>
    Path to the share to retirve
.EXAMPLE
    PS C:\> Get-NsSmbShare | ft
 
    filesystem shareState shareName guestOk encryption accessBasedEnum shareDescription shareQuotas clientSideCaching
    ---------- ---------- --------- ------- ---------- --------------- ---------------- ----------- -----------------
    p1/top1 online p1_top1 False False False False manual
    p1/top2/sub1 online p1_top2_sub1 False False True hello False manual
 
    Get all SMB shares
.EXMAPLE
    PS C:\> Get-NsFilesystem p1/top2/sub1 | Get-NsSmbShare | ft
 
    filesystem shareState shareName guestOk encryption accessBasedEnum shareDescription shareQuotas clientSideCaching
    ---------- ---------- --------- ------- ---------- --------------- ---------------- ----------- -----------------
    p1/top2/sub1 online p1_top2_sub1 False False True hello False manual
 
    Get the SMB Share for a filesystem
.EXAMPLE
    PS C:\> "p1_top1" | Get-NsSmbShare | ft
 
    filesystem shareState shareName guestOk encryption accessBasedEnum shareDescription shareQuotas clientSideCaching
    ---------- ---------- --------- ------- ---------- --------------- ---------------- ----------- -----------------
    p1/top1 online p1_top1 False False False False manual
 
    Get an SMB share by name
#>

    [cmdletbinding(DefaultParameterSetName='none')]
    Param (
        [Parameter( Mandatory=$true,
                    Position = 0,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    ParameterSetName='ByName',
                    HelpMessage="Name of the share to retirve")]
            [ValidatePattern("^(\w|-|_)+(|\$)$")]
            [string]$ShareName,
        [Parameter( Mandatory=$true,
                    ValueFromPipelineByPropertyName,
                    ParameterSetName='ByPath',
                    HelpMessage="Path to the share to retirve")]
            [ValidatePattern("^\w+(|(\/\w+)){1,}$")]
            [string]$Path

    )
    Process {
        try {
            # Set operation based on input
            switch ($PSCmdlet.ParameterSetName) {
                'ByName' {
                    $operation = "nas/smb?shareName=$([uri]::EscapeDataString($ShareName))"
                }
                'ByPath' {
                    $operation = "nas/smb?filesystem=$([uri]::EscapeDataString($Path))"
                }
                'none' {
                    $operation = "nas/smb"
                }
            }
            Write-Verbose $operation
            # Get the required filesystems
            $returnValue = (Invoke-NsApi -Operatation $operation).data | select -ExcludeProperty href -Property *
            $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.SmbShare')}
            Return $returnValue
        } catch {
            throw $_
        }
    }
}

Function New-NsSmbShare {
<#
.SYNOPSIS
    Shares a filesystem using SMB
.DESCRIPTION
    Shares a filesystem using SMB
.OUTPUTS
    Share object if created
.PARAMETER Path <String>
    The path to the filesystem to share
.PARAMETER Name <String>
    The name to give the share
.PARAMETER AccessBasedEnum
    Enable Access based enumeration feature for the share
.PARAMETER clientSideCaching <string>
    Client-side caching (CSC) options applied to this share - Defaults to manual'
.PARAMETER Encryption
    Enable encryption for share
.PARAMETER GuestOk
    Enable guest access to share
.PARAMETER Description <String>
    Description of share
.PARAMETER ShareQuotas
    SMB quotas presented and supported
.EXAMPLE
    PS C:\> New-NsSmbShare -Path p2/top2 -ShareName "test-share_1" | ft
 
    filesystem shareState shareName guestOk encryption accessBasedEnum shareDescription shareQuotas clientSideCaching
    ---------- ---------- --------- ------- ---------- --------------- ---------------- ----------- -----------------
    p2/top2 online test-share_1 False False False False manual
 
    Create a share called test-share_1 on filesystem p2/top2
#>

    [cmdletbinding(   
        ConfirmImpact = 'low',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The path to the filesystem to share')]
            [ValidatePattern("^\w+(|(\/\w+)){1,}$")]
            [string]$Path,
        [Parameter(Mandatory=$true,
                    Position = 1,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The name to give the share')]
            [ValidatePattern("^(\w|-|_)+(|\$)$")]
            [string]$ShareName,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Enable Access based enumeration feature for the share')]
            [switch]$AccessBasedEnum,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Client-side caching (CSC) options applied to this share - Defaults to manual')]
            [ValidateSet('manual','auto','vdo','disabled')]
            [string]$clientSideCaching = 'manual',
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Enable encryption for share')]
            [switch]$Encryption ,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Enable guest access to share')]
            [switch]$GuestOk,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Description of share')]
            [ValidateNotNullOrEmpty()]
            [string]$Description,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='SMB quotas presented and supported')]
            [switch]$ShareQuotas
    )

    #Test filesystem exists
    if (($Path | Get-NsFilesystem) -eq $null) {
        throw 'Filesystem does not exists'
    } 

    #Test the filesystem is not already shared
    if ((Get-NsSmbShare -Path $Path) -ne $null) {
        throw 'The filesystem is already shared'
    }

    #Test the share name does not exists
    if ((Get-NsSmbShare -Name $ShareName) -ne $null) {
        throw 'The share name already exists on the controller'
    }

    #Build request
    $request = @{}
    $request.add('filesystem',$Path)
    $request.add('shareName',$ShareName)
    $request.add('accessBasedEnum',$AccessBasedEnum.IsPresent)
    $request.add('clientSideCaching',"$clientSideCaching")
    $request.add('encryption',$Encryption.IsPresent)
    $request.add('guestOk',$GuestOk.IsPresent)
    if ($Description) {$request.add('shareDescription',$Description)}
    $request.add('shareQuotas',$ShareQuotas.IsPresent)

    try {
        if ($pscmdlet.ShouldProcess($Path,"Create Share")) {
            # Kick off the creation request
            Invoke-NsApi -Operatation "nas/smb" -Request ($request | ConvertTo-Json) | Out-Null
            Get-NsSmbShare -Path $Path
        }
    } catch {
        throw $_
    }
}

Function Set-NsSmbShare {
<#
.SYNOPSIS
    Sets the options for an SMB share
.DESCRIPTION
    Sets the options for an SMB share
.OUTPUTS
    Updated share object
.PARAMETER Path <String>
    The path to the filesystem for SMB share to change
.PARAMETER Name <String>
    The name to give the share
.PARAMETER AccessBasedEnum
    Enable Access based enumeration feature for the share
.PARAMETER clientSideCaching <string>
    Client-side caching (CSC) options applied to this share - Defaults to manual'
.PARAMETER Encryption
    Enable encryption for share
.PARAMETER GuestOk
    Enable guest access to share
.PARAMETER Description <String>
    Description of share
.PARAMETER ShareQuotas
    SMB quotas presented and supported
.EXAMPLE
    PS C:\> Set-NsSmbShare -Path p2/top2 -ShareName test-share3 -AccessBasedEnum | ft
 
    filesystem shareState shareName guestOk encryption accessBasedEnum shareDescription shareQuotas clientSideCaching
    ---------- ---------- --------- ------- ---------- --------------- ---------------- ----------- -----------------
    p2/top2 online test-share3 False False True False manual
 
    Change the share name of p2/top2 and enable ABE
#>

    [cmdletbinding(   
        ConfirmImpact = 'medium',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The path to the filesystem to share')]
            [ValidatePattern("^\w+(|(\/\w+)){1,}$")]
            [string]$Path,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='The name to give the share')]
            [ValidatePattern("^(\w|-|_)+(|\$)$")]
            [string]$ShareName,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Enable Access based enumeration feature for the share')]
            [switch]$AccessBasedEnum,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Client-side caching (CSC) options applied to this share - Defaults to manual')]
            [ValidateSet('manual','auto','vdo','disabled')]
            [string]$clientSideCaching = 'manual',
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Enable encryption for share')]
            [switch]$Encryption ,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Enable guest access to share')]
            [switch]$GuestOk,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Description of share')]
            [ValidateNotNullOrEmpty()]
            [string]$Description,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='SMB quotas presented and supported')]
            [switch]$ShareQuotas
    )
    Process {
        #Test the filesystem is shared
        if ((Get-NsSmbShare -Path $Path) -eq $null) {
            throw 'The filesystem is not shared'
        }

        #Test the share name does not exists
        if ((Get-NsSmbShare -Name $ShareName) -ne $null) {
            throw 'The share name already exists on the controller'
        }

        #Build request
        $request = @{}
        $request.add('shareName',$ShareName)
        $request.add('accessBasedEnum',$AccessBasedEnum.IsPresent)
        $request.add('clientSideCaching',"$clientSideCaching")
        $request.add('encryption',$Encryption.IsPresent)
        $request.add('guestOk',$GuestOk.IsPresent)
        if ($Description) {$request.add('shareDescription',$Description)}
        $request.add('shareQuotas',$ShareQuotas.IsPresent)

        try {
            if ($pscmdlet.ShouldProcess($Path,"Update Share")) {
                # Kick off the update request
                Invoke-NsApi -Operatation "nas/smb/$([uri]::EscapeDataString($Path))" -Request ($request | ConvertTo-Json) -Method 'put' | Out-Null
                Return Get-NsSmbShare -Path $Path
            }
        } catch {
            throw $_
        }
    }
}

Function Remove-NsSmbShare {
<#
.SYNOPSIS
    Removes the SMB share froma filesystem
.DESCRIPTION
    Removes the SMB share froma filesystem
.OUTPUTS
    True if the share is removed
.PARAMETER Path <String>
    The path to the filesystem for SMB share to remove
.EXAMPLE
    PS C:\> Remove-NsSmbShare -Path p2/top1
    True
 
    Remove the share name from p2/top1
#>

    [cmdletbinding(   
        ConfirmImpact = 'high',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipeline,
                    HelpMessage='The path to the filesystem to unshare')]
            [ValidatePattern("^\w+(|(\/\w+)){1,}$")]
            [string]$Path
    )

    Process {
        #Test the filesystem is shared
        if ((Get-NsSmbShare -Path $Path) -eq $null) {
            throw 'The filesystem is not shared'
        }

        try {
            if ($pscmdlet.ShouldProcess($Path,"Remove Share")) {
                # Kick off the update request
                Invoke-NsApi -Operatation "nas/smb/$([uri]::EscapeDataString($Path))" -Method 'delete' | Out-Null
                Return $true
            }
        } catch {
            throw $_
        }
    }
}

Function Get-NsFilesystemAcl {
<#
.SYNOPSIS
    Get acls for a filesystem
.DESCRIPTION
    Get the acl information for a filesystem
.OUTPUTS
    one or more acl objects
.PARAMETER Path <String>
    The path to the filesystem
.EXAMPLE
    PS C:\> Get-NsFilesystemAcl -Path p2/top2 | ft
 
    type principal permissions flags index
    ---- --------- ----------- ----- -----
    allow groupsid:Administrators@BUILTIN {list_directory, read_data, add_file, write_data...} {file_inherit, dir_inherit} 0
    allow owner@ {list_directory, read_data, add_file, write_data...} {file_inherit, dir_inherit} 1
    allow usersid:sis02cdcvm@rdg-home.ad.rdg.ac.uk {list_directory, read_data, read_xattr, execute...} {file_inherit, dir_inherit} 2
 
    Get the acls for p2/top2
#>

    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The path to the filesystem')]
            [ValidatePattern("^\w+(|(\/\w+)){1,}$")]
            [string]$Path
    )

    Process {
        try {
            $responce = Invoke-NsApi -Operatation "storage/filesystems/$([uri]::EscapeDataString($Path))/acl"
            Return $responce.data | select -ExcludeProperty href -Property *
        } catch {
            throw $_
        }
    }
}

Function Add-NsFilesystemAcl {
<#
.SYNOPSIS
    Adds to or Replaces the acls for a filesystem
.DESCRIPTION
    Add an acl to a filesystem
.OUTPUTS
    The cmdlet will wait (30sec) for the task to complete and return the updated acls, unless -NoWait was invoked
    If -NoWait was invoked the cmdlet will return the GUID of the update task
    If the cmdlet waits for more then 30sec it will return the GUID of the task.
.PARAMETER Path <String>
    The path to the filesystem
.PARAMETER Flags <[String[]>
    Controls how ACE is inherited by new directory entries - Defaults to 'file_inherit','dir_inherit'
.PARAMETER Permissions <[String[]>
    Set of permissions to allow or deny - Defaults to 'win_modify'
.PARAMETER Principal <String>
    User or group which ACE applies to. Special values 'owner@', 'group@' and 'everyone@' refer to user and group who own the file or just everyone else
.PARAMETER Type <String>
    Indicates if this ACE allows or denies permission - Defaults to 'allow'
.PARAMETER Replace
    Invoke to repalce existing ACLs rather than append to them
.PARAMETER NoWait
    Invoke to make the cmdlet start the Job and return the JobID
     
.EXAMPLE
    PS C:\> Add-NsFilesystemAcl -Path p2/top2 -Principal "usersid:user3@local.domain" | ft
 
    type principal permissions flags index
    ---- --------- ----------- ----- -----
    allow usersid:user1@local.domain {list_directory, read_data, read_xattr, execute...} {file_inherit, dir_inherit} 0
    allow usersid:user2@local.domain {list_directory, read_data, add_file, write_data...} {file_inherit, dir_inherit} 1
    allow groupsid:Administrators@BUILTIN {list_directory, read_data, add_file, write_data...} {file_inherit, dir_inherit} 2
    allow owner@ {list_directory, read_data, add_file, write_data...} {file_inherit, dir_inherit} 3
    allow usersid:user3@local.domain {list_directory, read_data, add_file, write_data...} {file_inherit, dir_inherit} 4
 
    Creates a new acl for user3 giving them modify access to the filesystem
#>

    [cmdletbinding(   
        ConfirmImpact = 'low',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The path to the filesystem')]
            [ValidatePattern("^\w+(|(\/\w+)){1,}$")]
            [string]$Path,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Controls how ACE is inherited by new directory entries')]
            [ValidateSet('file_inherit','dir_inherit','inherit_only','no_propagate','successful_access','failed_access','inherited')]
            [string[]]$Flags = @('file_inherit','dir_inherit'),
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Set of permissions to allow or deny')]
            [ValidateSet('full_set','modify_set','read_set','write_set','win_full','win_modify','win_read','read_data','list_directory','write_data','add_file','append_data','add_subdirectory','read_xattr','write_xattr','execute','read_attributes','write_attributes','delete','delete_child','read_acl','write_acl','write_owner','synchronize')]
            [string[]]$Permissions  = @('win_modify'),
        [Parameter(Mandatory=$true,
                    ValueFromPipelineByPropertyName,
                    HelpMessage="User or group which ACE applies to. Special values 'owner@', 'group@' and 'everyone@' refer to user and group who own the file or just everyone else")]
            [ValidateNotNullOrEmpty()]
            [string]$Principal,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Indicates if this ACE allows or denies permission')]
            [ValidateSet('allow','deny')]
            [string]$Type = 'allow',
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Repalce instead of append existing ACL')]
            [switch]$Replace,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Makes the cmdlet wait for the update to complete before returning')]
            [switch]$NoWait

    )
    Process {
        #Remove any duplicate flags
        $Flags = $Flags | select -Unique
        #Remove and Duplicate permissions
        $Permissions = $Permissions | select -Unique

        #Build request
        $request = @{}
        $request.add('flags',$Flags)
        $request.add('permissions',$Permissions)
        $request.add('principal',$Principal)
        $request.add('type',$Type)
        if (-Not $Replace) {$request.add('index',-1)}

        try {
            if ($pscmdlet.ShouldProcess($Path,"Add ACL")) {
                # Kick off the update request
                $responce = Invoke-NsApi -Operatation "storage/filesystems/$([uri]::EscapeDataString($Path))/acl" -Request ($request | ConvertTo-Json)
                # Grap the id from the result
                $JobId = $responce.links.href | Split-Path -Leaf
                # If we've been told to wait for compleation...
                if (-Not $NoWait) {
                    # Limit ourselves to 30 sec before returning filesystem
                    $count = 10
                    #Get job and check status
                    while ((Get-NsJobs -JobId $JobId).done -ne $true) {
                        Write-Verbose "acl not ready yet - sleeping"
                        sleep 5
                        $count -= 1
                        # If we've waited too long, just return the jobid
                        if ($count -eq 0) {Return $JobId}
                    }
                    # Filesystem has been updated so go get the new acls.
                    Return Get-NsFilesystemAcl -Path $Path
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $JobId
                }
                $responce
            } else {Write-Verbose ($request | ConvertTo-Json)}
        } catch {
            throw $_
        }
    }
}

Function Set-NsFilesystemAcl {
<#
.SYNOPSIS
    Modifies the acls for a filesystem
.DESCRIPTION
    Get the acl information for a file system
.OUTPUTS
    The cmdlet will wait (30sec) for the task to complete and return the updated acls, unless -NoWait was invoked.
    If -NoWait was invoked, the cmdlet will return the GUID of the update task
    If the cmdlet waits for more then 30sec it will return the GUID of the task.
.PARAMETER Path <String>
    The path to the filesystem
.PARAMETER Index <Int>
    The index of the acl to modify
.PARAMETER Flags <[String[]>
    Controls how ACE is inherited by new directory entries
.PARAMETER Permissions <[String[]>
    Set of permissions to allow or deny
.PARAMETER Principal <String>
    User or group which ACE applies to. Special values 'owner@', 'group@' and 'everyone@' refer to user and group who own the file or just everyone else
.PARAMETER Type <String>
    Indicates if this ACE allows or denies permission
.PARAMETER NoWait
    Invoke to make the cmdlet start the job and return the JobID.
     
.EXAMPLE
    PS C:\> Set-NsFilesystemAcl -Path p2/top2 -Index 1 -Type deny | ft
 
    type principal permissions flags index
    ---- --------- ----------- ----- -----
    allow usersid:user1@local.domain {list_directory, read_data, read_xattr, execute...} {file_inherit, dir_inherit} 0
    deny usersid:user2@local.domain {list_directory, read_data, add_file, write_data...} {file_inherit, dir_inherit} 1
    allow groupsid:Administrators@BUILTIN {list_directory, read_data, add_file, write_data...} {file_inherit, dir_inherit} 2
    allow owner@ {list_directory, read_data, add_file, write_data...} {file_inherit, dir_inherit} 3
 
    Changed the type of acl at index 1 to deny
#>

    [cmdletbinding(   
        ConfirmImpact = 'medium',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The path to the filesystem')]
            [ValidatePattern("^\w+(|(\/\w+)){1,}$")]
            [string]$Path,
        [Parameter(Mandatory=$true,
                    Position = 1,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The index of the acl to modify')]
            [int]$Index,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Controls how ACE is inherited by new directory entries')]
            [ValidateSet('file_inherit','dir_inherit','inherit_only','no_propagate','successful_access','failed_access','inherited')]
            [string[]]$Flags,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Set of permissions to allow or deny')]
            [ValidateSet('full_set','modify_set','read_set','write_set','win_full','win_modify','win_read','read_data','list_directory','write_data','add_file','append_data','add_subdirectory','read_xattr','write_xattr','execute','read_attributes','write_attributes','delete','delete_child','read_acl','write_acl','write_owner','synchronize')]
            [string[]]$Permissions,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage="User or group which ACE applies to. Special values 'owner@', 'group@' and 'everyone@' refer to user and group who own the file or just everyone else")]
            [ValidateNotNullOrEmpty()]
            [string]$Principal,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Indicates if this ACE allows or denies permission')]
            [ValidateSet('allow','deny')]
            [string]$Type,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Makes the cmdlet wait for the update to complete before returning')]
            [switch]$NoWait

    )
    Process {
        #Remove any duplicate flags
        if ($Flags) {$Flags = $Flags | select -Unique}
        #Remove and Duplicate permissions
        if ($Permissions) {$Permissions = $Permissions | select -Unique}


        #Test for Index
        $currentAcl = Get-NsFilesystemAcl -Path $Path | ? {$_.index -eq $Index}
        if ($currentAcl -eq $null) {
            throw "No acl for at $Index for $Path"
        }

        if (-Not $Flags) { $Flags = $currentAcl.flags}
        if (-Not $Permissions) { $Permissions = $currentAcl.permissions}
        if (-Not $Principal) { $Principal = $currentAcl.principal}
        if (-Not $Type) { $Type = $currentAcl.type}

        #Build request
        $request = @{}
        $request.add('flags',$Flags)
        $request.add('permissions',$Permissions)
        $request.add('principal',$Principal)
        $request.add('type',$Type)

        try {
            if ($pscmdlet.ShouldProcess($Path,"Update ACL")) {
                # Kick off the update request
                $responce = Invoke-NsApi -Operatation "storage/filesystems/$([uri]::EscapeDataString($Path))/acl/$Index" -Request ($request | ConvertTo-Json) -Method put
                # Grap the id from the result
                $JobId = $responce.links.href | Split-Path -Leaf
                # If we've been told to wait for compleation...
                if (-Not $NoWait) {
                    # Limit ourselves to 30 sec before returning filesystem
                    $count = 10
                    #Get job and check status
                    while ((Get-NsJobs -JobId $JobId).done -ne $true) {
                        Write-Verbose "acl not ready yet - sleeping"
                        sleep 5
                        $count -= 1
                        # If we've waited too long, just return the jobid
                        if ($count -eq 0) {Return $JobId}
                    }
                    # Filesystem has been updated so go get the new acls.
                    Return Get-NsFilesystemAcl -Path $Path
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $JobId
                }
                $responce
            } else {Write-Verbose ($request | ConvertTo-Json)}
        } catch {
            throw $_
        }
    }
}

Function Remove-NsFilesystemAcl {
<#
.SYNOPSIS
    Removes an acl from a file system
.DESCRIPTION
    Removes an acl from a file system by index
.OUTPUTS
    The cmdlet will wait (30sec) for the task to complete and return the updated acls, unless -NoWait was invoked
    If -NoWait was invoked the cmdlet will return the GUID of the update task
    If the cmdlet waits for more then 30sec it will return the GUID of the task.
.PARAMETER Path <String>
    The path to the filesystem
.PARAMETER Index <Int>
    The index of the acl to remove
.PARAMETER NoWait
    Invoke to make the cmdlet start the job ad return the JobID
     
.EXAMPLE
    PS C:\> Remove-NsFilesystemAcl -Path p2/top2 -Index 1 | ft
 
    type principal permissions flags index
    ---- --------- ----------- ----- -----
    allow usersid:user1@local.domain {list_directory, read_data, read_xattr, execute...} {file_inherit, dir_inherit} 0
    allow groupsid:Administrators@BUILTIN {list_directory, read_data, add_file, write_data...} {file_inherit, dir_inherit} 1
    allow owner@ {list_directory, read_data, add_file, write_data...} {file_inherit, dir_inherit} 3
 
    Remove the acl at index 1
#>

    [cmdletbinding(   
        ConfirmImpact = 'medium',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The path to the filesystem')]
            [ValidatePattern("^\w+(|(\/\w+)){1,}$")]
            [string]$Path,
        [Parameter(Mandatory=$true,
                    Position = 1,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The index of the acl to remove')]
            [int]$Index,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Makes the cmdlet wait for the update to complete before returning')]
            [switch]$Wait
    )

    try {
        #Test for Index
        $currentAcl = Get-NsFilesystemAcl -Path $Path | ? {$_.index -eq $Index}
        if ($currentAcl -eq $null) {
            throw "No acl for at $Index for $Path"
        }
        
        if ($pscmdlet.ShouldProcess($Path,"Remove ACL")) {
            # Kick off the update request
            $responce = Invoke-NsApi -Operatation "storage/filesystems/$([uri]::EscapeDataString($Path))/acl/$Index" -Method delete
            # Grap the id from the result
            $JobId = $responce.links.href | Split-Path -Leaf
            # If we've been told to wait for compleation...
            if (-Not $NoWait) {
                # Limit ourselves to 30 sec before returning filesystem
                $count = 10
                #Get job and check status
                while ((Get-NsJobs -JobId $JobId).done -ne $true) {
                    Write-Verbose "acl not ready yet - sleeping"
                    sleep 5
                    $count -= 1
                    # If we've waited too long, just return the jobid
                    if ($count -eq 0) {Return $JobId}
                }
                # Filesystem has been updated so go get the new acls.
                Return Get-NsFilesystemAcl -Path $Path
            } else {
                # Not been asked to wait so just return the jobid
                Return $JobId
            }
            $responce
        }
                
    } catch {
        throw $_
    }
}

Function ConvertTo-NsNfsSecurityContexts {
<#
.SYNOPSIS
     Converts a string into a nfsShare_securityContexts object
.DESCRIPTION
     Converts a string into a nfsShare_securityContexts object
.OUTPUTS
    A nfsShare_securityContexts object.
.EXAMPLE
    PS C:\> "host1","192.168.0.2","@192.168.2.0/24","domain:local.domain" | ConvertTo-NsNfsSecurityContexts
 
    entity etype
    ------ -----
    host1 fqdn
    192.168.0.2 fqdn
    192.168.2.0 network
    local.domain domain
#>

    Param (
        [Parameter(Position = 0,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The acl to convert')]
            [string]$acl
    )
    Process {
        if ($acl) {
            $newAcl = [pscustomobject]@{entity=$null;etype=$null}
            if ($acl[0] -eq '@') {
                $ip = $acl.Substring(1) -split '/'
                $newAcl | Add-Member -MemberType NoteProperty -Name 'mask'-Value $null
                $newAcl.mask = [int]($ip[1])
                $newAcl.etype = 'network'
                $newAcl.entity = $ip[0]
            } elseif ($acl -like "domain:*") {
                $newAcl.etype = 'domain'
                $newAcl.entity = $acl.Substring(7,($acl.Length -7))
            } else {
                $newAcl.etype = 'fqdn'
                $newAcl.entity = $acl
            }
            return $newAcl
        }
    }
}

Function ConvertFrom-NsNfsSecurityContexts {
<#
.SYNOPSIS
     Converts an nfsShare_securityContexts object into a string
.DESCRIPTION
     Converts an nfsShare_securityContexts object into a string
.OUTPUTS
    A string
.EXAMPLE
    PS C:\> "host1","192.168.0.2","@192.168.2.0/24","domain:local.domain" | ConvertTo-NsNfsSecurityContexts | ConvertFrom-NsNfsSecurityContexts
    host1
    192.168.0.2
    @192.168.2.0/24
    domain:local.domain
#>

    Param (
        [Parameter(Position = 0,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The acl to convert')]
            $acl
    )
    Process {
        if ($acl) {
            $newACL = @()
            foreach ($entry in $acl) {
                switch ($entry.etype) {
                    'fqdn' {$newACL += $entry.entity}
                    'network' {$newACL += "@$($entry.entity)/$($entry.mask)"}
                    'domain' {$newACL += "domain:$($entry.entity)"}
                }
            }
            return $newACL
        }
    }
}

Function Get-NsNfsShare {
<#
.SYNOPSIS
     Get information about one ot more NFS Shares
.DESCRIPTION
     Get information about one ot more NFS Shares
.OUTPUTS
    If nothing is specified, all NFS shares are returned.
    If a path is specified, the NFS share for that filesystem is retrived.
.PARAMETER Path <String>
    The path to the filesystem
.EXAMPLE
    PS C:\> Get-NsNfsShare | ft
 
    filesystem shareState mountPoint securityContexts anon nohide
    ---------- ---------- ---------- ---------------- ---- ------
    p1/top1 online /p1/top1 {@{readWriteList=System.Object[]; securityModes=System.Object[]}} -1 False
    p1/top2 online /p1/top2 {@{readOnlyList=System.Object[]; readWriteList=System.Object[]; root=System.Object[]; none=System.Object[]; securityModes=System.Object[]}} -1 False
    p1/top2/sub1 online /p1/top2/sub1 {@{readWriteList=System.Object[]; securityModes=System.Object[]}} -1 False
    p1/top3 online /export/top3 {@{readWriteList=System.Object[]; securityModes=System.Object[]}} -1 False
    p1/top5 online /p1/top5 {@{root=System.Object[]; securityModes=System.Object[]}} False
    p2/top1/sub1 online /p2/top1/sub1 {@{readWriteList=System.Object[]; securityModes=System.Object[]}} nobody False
     
    Get all nfs shares
.EXAMPLE
 
    PS C:\> Get-NsNfsShare p1/top1
 
    anon : -1
    filesystem : p1/top1
    mountPoint : /p1/top1
    nohide : False
    securityContexts : {@{readWriteList=System.Object[]; securityModes=System.Object[]}}
    shareState : online
 
    Get NFS share for path
#>

    [cmdletbinding(DefaultParameterSetName='none')]
    Param (
        [Parameter(Position = 0,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    ParameterSetName='ByPath',
                    HelpMessage='The path to the filesystem')]
            [ValidatePattern("^\w+(|(\/\w+)){1,}$")]
            [string]$Path
    )
    Process {
        try {
            # Set operation based on input
            switch ($PSCmdlet.ParameterSetName) {
                'ByPath' {
                    $operation = "nas/nfs?filesystem=$([uri]::EscapeDataString($Path))"
                }
                'none' {
                    $operation = "nas/nfs"
                }
            }
            Write-Verbose $operation
            # Get the required filesystems
            $returnValue = (Invoke-NsApi -Operatation $operation).data | select -ExcludeProperty href -Property *
            $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.NfsShare')}
            Return $returnValue
        } catch {
            throw $_
        }
    }
}

Function New-NsNfsShare {
<#
.SYNOPSIS
    Shares a filesystem using NFS
.DESCRIPTION
    Shares a filesystem using NFS
.OUTPUTS
    Share object if created
.PARAMETER Path <String>
    The path to the filesystem to share
.PARAMETER SecurityModes <[String[]]>
    An array of security modes for the share to support
.PARAMETER Root <[String[]]>
    An array of entities to manage root access for - Allowed values = FQDN,IP, Subnets in the format "@CIDR", Domains in the format "domain:domain.name"
.PARAMETER ReadWrite <[String[]]>
    An array of entities to manage read-write access for - Allowed values = FQDN,IP, Subnets in the format "@CIDR", Domains in the format "domain:domain.name"
.PARAMETER ReadOnly <[String[]]>
    An array of entities to manage read-only access for - Allowed values = FQDN,IP, Subnets in the format "@CIDR", Domains in the format "domain:domain.name"
.PARAMETER NoAccess <[String[]]>
    An array of entities to manage no access for - Allowed values = FQDN,IP, Subnets in the format "@CIDR", Domains in the format "domain:domain.name"
.PARAMETER Anonymous <String>
    Effective user name for unknown users - Defaults to -1, disabled
.PARAMETER RootMap <String>
    User identifier to remap root UID to
.PARAMETER NoHide
    Invoke to disable explicit mapping requiremnts for the client
.PARAMETER Recursive
    Invoke to apply share properties to nested folders
.EXAMPLE
    PS C:\> New-NsNfsShare -Path p1/top5 -ReadWrite "192.168.0.1","@192.168.1.0/24","ahost.local" -NoAccess "domain:bad.guys" -Root "backup.local"
 
 
    anon : -1
    filesystem : p1/top5
    mountPoint : /p1/top5
    nohide : False
    securityContexts : {@{readWriteList=System.Object[]; root=System.Object[]; none=System.Object[]; securityModes=System.Object[]}}
    shareState : online
 
    Create an nfs share on filesystem p2/top5
#>

    [cmdletbinding(   
        ConfirmImpact = 'low',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The path to the filesystem to share')]
            [ValidatePattern("^\w+(|(\/\w+)){1,}$")]
            [string]$Path,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='An array of valid security modes to enable')]
            [ValidateSet('none','sys','dh','krb5','krb5i','krb5p')]
            [string[]]$SecurityModes = 'sys',
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Entities allowed access as root - Allowed values = FQDN,IP, Subnets in the format "@CIDR", Domains in the format "domain:domain.name"')]
            [ValidatePattern("((?=^.{1,254}$)(^(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(?=^.{1,254}$)(^domain:(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)|(^@\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d\d$))")]
            [string[]]$Root,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Entities allowed access to read/write - Allowed values = FQDN,IP, Subnets in the format "@CIDR", Domains in the format "domain:domain.name"')]
            [ValidatePattern("((?=^.{1,254}$)(^(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(?=^.{1,254}$)(^domain:(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)|(^@\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d\d$))")]
            [string[]]$ReadWrite,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Entities allowed access to read only - Allowed values = FQDN,IP, Subnets in the format "@CIDR", Domains in the format "domain:domain.name"')]
            [ValidatePattern("((?=^.{1,254}$)(^(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(?=^.{1,254}$)(^domain:(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)|(^@\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d\d$))")]
            [string[]]$ReadOnly,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Entities denied access - Allowed values = FQDN,IP, Subnets in the format "@CIDR", Domains in the format "domain:domain.name"')]
            [ValidatePattern("((?=^.{1,254}$)(^(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(?=^.{1,254}$)(^domain:(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)|(^@\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d\d$))")]
            [string[]]$NoAccess,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Effective user name for unknown users - Default is -1 disabled')]
            [ValidatePattern("^\w+$")]
            [string]$Anonymous = '-1',
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='User identifier to remap root UID to')]
            [ValidatePattern("^\w+$")]
            [string]$RootMap,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Normally, if a server exports two filesystems one of which is
mounted on the other, then the client will have to mount both
filesystems explicitly to get access to them. If it just mounts the
parent, it will see an empty directory at the place where the other
filesystem is mounted. That filesystem is "hidden".
 
Setting the nohide option on a filesystem causes it not to be hidden,
and an appropriately authorised client will be able to move from the
parent to that filesystem without noticing the change.
 
However, some NFS clients do not cope well with this situation as, for
instance, it is then possible for two files in the one apparent
filesystem to have the same inode number.
 
The nohide option is currently only effective on single host exports.
It does not work reliably with netgroup, subnet, or wildcard exports.'
)]
            [switch]$NoHide,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Apply share properties to nested folders')]
            [switch]$Recursive 
    )

    #Test filesystem exists
    if (($Path | Get-NsFilesystem) -eq $null) {
        throw 'Filesystem does not exists'
    } 

    #Test the filesystem is not already shared
    if ((Get-NsNfsShare -Path $Path) -ne $null) {
        throw 'The filesystem is already shared'
    }

    #Build request
    $request = @{}
    $request.add('filesystem',$Path)
    $request.add('anon',$Anonymous)
    $request.add('nohide',$Nohide.IsPresent)
    $request.add('recursive',$Recursive.IsPresent)
    if ($RootMap) {$request.add('rootMapping',$RootMap)}
    #Now do securityContexts
    $securityContextsHash = @{}

    $securityContextsHash.Add('securityModes',$SecurityModes)
    
    $rootAcl = @($Root | % {ConvertTo-NsNfsSecurityContexts $_})
    $readWriteListAcL = @($ReadWrite | % {ConvertTo-NsNfsSecurityContexts $_})
    $readOnlyListAcl = @($ReadOnly | % {ConvertTo-NsNfsSecurityContexts $_})
    $noneAclList = @($NoAccess | % {ConvertTo-NsNfsSecurityContexts $_})
 
    if ($rootAcl) {$securityContextsHash.add('root',$rootAcl)}
    if ($readWriteListAcL) {$securityContextsHash.Add('readWriteList',$readWriteListAcL)}
    if ($readOnlyListAcl) {$securityContextsHash.Add('readOnlyList',$readOnlyListAcl)}
    if ($noneAclList) {$securityContextsHash.Add('none',$noneAclList)}

    $securityContexts = @($securityContextsHash)
    $request.add('securityContexts',$securityContexts)
    try {
        if ($pscmdlet.ShouldProcess($Path,"Create Nfs Share")) {
            # Kick off the creation request
            Invoke-NsApi -Operatation "nas/nfs" -Request ($request | ConvertTo-Json -Depth 4) | Out-Null
            Get-NsNfsShare -Path $Path
        }
    } catch {
        throw $_
    }
}

Function Set-NsNfsShare {
<#
.SYNOPSIS
    Sets the options for an NFS share
.DESCRIPTION
    Sets the options for an NFS share
.OUTPUTS
    Share object if updated
.PARAMETER Path <String>
    The path to the filesystem to update
.PARAMETER SecurityModes <[String[]]>
    An array of security modes for the share to support
.PARAMETER Root <[String[]]>
    An array of entities to manage root access for - Allowed values = FQDN,IP, Subnets in the format "@CIDR", Domains in the format "domain:domain.name"
.PARAMETER ReadWrite <[String[]]>
    An array of entities to manage read-write access for - Allowed values = FQDN,IP, Subnets in the format "@CIDR", Domains in the format "domain:domain.name"
.PARAMETER ReadOnly <[String[]]>
    An array of entities to manage read-only access for - Allowed values = FQDN,IP, Subnets in the format "@CIDR", Domains in the format "domain:domain.name"
.PARAMETER NoAccess <[String[]]>
    An array of entities to manage no access for - Allowed values = FQDN,IP, Subnets in the format "@CIDR", Domains in the format "domain:domain.name"
.PARAMETER Anonymous <String>
    Effective user name for unknown users - Defaults to -1, disabled
.PARAMETER RootMap <String>
    User identifier to remap root UID to
.PARAMETER NoHide
    Invoke to disable explicit mapping requiremnts for the client
.PARAMETER Recursive
    Invoke to apply share properties to nested folders
.EXAMPLE
    PS C:\> $acl = ConvertFrom-NsNfsSecurityContexts (Get-NsNfsShare p1/top5).securityContexts.readWriteList
 
    PS C:\> $acl
    192.168.0.1
    @192.168.1.0/24
    ahost.local
 
    PS C:\> $acl += "192.168.0.2"
    PS C:\> Set-NsNfsShare -Path p1/top5 -ReadWrite $acl
 
    anon : -1
    filesystem : p1/top5
    mountPoint : /p1/top5
    nohide : False
    securityContexts : {@{readWriteList=System.Object[]; root=System.Object[]; none=System.Object[]; securityModes=System.Object[]}}
    shareState : online
 
    PS C:\> ConvertFrom-NsNfsSecurityContexts (Get-NsNfsShare p1/top5).securityContexts.readWriteList
    192.168.0.1
    @192.168.1.0/24
    ahost.local
    192.168.0.2
 
    Get an existing acl entry, add an new entry, then set the new acl on the nfs share
 
#>

    [cmdletbinding(   
        ConfirmImpact = 'low',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The path to the filesystem to share')]
            [ValidatePattern("^\w+(|(\/\w+)){1,}$")]
            [string]$Path,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='An array of valid security modes to enable')]
            [ValidateSet('none','sys','dh','krb5','krb5i','krb5p')]
            [string[]]$SecurityModes,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Entities allowed access as root - Allowed values = FQDN,IP, FDQSubnets in the format "@CIDR", Domains in the format "domain:domain.name"')]
            [ValidatePattern("((?=^.{1,254}$)(^(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(?=^.{1,254}$)(^domain:(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)|(^@\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d\d$))")]
            [string[]]$Root,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Entities allowed access to read/write - Allowed values = FQDN,IP, FDQSubnets in the format "@CIDR", Domains in the format "domain:domain.name"')]
            [ValidatePattern("((?=^.{1,254}$)(^(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(?=^.{1,254}$)(^domain:(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)|(^@\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d\d$))")]
            [string[]]$ReadWrite,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Entities allowed access to read only - Allowed values = FQDN,IP, FDQSubnets in the format "@CIDR", Domains in the format "domain:domain.name"')]
            [ValidatePattern("((?=^.{1,254}$)(^(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(?=^.{1,254}$)(^domain:(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)|(^@\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d\d$))")]
            [string[]]$ReadOnly,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Entities denied access - Allowed values = FQDN,IP, FDQSubnets in the format "@CIDR", Domains in the format "domain:domain.name"')]
            [ValidatePattern("((?=^.{1,254}$)(^(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(?=^.{1,254}$)(^domain:(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)|(^@\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d\d$))")]
            [string[]]$NoAccess,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Effective user name for unknown users - Default is -1 disabled')]
            [ValidatePattern("^\w+$")]
            [string]$Anonymous,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='User identifier to remap root UID to')]
            [ValidatePattern("^\w+$")]
            [string]$RootMap,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Normally, if a server exports two filesystems one of which is
mounted on the other, then the client will have to mount both
filesystems explicitly to get access to them. If it just mounts the
parent, it will see an empty directory at the place where the other
filesystem is mounted. That filesystem is "hidden".
 
Setting the nohide option on a filesystem causes it not to be hidden,
and an appropri- ately authorised client will be able to move from the
parent to that filesystem without noticing the change.
 
However, some NFS clients do not cope well with this situation as, for
instance, it is then possible for two files in the one apparent
filesystem to have the same inode number.
 
The nohide option is currently only effective on single host exports.
It does not work reliably with netgroup, subnet, or wildcard exports.'
)]
            [switch]$NoHide,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Apply share properties to nested folders')]
            [switch]$Recursive 
    )

    #Test filesystem exists
    if (($Path | Get-NsFilesystem) -eq $null) {
        throw 'Filesystem does not exists'
    } 

    #Test the filesystem is already shared
    if ((Get-NsNfsShare -Path $Path) -eq $null) {
        throw 'The filesystem is not shared'
    }

    #Get existing settings
    $currentNfs = Get-NsNfsShare -Path $Path


    #Build request
    $request = @{}
    if ($Anonymous) {$request.add('anon',$Anonymous)}
    else {$request.add('anon',$currentNfs.anon)}

    if ($Nohide.IsPresent) {$request.add('nohide',$Nohide.IsPresent)}
    else {$request.add('nohide',$currentNfs.nohide)}

    $request.add('recursive',$Recursive.IsPresent)
    
    if ($RootMap) {$request.add('rootMapping',$RootMap)}
    else {if ($currentNfs.rootMapping) {$request.add('rootMapping',$currentNfs.rootMapping)}}


    #Now do securityContexts
    $securityContextsHash = @{}

    if ($SecurityModes) {$securityContextsHash.Add('securityModes',$SecurityModes)}
    
    $rootAcl = @($Root | % {ConvertTo-NsNfsSecurityContexts $_})
    $readWriteListAcL = @($ReadWrite | % {ConvertTo-NsNfsSecurityContexts $_})
    $readOnlyListAcl = @($ReadOnly | % {ConvertTo-NsNfsSecurityContexts $_})
    $noneAclList = @($NoAccess | % {ConvertTo-NsNfsSecurityContexts $_})
 
    if ($rootAcl) {$securityContextsHash.add('root',$rootAcl)}
    else {if ($currentNfs.securityContexts.root) {$securityContextsHash.add('root',@($currentNfs.securityContexts.root))}}
    if ($readWriteListAcL) {$securityContextsHash.Add('readWriteList',$readWriteListAcL)}
    else {if ($currentNfs.securityContexts.readWriteList) {$securityContextsHash.add('readWriteList',@($currentNfs.securityContexts.readWriteList))}}
    if ($readOnlyListAcl) {$securityContextsHash.Add('readOnlyList',$readOnlyListAcl)}
    else {if ($currentNfs.securityContexts.readOnlyList) {$securityContextsHash.add('readOnlyList',@($currentNfs.securityContexts.readOnlyList))}}
    if ($noneAclList) {$securityContextsHash.Add('none',$noneAclList)}
    else {if ($currentNfs.securityContexts.none) {$securityContextsHash.add('none',@($currentNfs.securityContexts.none))}}

    $securityContexts = @($securityContextsHash)
    $request.add('securityContexts',$securityContexts)
    
    try {
        if ($pscmdlet.ShouldProcess($Path,"update Nfs Share")) {
            # Kick off the creation request
            Invoke-NsApi -Operatation "nas/nfs/$([uri]::EscapeDataString($Path))" -Request ($request | ConvertTo-Json -Depth 4) -Method put | Out-Null
            Get-NsNfsShare -Path $Path
        }
    } catch {
        throw $_
    }
}

Function Remove-NsNfsShare {
<#
.SYNOPSIS
    Removes the NFS share froma filesystem
.DESCRIPTION
    Removes the NFS share froma filesystem
.OUTPUTS
    True if the share is removed
.PARAMETER Path <String>
    The path to the filesystem for SMB share to remove
.PARAMETER Recursive
    Invoke to force all nested datasets also to be unshared
.EXAMPLE
    PS C:\> Remove-NsNfsShare -Path p1/top5 -Confirm:$false
    True
 
    Remove the share from p1/top5
#>

    [cmdletbinding(   
        ConfirmImpact = 'high',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipeline,
                    HelpMessage='The path to the filesystem to unshare')]
            [ValidatePattern("^\w+(|(\/\w+)){1,}$")]
            [string]$Path,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='force all nested filesystems also to be unshared')]
            [switch]$Recursive 
    )

    Process {
        #Test the filesystem is shared
        if ((Get-NsNfsShare -Path $Path) -eq $null) {
            throw 'The filesystem is not shared'
        }

        try {
            if ($pscmdlet.ShouldProcess($Path,"Remove Share")) {
                # Kick off the update request
                Invoke-NsApi -Operatation "nas/nfs/$([uri]::EscapeDataString($Path))?recursive=$($Recursive.IsPresent)" -Method 'delete' | Out-Null
                Return $true
            }
        } catch {
            throw $_
        }
    }
}