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 retrieve 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 default 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 object containing the username and password used to connect 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
        }
        $response = Invoke-RestMethod -Uri $requestUrl -Method Post -Body ($login | ConvertTo-Json) -ContentType "application/json" -Verbose:$false
        #Write connection details to script wide variable to ruse by other functions
        $script:nsController = [pscustomobject]@{name=$Name;port=$Port;baseUrl=$baseUrl;user=$Credential.UserName;token=$response.token}
        $script:nsController.psobject.TypeNames.Insert(0,'Ns.Controller')
        Write-Verbose ($script:nsController | fl | Out-String)
        Return $script:nsController
    } catch {
        if ($_.response.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]$Operation,
        [Parameter()]
            [ValidateSet('post','get','delete','put')]
            [string]$Method
            
        )

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

        #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 ignoring 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/$Operation"
        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)
                $response = Invoke-RestMethod -Uri $requestUrl -Method $Method -Body $Request -Headers $Header -Verbose:$false -ErrorVariable RestError
            }
            default {
                $response = Invoke-RestMethod -Uri $requestUrl -Method $Method -Headers $Header -Verbose:$false -ErrorVariable RestError
            }

        }
        # Return the response
        Return $response
    } 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 -Operation $Operation -Method $Method"
                    $response = $null
                    switch ($method) {
                        {$_ -eq 'post' -or $_ -eq 'put'} {
                            $response = Invoke-NsApi -Operation $Operation -Request $Request -Method $Method
                        }
                        default {
                            $response = Invoke-NsApi -Operation $Operation -Method $Method
                        }
                    }
                    Return $response
                } catch {
                    throw "Could not re-connect to $($Script:nsController.name) : ($_.Exception.Message)"
                }
            }
            404 { #NotFound
                # No results were found, return nothing
            }
            400 {
                throw ($RestError.Message | ConvertFrom-Json).message
            }
            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 retrieved 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 retrieved including the pool hosting the controller's OS.
    If a name is specified, only the named pool is retrieved.
.OUTPUTS
    Nothing if no matching pools are found.
    Otherwise, matching Pool objects
.PARAMETER Name <String>
    The name of the pool to retrieve
.PARAMETER All
    Invoke to retrieve 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
 
    Retrieve 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
 
    Retrieve 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
 
    Retrieve 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 retrieve")]
            [ValidatePattern("^\w+$")]
            [string]$Name,
        [Parameter(ParameterSetName='All',
                HelpMessage="Invoke to retrieve 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 -Operation "storage/pools?poolName=$([uri]::EscapeDataString($Name))"
            $returnValue = $pools.data
        } else {
            $pools = Invoke-NsApi -Operation "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 retrieved.
    If Recurse is invoked, all child filsystems from the pool or path are retrieved.
    If Detailed is invoked, extended attributes are retrieved for all filesystems matching the request.
.OUTPUTS
    Nothing if no matching filesystems are found.
    Otherwise, matching Filesystem objects
.PARAMETER Path <String>
    The path to the filesystem to retrieve
.PARAMETER Recurse
    Invoke to request child filesystems are retrieved
.PARAMETER Detailed
    Invoke to request extended attributes of the filesystems are retrieved
.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 descendants.
.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 retrieve')]
            [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?limit=300&path=$([uri]::EscapeDataString($Path))"
                    } else {
                        # using parent causes all sub filesystems to be returned.
                        $operation = "storage/filesystems?limit=300&parent=$([uri]::EscapeDataString($Path))&recursive=$Recurse"
                    }
                }
                'none' {
                    $operation = "storage/filesystems?limit=300"
                }
            }
            Write-Verbose $operation
            # Get the required filesystems
            $filesystems = Invoke-NsApi -Operation $operation
            $filesystemsData = $filesystems.data
            while ($filesystems.links | ? {$_.rel -eq "next"}) {
                $operation = ($filesystems.links | ? {$_.rel -eq "next"}).href.Substring(1) -replace 'amp;',''
                Write-Verbose $operation
                $filesystems = Invoke-NsApi -Operation $operation
                $filesystemsData += $filesystems.data
            }
            # Do we want detailed output? if so we are going to have to go and get each filesystem separately
            if ($Detailed) {
                $newFilesystems = @()
                Foreach ($filesystem in $filesystemsData) {
                    $operation = "storage/filesystems/$([uri]::EscapeDataString($filesystem.path))"
                    Write-Verbose $operation
                    $newFilesystems += Invoke-NsApi -Operation $operation
                }
                $returnValue = $newFilesystems
            } else {
                $returnValue =  $filesystemsData
            }
            $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 -Operation "jobStatus$jobQuery"
            $jobs.data
        } catch {
            throw $_
        }
    }
}

Function Get-NsJobResult {
    Param (
        [Parameter(Mandatory=$true,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    Position = 0)]
            [ValidateNotNullOrEmpty()]
            [ValidatePattern("^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$")]
            [string]$JobId
     )
    Process {
        try {
            $jobs = Invoke-NsApi -Operation "jobStatus/$JobId"
            return $true
        } 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 attribute
 
    Create new filesystem with quotas and custom attributes.
#>

    [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 Array 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 than 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
            $response = Invoke-NsApi -Operation "storage/filesystems" -Request ($request | ConvertTo-Json)
            # Grap the id from the result
            $JobId = $response.links.href | Split-Path -Leaf
            # If we've been told to wait for completion...
            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}
                }
                # Check result
                Get-NsJobResult $JobId | Out-Null
                # 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 Set-NsFilesystemOwner {
<#
.SYNOPSIS
    Sets the Owner on a filesystem
.DESCRIPTION
    Sets the Owner on a filesystem
.OUTPUTS
    True, if the owner is changed
.PARAMETER Path <String>
    The path to the filesystem to change the owner for
.PARAMETER User <String>
    The user to set as the owner of the filesystem - can be username or number
.PARAMETER Group <String>
    The group to set as the group of the filesystem - can be username or number
.PARAMETER NoWait
    Invoke to make the cmdlet start the job and return the GUID of the JobID
.EXAMPLE
    PS C:\temp> Set-NsFilesystemOwner p1/nfstest -User 6465
    True
 
    Sets the user with the uid 6564 as the owner of the filesyste,
.EXAMPLE
    PS C:\temp> Set-NsFilesystemOwner p1/nfstest -User root -Group 6465
    True
 
    Sets root as the owner and the group with the gid 6465 as the group
#>

    [cmdletbinding(   
        ConfirmImpact = 'low',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The path to the filesystem to change the owner for')]
            [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")]
            [string]$Path,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='The user to set as the owner of the filesystem - can be username or number')]
            [ValidatePattern("^\w+$")]
            [string]$User,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='The group to set as the group of the filesystem - can be username or number')]
            [ValidatePattern("^\w+$")]
            [string]$Group,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Makes the cmdlet wait for the removal to complete before returning true')]
            [switch]$NoWait
    )

    Process {
        try {
            if (($User -eq "") -and ($Group -eq "")) {
                throw 'You must specify a user and/or a group'
            }

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

               if ($pscmdlet.ShouldProcess($Path,'Set Owner')) {

                #Build request
                $request = @{}
                if ($User -ne "") {$request.add('user',$User)}
                if ($Group -ne "") {$request.add('group',$Group)}

                # Kick off the request
                $response = Invoke-NsApi -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))/setOwner" -Request ($request | ConvertTo-Json)
                # Grap the id from the result
                $JobId = $response.links.href | Split-Path -Leaf
                # If we've been told to wait for completion...
                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 "Owner not ready yet - sleeping"
                        sleep 5
                        $count -= 1
                        # If we've waited too long, just return the jobid
                        if ($count -eq 0) {Return $JobId}
                    }
                    # Check result
                    Get-NsJobResult $JobId | Out-Null
                    # Filesystem has been created so go get the details.
                    Return $true
                } 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 deletion 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

    )

    Process {

        #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
                $response = Invoke-NsApi -Operation $Operation -Method 'delete'
                # Grap the id from the result
                $JobId = $response.links.href | Split-Path -Leaf
                # If we've been told to wait for completion...
                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}
                    }
                    # Check result
                    Get-NsJobResult $JobId | Out-Null
                    # 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 or more SMB Shares
.DESCRIPTION
    Get information about one or 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 retrieved.
    If a path is specified, the SMB share with that filesystem is retrieved.
.PARAMETER ShareName <String>
    Name of the share to retrieve
.PARAMETER Path <String>
    Path to the share to retrieve
