NexentaFusion.psm1

Function New-DynamicParameter {
<#
.SYNOPSIS
    Helper function to simplify creating dynamic parameters
 
.DESCRIPTION
    Helper function to simplify creating dynamic parameters.
 
    Example use cases:
        Include parameters only if your environment dictates it
        Include parameters depending on the value of a user-specified parameter
        Provide tab completion and intellisense for parameters, depending on the environment
 
    Please keep in mind that all dynamic parameters you create, will not have corresponding variables created.
        Use New-DynamicParameter with 'CreateVariables' switch in your main code block,
        ('Process' for advanced functions) to create those variables.
        Alternatively, manually reference $PSBoundParameters for the dynamic parameter value.
 
    This function has two operating modes:
 
    1. All dynamic parameters created in one pass using pipeline input to the function. This mode allows to create dynamic parameters en masse,
    with one function call. There is no need to create and maintain custom RuntimeDefinedParameterDictionary.
 
    2. Dynamic parameters are created by separate function calls and added to the RuntimeDefinedParameterDictionary you created beforehand.
    Then you output this RuntimeDefinedParameterDictionary to the pipeline. This allows more fine-grained control of the dynamic parameters,
    with custom conditions and so on.
 
.NOTES
    https://gallery.technet.microsoft.com/scriptcenter/New-DynamicParameter-63389a46
    Credits to jrich523 and ramblingcookiemonster for their initial code and inspiration:
        https://github.com/RamblingCookieMonster/PowerShell/blob/master/New-DynamicParam.ps1
        http://ramblingcookiemonster.wordpress.com/2014/11/27/quick-hits-credentials-and-dynamic-parameters/
        http://jrich523.wordpress.com/2013/05/30/powershell-simple-way-to-add-dynamic-parameters-to-advanced-function/
 
    Credit to BM for alias and type parameters and their handling
 
.PARAMETER Name
    Name of the dynamic parameter
 
.PARAMETER Type
    Type for the dynamic parameter. Default is string
 
.PARAMETER Alias
    If specified, one or more aliases to assign to the dynamic parameter
 
.PARAMETER Mandatory
    If specified, set the Mandatory attribute for this dynamic parameter
 
.PARAMETER Position
    If specified, set the Position attribute for this dynamic parameter
 
.PARAMETER HelpMessage
    If specified, set the HelpMessage for this dynamic parameter
 
.PARAMETER DontShow
    If specified, set the DontShow for this dynamic parameter.
    This is the new PowerShell 4.0 attribute that hides parameter from tab-completion.
    http://www.powershellmagazine.com/2013/07/29/pstip-hiding-parameters-from-tab-completion/
 
.PARAMETER ValueFromPipeline
    If specified, set the ValueFromPipeline attribute for this dynamic parameter
 
.PARAMETER ValueFromPipelineByPropertyName
    If specified, set the ValueFromPipelineByPropertyName attribute for this dynamic parameter
 
.PARAMETER ValueFromRemainingArguments
    If specified, set the ValueFromRemainingArguments attribute for this dynamic parameter
 
.PARAMETER ParameterSetName
    If specified, set the ParameterSet attribute for this dynamic parameter. By default parameter is added to all parameters sets.
 
.PARAMETER AllowNull
    If specified, set the AllowNull attribute of this dynamic parameter
 
.PARAMETER AllowEmptyString
    If specified, set the AllowEmptyString attribute of this dynamic parameter
 
.PARAMETER AllowEmptyCollection
    If specified, set the AllowEmptyCollection attribute of this dynamic parameter
 
.PARAMETER ValidateNotNull
    If specified, set the ValidateNotNull attribute of this dynamic parameter
 
.PARAMETER ValidateNotNullOrEmpty
    If specified, set the ValidateNotNullOrEmpty attribute of this dynamic parameter
 
.PARAMETER ValidateRange
    If specified, set the ValidateRange attribute of this dynamic parameter
 
.PARAMETER ValidateLength
    If specified, set the ValidateLength attribute of this dynamic parameter
 
.PARAMETER ValidatePattern
    If specified, set the ValidatePattern attribute of this dynamic parameter
 
.PARAMETER ValidateScript
    If specified, set the ValidateScript attribute of this dynamic parameter
 
.PARAMETER ValidateSet
    If specified, set the ValidateSet attribute of this dynamic parameter
 
.PARAMETER Dictionary
    If specified, add resulting RuntimeDefinedParameter to an existing RuntimeDefinedParameterDictionary.
    Appropriate for custom dynamic parameters creation.
 
    If not specified, create and return a RuntimeDefinedParameterDictionary
    Aappropriate for a simple dynamic parameter creation.
 
.EXAMPLE
    Create one dynamic parameter.
 
    This example illustrates the use of New-DynamicParameter to create a single dynamic parameter.
    The Drive's parameter ValidateSet is populated with all available volumes on the computer for handy tab completion / intellisense.
 
    Usage: Get-FreeSpace -Drive <tab>
 
    function Get-FreeSpace
    {
        [CmdletBinding()]
        Param()
        DynamicParam
        {
            # Get drive names for ValidateSet attribute
            $DriveList = ([System.IO.DriveInfo]::GetDrives()).Name
 
            # Create new dynamic parameter
            New-DynamicParameter -Name Drive -ValidateSet $DriveList -Type ([array]) -Position 0 -Mandatory
        }
 
        Process
        {
            # Dynamic parameters don't have corresponding variables created,
            # you need to call New-DynamicParameter with CreateVariables switch to fix that.
            New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
 
            $DriveInfo = [System.IO.DriveInfo]::GetDrives() | Where-Object {$Drive -contains $_.Name}
            $DriveInfo |
                ForEach-Object {
                    if(!$_.TotalFreeSpace)
                    {
                        $FreePct = 0
                    }
                    else
                    {
                        $FreePct = [System.Math]::Round(($_.TotalSize / $_.TotalFreeSpace), 2)
                    }
                    New-Object -TypeName psobject -Property @{
                        Drive = $_.Name
                        DriveType = $_.DriveType
                        'Free(%)' = $FreePct
                    }
                }
        }
    }
 
.EXAMPLE
    Create several dynamic parameters not using custom RuntimeDefinedParameterDictionary (requires piping).
 
    In this example two dynamic parameters are created. Each parameter belongs to the different parameter set, so they are mutually exclusive.
 
    The Drive's parameter ValidateSet is populated with all available volumes on the computer.
    The DriveType's parameter ValidateSet is populated with all available drive types.
 
    Usage: Get-FreeSpace -Drive <tab>
        or
    Usage: Get-FreeSpace -DriveType <tab>
 
    Parameters are defined in the array of hashtables, which is then piped through the New-Object to create PSObject and pass it to the New-DynamicParameter function.
    Because of piping, New-DynamicParameter function is able to create all parameters at once, thus eliminating need for you to create and pass external RuntimeDefinedParameterDictionary to it.
 
    function Get-FreeSpace
    {
        [CmdletBinding()]
        Param()
        DynamicParam
        {
            # Array of hashtables that hold values for dynamic parameters
            $DynamicParameters = @(
                @{
                    Name = 'Drive'
                    Type = [array]
                    Position = 0
                    Mandatory = $true
                    ValidateSet = ([System.IO.DriveInfo]::GetDrives()).Name
                    ParameterSetName = 'Drive'
                },
                @{
                    Name = 'DriveType'
                    Type = [array]
                    Position = 0
                    Mandatory = $true
                    ValidateSet = [System.Enum]::GetNames('System.IO.DriveType')
                    ParameterSetName = 'DriveType'
                }
            )
 
            # Convert hashtables to PSObjects and pipe them to the New-DynamicParameter,
            # to create all dynamic paramters in one function call.
            $DynamicParameters | ForEach-Object {New-Object PSObject -Property $_} | New-DynamicParameter
        }
        Process
        {
            # Dynamic parameters don't have corresponding variables created,
            # you need to call New-DynamicParameter with CreateVariables switch to fix that.
            New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
 
            if($Drive)
            {
                $Filter = {$Drive -contains $_.Name}
            }
            elseif($DriveType)
            {
                $Filter = {$DriveType -contains $_.DriveType}
            }
 
            $DriveInfo = [System.IO.DriveInfo]::GetDrives() | Where-Object $Filter
            $DriveInfo |
                ForEach-Object {
                    if(!$_.TotalFreeSpace)
                    {
                        $FreePct = 0
                    }
                    else
                    {
                        $FreePct = [System.Math]::Round(($_.TotalSize / $_.TotalFreeSpace), 2)
                    }
                    New-Object -TypeName psobject -Property @{
                        Drive = $_.Name
                        DriveType = $_.DriveType
                        'Free(%)' = $FreePct
                    }
                }
        }
    }
 
.EXAMPLE
    Create several dynamic parameters, with multiple Parameter Sets, not using custom RuntimeDefinedParameterDictionary (requires piping).
 
    In this example three dynamic parameters are created. Two of the parameters are belong to the different parameter set, so they are mutually exclusive.
    One of the parameters belongs to both parameter sets.
 
    The Drive's parameter ValidateSet is populated with all available volumes on the computer.
    The DriveType's parameter ValidateSet is populated with all available drive types.
    The DriveType's parameter ValidateSet is populated with all available drive types.
    The Precision's parameter controls number of digits after decimal separator for Free Space percentage.
 
    Usage: Get-FreeSpace -Drive <tab> -Precision 2
        or
    Usage: Get-FreeSpace -DriveType <tab> -Precision 2
 
    Parameters are defined in the array of hashtables, which is then piped through the New-Object to create PSObject and pass it to the New-DynamicParameter function.
    If parameter with the same name already exist in the RuntimeDefinedParameterDictionary, a new Parameter Set is added to it.
    Because of piping, New-DynamicParameter function is able to create all parameters at once, thus eliminating need for you to create and pass external RuntimeDefinedParameterDictionary to it.
 
    function Get-FreeSpace
    {
        [CmdletBinding()]
        Param()
        DynamicParam
        {
            # Array of hashtables that hold values for dynamic parameters
            $DynamicParameters = @(
                @{
                    Name = 'Drive'
                    Type = [array]
                    Position = 0
                    Mandatory = $true
                    ValidateSet = ([System.IO.DriveInfo]::GetDrives()).Name
                    ParameterSetName = 'Drive'
                },
                @{
                    Name = 'DriveType'
                    Type = [array]
                    Position = 0
                    Mandatory = $true
                    ValidateSet = [System.Enum]::GetNames('System.IO.DriveType')
                    ParameterSetName = 'DriveType'
                },
                @{
                    Name = 'Precision'
                    Type = [int]
                    # This will add a Drive parameter set to the parameter
                    Position = 1
                    ParameterSetName = 'Drive'
                },
                @{
                    Name = 'Precision'
                    # Because the parameter already exits in the RuntimeDefinedParameterDictionary,
                    # this will add a DriveType parameter set to the parameter.
                    Position = 1
                    ParameterSetName = 'DriveType'
                }
            )
 
            # Convert hashtables to PSObjects and pipe them to the New-DynamicParameter,
            # to create all dynamic paramters in one function call.
            $DynamicParameters | ForEach-Object {New-Object PSObject -Property $_} | New-DynamicParameter
        }
        Process
        {
            # Dynamic parameters don't have corresponding variables created,
            # you need to call New-DynamicParameter with CreateVariables switch to fix that.
            New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
 
            if($Drive)
            {
                $Filter = {$Drive -contains $_.Name}
            }
            elseif($DriveType)
            {
                $Filter = {$DriveType -contains $_.DriveType}
            }
 
            if(!$Precision)
            {
                $Precision = 2
            }
 
            $DriveInfo = [System.IO.DriveInfo]::GetDrives() | Where-Object $Filter
            $DriveInfo |
                ForEach-Object {
                    if(!$_.TotalFreeSpace)
                    {
                        $FreePct = 0
                    }
                    else
                    {
                        $FreePct = [System.Math]::Round(($_.TotalSize / $_.TotalFreeSpace), $Precision)
                    }
                    New-Object -TypeName psobject -Property @{
                        Drive = $_.Name
                        DriveType = $_.DriveType
                        'Free(%)' = $FreePct
                    }
                }
        }
    }
 
.Example
    Create dynamic parameters using custom dictionary.
 
    In case you need more control, use custom dictionary to precisely choose what dynamic parameters to create and when.
    The example below will create DriveType dynamic parameter only if today is not a Friday:
 
    function Get-FreeSpace
    {
        [CmdletBinding()]
        Param()
        DynamicParam
        {
            $Drive = @{
                Name = 'Drive'
                Type = [array]
                Position = 0
                Mandatory = $true
                ValidateSet = ([System.IO.DriveInfo]::GetDrives()).Name
                ParameterSetName = 'Drive'
            }
 
            $DriveType = @{
                Name = 'DriveType'
                Type = [array]
                Position = 0
                Mandatory = $true
                ValidateSet = [System.Enum]::GetNames('System.IO.DriveType')
                ParameterSetName = 'DriveType'
            }
 
            # Create dictionary
            $DynamicParameters = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
 
            # Add new dynamic parameter to dictionary
            New-DynamicParameter @Drive -Dictionary $DynamicParameters
 
            # Add another dynamic parameter to dictionary, only if today is not a Friday
            if((Get-Date).DayOfWeek -ne [DayOfWeek]::Friday)
            {
                New-DynamicParameter @DriveType -Dictionary $DynamicParameters
            }
 
            # Return dictionary with dynamic parameters
            $DynamicParameters
        }
        Process
        {
            # Dynamic parameters don't have corresponding variables created,
            # you need to call New-DynamicParameter with CreateVariables switch to fix that.
            New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
 
            if($Drive)
            {
                $Filter = {$Drive -contains $_.Name}
            }
            elseif($DriveType)
            {
                $Filter = {$DriveType -contains $_.DriveType}
            }
 
            $DriveInfo = [System.IO.DriveInfo]::GetDrives() | Where-Object $Filter
            $DriveInfo |
                ForEach-Object {
                    if(!$_.TotalFreeSpace)
                    {
                        $FreePct = 0
                    }
                    else
                    {
                        $FreePct = [System.Math]::Round(($_.TotalSize / $_.TotalFreeSpace), 2)
                    }
                    New-Object -TypeName psobject -Property @{
                        Drive = $_.Name
                        DriveType = $_.DriveType
                        'Free(%)' = $FreePct
                    }
                }
        }
    }
#>

    [CmdletBinding(PositionalBinding = $false, DefaultParameterSetName = 'DynamicParameter')]
    Param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [System.Type]$Type = [string],

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [string[]]$Alias,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [switch]$Mandatory,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [int]$Position,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [string]$HelpMessage,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [switch]$DontShow,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [switch]$ValueFromPipeline,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [switch]$ValueFromPipelineByPropertyName,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [switch]$ValueFromRemainingArguments,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [string]$ParameterSetName = '__AllParameterSets',

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [switch]$AllowNull,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [switch]$AllowEmptyString,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [switch]$AllowEmptyCollection,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [switch]$ValidateNotNull,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [switch]$ValidateNotNullOrEmpty,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [ValidateCount(2,2)]
        [int[]]$ValidateCount,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [ValidateCount(2,2)]
        [int[]]$ValidateRange,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [ValidateCount(2,2)]
        [int[]]$ValidateLength,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [ValidateNotNullOrEmpty()]
        [string]$ValidatePattern,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [ValidateNotNullOrEmpty()]
        [scriptblock]$ValidateScript,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [ValidateNotNullOrEmpty()]
        [string[]]$ValidateSet,

        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({
            if(!($_ -is [System.Management.Automation.RuntimeDefinedParameterDictionary]))
            {
                Throw 'Dictionary must be a System.Management.Automation.RuntimeDefinedParameterDictionary object'
            }
            $true
        })]
        $Dictionary = $false,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'CreateVariables')]
        [switch]$CreateVariables,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'CreateVariables')]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({
            # System.Management.Automation.PSBoundParametersDictionary is an internal sealed class,
            # so one can't use PowerShell's '-is' operator to validate type.
            if($_.GetType().Name -ne 'PSBoundParametersDictionary')
            {
                Throw 'BoundParameters must be a System.Management.Automation.PSBoundParametersDictionary object'
            }
            $true
        })]
        $BoundParameters
    )

    Begin
    {
        Write-Verbose 'Creating new dynamic parameters dictionary'
        $InternalDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary

        Write-Verbose 'Getting common parameters'
        function _temp { [CmdletBinding()] Param() }
        $CommonParameters = (Get-Command _temp).Parameters.Keys
    }

    Process
    {
        if($CreateVariables)
        {
            Write-Verbose 'Creating variables from bound parameters'
            Write-Debug 'Picking out bound parameters that are not in common parameters set'
            $BoundKeys = $BoundParameters.Keys | Where-Object { $CommonParameters -notcontains $_ }

            foreach($Parameter in $BoundKeys)
            {
                Write-Debug "Setting existing variable for dynamic parameter '$Parameter' with value '$($BoundParameters.$Parameter)'"
                Set-Variable -Name $Parameter -Value $BoundParameters.$Parameter -Scope 1 -Force
            }
        }
        else
        {
            Write-Verbose 'Looking for cached bound parameters'
            Write-Debug 'More info: https://beatcracker.wordpress.com/2014/12/18/psboundparameters-pipeline-and-the-valuefrompipelinebypropertyname-parameter-attribute'
            $StaleKeys = @()
            $StaleKeys = $PSBoundParameters.GetEnumerator() |
                        ForEach-Object {
                            if($_.Value.PSobject.Methods.Name -match '^Equals$')
                            {
                                # If object has Equals, compare bound key and variable using it
                                if(!$_.Value.Equals((Get-Variable -Name $_.Key -ValueOnly -Scope 0)))
                                {
                                    $_.Key
                                }
                            }
                            else
                            {
                                # If object doesn't has Equals (e.g. $null), fallback to the PowerShell's -ne operator
                                if($_.Value -ne (Get-Variable -Name $_.Key -ValueOnly -Scope 0))
                                {
                                    $_.Key
                                }
                            }
                        }
            if($StaleKeys)
            {
                [string[]]"Found $($StaleKeys.Count) cached bound parameters:" +  $StaleKeys | Write-Debug
                Write-Verbose 'Removing cached bound parameters'
                $StaleKeys | ForEach-Object {[void]$PSBoundParameters.Remove($_)}
            }

            # Since we rely solely on $PSBoundParameters, we don't have access to default values for unbound parameters
            Write-Verbose 'Looking for unbound parameters with default values'

            Write-Debug 'Getting unbound parameters list'
            $UnboundParameters = (Get-Command -Name ($PSCmdlet.MyInvocation.InvocationName)).Parameters.GetEnumerator()  |
                                        # Find parameters that are belong to the current parameter set
                                        Where-Object { $_.Value.ParameterSets.Keys -contains $PsCmdlet.ParameterSetName } |
                                            Select-Object -ExpandProperty Key |
                                                # Find unbound parameters in the current parameter set
                                                Where-Object { $PSBoundParameters.Keys -notcontains $_ }

            # Even if parameter is not bound, corresponding variable is created with parameter's default value (if specified)
            Write-Debug 'Trying to get variables with default parameter value and create a new bound parameter''s'
            $tmp = $null
            foreach($Parameter in $UnboundParameters)
            {
                $DefaultValue = Get-Variable -Name $Parameter -ValueOnly -Scope 0
                if(!$PSBoundParameters.TryGetValue($Parameter, [ref]$tmp) -and $DefaultValue)
                {
                    $PSBoundParameters.$Parameter = $DefaultValue
                    Write-Debug "Added new parameter '$Parameter' with value '$DefaultValue'"
                }
            }

            if($Dictionary)
            {
                Write-Verbose 'Using external dynamic parameter dictionary'
                $DPDictionary = $Dictionary
            }
            else
            {
                Write-Verbose 'Using internal dynamic parameter dictionary'
                $DPDictionary = $InternalDictionary
            }

            Write-Verbose "Creating new dynamic parameter: $Name"

            # Shortcut for getting local variables
            $GetVar = {Get-Variable -Name $_ -ValueOnly -Scope 0}

            # Strings to match attributes and validation arguments
            $AttributeRegex = '^(Mandatory|Position|ParameterSetName|DontShow|HelpMessage|ValueFromPipeline|ValueFromPipelineByPropertyName|ValueFromRemainingArguments)$'
            $ValidationRegex = '^(AllowNull|AllowEmptyString|AllowEmptyCollection|ValidateCount|ValidateLength|ValidatePattern|ValidateRange|ValidateScript|ValidateSet|ValidateNotNull|ValidateNotNullOrEmpty)$'
            $AliasRegex = '^Alias$'

            Write-Debug 'Creating new parameter''s attirubutes object'
            $ParameterAttribute = New-Object -TypeName System.Management.Automation.ParameterAttribute

            Write-Debug 'Looping through the bound parameters, setting attirubutes...'
            switch -regex ($PSBoundParameters.Keys)
            {
                $AttributeRegex
                {
                    Try
                    {
                        $ParameterAttribute.$_ = . $GetVar
                        Write-Debug "Added new parameter attribute: $_"
                    }
                    Catch
                    {
                        $_
                    }
                    continue
                }
            }

            if($DPDictionary.Keys -contains $Name)
            {
                Write-Verbose "Dynamic parameter '$Name' already exist, adding another parameter set to it"
                $DPDictionary.$Name.Attributes.Add($ParameterAttribute)
            }
            else
            {
                Write-Verbose "Dynamic parameter '$Name' doesn't exist, creating"

                Write-Debug 'Creating new attribute collection object'
                $AttributeCollection = New-Object -TypeName Collections.ObjectModel.Collection[System.Attribute]

                Write-Debug 'Looping through bound parameters, adding attributes'
                switch -regex ($PSBoundParameters.Keys)
                {
                    $ValidationRegex
                    {
                        Try
                        {
                            $ParameterOptions = New-Object -TypeName "System.Management.Automation.${_}Attribute" -ArgumentList (. $GetVar) -ErrorAction Stop
                            $AttributeCollection.Add($ParameterOptions)
                            Write-Debug "Added attribute: $_"
                        }
                        Catch
                        {
                            $_
                        }
                        continue
                    }

                    $AliasRegex
                    {
                        Try
                        {
                            $ParameterAlias = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList (. $GetVar) -ErrorAction Stop
                            $AttributeCollection.Add($ParameterAlias)
                            Write-Debug "Added alias: $_"
                            continue
                        }
                        Catch
                        {
                            $_
                        }
                    }
                }

                Write-Debug 'Adding attributes to the attribute collection'
                $AttributeCollection.Add($ParameterAttribute)

                Write-Debug 'Finishing creation of the new dynamic parameter'
                $Parameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList @($Name, $Type, $AttributeCollection)

                Write-Debug 'Adding dynamic parameter to the dynamic parameter dictionary'
                $DPDictionary.Add($Name, $Parameter)
            }
        }
    }

    End
    {
        if(!$CreateVariables -and !$Dictionary)
        {
            Write-Verbose 'Writing dynamic parameter dictionary to the pipeline'
            $DPDictionary
        }
    }
}

