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 $_ } } } |