.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 retrieve")]
            [ValidatePattern("^(\w|-|_|\.)+(|\$)$")]
            [string]$ShareName,
        [Parameter( Mandatory=$true,
                    ValueFromPipelineByPropertyName,
                    ParameterSetName='ByPath',
                    HelpMessage="Path to the share to retrieve")]
            [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")]
            [string]$Path

    )
    Process {
        try {
            # Set operation based on input
            switch ($PSCmdlet.ParameterSetName) {
                'ByName' {
                    $operation = "nas/smb?limit=300&shareName=$([uri]::EscapeDataString($ShareName))"
                }
                'ByPath' {
                    $operation = "nas/smb?limit=300&filesystem=$([uri]::EscapeDataString($Path))"
                }
                'none' {
                    $operation = "nas/smb?limit=300"
                }
            }
            Write-Verbose $operation
            # Get the required filesystems
            $shares = Invoke-NsApi -Operation $operation
            $sharesData = $shares.data
            while ($shares.links | ? {$_.rel -eq "next"}) {
                $operation = ($shares.links | ? {$_.rel -eq "next"}).href.Substring(1) -replace 'amp;',''
                Write-Verbose $operation
                $shares = Invoke-NsApi -Operation $operation
                $sharesData += $shares.data
            }
            $returnValue = $sharesData | 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 -ShareName $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 -Operation "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 -ShareName $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 -Operation "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 from a filesystem
.DESCRIPTION
    Removes the SMB share from a 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 -Operation "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 {
            $response = Invoke-NsApi -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))/acl"
            Return $response.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 replace 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('none','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='Replace 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 = @{}
        if ($Flags -ne 'none') {
            $request.add('flags',$Flags)
        } else {
               $request.add('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
                $response = Invoke-NsApi -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))/acl" -Request ($request | ConvertTo-Json)
                # Grap the id from the result
                $JobId = $response.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}
                    }
                    # Check result
                    Get-NsJobResult $JobId | Out-Null
                    # 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
                }
                $response
            } 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
                $response = Invoke-NsApi -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))/acl/$Index" -Request ($request | ConvertTo-Json) -Method put
                # Grap the id from the result
                $JobId = $response.links.href | Split-Path -Leaf
                # If we've been told to wait for completion...
                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}
                    }
                    # Check result
                    Get-NsJobResult $JobId | Out-Null
                    # 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
                }
                $response
            } 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 than 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]$NoWait
    )

    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
            $response = Invoke-NsApi -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))/acl/$Index" -Method delete
            # Grap the id from the result
            $JobId = $response.links.href | Split-Path -Leaf
            # If we've been told to wait for completion...
            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}
                }
                # Check result
                Get-NsJobResult $JobId | Out-Null
                # 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
            }
            $response
        }
                
    } 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 retrieved.
.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?limit=300&filesystem=$([uri]::EscapeDataString($Path))"
                }
                'none' {
                    $operation = "nas/nfs?limit=300"
                }
            }

            Write-Verbose $operation
            # Get the required filesystems
            $shares = Invoke-NsApi -Operation $operation
            $sharesData = $shares.data
            while ($shares.links | ? {$_.rel -eq "next"}) {
                $operation = ($shares.links | ? {$_.rel -eq "next"}).href.Substring(1) -replace 'amp;',''
                Write-Verbose $operation
                $shares = Invoke-NsApi -Operation $operation
                $sharesData += $shares.data
            }

            $returnValue = $sharesData
            $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 requirements 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+|-1)$")]
            [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 -Operation "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 requirements 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, 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+|-1)$")]
            [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 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 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 -Operation "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 -Operation "nas/nfs/$([uri]::EscapeDataString($Path))?recursive=$($Recursive.IsPresent)" -Method 'delete' | Out-Null
                Return $true
            }
        } catch {
            throw $_
        }
    }
}

Function Get-NsUserUtilization {
<#
.SYNOPSIS
    Retrieves the user userspace Utilization for a filesystem
.DESCRIPTION
    Retrieves the user userspace Utilization for a filesystem
.OUTPUTS
    If no user is specified, all userspaceUtilization objects for the filesystem
    Otherwise the userspaceUtilization object for the requested user on the filesystem
.PARAMETER Path <String>
    The path to the filesystem to query
.PARAMETER User <String>
    The username to return
.EXAMPLE
    PS C:\> Get-NsUserUtilization p1/top6 | ft
 
    name type class quota used
    ---- ---- ----- ----- ----
    root user POSIX 0 11776
    user1@domain user SMB 0 955878912
 
    Get all user userspace Utilization for a filesystem
.EXAMPLE
 
    PS C:\> "user1@domain" | Get-NsUserUtilization p1/top6
 
    name : user1@domain
    type : user
    class : SMB
    quota : 0
    used : 955878912
 
    Get a specific user userspace Utilization for a filesystem
 
#>

    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The path to the filesystem to query')]
            [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")]
            [alias('filesystem')]
            [string]$Path,
        [Parameter(ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='Username to retrieve utilization data for')]
            [ValidateNotNullOrEmpty()]
            [string]$User
    )

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

            # We cant query for a specific user so do one big get and filter the results.
            # Kick off the update request
            $response = Invoke-NsApi -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))/userspaceUtilization" -Request "{}"
            # Grap the id from the result
            $JobId = $response.links.href | Split-Path -Leaf
            # 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 "query not ready yet - sleeping"
                sleep 5
                $count -= 1
                # If we've waited too long, just return the jobid
                if ($count -eq 0) {Return $JobId}
            }
            # Check result
            Get-NsJobResult $JobId | Out-Null
            # Filesystem has been queried so get the results.
            $response = Invoke-NsApi -Operation "jobStatus/$JobId"
            
        } catch {throw $_}

    }

    Process {
        if (-not $User) {
            $returnValue = $response.userspace | ? {$_.type -eq 'user'} 
        } else {
            $returnValue = $response.userspace | ? {($_.name -eq $User) -and ($_.type -eq 'user')}
        }
        $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.UsUtil')}
        return $returnValue
    }
}

Function Get-NsGroupUtilization {
<#
.SYNOPSIS
    Retrieves the group userspace Utilization for a filesystem
.DESCRIPTION
    Retrieves the group userspace Utilization for a filesystem
.OUTPUTS
    If no group is specified, all userspaceUtilization objects for the filesystem
    Otherwise the userspaceUtilization object for the requested group on the filesystem
.PARAMETER Path <String>
    The path to the filesystem to query
.PARAMETER Group <String>
    The username to return
.EXAMPLE
    PS C:\> Get-NsGroupUtilization p1/top6 | ft
 
    name type class quota used
    ---- ---- ----- ----- ----
    root group POSIX 0 11776
    Domain Users@domin group SMB 0 955878912
.EXAMPLE
    PS C:\> Get-NsGroupUtilization -Path p1/top6 -Group "Domain Users@domain"
 
    name : Domain Users@domain
    type : group
    class : SMB
    quota : 0
    used : 955878912
#>

    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The path to the filesystem to query')]
            [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")]
            [alias('filesystem')]
            [string]$Path,
        [Parameter(ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='Username to retrive utilization data for')]
            [ValidateNotNullOrEmpty()]
            [string]$Group
    )

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

            # We cant query for a specific user so do one big get and filter the results.
            # Kick off the update request
            $response = Invoke-NsApi -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))/userspaceUtilization" -Request "{}"
            # Grap the id from the result
            $JobId = $response.links.href | Split-Path -Leaf
            # 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 "query 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 queried so get the results.
            $response = Invoke-NsApi -Operation "jobStatus/$JobId"
            
        } catch {throw $_}

    }

    Process {
        if (-not $Group) {
            $returnValue = $response.userspace | ? {$_.type -eq 'group'} 
        } else {
            $returnValue = $response.userspace | ? {($_.name -eq $Group) -and ($_.type -eq 'group')}
        }
        $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.UsUtil')}
        return $returnValue
    }
}

Function Get-NsUserQuota {
<#
.SYNOPSIS
   Retrieves user quotas for a filesystem
.DESCRIPTION
    Retrieves user quotas for a filesystem
.OUTPUTS
    If no user is specified, all quota objects for the filesystem
    Otherwise the quota object for the requested user on the filesystem
.PARAMETER Path <String>
    The path to the filesystem to get the quota from
.PARAMETER User <String>
    The user to set a quota for
.EXAMPLE
    PS C:\> Get-NsUserQuota -Path p1/collab1
 
    User Quota
    ---- -----
    6565 1073741824
    S-1-5-21-1643737065-1150890963-312552118-56130 1099511627776
 
    Get all user quotas for a file system
#>

    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The path to the filesystem to set the quota on')]
            [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")]
            [string]$Path,
        [Parameter(Position = 1,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The user to set a quota for')]
            [ValidatePattern("^([\w.]+|S(-\d+)+)$")]
            [alias('sid')]
            [string]$User
    )

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

            if ($User) {
                $operation = "storage/filesystems/$([uri]::EscapeDataString($Path))?fields=userquota%40$User"
                $response = Invoke-NsApi -Operation $Operation
                $returnValue = [pscustomobject]@{User=$User;Quota=$response."userquota@$User"}
                $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.UsQuota')}
            } else {
                $results = Get-NsFilesystem -Path $Path -Detailed
                $returnValue = $results | Get-Member | ? {$_.Name -like "userquota@*"} | % {[pscustomobject]@{User=($_.Name -split '@')[1];Quota=$results.($_.Name)}}
                $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.UsQuota')}
            }
            return $returnValue
        } catch {
            throw $_
        }
    }
}