Function Connect-Controller {
<#
.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-Controller to be called again without requiring credentials.
.OUTPUTS
    Nothing.
.PARAMETER Name <String>
    The FQDN or IP for the controller to connect to.
.PARAMETER Applicance <String>
    The name of the applicance in fution to invoke requests against.
.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-Controller -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-Controller -Name nexentastor-controller.local -Credential $cred
    Connect to the controller on the default port
.EXAMPLE
    Connect-Controller -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="Name of the NexentaStor applicance to connect to")]
            [Parameter(ParameterSetName='Cred')]
            [ValidateNotNullOrEmpty()]
            [string]$Applicance,
        [Parameter(Mandatory=$false,
                    ParameterSetName='Default',
                    ValueFromPipelineByPropertyName=$true,
                    HelpMessage="API Port of the NexentaStor controller to connect to - Defaults to 8443")]
            [Parameter(ParameterSetName='Cred')]
            [ValidateNotNullOrEmpty()]
            [int]$Port = 8443,
        [Parameter(Mandatory=$true,
                    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,
                    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

        #Are we connected to fusion?
        try {
            $Operation = "appliances"
            $token = $response.token
            $requestUrl = "$baseUrl/appliances"
            $Header = @{'Authorization' = "Bearer $token";'Content-Type' = 'application/json'}
            $response = Invoke-RestMethod -Uri $requestUrl -Method Get -Headers $Header -Verbose:$false -ErrorVariable RestError
            $isFusion = $true
        } catch {
            if (($RestError.Message | ConvertFrom-Json).message -eq '/appliances does not exist') {
                $isFusion = $false
            } else {
                throw $_
            }
        }

        #If we are connected to fusion and we have an applicance get the id
        if ($isFusion -and $Applicance) {
            if (-Not ($response.data | ? {$_.name -like $Applicance})) {
                throw "$Applicance does not exist"
            } else {
                $newApplicance = ($response.data | ? {$_.name -like $Applicance})
            }
        } elseif (-Not $isFusion -and $Applicance) {
            throw 'You cannot specify an applicance for a non fusion controller'
        }

        #if ($Applicance -eq "") {$newApplicance = $null}
        #else {$newApplicance = $Applicance}

        #Write connection details to script wide variable to ruse by other functions
        $script:nsController = [pscustomobject]@{name=$Name;port=$Port;baseUrl=$baseUrl;isFusion=$isFusion;applicance=$newApplicance;user=$Credential.UserName;token=$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-Api {
    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
            
        )


        #Validate Json if included
        if ($Request) {
            try {
                $Request | ConvertFrom-Json -ErrorAction stop | Out-Null
            } catch {
                throw 'Request is not a valid Json string'
            }
        }

        #Define a hash of valid operations and their associated default action
        $OperationSet = @{}
        $OperationSet.add('auth/status','get')
        $OperationSet.add('appliances','get')
        $OperationSet.add('appliances/APPID/pools','get')
        $OperationSet.add('appliances/APPID/filesystems','get')
        $OperationSet.add('nodes/NODEID/query/storage/filesystems','get')
        $OperationSet.add('nodes/NODEID/query/jobStatus','get')
        $OperationSet.add('appliances/APPID/nas/smb','get')
        $OperationSet.add('nodes/NODEID/query/nas/smb','get')
        $OperationSet.add('appliances/APPID/hpr/services','get')
        $OperationSet.add('nodes/NODEID/query/hpr/services','get')
        $OperationSet.add('appliances/APPID/snapshots','get')
        $OperationSet.add('nodes/NODEID/query/storage/snapshots','get')
        $OperationSet.add('appliances/APPID/nas/nfs','get')
        $OperationSet.add('nodes/NODEID/query/nas/nfs','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')
# $OperationSet.add('appliances','get')
# $OperationSet.add('appliances/APPID/pools','get')
# $OperationSet.add('nodes','get')


        
# $ApplicanceRewrite.add('storage/filesystems','appliances/APPID/filesystems')
# $ApplicanceRewrite.add('nas/smb','appliances/APPID/nas/smb')
# $ApplicanceRewrite.add('nas/nfs','appliances/APPID/nas/nfs')
# $ApplicanceRewrite.add('hpr/services','appliances/APPID/hpr/services')
# $ApplicanceRewrite.add('storage/snapshots','appliances/APPID/snapshots')

        #Operations we can only run via fusion if we are connected to an applicance
        $ApplicanceOnlyOperations = @('appliances/APPID/pools','appliances/APPID/filesystems','appliances/APPID/nas/smb')

        #Operations we can only run against fusion - we don't care if we are connected to an applicance
        #$FusionOnlyOperations = @()

        #Validate Operation
        # Replace node id with string for matching
        if ($Operation -match '^nodes\/') {
            $OperationArray = (($Operation -replace "(^nodes\/)(\w+)(\/)",'$1NODEID$3') -split '\?')[0] -split '/'
        } else {
            $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]
                    $MatchedOperation = $key
                    Write-Verbose "Matched : $MatchedOperation"
                }
                break
            }
            if ($x -eq 1) {
                throw "Operation $Operation could not be found in OperationSet"
            }
        }   

        #Override default OpSet if a request is included
        if (($Method -eq 'get') -and ($Request)) {
           $Method = 'post'
        }


        #Bomb out if we are trying to run an applicance based query against fusion directly
        if (($Script:nsController.isFusion) -and ($Script:nsController.applicance -eq $null) -and ($ApplicanceOnlyOperations -contains "$MatchedOperation") ) {
            throw "$MatchedOperation is only avalable when connected to an applicance via fusion"
        }

        #Replace APPID with the current connected application
        if ($Operation -like "*APPID*") {
            $Operation = $Operation -replace 'APPID',$Script:nsController.applicance.id
        }

        #Bomb out if we are calling a fusion operation and we are not connected to fusion
        #if ((-Not $Script:nsController.isFusion) -and ($FusionOnlyOperations -contains "$MatchedOperation") ) {
        # throw "$MatchedOperation is only avalable when connected to fusion"
        #}

        ##Rewrite the request from the calling function to support query via fusion if we are connect to fusion
        #if (($Script:nsController.isFusion) -and ($Script:nsController.applicance -ne $null) -and ($ApplicanceOnlyOperations -contains "$MatchedOperation") -and ($ApplicanceRewrite.ContainsKey("$MatchedOperation"))) {
        # $Replacement = $ApplicanceRewrite[$MatchedOperation] -replace 'APPID',$Script:nsController.applicance
        # $Operation = $Operation -replace $MatchedOperation,$Replacement
        # Write-Verbose $Operation
        #}


        #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
        #Tag limit onto url
        if ($Operation -match '\?') {
            $requestUrl = "$baseUrl/$($Operation)"#&limit=300"
        } else {
            $requestUrl = "$baseUrl/$($Operation)"#?limit=300"
        }
        Write-Verbose "Url : $requestUrl"
        Write-Verbose "Method : $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)"
                    if ($Script:nsController.applicance) {
                        Connect-Controller -Name $Script:nsController.name -Port $Script:nsController.port -Applicance $Script:nsController.applicance.name | Out-Null
                    } else {
                       Connect-Controller -Name $Script:nsController.name -Port $Script:nsController.port | Out-Null
                    }
                    Write-Verbose "Reconnected....Retrying request."
                    Write-Verbose "Invoke-Api -Operation $Operation -Method $Method"
                    $response = $null
                    switch ($method) {
                        {$_ -eq 'post' -or $_ -eq 'put'} {
                            $response = Invoke-Api -Operation $Operation -Request $Request -Method $Method
                        }
                        default {
                            $response = Invoke-Api -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
                Write-Warning ($RestError.Message | ConvertFrom-Json).message
            }
            400 {
                throw ($RestError.Message | ConvertFrom-Json).message
            }
            default {
                # Not an error we know how to handle, pass it up the stack
                Write-Warning "Not an return code we know how to handle $($_.Exception.Response.StatusCode.value__)"
                throw $_
            }
        }
    } finally {
        [System.Net.ServicePointManager]::CertificatePolicy = $CertificatePolicy
        [Net.ServicePointManager]::SecurityProtocol = $tlsBackup
    }
}   

Function Get-JobResult {
    Param (
        [Parameter(Mandatory=$true,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    Position = 0)]
        [ValidatePattern("^\/nodes\/\w+\/query\/jobStatus\/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$")]
        [string]$Href
     )
    Process {
        try {
            $job = Invoke-Api -Operation $Href.TrimStart('/')
            if ($job.statusCode) {
                switch ($job.statusCode) {
                    '200' {return 'ok'}
                    '201' {return 'ok'} # Created
                    '202' {return 'processing'}
                    default {return $job}
                }
            }

            if ($job.done -ne $null) {
                if ($job.done -eq $true) {return 'ok'}
                if ($job.done -eq $false) {return 'processing'}
            }
            return $job
        } catch {
            throw $_
        }
    }
}

Function Get-Applicance {
<#
.SYNOPSIS
    Get applicances from fusion
.DESCRIPTION
    Get from fusion
.OUTPUTS
    An Array of matching NsApplicance objects
.PARAMETER Name <String>
    The name of the applicance to get
.PARAMETER Id <String>
    The Id of the applicance to get
.EXAMPLE
    PS C:\temp> Get-Applicance | ft
 
    id type name registeredAt garbageThresholds nodes licenseStatus
    -- ---- ---- ------------ ----------------- ----- -------------
    68450692 single stor-nex-51-1 2018-02-09T12:18:49.149Z {"logs":"now-3M","events":"now-3M","realtime-analytics":"now-1w"} @{68450692=} @{licenseValid=valid; enabledFeatures=System.Object[]; isCommun...
    fd245d8 RSFCluster stor-nex-act 2018-02-09T12:27:19.886Z {"logs":"now-3M","events":"now-3M","realtime-analytics":"now-1w"} @{fd245d8=; 64d247d9=} @{licenseValid=valid; enabledFeatures=System.Object[]; isCommun...
 
    Get all applicances
 
.EXAMPLE
    PS C:\temp> Get-Applicance -Name stor-nex-51-1
 
    id : 68450692
    type : single
    name : stor-nex-51-1
    registeredAt : 2018-02-09T12:18:49.149Z
    garbageThresholds : {"logs":"now-3M","events":"now-3M","realtime-analytics":"now-1w"}
    nodes : @{68450692=}
    licenseStatus : @{licenseValid=valid; enabledFeatures=System.Object[]; isCommunityEdition=False}
 
    Get the applicance by name
#>


    [cmdletbinding(DefaultParameterSetName='none')]
    Param (
        [Parameter(Position = 0,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    ParameterSetName='byName',
                    HelpMessage='The name of the applicance to get')]
            [ValidateNotNullOrEmpty()]
            [string]$Name,
        [Parameter(Position = 0,
                    ValueFromPipeline,
                    ValueFromPipelineByPropertyName,
                    ParameterSetName='byID',
                    HelpMessage='The ID of the applicance to get')]
            [ValidateNotNullOrEmpty()]
            [string]$Id
    )

    Process {
        try {
            # Set operation
            $operation = "appliances?limit=300"
            # Get the required filesystems
            $appliances = Invoke-Api -Operation $operation
            $appliancesData = $appliances.data
            while ($appliances.links | ? {$_.rel -eq "next"}) {
                $operation = ($appliances.links | ? {$_.rel -eq "next"}).href.Substring(1) -replace 'amp;',''
                Write-Verbose $operation
                $appliances = Invoke-Api -Operation $operation
                $appliancesData += $appliances.data
            }
            switch ($PSCmdlet.ParameterSetName) {
                'ByName' {
                    $returnValue =  $appliancesData | select -ExcludeProperty href -Property * | ? {$_.name -like $Name}
                }
                'ByID' {
                    $returnValue =  $appliancesData | select -ExcludeProperty href -Property * | ? {$_.id -like $Id}
                }
                'none' {
                    $returnValue =  $appliancesData | select -ExcludeProperty href -Property *
                }
            }
            $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.Appliance')}
            Return $returnValue
        } catch {
            throw $_
        }
    }
}

Function Get-Pool {
<#
.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-Pool
 
    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-Pool -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-Pool -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-Api -Operation "appliances/APPID/pools?poolName=$([uri]::EscapeDataString($Name))&limit=300"
            $returnValue = $pools.data
        } else {
            $pools = Invoke-Api -Operation "appliances/APPID/pools?limit=300"
            # 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-Filesystem {
<#
.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-Pool | Get-Filesystem -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-Filesystem 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 = "appliances/APPID/filesystems?path=$([uri]::EscapeDataString($Path))&limit=300"
                    } else {
                        # using parent causes all sub filesystems to be returned.
                        $operation = "appliances/APPID/filesystems?parent=$([uri]::EscapeDataString($Path))&recursive=$Recurse&limit=300"
                    }
                }
                'none' {
                    $operation = "appliances/APPID/filesystems?limit=300"
                }
            }
            Write-Verbose $operation
            # Get the required filesystems
            $filesystems = Invoke-Api -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-Api -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 = "nodes/$($filesystem.nodeId)/query/storage/filesystems/$([uri]::EscapeDataString($filesystem.path))"
                    Write-Verbose $Operation
                    $newFilesystems += Invoke-Api -Operation $Operation
                }
                $returnValue = $newFilesystems
            } else {
                $returnValue =  $filesystemsData
            }
            $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.Filesystem')}
            Return $returnValue
        } catch {
            throw $_
        }
    }
}