Function Get-NsGroupQuota {
<#
.SYNOPSIS
   Retrieves group quotas for a filesystem
.DESCRIPTION
    Retrieves group quotas for a filesystem
.OUTPUTS
    If no group is specified, all quota objects for the filesystem
    Otherwise the quota object for the requested group on the filesystem
.PARAMETER Path <String>
    The path to the filesystem to get the quota from
.PARAMETER Group <String>
    The group to set a quota for
.EXAMPLE
    PS C:\> Get-NsGroupQuota -Path p1/top1
 
    Group Quota
    ----- -----
    S-1-5-21-1643737065-1150890963-312552118-242249 524288000
 
    Get all group quotas for a file system
#>

    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The path to the filesystem to set the quota on')]
            [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")]
            [string]$Path,
        [Parameter(Position = 1,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The group to set a quota for')]
            [ValidatePattern("^([\w.]+|S(-\d+)+)$")]
            [alias('sid')]
            [string]$Group
    )

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

            if ($Group) {
                $operation = "storage/filesystems/$([uri]::EscapeDataString($Path))?fields=groupquota%40$Group"
                $response = Invoke-NsApi -Operation $Operation
                $returnValue = [pscustomobject]@{Group=$Group;Quota=$response."groupquota@$Group"}
                $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.GrQuota')}
            } else {
                $results = Get-NsFilesystem -Path $Path -Detailed
                $returnValue = $results | Get-Member | ? {$_.Name -like "groupquota@*"} | % {[pscustomobject]@{Group=($_.Name -split '@')[1];Quota=$results.($_.Name)}}
                $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.GrQuota')}
            }
            return $returnValue
        } catch {
            throw $_
        }
    }
}

Function Set-NsUserQuota {
<#
.SYNOPSIS
    Sets a user quota for the given filesystem
.DESCRIPTION
    Sets a user quota for the given filesystem
.OUTPUTS
    True if the quota as set correctly
.PARAMETER Path <String>
    The path to the filesystem to set the quota on
.PARAMETER User <String>
    The user to set a quota for
.PARAMETER Quota <String>
    The user quota (in [int]Mb/[int]Gb/[int]Tb) for the filesystem,
.PARAMETER NoWait
    Invoke to make the cmdlet start the job and return the GUID of the JobID
.EXAMPLE
    PS C:\> Set-NsUserQuota -Path p1/top1 -User "6565" -Quota 1Gb
    True
 
    Sets a quota for a unix uid
.EXAMPLE
    PS C:\> Set-NsUserQuota -Path p1/top1 -User "S-1-5-21-1643737365-1170899993-312552118-56130" -Quota 1Gb
    True
 
    Sets a quota for an active directory user
.EXAMPLE
    PS C:\> (Get-aduser user1).sid | Set-NsUserQuota p1/collab1 -Quota 500Mb
    True
 
    Pipe the sid of an AD account into the cmdlet
#>

    [cmdletbinding(   
        ConfirmImpact = 'low',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The path to the filesystem to set the quota on')]
            [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")]
            [string]$Path,
        [Parameter(Mandatory=$true,
                    Position = 1,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The user to set a quota for')]
            [ValidatePattern("^([\w.]+|S(-\d+)+)$")]
            [alias('sid')]
            [string]$User,
        [Parameter(Mandatory=$true,
                    Position=2,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The user quota (in [int]Mb/[int]Gb/[int]Tb) for the filesystem,')]
            [ValidatePattern("^\d+[MGTmgt][Bb]$")]
            [string]$Quota,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Makes the cmdlet wait for the creation to complete before returning true')]
            [switch]$NoWait
    )

    Process {
        Try {
            #Test filesystem exists
            if (($Path | Get-NsFilesystem) -eq $null) {
                throw 'Filesystem does not exists'
            }
            $QuotaBytes = $Quota /1
            $request = @{}
            $request.add("userquota@$User",$QuotaBytes)
            if ($pscmdlet.ShouldProcess($Path,"Set quota")) {
                # Kick off the creation request
                $response = Invoke-NsApi -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))" -Request ($request | ConvertTo-Json) -Method put
                # Grap the id from the result
                $JobId = $response.links.href | Split-Path -Leaf
                # If we've been told to wait for completion...
                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 "quota not ready yet - sleeping"
                        sleep 5
                        $count -= 1
                        # If we've waited too long, just return the jobid
                        if ($count -eq 0) {Return $JobId}
                    }
                    #All done
                    # Check result
                    Get-NsJobResult $JobId | Out-Null
                    Return $true
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $JobId
                }
            }
        } catch {
            throw $_
        }
    }
}

Function Set-NsGroupQuota {
<#
.SYNOPSIS
    Sets a group quota for the given filesystem
.DESCRIPTION
    Sets a group quota for the given filesystem
.OUTPUTS
    True if the quota as set correctly
.PARAMETER Path <String>
    The path to the filesystem to set the quota on
.PARAMETER Group <String>
    The group to set a quota for
.PARAMETER Quota <String>
    The group quota (in [int]Mb/[int]Gb/[int]Tb) for the filesystem,
.PARAMETER NoWait
    Invoke to make the cmdlet start the job and return the GUID of the JobID
.EXAMPLE
    PS C:\> Set-NsGroupQuota -Path p1/top1 -Group "6525" -Quota 1Gb
    True
 
    Sets a quota for a unix gid
.EXAMPLE
    PS C:\> Set-NsGroupQuota -Path p1/top1 -Group "S-1-5-21-1643337385113899993-312552118-56130" -Quota 1Gb
    True
 
    Sets a quota for an active directory group
.EXAMPLE
    PS C:\> (Get-adgroup group1).sid | Set-NsGroupQuota p1/collab1 -Quota 500Mb
    True
 
    Pipe the sid of an AD group into the cmdlet
#>

    [cmdletbinding(   
        ConfirmImpact = 'low',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The path to the filesystem to set the quota on')]
            [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")]
            [string]$Path,
        [Parameter(Mandatory=$true,
                    Position = 1,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The group to set a quota for')]
            [ValidatePattern("^([\w.]+|S(-\d+)+)$")]
            [alias('sid')]
            [string]$Group,
        [Parameter(Mandatory=$true,
                    Position=2,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The group quota (in [int]Mb/[int]Gb/[int]Tb) for the filesystem,')]
            [ValidatePattern("^\d+[MGTmgt][Bb]$")]
            [string]$Quota,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Makes the cmdlet wait for the creation to complete before returning true')]
            [switch]$NoWait
    )

    Process {
        Try {
            #Test filesystem exists
            if (($Path | Get-NsFilesystem) -eq $null) {
                throw 'Filesystem does not exists'
            }
            $QuotaBytes = $Quota /1
            $request = @{}
            $request.add("groupquota@$Group",$QuotaBytes)
            if ($pscmdlet.ShouldProcess($Path,"Set quota")) {
                # Kick off the creation request
                $response = Invoke-NsApi -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))" -Request ($request | ConvertTo-Json) -Method put
                # Grap the id from the result
                $JobId = $response.links.href | Split-Path -Leaf
                # If we've been told to wait for completion...
                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 "quota not ready yet - sleeping"
                        sleep 5
                        $count -= 1
                        # If we've waited too long, just return the jobid
                        if ($count -eq 0) {Return $JobId}
                    }
                    #All done
                    # Check result
                    Get-NsJobResult $JobId | Out-Null
                    Return $true
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $JobId
                }
            }
        } catch {
            throw $_
        }
    }
}

Function Remove-NsUserQuota {
<#
.SYNOPSIS
    Remove a user quota from the given filesystem
.DESCRIPTION
    Remove a user quota from the given filesystem
.OUTPUTS
    True if the quota as removed correctly
.PARAMETER Path <String>
    The path to the filesystem to remove the quota from
.PARAMETER User <String>
    The user to remove the quota for
.PARAMETER NoWait
    Invoke to make the cmdlet start the job and return the GUID of the JobID
.EXAMPLE
    PS C:\> Remove-NsUserQuota p1/collab1 -User 6565
    True
 
    Remove a quota for a unix uid
#>

    [cmdletbinding(   
        ConfirmImpact = 'medium',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The path to the filesystem to remove the quota from')]
            [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")]
            [string]$Path,
        [Parameter(Mandatory=$true,
                    Position = 1,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The user to remove a quota for')]
            [ValidatePattern("^([\w.]+|S(-\d+)+)$")]
            [alias('sid')]
            [string]$User,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Makes the cmdlet wait for the removal to complete before returning true')]
            [switch]$NoWait
    )

    Process {
        Try {
            #Test filesystem exists
            if (($Path | Get-NsFilesystem) -eq $null) {
                throw 'Filesystem does not exists'
            }
            $QuotaBytes = 0
            $request = @{}
            $request.add("userquota@$User",$QuotaBytes)
            if ($pscmdlet.ShouldProcess($Path,"Remove quota")) {
                # Kick off the creation request
                $response = Invoke-NsApi -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))" -Request ($request | ConvertTo-Json) -Method put
                # Grap the id from the result
                $JobId = $response.links.href | Split-Path -Leaf
                # If we've been told to wait for completion...
                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 "quota not ready yet - sleeping"
                        sleep 5
                        $count -= 1
                        # If we've waited too long, just return the jobid
                        if ($count -eq 0) {Return $JobId}
                    }
                    #All done
                    # Check result
                    Get-NsJobResult $JobId | Out-Null
                    Return $true
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $JobId
                }
            }
        } catch {
            throw $_
        }
    }
}

Function Remove-NsGroupQuota {
<#
.SYNOPSIS
    Remove a group quota from the given filesystem
.DESCRIPTION
    Remove a group quota from the given filesystem
.OUTPUTS
    True if the quota as removed correctly
.PARAMETER Path <String>
    The path to the filesystem to remove the quota from
.PARAMETER Group <String>
    The group to remove the quota for
.PARAMETER NoWait
    Invoke to make the cmdlet start the job and return the GUID of the JobID
.EXAMPLE
    PS C:\> Remove-NsGroupQuota p1/collab1 -Group "S-1-5-21-1643737065-1150840763-312552118-242249"
    True
 
    Remove a quota for an active directory group
#>

    [cmdletbinding(   
        ConfirmImpact = 'medium',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The path to the filesystem to remove the quota from')]
            [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")]
            [string]$Path,
        [Parameter(Mandatory=$true,
                    Position = 1,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The group to remove a quota for')]
            [ValidatePattern("^([\w.]+|S(-\d+)+)$")]
            [alias('sid')]
            [string]$Group,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Makes the cmdlet wait for the removal to complete before returning true')]
            [switch]$NoWait
    )

    Process {
        Try {
            #Test filesystem exists
            if (($Path | Get-NsFilesystem) -eq $null) {
                throw 'Filesystem does not exists'
            }
            $QuotaBytes = 0
            $request = @{}
            $request.add("groupquota@$Group",$QuotaBytes)
            if ($pscmdlet.ShouldProcess($Path,"Remove quota")) {
                # Kick off the creation request
                $response = Invoke-NsApi -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))" -Request ($request | ConvertTo-Json) -Method put
                # Grap the id from the result
                $JobId = $response.links.href | Split-Path -Leaf
                # If we've been told to wait for completion...
                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 "quota not ready yet - sleeping"
                        sleep 5
                        $count -= 1
                        # If we've waited too long, just return the jobid
                        if ($count -eq 0) {Return $JobId}
                    }
                    #All done
                    # Check result
                    Get-NsJobResult $JobId | Out-Null
                    Return $true
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $JobId
                }
            }
        } catch {
            throw $_
        }
    }
}

Function Get-NsSnapshotProtectionService {
<#
.SYNOPSIS
    Get Snaphost based Protection Services
.DESCRIPTION
    Get Snaphost based Protection Services
.OUTPUTS
    Nothing if no matching filesystems are found.
    All snaphot protection services if no criteria given
    Otherwise, matching snaphot protection services
.PARAMETER Path <String>
    The path of a filesystem to retrieve services for
.PARAMETER Name <String>
    The name of a service to retrieve
.PARAMETER byPath
    Invoke to resolve ambiguity between piped inputs
.PARAMETER byName
    Invoke to resolve ambiguity between piped inputs
.EXAMPLE
    PS C:\temp> Get-NsFilesystem p1/collab-01 | Get-NsSnapshotProtectionService -ByPath | ft
 
    name id managerNodes isManager runNumber recursive sourceDataset type state isLocked
    ---- -- ------------ --------- --------- --------- ------------- ---- ----- --------
    collab-01-hourly 6c1da6c1-05a7-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 True p1/collab-01 scheduled enabled False
    twsset/sdf 7bacd8d1-05a8-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 False p1/collab-01 scheduled enabled False
 
    Get all services for a filesystem
.EXAMPLE
    PS C:\temp> Get-NsSnapshotProtectionService | ft
 
    name id managerNodes isManager runNumber recursive sourceDataset type state isLocked
    ---- -- ------------ --------- --------- --------- ------------- ---- ----- --------
    p1-top2 d32e58e0-ff86-11e7-9b3d-773bc8b1c3e3 @{primary=} True 0 True p1/top2 scheduled faulted False
    daily 4121b941-0046-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 True p1/top2 scheduled faulted False
    test-hpr-ss 58f73c10-0515-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 True p1/test scheduled enabled False
    collab-01-hourly 6c1da6c1-05a7-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 True p1/collab-01 scheduled enabled False
    twsset/sdf 7bacd8d1-05a8-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 False p1/collab-01 scheduled enabled False
 
 
    Get all services
#>

    [cmdletbinding(DefaultParameterSetName='none')]
    Param (
        [Parameter(ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    ParameterSetName = 'byPath',
                    HelpMessage='The path of a filesystem to retrieve services for')]
            [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")]
            [Alias('sourceDataset')]
            [string]$Path,
        [Parameter(ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    ParameterSetName = 'byName',
                    HelpMessage='The name of a service to retrieve')]
            [ValidatePattern("^(\w|-|_|\.|\/)+$")]
            [string]$Name,
        [Parameter(ParameterSetName='byPath',
                    HelpMessage='Invoke to resolve ambiguity between piped inputs')]
            [switch]$ByPath,             
        [Parameter(ParameterSetName='byName',
                    HelpMessage='Invoke to resolve ambiguity between piped inputs')]
            [switch]$ByName   

        )
    Process {
        try {
            switch ($PSCmdlet.ParameterSetName) {
                'ByPath' {
                        $operation = "hpr/services?type=scheduled&sourceDataset=$([uri]::EscapeDataString($Path))"
                }
                'ByName' {
                        $operation = "hpr/services?type=scheduled&name=$([uri]::EscapeDataString($Name))"
                }
                'none' {
                    $operation = "hpr/services?type=scheduled"
                }
            }
            Write-Verbose $operation
            # Get the required filesystems
            $services = Invoke-NsApi -Operation $operation
            $returnValue = $services.data | ? {$_.destinationDataset -eq $null}
            $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.HprSnapshot')}
            Return $returnValue
        } catch {throw $_}
    }
}

Function New-NsSnapshotProtectionService {
<#
.SYNOPSIS
    Creates a Snaphost based Protection Service for a filesystem
.DESCRIPTION
    Creates a Snaphost based Protection Service for a filesystem
.OUTPUTS
    The cmdlet will wait (30sec) for the task to complete and return an NsSnapshotProtectionService object for the created service
    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 of a filesystem to create the service for
.PARAMETER Name <String>
    The name of a service to create
.PARAMETER Minutes <String>
    A valid minutes cron entry - defaults to 0
.PARAMETER Hours <String>
    A valid hours cron entry - defaults to *
.PARAMETER DaysOfMonth <String>
    A valid days of the month cron entry - defaults to *
.PARAMETER Months <String>
    A valid months cron entry - defaults to *
.PARAMETER DaysOfWeek <String>
    A valid days of the week cron entry - defaults to *
.PARAMETER Retain <Int>
    The number of snapshots to retain - defaults to 1
.PARAMETER Recurse
    Invoke to protect all child filesystems
.PARAMETER NoWait
    Invoke to make the cmdlet start the job and return the GUID of the JobID
.EXAMPLE
    PS C:\temp> New-NsSnapshotProtectionService -Path p1/collab-02 -Name collab2 -Hours 6-18/3 -Retain 5 -Recurse
 
    name : collab2
    id : 15ae3e31-05cf-11e8-9b3d-773bc8b1c3e3
    managerNodes : @{primary=}
    isManager : True
    runNumber : 0
    recursive : True
    sourceDataset : p1/collab-02
    type : scheduled
    state : disabled
    isLocked : False
    isSyncing : False
    kstatId : 0000000000000006
    schedules : {@{cron=* 6-18/3 * * *; scheduleName=c9a56193-8cd4-48f2-be9d-ca6d4cf5cde9; keepSource=5; disabled=False; scheduleId=15ae3e30-05cf-11e8-9b3d-773bc8b1c3e3}}
    isRunning : False
    href : /hpr/services/collab2
 
    Create a shecduled snaphot protection service running every 3 hours between 06:00 and 18:00 keeping 5 snaphosts and including child filesystems
#>

    [cmdletbinding(   
        ConfirmImpact = 'low',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The path of the filesystem to create service for')]
            [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")]
            [Alias('sourceDataset')]
            [string]$Path,
        [Parameter(Mandatory=$true,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The name of the service')]
            [ValidatePattern("^(\w|-|_|\.|\/)+$")]
            [string]$Name,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Cron pattern defining the minutes')]
                    [ValidateNotNullOrEmpty()]
            [string]$Minutes = '0',
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Cron pattern defining the hours')]
                    [ValidateNotNullOrEmpty()]
            [string]$Hours = '1',
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Cron pattern defining the days of the month')]
                    [ValidateNotNullOrEmpty()]
            [string]$DaysOfMonth = '*',        
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Cron pattern defining the months')]
                    [ValidateNotNullOrEmpty()]
            [string]$Months = '*',        
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Cron pattern defining the days of the week')]
            [string]$DaysOfWeek = '*',        
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='The number of snaphots to retain')]
            [int]$Retain = 1,        
        [Parameter(ParameterSetName='byPath',
                    HelpMessage='Invoke to snapshot ')]
            [switch]$Recurse,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Makes the cmdlet wait for the creation to complete before returning a service object.')]
            [switch]$NoWait
        )

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

            #Test service name exists
            if (Get-NsSnapshotProtectionService -Name $Name) {
                throw 'A service with that name already exists'
            }

            $cron = "$Minutes $Hours $DaysOfMonth $Months $DaysOfWeek"
            if ($cron -eq "* * * * *") {
                throw "[$cron] is invalid"
            }

            #Build request
            $request = @{}
            $request.add('type','scheduled')
            $request.add('name',$Name)
            $request.add('recursive',$Recurse.IsPresent)
            $request.add('sourceDataset',$Path)
            $schedule = @{}
            $schedule.add('cron',$cron)
            $schedule.add('keepSource',$Retain)
            $schedule.add('scheduleName',[guid]::NewGuid())
            $request.add('schedules',@($schedule))
            if ($pscmdlet.ShouldProcess($Path,"Create service")) {
                # Kick off the creation request
                $response = Invoke-NsApi -Operation "hpr/services" -Request ($request | ConvertTo-Json)
                # Grap the id from the result
                $JobId = $response.links.href | Split-Path -Leaf
                # If we've been told to wait for completion...
                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 "service not ready yet - sleeping"
                        sleep 5
                        $count -= 1
                        # If we've waited too long, just return the jobid
                        if ($count -eq 0) {Return $JobId}
                    }
                    # Check result
                    Get-NsJobResult $JobId | Out-Null
                    # Filesystem has been created so go get the details.
                    $service = Get-NsSnapshotProtectionService -Name $Name
                    if (-Not $service) {
                        throw "Service creation failed - $JobId"
                    }
                    return $service
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $JobId
                }
            } else {
                Write-Verbose ($request | ConvertTo-Json)
            }            

        } catch {throw $_}
}

Function Remove-NsSnapshotProtectionService {
<#
.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 than 30sec it will return the GUID of the task.
.PARAMETER Name <String>
    The name of the service to remove
.PARAMETER Force
    Invoke to delete service forcibly
.PARAMETER destroySnapshots
    Invoke to destroy snapshots
.PARAMETER NoWait
    Invoke to make the cmdlet start the job ad return the JobID
.EXAMPLE
    PS C:\temp> Remove-NsSnapshotProtectionService collab-03 -destroySnapshots
    True
 
    Remove the Snapshot Protection Service called collab-03 and destroy it's snapshots
.EXMAPLE
    PS C:\temp> Get-NsSnapshotProtectionService -Name collab-04 | Disable-NsProtectionService | Remove-NsSnapshotProtectionService -destroySnapshots
    True
 
    Get the Snapshot Protection Service called collab-04, disable it and then remove it.
#>

    [cmdletbinding(   
        ConfirmImpact = 'High',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The name of the service to remove')]
            [ValidatePattern("^(\w|-|_|\.|\/)+$")]
            [string]$Name,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Delete service forcibly')]
            [switch]$Force,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Destroy snapshots')]
            [switch]$destroySnapshots,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Makes the cmdlet wait for the update to complete before returning')]
            [switch]$NoWait
    )

    Process {
        try {
            #Test service exists
            if (($service = Get-NsSnapshotProtectionService -Name $Name) -eq $null) {
                throw 'The service does not exist'
            }
        
            if ($service.state -eq 'enabled') {
                throw 'Invalid service state "enabled" (allowed state(s): "disabled", "faulted"'
            } 

            if ($pscmdlet.ShouldProcess($Name,"Remove Snapshot Protection Service")) {
                # Kick off the update request
                $response = Invoke-NsApi -Operation "hpr/services/$([uri]::EscapeDataString($Name))?force=$($Force.IsPresent)&destroySourceSnapshots=$($destroySnapshots.IsPresent)" -Method delete
                # Grap the id from the result
                $JobId = $response.links.href | Split-Path -Leaf
                # If we've been told to wait for completion...
                if (-Not $NoWait) {
                    # Limit ourselves to 30 sec before returning true
                    $count = 10
                    #Get job and check status
                    while ((Get-NsJobs -JobId $JobId).done -ne $true) {
                        Write-Verbose "service not removed yet - sleeping"
                        sleep 5
                        $count -= 1
                        # If we've waited too long, just return the jobid
                        if ($count -eq 0) {Return $JobId}
                    }
                    # Check result
                    Get-NsJobResult $JobId | Out-Null
                    # Service has been removed so return true.
                    Return $true
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $JobId
                }
                $response
            }
                
        } catch {
            throw $_
        }
    }
}

Function Get-NsReplicationProtectionService {
<#
.SYNOPSIS
    Get Snaphost based Protection Services
.DESCRIPTION
    Get Snaphost based Protection Services
.OUTPUTS
    Nothing if no matching filesystems are found.
    All snaphot protection services if no criteria given
    Otherwise, matching snaphot protection services
.PARAMETER Path <String>
    The path of a filesystem to retrieve services for
.PARAMETER Name <String>
    The name of a service to retrieve
.PARAMETER byPath
    Invoke to resolve ambiguity between piped inputs
.PARAMETER byName
    Invoke to resolve ambiguity between piped inputs
.EXAMPLE
    PS C:\temp> Get-NsFilesystem p1/collab-01 | Get-NsSnapshotProtectionService -ByPath | ft
 
    name id managerNodes isManager runNumber recursive sourceDataset type state isLocked
    ---- -- ------------ --------- --------- --------- ------------- ---- ----- --------
    collab-01-hourly 6c1da6c1-05a7-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 True p1/collab-01 scheduled enabled False
    twsset/sdf 7bacd8d1-05a8-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 False p1/collab-01 scheduled enabled False
 
    Get all services for a filesystem
.EXAMPLE
    PS C:\temp> Get-NsSnapshotProtectionService | ft
 
    name id managerNodes isManager runNumber recursive sourceDataset type state isLocked
    ---- -- ------------ --------- --------- --------- ------------- ---- ----- --------
    p1-top2 d32e58e0-ff86-11e7-9b3d-773bc8b1c3e3 @{primary=} True 0 True p1/top2 scheduled faulted False
    daily 4121b941-0046-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 True p1/top2 scheduled faulted False
    test-hpr-ss 58f73c10-0515-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 True p1/test scheduled enabled False
    collab-01-hourly 6c1da6c1-05a7-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 True p1/collab-01 scheduled enabled False
    twsset/sdf 7bacd8d1-05a8-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 False p1/collab-01 scheduled enabled False
 
 
    Get all services
#>

    [cmdletbinding(DefaultParameterSetName='none')]
    Param (
        [Parameter(ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    ParameterSetName = 'byPath',
                    HelpMessage='The path of a filesystem to retrieve services for')]
            [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")]
            [Alias('sourceDataset')]
            [string]$Path,
        [Parameter(ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    ParameterSetName = 'byName',
                    HelpMessage='The name of a service to retrieve')]
            [ValidatePattern("^(\w|-|_|\.|\/)+$")]
            [string]$Name,
        [Parameter(ParameterSetName='byPath',
                    HelpMessage='Invoke to resolve ambiguity between piped inputs')]
            [switch]$ByPath,             
        [Parameter(ParameterSetName='byName',
                    HelpMessage='Invoke to resolve ambiguity between piped inputs')]
            [switch]$ByName   

        )
    Process {
        try {
            switch ($PSCmdlet.ParameterSetName) {
                'ByPath' {
                        $operation = "hpr/services?type=scheduled&sourceDataset=$([uri]::EscapeDataString($Path))"
                }
                'ByName' {
                        $operation = "hpr/services?type=scheduled&name=$([uri]::EscapeDataString($Name))"
                }
                'none' {
                    $operation = "hpr/services?type=scheduled"
                }
            }
            Write-Verbose $operation
            # Get the required filesystems
            $services = Invoke-NsApi -Operation $operation
            $returnValue = $services.data | ? {$_.destinationDataset -ne $null}
            $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.HprSnapshot')}
            Return $returnValue
        } catch {throw $_}
    }
}

Function Enable-NsProtectionService {
<#
.SYNOPSIS
    Enables a Snaphost based Protection Service for a filesystem
.DESCRIPTION
    Enables a Snaphost based Protection Service for a filesystem
.OUTPUTS
    The cmdlet will wait (30sec) for the task to complete and return an NsSnapshotProtectionService object for the Enabled service
    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 Name <String>
    The name of a service to create
.PARAMETER NoWait
    Invoke to make the cmdlet start the job and return the GUID of the JobID
.EXAMPLE
    PS C:\temp> Enable-NsProtectionService collab2
 
 
    name : collab2
    id : 15ae3e31-05cf-11e8-9b3d-773bc8b1c3e3
    managerNodes : @{primary=}
    isManager : True
    runNumber : 0
    recursive : True
    sourceDataset : p1/collab-02
    type : scheduled
    state : enabled
    isLocked : False
    isSyncing : False
    kstatId : 0000000000000006
    schedules : {@{cron=* 6-18/3 * * *; scheduleName=c9a56193-8cd4-48f2-be9d-ca6d4cf5cde9; keepSource=5; disabled=False; scheduleId=15ae3e30-05cf-11e8-9b3d-773bc8b1c3e3}}
    isRunning : False
    href : /hpr/services/collab2
 
    Enable a snapshot service called collab2
#>

    [cmdletbinding(   
        ConfirmImpact = 'low',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The name of the service to enable')]
            [ValidatePattern("^(\w|-|_|\.|\/)+$")]
            [string]$Name,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Makes the cmdlet wait for the enable to complete before returning a service object.')]
            [switch]$NoWait
    )

    Process {
        #Test service exists
        if ((Get-NsSnapshotProtectionService -Name $Name) -eq $null) {
            if ((Get-NsReplicationProtectionService -Name $Name) -eq $null) {
                throw 'The service does not exist'
            }
        }    

        if ($pscmdlet.ShouldProcess($Name,"Enable service")) {
            # Kick off the creation request
            $response = Invoke-NsApi -Operation "hpr/services/$([uri]::EscapeDataString($Name))/enable" -Request "{}" 
            # Grap the id from the result
            $JobId = $response.links.href | Split-Path -Leaf
            # If we've been told to wait for completion...
            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 "service not ready yet - sleeping"
                    sleep 5
                    $count -= 1
                    # If we've waited too long, just return the jobid
                    if ($count -eq 0) {Return $JobId}
                }
                # Check result
                Get-NsJobResult $JobId | Out-Null
                # Service has been enabled so go get the details.
                $service = Get-NsSnapshotProtectionService -Name $Name
                return $service
            } else {
                # Not been asked to wait so just return the jobid
                Return $JobId
            }
        }
    }
}

Function Disable-NsProtectionService {
<#
.SYNOPSIS
    Disables a Snaphost based Protection Service for a filesystem
.DESCRIPTION
    Disables a Snaphost based Protection Service for a filesystem
.OUTPUTS
    The cmdlet will wait (30sec) for the task to complete and return an NsSnapshotProtectionService object for the Disabled service
    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 Name <String>
    The name of a service to create
.PARAMETER NoWait
    Invoke to make the cmdlet start the job and return the GUID of the JobID
.EXAMPLE
    PS C:\temp> Disable-NsProtectionService collab2
 
 
    name : collab2
    id : 15ae3e31-05cf-11e8-9b3d-773bc8b1c3e3
    managerNodes : @{primary=}
    isManager : True
    runNumber : 0
    recursive : True
    sourceDataset : p1/collab-02
    type : scheduled
    state : disabled
    isLocked : False
    isSyncing : False
    kstatId : 0000000000000006
    schedules : {@{cron=* 6-18/3 * * *; scheduleName=c9a56193-8cd4-48f2-be9d-ca6d4cf5cde9; keepSource=5; disabled=False; scheduleId=15ae3e30-05cf-11e8-9b3d-773bc8b1c3e3}}
    isRunning : False
    href : /hpr/services/collab2
 
    Disable a snapshot service called collab2
#>

    [cmdletbinding(   
        ConfirmImpact = 'low',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    Position = 0,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The name of the service to disable')]
            [ValidatePattern("^(\w|-|_|\.|\/)+$")]
            [string]$Name,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Makes the cmdlet wait for the disable to complete before returning a service object.')]
            [switch]$NoWait
    )

    Process {
        #Test service exists
        if ((Get-NsSnapshotProtectionService -Name $Name) -eq $null) {
            if ((Get-NsReplicationProtectionService -Name $Name) -eq $null) {
                throw 'The service does not exist'
            }
        }     

        if ($pscmdlet.ShouldProcess($Name,"Disable service")) {
            # Kick off the creation request
            $response = Invoke-NsApi -Operation "hpr/services/$([uri]::EscapeDataString($Name))/disable" -Request "{}" 
            # Grap the id from the result
            $JobId = $response.links.href | Split-Path -Leaf
            # If we've been told to wait for completion...
            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 "service not ready yet - sleeping"
                    sleep 5
                    $count -= 1
                    # If we've waited too long, just return the jobid
                    if ($count -eq 0) {Return $JobId}
                }
                # Check result
                Get-NsJobResult $JobId | Out-Null
                # Service has been disabled so go get the details.
                $service = Get-NsSnapshotProtectionService -Name $Name
                return $service
            } else {
                # Not been asked to wait so just return the jobid
                Return $JobId
            }
        }
    }
}

Function Add-NsProtectionServiceSchedule {
<#
.SYNOPSIS
    Add a schedule to a Snaphost based Protection Service for a filesystem
.DESCRIPTION
    Add a schedule to a Snaphost based Protection Service for a filesystem
.service
    The cmdlet will wait (30sec) for the task to complete and return an NsSnapshotProtectionSchedule object for the service
    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 Name <String>
    The name of a service to create
.PARAMETER Minutes <String>
    A valid minutes cron entry - defaults to 0
.PARAMETER Hours <String>
    A valid hours cron entry - defaults to *
.PARAMETER DaysOfMonth <String>
    A valid days of the month cron entry - defaults to *
.PARAMETER Months <String>
    A valid months cron entry - defaults to *
.PARAMETER DaysOfWeek <String>
    A valid days of the week cron entry - defaults to *
.PARAMETER Retain <Int>
    The number of snapshots to retain - defaults to 1
.PARAMETER NoWait
    Invoke to make the cmdlet start the job and return the GUID of the JobID
.EXAMPLE
    PS C:\temp> Add-NsProtectionServiceSchedule -Name collab2 -Minutes 10 -Hours 1 -DaysOfWeek "2-6" -Retain 7
 
 
    cron : 10 1 * * 2-6
    scheduleName : 227cddc6-bec5-4eed-8ee8-7990337be855
    keepSource : 7
    disabled : False
    scheduleId : eb553b70-05d3-11e8-9b3d-773bc8b1c3e3
    links : {@{rel=collection; href=/hpr/services/collab2/schedules}, @{rel=self; href=/hpr/services/collab2/schedules/227cddc6-bec5-4eed-8ee8-7990337be855}, @{rel=action/update; method=PUT;
                   href=/hpr/services/collab2/schedules/227cddc6-bec5-4eed-8ee8-7990337be855}, @{rel=action/delete; method=DELETE; href=/hpr/services/collab2/schedules/227cddc6-bec5-4eed-8ee8-7990337be855}}
 
    Add a schedule to the collab2 service
#>

    [cmdletbinding(   
        ConfirmImpact = 'low',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The name of the service')]
            [ValidatePattern("^(\w|-|_|\.|\/)+$")]
            [string]$Name,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Cron pattern defining the minutes')]
                    [ValidateNotNullOrEmpty()]
            [string]$Minutes = '0',
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Cron pattern defining the hours')]
                    [ValidateNotNullOrEmpty()]
            [string]$Hours = '1',
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Cron pattern defining the days of the month')]
                    [ValidateNotNullOrEmpty()]
            [string]$DaysOfMonth = '*',        
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Cron pattern defining the months')]
                    [ValidateNotNullOrEmpty()]
            [string]$Months = '*',        
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Cron pattern defining the days of the week')]
            [string]$DaysOfWeek = '*',        
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='The number of snaphots to retain')]
            [int]$Retain = 1,        
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Makes the cmdlet wait for the creation to complete before returning a service object.')]
            [switch]$NoWait
        )

        try {
            #Test service exists
            if ((Get-NsSnapshotProtectionService -Name $Name) -eq $null) {
                if ((Get-NsReplicationProtectionService -Name $Name) -eq $null) {
                    throw 'The service does not exist'
                }
            }  

            $cron = "$Minutes $Hours $DaysOfMonth $Months $DaysOfWeek"
            if ($cron -eq "* * * * *") {
                throw "[$cron] is invalid"
            }

            #Build request
            $request = @{}
            $request.add('cron',$cron)
            $request.add('keepSource',$Retain)
            $scheduleName=[guid]::NewGuid()
            $request.add('scheduleName',$scheduleName)

            if ($pscmdlet.ShouldProcess($Name,"Add schedule")) {
                # Kick off the creation request
                $response = Invoke-NsApi -Operation "hpr/services/$([uri]::EscapeDataString($Name))/schedules" -Request ($request | ConvertTo-Json)
                # Grap the id from the result
                $JobId = $response.links.href | Split-Path -Leaf
                # If we've been told to wait for completion...
                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 "service not ready yet - sleeping"
                        sleep 5
                        $count -= 1
                        # If we've waited too long, just return the jobid
                        if ($count -eq 0) {Return $JobId}
                    }
                    # Check result
                    Get-NsJobResult $JobId | Out-Null
                    # Schedule has been added so go get the details.
                    $schedule = Invoke-NsApi -Operation "hpr/services/$([uri]::EscapeDataString($Name))/schedules/$scheduleName"
                    return $schedule
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $JobId
                }
            } else {
                Write-Verbose ($request | ConvertTo-Json)
            }            

        } catch {throw $_}
}

Function Remove-NsProtectionServiceSchedule {
<#
.SYNOPSIS
    Removes a schedule from a Snaphost based Protection Service for a filesystem
.DESCRIPTION
    Removes a schedule from a Snaphost based Protection Service for a filesystem
.OUTPUTS
    The cmdlet will wait (30sec) for the task to complete and return an NsSnapshotProtectionSchedule object for the service
    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 ServiceName <String>
    The name of a service to remove the schedule from
.PARAMETER ScheduleName <String>
    The name of a the schedule to remove
.PARAMETER NoWait
    Invoke to make the cmdlet start the job and return the GUID of the JobID
.EXAMPLE
    PS C:\temp> Remove-NsProtectionServiceSchedule -ServiceName collab2 -ScheduleName caa0dee5-d994-413c-8f61-963fb638bc10
 
    name : collab2
    id : 15ae3e31-05cf-11e8-9b3d-773bc8b1c3e3
    managerNodes : @{primary=}
    isManager : True
    runNumber : 0
    recursive : True
    sourceDataset : p1/collab-02
    type : scheduled
    state : enabled
    isLocked : False
    isSyncing : False
    kstatId : 0000000000000006
    schedules : {@{cron=10 6-18/3 * * *; scheduleName=c9a56193-8cd4-48f2-be9d-ca6d4cf5cde9; keepSource=5; disabled=False; scheduleId=15ae3e30-05cf-11e8-9b3d-773bc8b1c3e3}, @{cron=10 1 * * SUN,TUE-SAT;
                    scheduleName=227cddc6-bec5-4eed-8ee8-7990337be855; keepSource=5; disabled=False; scheduleId=eb553b70-05d3-11e8-9b3d-773bc8b1c3e3}, @{cron=10 1 * * 1; scheduleName=47b7317a-1875-48df-b735-31ef6b2836a3; keepSource=2;
                    disabled=False; scheduleId=819a7870-05d4-11e8-9b3d-773bc8b1c3e3}}
    isRunning : False
    href : /hpr/services/collab2
 
    Remove a schedule to from the collab2 service
#>

    [cmdletbinding(   
        ConfirmImpact = 'medium',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The name of a service to remove the schedule from')]
            [ValidatePattern("^(\w|-|_|\.|\/)+$")]
            [string]$ServiceName,
        [Parameter(Mandatory=$true,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The name of a the schedule to remove')]
                    [ValidateNotNullOrEmpty()]
            [string]$ScheduleName,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Makes the cmdlet wait for the creation to complete before returning a service object.')]
            [switch]$NoWait
        )

        try {
            #Test service exists
            if ((Get-NsSnapshotProtectionService -Name $Name) -eq $null) {
                if ((Get-NsReplicationProtectionService -Name $Name) -eq $null) {
                    throw 'The service does not exist'
                }
            }  

            #Test the schedule exists
            $schedule = Invoke-NsApi -Operation "hpr/services/$([uri]::EscapeDataString($ServiceName))/schedules/$scheduleName"
            if ($schedule -eq $null) {
                throw "Failed to remove schedule - $JobId"
            }

            if ($pscmdlet.ShouldProcess($ServiceName,"Remove schedule")) {
                # Kick off the creation request
                $response = Invoke-NsApi -Operation "hpr/services/$([uri]::EscapeDataString($ServiceName))/schedules/$scheduleName" -Method delete
                # Grap the id from the result
                $JobId = $response.links.href | Split-Path -Leaf
                # If we've been told to wait for completion...
                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 "service not ready yet - sleeping"
                        sleep 5
                        $count -= 1
                        # If we've waited too long, just return the jobid
                        if ($count -eq 0) {Return $JobId}
                    }
                    # Check result
                    Get-NsJobResult $JobId | Out-Null
                    $service = Get-NsSnapshotProtectionService -Name $ServiceName
                    return $service
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $JobId
                }
            }          

        } catch {throw $_}
}

Function Get-NsSnapshotProtectionServiceSnapshot {
<#
.SYNOPSIS
    Get a snapshot
.DESCRIPTION
    Get a snapshot
.OUTPUTS
    An Array of matching NsSnapshot objects
.PARAMETER Name <String>
    The name of the snapshot to get
.PARAMETER Service <String>
    Get snaphosts assosiated with a HPR Service
.PARAMETER Path <String>
    Get snapshots for a given path
.EXAMPLE
    PS C:\temp> Get-NsSnapshotProtectionServiceSnapshot -Service collab-04 | ft
 
    path pool parent name creationTime
    ---- ---- ------ ---- ------------
    p1/collab-04/ee-p1may2007exams@snapping-2018-02-01-01-20-01-473 p1 p1/collab-04/ee-p1may2007exams snapping-2018-02-01-01-20-01-473 2018-02-01T01:20:...
    p1/collab-04/ee-6000_teaching_rom@snapping-2018-02-01-01-20-01-473 p1 p1/collab-04/ee-6000_teaching_rom snapping-2018-02-01-01-20-01-473 2018-02-01T01:20:...
    p1/collab-04/its-asset-management@snapping-2018-02-01-01-20-01-473 p1 p1/collab-04/its-asset-management snapping-2018-02-01-01-20-01-473 2018-02-01T01:20:...
    p1/collab-04/its-it-tel@snapping-2018-02-01-01-20-01-473 p1 p1/collab-04/its-it-tel snapping-2018-02-01-01-20-01-473 2018-02-01T01:20:...
    p1/collab-04/its-apphelp@snapping-2018-02-01-01-20-01-473 p1 p1/collab-04/its-apphelp snapping-2018-02-01-01-20-01-473 2018-02-01T01:20:...
    p1/collab-04@snapping-2018-02-01-01-20-01-473
 
    Get snaphosts assosiated with the collab-04 HPR Service
 
.EXAMPLE
    PS C:\temp> Get-NsSnapshotProtectionServiceSnapshot -Path p1/collab-02/pso-research_data_management | ft
 
    path pool parent name creationTime parentType bytesLogicalUsed bytesReferenced bytesUsed compressionRatio
    ---- ---- ------ ---- ------------ ---------- ---------------- --------------- --------- ----------------
    p1/collab-02/pso-... p1 p1/collab-02/pso-... snapping-2018-01-... 2018-01-30T15:25:... filesystem 0 24576 0 1
    p1/collab-02/pso-... p1 p1/collab-02/pso-... snapping-2018-01-... 2018-01-30T15:26:... filesystem 0 24576 0 1
    p1/collab-02/pso-... p1 p1/collab-02/pso-... snapping-2018-01-... 2018-01-30T15:27:... filesystem 0 24576 0 1
 
    Get snaphosts for the filesystem p1/collab-02/pso-research_data_management
#>

    Param (
        [Parameter(Position = 0,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The name of the snapshot to get')]
            [ValidateNotNullOrEmpty()]
            [string]$Name,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Get snaphosts assosiated with a HPR Service')]
            [ValidatePattern("^(\w|-|_|\.|\/)+$")]
            [Alias('hprService')]
            [string]$Service,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Get snapshots for a given path')]
            [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")]
            [Alias('parent')]
            [string]$Path
    )

    Process {
        try {
            # Set operation
            $operation = "storage/snapshots?limit=300"
            if ($Name) {
                $operation = "$operation&name=$Name"
            }
            if ($Service) {
                $HprService = Get-NsSnapshotProtectionService -Name $Service
                $operation = "$operation&hprService=$($HprService.id)"
            }
            if ($Path) {
                $operation = "$operation&parent=$([uri]::EscapeDataString($Path))"
            }

            Write-Verbose $operation
            # Get the required filesystems
            $snapshots = Invoke-NsApi -Operation $operation
            $snapshotsData = $snapshots.data
            while ($snapshots.links | ? {$_.rel -eq "next"}) {
                $operation = ($snapshots.links | ? {$_.rel -eq "next"}).href.Substring(1) -replace 'amp;',''
                Write-Verbose $operation
                $snapshots = Invoke-NsApi -Operation $operation
                $snapshotsData += $snapshots.data
            }
            $returnValue =  $snapshotsData
            $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.Snaphot')}
            Return $returnValue
        } catch {
            throw $_
        }
    }
}

Function Get-NsReplicationProtectionServiceSnapshot {
<#
.SYNOPSIS
    Get a snapshot
.DESCRIPTION
    Get a snapshot
.OUTPUTS
    An Array of matching NsSnapshot objects
.PARAMETER Name <String>
    The name of the snapshot to get
.PARAMETER Service <String>
    Get snaphosts assosiated with a HPR Service
.PARAMETER Path <String>
    Get snapshots for a given path
.EXAMPLE
    PS C:\temp> Get-NsReplicationProtectionServiceSnapshot -Service collabs-dr | ft
 
    path pool parent name creationTime parentType bytesLogicalUsed bytesReferenced bytesUsed compressionR
                                                                                                                                                                                                                      atio
    ---- ---- ------ ---- ------------ ---------- ---------------- --------------- --------- ------------
    p1/collabs/collab-01/share-01@hpr-2018-02-08-09-50-00-888 p1 p1/collabs/collab-01/share-01 hpr-2018-02-08-09-50-00-888 2018-02-08T09:50:00.000Z filesystem 0 24576 0 1
    dr1/collabs/collab-01/share-01@hpr-2018-02-08-09-50-00-888 dr1 dr1/collabs/collab-01/share-01 hpr-2018-02-08-09-50-00-888 2018-02-08T09:50:00.000Z filesystem 0 24576 0 1
    dr1/collabs/collab-01@hpr-2018-02-08-09-50-00-888 dr1 dr1/collabs/collab-01 hpr-2018-02-08-09-50-00-888 2018-02-08T09:50:00.000Z filesystem 0 33917440 0 14.89
    p1/collabs@hpr-2018-02-08-09-50-00-888 p1 p1/collabs hpr-2018-02-08-09-50-00-888 2018-02-08T09:50:00.000Z filesystem 0 24576 0 1
    dr1/collabs@hpr-2018-02-08-09-50-00-888 dr1 dr1/collabs hpr-2018-02-08-09-50-00-888 2018-02-08T09:50:00.000Z filesystem 0 24576 0 1
    p1/collabs/collab-01@hpr-2018-02-08-09-50-00-888 p1 p1/collabs/collab-01 hpr-2018-02-08-09-50-00-888 2018-02-08T09:50:00.000Z filesystem 0 33913344 0 14.89
    dr1/collabs/collab-01/share-01@hpr-2018-02-08-09-55-00-896 dr1 dr1/collabs/collab-01/share-01 hpr-2018-02-08-09-55-00-896 2018-02-08T09:55:00.000Z filesystem 0 24576 0 1
    p1/collabs/collab-01/share-01@hpr-2018-02-08-09-55-00-896 p1 p1/collabs/collab-01/share-01 hpr-2018-02-08-09-55-00-896 2018-02-08T09:55:00.000Z filesystem 0 24576 0 1
    dr1/collabs@hpr-2018-02-08-09-55-00-896 dr1 dr1/collabs hpr-2018-02-08-09-55-00-896 2018-02-08T09:55:00.000Z filesystem 0 24576 0 1
    p1/collabs/collab-01@hpr-2018-02-08-09-55-00-896 p1 p1/collabs/collab-01 hpr-2018-02-08-09-55-00-896 2018-02-08T09:55:00.000Z filesystem 0 33913344 0 14.89
    dr1/collabs/collab-01@hpr-2018-02-08-09-55-00-896 dr1 dr1/collabs/collab-01 hpr-2018-02-08-09-55-00-896 2018-02-08T09:55:00.000Z filesystem 0 33917440 0 14.89
    p1/collabs@hpr-2018-02-08-09-55-00-896 p1 p1/collabs hpr-2018-02-08-09-55-00-896 2018-02-08T09:55:00.000Z filesystem 0 24576 0 1
 
    Get snaphosts assosiated with the collabs-dr HPR Service
 
.EXAMPLE
    PS C:\temp> Get-NsReplicationProtectionServiceSnapshot -Path dr1/collabs/collab-01 | ft
 
    path pool parent name creationTime parentType bytesLogicalUsed bytesReferenced bytesUsed compressionRa
                                                                                                                                                                                                      tio
    ---- ---- ------ ---- ------------ ---------- ---------------- --------------- --------- -------------
    dr1/collabs/collab-01@hpr-2018-02-07-15-00-00-708 dr1 dr1/collabs/collab-01 hpr-2018-02-07-15-00-00-708 2018-02-07T15:00:02.000Z filesystem 0 33917440 0 14.89
    dr1/collabs/collab-01@hpr-2018-02-07-15-10-00-436 dr1 dr1/collabs/collab-01 hpr-2018-02-07-15-10-00-436 2018-02-07T15:10:01.000Z filesystem 0 33917440 0 14.89
    dr1/collabs/collab-01@hpr-2018-02-07-15-15-00-846 dr1 dr1/collabs/collab-01 hpr-2018-02-07-15-15-00-846 2018-02-07T15:15:01.000Z filesystem 0 33917440 0 14.89
 
    Get snaphosts for the filesystem dr1/collabs/collab-01
#>

    Param (
        [Parameter(Position = 0,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The name of the snapshot to get')]
            [ValidateNotNullOrEmpty()]
            [string]$Name,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Get snaphosts assosiated with a HPR Service')]
            [ValidatePattern("^(\w|-|_|\.|\/)+$")]
            [Alias('hprService')]
            [string]$Service,
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Get snapshots for a given path')]
            [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")]
            [Alias('parent')]
            [string]$Path
    )

    Process {
        try {
            # Set operation
            $operation = "storage/snapshots?limit=300"
            if ($Name) {
                $operation = "$operation&name=$Name"
            }
            if ($Service) {
                $HprService = Get-NsReplicationProtectionService -Name $Service
                $operation = "$operation&hprService=$($HprService.id)"
            }
            if ($Path) {
                $operation = "$operation&parent=$([uri]::EscapeDataString($Path))"
            }

            Write-Verbose $operation
            # Get the required filesystems
            $snapshots = Invoke-NsApi -Operation $operation
            $snapshotsData = $snapshots.data
            while ($snapshots.links | ? {$_.rel -eq "next"}) {
                $operation = ($snapshots.links | ? {$_.rel -eq "next"}).href.Substring(1) -replace 'amp;',''
                Write-Verbose $operation
                $snapshots = Invoke-NsApi -Operation $operation
                $snapshotsData += $snapshots.data
            }
            $returnValue =  $snapshotsData
            $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.Snaphot')}
            Return $returnValue
        } catch {
            throw $_
        }
    }
}