Function New-Filesystem {
<#
.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 href of the creation task
    If the cmdlet waits for more then 30sec it will return the href 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-FileSystem -Path p2/top1 -NoWait
    c36fea60-faac-11e7-a2ef-2b309711a489
 
    Create a new filesystem
.EXMAPLE
    PS C:\> New-FileSystem -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-FileSystem -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-Filesystem -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-Filesystem) -ne $null) {
        throw 'Filesystem already exists'
    }

    #Test parent Exists
    $parentPath = ($Path | Split-Path) -replace '\\','/'
    if (($parentPath | Get-Filesystem) -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")) {
            #Get node running the pool
            $node = (Get-Pool -Name ($Path -split '/')[0]).nodeId
            # Kick off the creation request
            $response = Invoke-Api -Operation "nodes/$node/query/storage/filesystems" -Request ($request | ConvertTo-Json)
            # Grap the id from the result
            $jobHref = $response.links.href
            # 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
                $result = $null
                while ($true) {
                    $result = Get-JobResult -Href $jobHref
                    if ($result -ne 'processing') {break}
                    $count -= 1
                    # If we've waited too long, just return the href
                    if ($count -eq 0) {Return $jobHref}
                }

                if ($result -ne 'ok'){
                    Write-Warning ($result | Out-String)
                    throw $result
                } else {
                    Return Get-Filesystem -Path $Path -Detailed
                }
            } else {
                # Not been asked to wait so just return the jobid
                Return $jobHref
            }
        }
    } catch {
        throw $_
    }
}

Function Set-Filesystem {
<#
.SYNOPSIS
    Sets properties of a filesystem
.DESCRIPTION
    Sets properties of a filesystem
.OUTPUTS
    The cmdlet will wait (30sec) for the task to complete and return a Filesystem object for the updated filesystem
    If -NoWait was invoked it will return the href of the creation task
    If the cmdlet waits for more then 30sec it will return the href of the task.
.PARAMETER Path <String>
    The path for the filesystem to set
.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
 
#>

    [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 filesystem exists
    $filesystem = $Path | Get-Filesystem
    if ($filesystem -eq $null) {
        throw 'Filesystem does not exists'
    }

    #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 = @{}
    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,"Set Filesystem")) {
            #Get node running the pool
            $node = (Get-Pool -Name ($Path -split '/')[0]).nodeId
            # Kick off the creation request
            $response = Invoke-Api -Operation "nodes/$node/query/storage/filesystems/$([uri]::EscapeDataString($Path))" -Request ($request | ConvertTo-Json) -Method put
            # Grap the id from the result
            $jobHref = $response.links.href
            # 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
                $result = $null
                while ($true) {
                    $result = Get-JobResult -Href $jobHref
                    if ($result -ne 'processing') {break}
                    $count -= 1
                    # If we've waited too long, just return the href
                    if ($count -eq 0) {Return $jobHref}
                }

                if ($result -ne 'ok'){
                    Write-Warning ($result | Out-String)
                    throw $result
                } else {
                    Return Get-Filesystem -Path $Path -Detailed
                }
            } else {
                # Not been asked to wait so just return the jobid
                Return $jobHref
            }
        }
    } catch {
        throw $_
    }
}

Function Remove-Filesystem {
<#
.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-Filesystem -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-Filesystem -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
        $filesystem = $Path | Get-Filesystem
        if ($filesystem -eq $null) {
            throw 'Filesystem does not exists'
        }

        try {
            if ($pscmdlet.ShouldProcess($Path,"Remove Filesystem")) {
                $node = $filesystem.nodeId
                $Operation = "nodes/$node/query/storage/filesystems/$([uri]::EscapeDataString($Path))?force=$(-Not $NoForce)&snapshots=$(-Not $NoSnapshots)"
                Write-Verbose $Operation
                # Kick off the deletion request
                $response = Invoke-Api -Operation $Operation -Method 'delete'
                # Grap the id from the result
                $jobHref = $response.links.href
                # 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
                    $result = $null
                    while ($true) {
                        $result = Get-JobResult -Href $jobHref
                        if ($result -ne 'processing') {break}
                        $count -= 1
                        # If we've waited too long, just return the href
                        if ($count -eq 0) {Return $jobHref}
                    }

                    if ($result -ne 'ok'){
                        Write-Warning ($result | Out-String)
                        throw $result
                    } else {
                        Return $true
                    }
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $jobHref
                }
            }
        }catch {
            throw $_
        }
    }
}

Function Get-SmbShare {
<#
.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-SmbShare | 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-Filesystem p1/top2/sub1 | Get-SmbShare | 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-SmbShare | 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 = "appliances/APPID/nas/smb?shareName=$([uri]::EscapeDataString($ShareName))"
                }
                'ByPath' {
                    $operation = "appliances/APPID/nas/smb?filesystem=$([uri]::EscapeDataString($Path))"
                }
                'none' {
                    $operation = "appliances/APPID/nas/smb"
                }
            }
            Write-Verbose $operation
            # Get the required filesystems
            $shares = Invoke-Api -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-Api -Operation $operation
                $sharesData += $shares.data
            }
            $returnValue = $sharesData | select -ExcludeProperty href -Property *
            $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.SmbShare')}
            Return $returnValue
        } catch {
            throw $_
        }
    }
}

Function New-SmbShare {
<#
.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-SmbShare -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
    $filesystem = $Path | Get-Filesystem
    if ($filesystem -eq $null) {
        throw 'Filesystem does not exists'
    }

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

    #Test the share name does not exists
    if ((Get-SmbShare -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")) {
            $node = $filesystem.nodeId
            # Kick off the creation request
            Invoke-Api -Operation "nodes/$node/query/nas/smb" -Request ($request | ConvertTo-Json) | Out-Null
            sleep 2
            Get-SmbShare -Path $Path
        }
    } catch {
        throw $_
    }
}

Function Set-SmbShare {
<#
.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-SmbShare -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
        $existingShare = Get-SmbShare -Path $Path
        if ($existingShare -eq $null) {
            throw 'The filesystem is not shared'
        }

        #Test the share name does not exists
        if ((Get-SmbShare -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")) {
                #Get node running the share
                $node = $existingShare.nodeId
                # Kick off the update request
                Invoke-Api -Operation "nodes/$node/query/nas/smb/$([uri]::EscapeDataString($Path))" -Request ($request | ConvertTo-Json) -Method 'put' | Out-Null
                sleep 2
                Return Get-SmbShare -Path $Path
            }
        } catch {
            throw $_
        }
    }
}

Function Remove-SmbShare {
<#
.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-SmbShare -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
        $existingShare = Get-SmbShare -Path $Path
        if ($existingShare -eq $null) {
            throw 'The filesystem is not shared'
        }

        try {
            if ($pscmdlet.ShouldProcess($Path,"Remove Share")) {
                #Get node running the share
                $node = $existingShare.nodeId
                # Kick off the update request
                Invoke-Api -Operation "nodes/$node/query/nas/smb/$([uri]::EscapeDataString($Path))" -Method 'delete' | Out-Null
                Return $true
            }
        } catch {
            throw $_
        }
    }
}

Function ConvertTo-NfsSecurityContexts {
<#
.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-NfsSecurityContexts
 
    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-NfsSecurityContexts {
<#
.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-NfsSecurityContexts | ConvertFrom-NfsSecurityContexts
    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-NfsShare {
<#
.SYNOPSIS
    Get information about one or more NFS Shares
.DESCRIPTION
    Get information about one or more NFS Shares
.OUTPUTS
    If nothing is specified, all NFS shares are returned.
    If a Name is specified, the NFS share with that name is retrieved.
    If a path is specified, the NFS share with that filesystem is retrieved.
.PARAMETER Path <String>
    Path to the share to retrieve
.EXAMPLE
    PS C:\> Get-NfsShare | 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-NfsShare 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( 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) {
                'ByPath' {
                    $operation = "appliances/APPID/nas/nfs?filesystem=$([uri]::EscapeDataString($Path))"
                }
                'none' {
                    $operation = "appliances/APPID/nas/nfs"
                }
            }
            Write-Verbose $operation
            # Get the required filesystems
            $shares = Invoke-Api -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-Api -Operation $operation
                $sharesData += $shares.data
            }
            $returnValue = $sharesData | select -ExcludeProperty href -Property *
            $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.NfsShare')}
            Return $returnValue
        } catch {
            throw $_
        }
    }
}

Function New-NfsShare {
<#
.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 - Defaults to sys
.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-NfsShare -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 - Default is sys')]
            [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 -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
    $filesystem = $Path | Get-Filesystem
    if ($filesystem -eq $null) {
        throw 'Filesystem does not exists'
    }

    #Test the filesystem is not already shared
    if ((Get-NfsShare -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-NfsSecurityContexts $_})
    $readWriteListAcL = @($ReadWrite | % {ConvertTo-NfsSecurityContexts $_})
    $readOnlyListAcl = @($ReadOnly | % {ConvertTo-NfsSecurityContexts $_})
    $noneAclList = @($NoAccess | % {ConvertTo-NfsSecurityContexts $_})
 
    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")) {
            $node = $filesystem.nodeId
            # Kick off the creation request
            Invoke-Api -Operation "nodes/$node/query/nas/nfs" -Request ($request | ConvertTo-Json -Depth 4) | Out-Null
            sleep 1
            Get-NfsShare -Path $Path
        }
    } catch {
        throw $_
    }
}

Function Set-NfsShare {
<#
.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-NfsSecurityContexts (Get-NfsShare 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-NfsShare -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-NfsSecurityContexts (Get-NfsShare 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 - Default is sys')]
            [ValidateSet('none','sys','dh','krb5','krb5i','krb5p')]
            [string[]]$SecurityModes,
        [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 -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
    $filesystem = $Path | Get-Filesystem
    if ($filesystem -eq $null) {
        throw 'Filesystem does not exists'
    }
    
    #Test the filesystem is already shared
    $currentNfs = Get-NfsShare -Path $Path
    if ($currentNfs -eq $null) {
        throw 'The filesystem is not shared'
    }


    #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-NfsSecurityContexts $_})
    $readWriteListAcL = @($ReadWrite | % {ConvertTo-NfsSecurityContexts $_})
    $readOnlyListAcl = @($ReadOnly | % {ConvertTo-NfsSecurityContexts $_})
    $noneAclList = @($NoAccess | % {ConvertTo-NfsSecurityContexts $_})
 
    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")) {
            $node = $filesystem.nodeId
            # Kick off the creation request
            Invoke-Api -Operation "nodes/$node/query/nas/nfs/$([uri]::EscapeDataString($Path))" -Request ($request | ConvertTo-Json -Depth 4) -Method put | Out-Null
            sleep 1
            Get-NfsShare -Path $Path
        }
    } catch {
        throw $_
    }
}

Function Remove-NfsShare {
<#
.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-NfsShare -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
    )

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

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

        try {
            if ($pscmdlet.ShouldProcess($Path,"Remove Share")) {
                #Get node running the share
                $node = $filesystem.nodeId
                # Kick off the update request
                Invoke-Api -Operation "nodes/$node/query/nas/nfs/$([uri]::EscapeDataString($Path))" -Method 'delete' | Out-Null
                Return $true
            }
        } catch {
            throw $_
        }
    }
}

Function Get-FilesystemAcl {
<#
.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-FilesystemAcl -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 {

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

        try {
            $node = $filesystem.nodeId
            $response = Invoke-Api -Operation "nodes/$node/query/storage/filesystems/$([uri]::EscapeDataString($Path))/acl"
            Return $response.data | select -ExcludeProperty href -Property *
        } catch {
            throw $_
        }
    }
}

Function Add-FilesystemAcl {
<#
.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-FilesystemAcl -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 {

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

        #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")) {
                $node = $filesystem.nodeId
                # Kick off the update request
                $response = Invoke-Api -Operation "nodes/$node/query/storage/filesystems/$([uri]::EscapeDataString($Path))/acl" -Request ($request | ConvertTo-Json)
                # Grap the id from the result
                $jobHref = $response.links.href
                # 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
                    $result = $null
                    while ($true) {
                        $result = Get-JobResult -Href $jobHref
                        if ($result -ne 'processing') {break}
                        $count -= 1
                        # If we've waited too long, just return the href
                        if ($count -eq 0) {Return $jobHref}
                    }

                    if ($result -ne 'ok'){
                        Write-Warning ($result | Out-String)
                        throw $result
                    } else {
                        Return Get-Filesystem -Path $Path -Detailed
                    }
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $jobHref
                }
                $response
            } else {Write-Verbose ($request | ConvertTo-Json)}
        } catch {
            throw $_
        }
    }
}

Function Set-FilesystemAcl {
<#
.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-FilesystemAcl -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 {

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

        #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-FilesystemAcl -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")) {
                $node = $filesystem.nodeId
                # Kick off the update request
                $response = Invoke-Api -Operation "nodes/$node/query/storage/filesystems/$([uri]::EscapeDataString($Path))/acl/$Index" -Request ($request | ConvertTo-Json) -Method put
                # Grap the id from the result
                $jobHref = $response.links.href
                # 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
                    $result = $null
                    while ($true) {
                        $result = Get-JobResult -Href $jobHref
                        if ($result -ne 'processing') {break}
                        $count -= 1
                        # If we've waited too long, just return the href
                        if ($count -eq 0) {Return $jobHref}
                    }

                    if ($result -ne 'ok'){
                        Write-Warning ($result | Out-String)
                        throw $result
                    } else {
                        Return Get-Filesystem -Path $Path -Detailed
                    }
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $jobHref
                }
                $response
            } else {Write-Verbose ($request | ConvertTo-Json)}
        } catch {
            throw $_
        }
    }
}

Function Remove-FilesystemAcl {
<#
.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-FilesystemAcl -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 filesystem exists
        $filesystem = $Path | Get-Filesystem
        if ($filesystem -eq $null) {
            throw 'Filesystem does not exists'
        }

        #Test for Index
        $currentAcl = Get-FilesystemAcl -Path $Path | ? {$_.index -eq $Index}
        if ($currentAcl -eq $null) {
            throw "No acl for at $Index for $Path"
        }
        
        if ($pscmdlet.ShouldProcess($Path,"Remove ACL")) {
            $node = $filesystem.nodeId
            # Kick off the update request
            $response = Invoke-Api -Operation "nodes/$node/query/storage/filesystems/$([uri]::EscapeDataString($Path))/acl/$Index" -Method delete
            # Grap the id from the result
            $jobHref = $response.links.href
            # 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
                    $result = $null
                    while ($true) {
                        $result = Get-JobResult -Href $jobHref
                        if ($result -ne 'processing') {break}
                        $count -= 1
                        # If we've waited too long, just return the href
                        if ($count -eq 0) {Return $jobHref}
                    }

                    if ($result -ne 'ok'){
                        Write-Warning ($result | Out-String)
                        throw $result
                    } else {
                        Return Get-Filesystem -Path $Path -Detailed
                    }
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $jobHref
                }
            $response
        }
                
    } catch {
        throw $_
    }
}

Function Get-ProtectionService {
<#
.SYNOPSIS
    Get Protection Services
.DESCRIPTION
    Get Services
.OUTPUTS
    Nothing if no matching protection services are found.
    All protection services if no criteria given
    Otherwise, matching protection services
.PARAMETER Type <String>
    A spacific type or protection service
.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-ProtectionService | ft
 
    name managerNodes isManager runNumber recursive sourceDataset type state isLocked isSyncing
    ---- ------------ --------- --------- --------- ------------- ---- ----- -------- ---------
    Gold @{primary=; secondary=} True 0 False pool1/gold scheduled enabled False False
    gold @{primary=; secondary=} True 21 True pool1/gold scheduled enabled False False
    basic @{primary=; secondary=} True 23 True pool1/basic scheduled enabled False False
 
    Get all services
.EXAMPLE
    PS C:\temp> Get-ProtectionService -Type ScheduledSnapshots | ft
 
    name managerNodes isManager runNumber recursive sourceDataset type state isLocked isSyncing
    ---- ------------ --------- --------- --------- ------------- ---- ----- -------- ---------
    Gold @{primary=; secondary=} True 0 False pool1/gold scheduled enabled False False
 
    Get all Schedulued Snapshot Services
.EXAMPLE
    PS C:\temp> Get-ProtectionService -Type ScheduledReplication -Name gold | ft
 
    name remoteNode managerNodes isManager isSource runNumber recursive sourceDataset destinationDataset type
    ---- ---------- ------------ --------- -------- --------- --------- ------------- ------------------ ----
    gold @{proto=https; host=stor-nex-eg-01-rep2; port=8443} @{primary=; secondary=} True True 21 True pool1/gold dr-pool1/gold scheduled
 
    Get all Schedulued Replication Services called gold
#>

    [cmdletbinding(DefaultParameterSetName='none')]
    Param (
        [Parameter(ValueFromPipelineByPropertyName,
                    HelpMessage='Set of permissions to allow or deny')]
            [ValidateSet('ScheduledSnapshots','ScheduledReplication','ConinuousReplication')]
            [string]$Type,
        [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 = "appliances/APPID/hpr/services?dataset=$([uri]::EscapeDataString($Path))"
                }
                'ByName' {
                        $operation = "appliances/APPID/hpr/services?name=$([uri]::EscapeDataString($Name))"
                }
                'none' {
                    $operation = "appliances/APPID/hpr/services"
                }
            }
            if ($Type) {
                $Type = $Type.substring(0,1).ToLower()+$Type.substring(1)
                if ($operation -match ".*\?") {
                    $operation += "&serviceType=$Type"                        
                } else {
                    $operation += "?serviceType=$Type"
                }
            }


            Write-Verbose $operation
            # Get the required filesystems
            $services = Invoke-Api -Operation $operation
            $returnValue = $services.data
            $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.HprSnapshot')}
            Return $returnValue
        } catch {throw $_}
    }
}

Function New-ProtectionService {
<#
.SYNOPSIS
    Creates a new Protection Services
.DESCRIPTION
    Creates a new Protection Services
.OUTPUTS
    The cmdlet will wait (30sec) for the task to complete and return a protectionservice object for the created protection service
    If -NoWait was invoked it will return the href of the creation task
    If the cmdlet waits for more then 30sec it will return the href of the task.
.PARAMETER Type <String>
    The type of protection service to create
.PARAMETER Path <String>
    The path of a filesystem to create the protection service for
.PARAMETER Name <String>
    The name of a service to create
.PARAMETER Recurse
    Should the protection service be appllied to child filesystems
.PARAMETER TargetPath
    Dynamic Parameter - Only avalable when creating ScheduledReplication services
    The Path for the filesystem to be setup on the remote target
.PARAMETER Node
    Dynamic Parameter - Only avalable when creating ScheduledReplication services
    The IP or fqdn of the remote node currently running the target pool
.EXAMPLE
 
#>

    [cmdletbinding(   
        ConfirmImpact = 'low',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                ValueFromPipelineByPropertyName,
                HelpMessage='Set of permissions to allow or deny')]
            [ValidateSet('ScheduledSnapshots','ScheduledReplication','ConinuousReplication')]
            [string]$Type,
        [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(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
    )

    DynamicParam {
        if ($Type -eq 'ScheduledReplication') {
            # Array of hashtables that hold values for dynamic parameters
            $DynamicParameters = @(
                @{
                    Name = 'TargetPath'
                    Alias = 'destinationDataset'
                    Mandatory = $true
                    ValueFromPipelineByPropertyName = $true
                    ValidatePattern = "^[\w\-\:\.]+(|(\/[\w\-\:\.]+)){1,}$"
                    HelpMessage = 'The remote path of the filesystem to create'
                    
                    
                },
                @{
                    Name = 'Node'
                    Mandatory = $true
                    ValueFromPipelineByPropertyName = $true
                    ValidatePattern = "^(\w|-|_|\.|\/)+$"
                    HelpMessage = 'The remote path of the filesystem to create'
                }
            )

            # Convert hashtables to PSObjects and pipe them to the New-DynamicParameter,
            # to create all dynamic paramters in one function call.
            $DynamicParameters | ForEach-Object {New-Object PSObject -Property $_} | New-DynamicParameter


        }
    }
    process {

            # Dynamic parameters don't have corresponding variables created,
            # you need to call New-DynamicParameter with CreateVariables switch to fix that.
            New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters

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

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

            #Build request
            $request = @{}
            $request.add('name',$Name)
            $request.add('recursive',$Recurse.IsPresent)
            $request.add('sourceDataset',$Path)
            switch ($Type) {
                'ScheduledSnapshots' {
                    $request.add('type','scheduled')
                }
                'ScheduledReplication' {
                    $request.add('type','scheduled')
                    $request.add('destinationDataset',$TargetPath)
                    $request.add('isSource',$true)
                    $remoteNode = @{}
                    $remoteNode.add('host',$Node)
                    $remoteNode.add('port',8443)
                    $remoteNode.add('proto','https')
                    $request.add('remoteNode',$remoteNode)
                }
            }

            if ($pscmdlet.ShouldProcess($Path,"Create service")) {
                $node = $filesystem.nodeId
                # Kick off the update request
                $response = Invoke-Api -Operation "nodes/$node/query/hpr/services" -Request ($request | ConvertTo-Json)
                # Grap the id from the result
                $jobHref = $response.links.href
                # 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
                    $result = $null
                    while ($true) {
                        $result = Get-JobResult -Href $jobHref
                        if ($result -ne 'processing') {break}
                        $count -= 1
                        # If we've waited too long, just return the href
                        if ($count -eq 0) {Return $jobHref}
                    }

                    if ($result -ne 'ok'){
                        Write-Warning ($result | Out-String)
                        throw $result
                    } else {
                        Return Get-ProtectionService -Name $Name
                    }
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $jobHref
                }
            } else {
                Write-Verbose ($request | ConvertTo-Json)
            }            

        } catch {throw $_}
    }
}

Function Remove-ProtectionService {
<#
.SYNOPSIS
    Removes a protection service
.DESCRIPTION
    Removes a protection service
.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
.PARAMETER DestroyTargetSnapshots
    Dynamic Parameter - Only avalable when removing ScheduledReplication services
    Invoke to remove service owned snapshots on the remote node
.PARAMETER DestroyTarget
    Dynamic Parameter - Only avalable when removing ScheduledReplication services
    Invoke to remove the filesystem on the remote node
.EXAMPLE
    PS C:\temp> Remove-ProtectionService collab-03 -destroySnapshots
    True
 
    Remove the Snapshot Protection Service called collab-03 and destroy it's snapshots
.EXMAPLE
    PS C:\temp> Get-ProtectionService -Name collab-04 | Disable-ProtectionService | Remove-ProtectionService -destroySnapshots
    True
 
    Get the Snapshot Protection Service called collab-04, disable it and then remove it.
#>

    [cmdletbinding(   
        ConfirmImpact = 'High',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Mandatory=$true,
                ValueFromPipelineByPropertyName,
                HelpMessage='Set of permissions to allow or deny')]
            [ValidateSet('ScheduledSnapshots','ScheduledReplication','ConinuousReplication')]
            [string]$Type,
        [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
    )
    DynamicParam {
        if ($Type -eq 'ScheduledReplication') {
            # Array of hashtables that hold values for dynamic parameters
            $DynamicParameters = @(
                @{
                    Name = 'DestroyTargetSnapshots'
                    Type = [switch]
                    ValueFromPipelineByPropertyName = $true
                    HelpMessage = 'Destroy Remote snapshots'
                },
                @{
                    Name = 'DestroyTarget'
                    Type = [switch]
                    ValueFromPipelineByPropertyName = $true
                    HelpMessage = 'Destroy Remote filesystem'
                }
            )

            # Convert hashtables to PSObjects and pipe them to the New-DynamicParameter,
            # to create all dynamic paramters in one function call.
            $DynamicParameters | ForEach-Object {New-Object PSObject -Property $_} | New-DynamicParameter
        }
    }
    Process {
        # Dynamic parameters don't have corresponding variables created,
        # you need to call New-DynamicParameter with CreateVariables switch to fix that.
        New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
        try {
            #Test service exists
            if (($service = Get-ProtectionService -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 Protection Service")) {
                $node = $service.nodeId
                #Setup the operation
                $operation = "nodes/$node/query/hpr/services/$([uri]::EscapeDataString($Name))?force=$($Force.IsPresent)&destroySourceSnapshots=$($DestroySnapshots.IsPresent)"
                if ($DestroyTargetSnapshots) {$operation += "&destroyDestinationSnapshots=true"}
                if ($DestroyTarget) {$operation += "&destroyDestination=true"}
                # Kick off the update request
                $response = Invoke-Api -Operation $operation -Method delete
                # Grap the id from the result
                $jobHref = $response.links.href
                # 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
                    $result = $null
                    while ($true) {
                        $result = Get-JobResult -Href $jobHref
                        if ($result -ne 'processing') {break}
                        $count -= 1
                        # If we've waited too long, just return the href
                        if ($count -eq 0) {Return $jobHref}
                    }

                    if ($result -ne 'ok'){
                        Write-Warning ($result | Out-String)
                        throw $result
                    } else {
                        Return $true
                    }
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $jobHref
                }               
                $response
            }
                
        } catch {
            throw $_
        }
    }
}

Function Add-ProtectionServiceSchedule {
<#
.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 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-ProtectionServiceSchedule -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(ValueFromPipelineByPropertyName,
                HelpMessage='Set of permissions to allow or deny')]
            [ValidateSet('ScheduledSnapshots','ScheduledReplication','ConinuousReplication')]
            [string]$Type,
        [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
        )
    DynamicParam {
        if ($Type -eq 'ScheduledReplication') {
            # Array of hashtables that hold values for dynamic parameters
            $DynamicParameters = @(
                @{
                    Name = 'RetainRemote'
                    Type = [int]
                    Mandatory = $true
                    ValueFromPipelineByPropertyName = $true
                    HelpMessage = 'Number of remote snapshots to retain'
                }
            )

            # Convert hashtables to PSObjects and pipe them to the New-DynamicParameter,
            # to create all dynamic paramters in one function call.
            $DynamicParameters | ForEach-Object {New-Object PSObject -Property $_} | New-DynamicParameter
        }
    }
    process {
        # Dynamic parameters don't have corresponding variables created,
        # you need to call New-DynamicParameter with CreateVariables switch to fix that.
        New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
        try {
            #Test service exists
            if (($service = Get-ProtectionService -Name $Name) -eq $null) {
                throw 'The service does not exist'
            }  

            if (($service.serviceType -eq 'scheduledReplication') -and (-Not $RetainRemote)) {
                throw 'ScheduledReplication services require a remote retention number'
            }

            $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()
            if ($RetainRemote) {$request.add('keepDestination',$RetainRemote)}
            $request.add('scheduleName',$scheduleName)

            if ($pscmdlet.ShouldProcess($Name,"Add schedule")) {
                $node = $service.nodeId
                # Kick off the creation request
                $response = Invoke-Api -Operation "nodes/$node/query/hpr/services/$([uri]::EscapeDataString($Name))/schedules" -Request ($request | ConvertTo-Json)
                # Grap the id from the result
                $jobHref = $response.links.href
                # 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
                    $result = $null
                    while ($true) {
                        $result = Get-JobResult -Href $jobHref
                        if ($result -ne 'processing') {break}
                        $count -= 1
                        # If we've waited too long, just return the href
                        if ($count -eq 0) {Return $jobHref}
                    }

                    if ($result -ne 'ok'){
                        Write-Warning ($result | Out-String)
                        throw $result
                    } else {
                        $service = Get-ProtectionService -Name $Name
                        return $service
                    }
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $jobHref
                }               
            } else {
                Write-Verbose ($request | ConvertTo-Json)
            }            

        } catch {throw $_}
    }
}

Function Remove-ProtectionServiceSchedule {
<#
.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 NsScheduledSnapshotSchedule 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-ProtectionServiceSchedule -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 (($service = Get-ProtectionService -Name $ServiceName) -eq $null) {
                throw 'The service does not exist'
            }  

            $node = $service.nodeId
            #Test the schedule exists
            $schedule = Invoke-Api -Operation "nodes/$node/query/hpr/services/$([uri]::EscapeDataString($ServiceName))/schedules/$scheduleName"
            if ($schedule -eq $null) {
                throw "schedule does not exist"
            }

            if ($pscmdlet.ShouldProcess($ServiceName,"Remove schedule")) {
                # Kick off the creation request
                $response = Invoke-Api -Operation "nodes/$node/query/hpr/services/$([uri]::EscapeDataString($ServiceName))/schedules/$scheduleName" -Method delete
                # Grap the id from the result
                $jobHref = $response.links.href
                # 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
                    $result = $null
                    while ($true) {
                        $result = Get-JobResult -Href $jobHref
                        if ($result -ne 'processing') {break}
                        $count -= 1
                        # If we've waited too long, just return the href
                        if ($count -eq 0) {Return $jobHref}
                    }

                    if ($result -ne 'ok'){
                        Write-Warning ($result | Out-String)
                        throw $result
                    } else {
                        $service = Get-ProtectionService -Name $ServiceName
                        return $service
                    }
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $jobHref
                }
            }          

        } catch {throw $_}
}

Function Enable-ProtectionService {
<#
.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 NsScheduledSnapshotService 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-ProtectionService 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 (($service = Get-ProtectionService -Name $Name) -eq $null) {
            throw 'The service does not exist'
        }    

        if ($pscmdlet.ShouldProcess($Name,"Enable service")) {
            $node = $service.nodeId
            # Kick off the creation request
            $response = Invoke-Api -Operation "nodes/$node/query/hpr/services/$([uri]::EscapeDataString($Name))/enable" -Request "{}" 
            # Grap the id from the result
            $jobHref = $response.links.href
            # 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
                $result = $null
                while ($true) {
                    $result = Get-JobResult -Href $jobHref
                    if ($result -ne 'processing') {break}
                    $count -= 1
                    # If we've waited too long, just return the href
                    if ($count -eq 0) {Return $jobHref}
                }

                if ($result -ne 'ok'){
                    Write-Warning ($result | Out-String)
                    throw $result
                } else {
                    $service = Get-ProtectionService -Name $Name
                    return $service
                }
            } else {
                # Not been asked to wait so just return the jobid
                Return $jobHref
            }
        }
    }
}

Function Disable-ProtectionService {
<#
.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 NsScheduledSnapshotService 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-ProtectionService 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 (($service = Get-ProtectionService -Name $Name) -eq $null) {
            throw 'The service does not exist'

        }     

        if ($pscmdlet.ShouldProcess($Name,"Disable service")) {
            $node = $service.nodeId
            # Kick off the creation request
            $response = Invoke-Api -Operation "nodes/$node/query/hpr/services/$([uri]::EscapeDataString($Name))/disable" -Request "{}" 
            # Grap the id from the result
            $jobHref = $response.links.href
            # 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
                $result = $null
                while ($true) {
                    $result = Get-JobResult -Href $jobHref
                    if ($result -ne 'processing') {break}
                    $count -= 1
                    # If we've waited too long, just return the href
                    if ($count -eq 0) {Return $jobHref}
                }

                if ($result -ne 'ok'){
                    Write-Warning ($result | Out-String)
                    throw $result
                } else {
                    $service = Get-ProtectionService -Name $Name
                    return $service
                }
            } else {
                # Not been asked to wait so just return the jobid
                Return $jobHref
            }
        }
    }
}

Function Get-ProtectionServiceSnapshot {
<#
.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-ProtectionServiceSnapshot -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-ProtectionServiceSnapshot -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 = "appliances/APPID/snapshots?limit=300"
            if ($Name) {
                $operation = "$operation&name=$Name"
            }
            if ($Service) {
                $HprService = Get-ProtectionService -Name $Service
                $operation = "$operation&hprService=$($HprService.serviceId)"
            }
            if ($Path) {
                $operation = "$operation&parent=$([uri]::EscapeDataString($Path))"
            }

            Write-Verbose $operation
            # Get the required filesystems
            $snapshots = Invoke-Api -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-Api -Operation $operation
                $snapshotsData += $snapshots.data
            }
            $returnValue =  $snapshotsData
            $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.Snaphot')}
            Return $returnValue
        } catch {
            throw $_
        }
    }
}

Function Remove-ProtectionServiceSnapshot {
    [cmdletbinding(   
        ConfirmImpact = 'low',
        SupportsShouldProcess   
    )]
    Param (
        [Parameter(Position = 0,
                    Mandatory=$true,
                    ValueFromPipelineByPropertyName,
                    HelpMessage='The name of the snapshot to get')]
            [ValidatePattern("^(?:\w|-|_)+$")]
            [string]$Name,
        [Parameter(ValueFromPipelineByPropertyName,
                    DontShow,
                    HelpMessage='The full path to the snapshot to delete')]
            [ValidatePattern("^[\w\-\:\.]+(|(\/[\w\-\:\.@]+)){1,}$")]
            [string]$Path,
        [Parameter(ValueFromPipelineByPropertyName,
                    DontShow,
                    HelpMessage='id of node running pool')]
            [ValidatePattern("^\w+$")]
            [string]$NodeId

    )
    process {
        
        #Do we have a node id and path? if not we need to get the service and set them.
        if ((-Not $NodeId) -or (-Not $Path)) {
            try {
                $snapshot = Get-ProtectionServiceSnapshot -Name $Name
                $NodeId = $snapshot.nodeId
                $Path = $snapshot.path
            } catch {
                throw 'Unable to retrive snapshot'
            }
        }

        try {
            if ($pscmdlet.ShouldProcess($Name,"Remove snapshot")) {
                $node = $NodeId
                # Kick off the creation request
                $response = Invoke-Api -Operation "nodes/$node/query/storage/snapshots/$([uri]::EscapeDataString($Path))" -Method delete
                # Grap the id from the result
                $jobHref = $response.links.href
                # 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
                    $result = $null
                    while ($true) {
                        $result = Get-JobResult -Href $jobHref
                        if ($result -ne 'processing') {break}
                        $count -= 1
                        # If we've waited too long, just return the href
                        if ($count -eq 0) {Return $jobHref}
                    }

                    if ($result -ne 'ok'){
                        Write-Warning ($result | Out-String)
                        throw $result
                    } else {
                        return $true
                    }
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $jobHref
                }
            }
            
        } catch {throw $_}
    }
}

Function Get-UserQuota {
<#
.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-UserQuota -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 get a quota for')]
            [ValidatePattern("^([\w.]+|S(-\d+)+)$")]
            [alias('sid')]
            [string]$User
    )

    Process {
        Try {
            #Test filesystem exists
            $filesystem = $Path | Get-Filesystem
            if ($filesystem -eq $null) {
                throw 'Filesystem does not exists'
            }
            $node = $filesystem.nodeId
            if ($User) {
                $operation = "nodes/$node/query/storage/filesystems/$([uri]::EscapeDataString($Path))?fields=userquota%40$User"
                $response = Invoke-Api -Operation $Operation
                $returnValue = [pscustomobject]@{User=$User;Quota=$response."userquota@$User"}
                $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.UsQuota')}
            } else {
                $results = Get-Filesystem -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 Set-UserQuota {
<#
.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-UserQuota -Path p1/top1 -User "6565" -Quota 1Gb
    True
 
    Sets a quota for a unix uid
.EXAMPLE
    PS C:\> Set-UserQuota -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-UserQuota 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
            $filesystem = $Path | Get-Filesystem
            if ($filesystem -eq $null) {
                throw 'Filesystem does not exists'
            }
            $node = $filesystem.nodeId
            $QuotaBytes = $Quota /1
            $request = @{}
            $request.add("userquota@$User",$QuotaBytes)
            if ($pscmdlet.ShouldProcess($Path,"Set quota")) {
                # Kick off the creation request
                $response = Invoke-Api -Operation "nodes/$node/query/storage/filesystems/$([uri]::EscapeDataString($Path))" -Request ($request | ConvertTo-Json) -Method put
                # Grap the id from the result
               # Grap the id from the result
                $jobHref = $response.links.href
                # 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
                    $result = $null
                    while ($true) {
                        $result = Get-JobResult -Href $jobHref
                        if ($result -ne 'processing') {break}
                        $count -= 1
                        # If we've waited too long, just return the href
                        if ($count -eq 0) {Return $jobHref}
                    }

                    if ($result -ne 'ok'){
                        Write-Warning ($result | Out-String)
                        throw $result
                    } else {
                        Return Get-UserQuota -Path $Path -User $User
                    }
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $jobHref
                }
            }
        } catch {
            throw $_
        }
    }
}

Function Set-FilesystemOwner {
<#
.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-FilesystemOwner p1/nfstest -User 6465
    True
 
    Sets the user with the uid 6564 as the owner of the filesyste,
.EXAMPLE
    PS C:\temp> Set-FilesystemOwner 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
            $filesystem = $Path | Get-Filesystem
            if ($filesystem -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
                $node = $filesystem.nodeId
                $response = Invoke-Api -Operation "nodes/$node/query/storage/filesystems/$([uri]::EscapeDataString($Path))/setOwner" -Request ($request | ConvertTo-Json)
                # Grap the id from the result
                $jobHref = $response.links.href
                # 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
                    $result = $null
                    while ($true) {
                        $result = Get-JobResult -Href $jobHref
                        if ($result -ne 'processing') {break}
                        $count -= 1
                        # If we've waited too long, just return the href
                        if ($count -eq 0) {Return $jobHref}
                    }

                    if ($result -ne 'ok'){
                        Write-Warning ($result | Out-String)
                        throw $result
                    } else {
                        Return $true
                    }
                } else {
                    # Not been asked to wait so just return the jobid
                    Return $jobHref
                }
            }
        } catch {
            throw $_
        }
    }
}