NexentaStor.psm1
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 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="API Port of the NexentaStor controller to connect to - Defaults to 8443")] [ValidateNotNullOrEmpty()] [int]$Port = 8443, [Parameter(Mandatory=$true, Position=3, ParameterSetName='Cred', ValueFromPipelineByPropertyName=$true, HelpMessage="A credential object containing the username and password used to connect to the NexentaStor API")] [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential, [Parameter(Mandatory=$false, Position=4, ParameterSetName='Cred', ValueFromPipelineByPropertyName=$true, HelpMessage="Include if you wish to save the credentials as a secure string for later recall")] [switch]$Save ) #Read in or save credentials $secrets = "$($Env:APPDATA)\NexentaStor" if ($PSCmdlet.ParameterSetName -eq 'Cred') { if ($Save) { if (-Not (Test-Path $secrets)) {New-Item -ItemType Directory -Path $secrets | out-null} $detail = @{ user = $Credential.UserName pass = $Credential.Password | ConvertFrom-SecureString } $detail | ConvertTo-Json | Out-File "$secrets\$($Name.ToLower()).sec" } } else { if (Test-Path "$Secrets\$($Name.ToLower()).sec") { $detail = Get-Content "$secrets\$($Name.ToLower()).sec" | ConvertFrom-Json $Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $detail.user, ($detail.pass | ConvertTo-SecureString) } else { throw "Could not read stored Credential from $Sscrets\$($Name.ToLower()).sec" } } #Set type for ingnoring ssl cert errors add-type @" using System.Net; using System.Security.Cryptography.X509Certificates; public class IDontCarePolicy : ICertificatePolicy { public IDontCarePolicy() {} public bool CheckValidationResult( ServicePoint sPoint, X509Certificate cert, WebRequest wRequest, int certProb) { return true; } } "@ # Connect to NexentaStore and save script wide variable try { # Clean out any old variables Remove-Variable nsController -Scope script -ErrorAction SilentlyContinue # Disable SSL checks $CertificatePolicy = [System.Net.ServicePointManager]::CertificatePolicy [System.Net.ServicePointManager]::CertificatePolicy = new-object IDontCarePolicy #Set tls $tlsBackup = [Net.ServicePointManager]::SecurityProtocol [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 #Build base url $baseUrl = "https://$($Name):$Port" #Get Token $requestUrl = "$baseUrl/auth/login" $login = @{ "password" = $Credential.GetNetworkCredential().password "username" = $Credential.UserName } $response = Invoke-RestMethod -Uri $requestUrl -Method Post -Body ($login | ConvertTo-Json) -ContentType "application/json" -Verbose:$false #Write connection details to script wide variable to ruse by other functions $script:nsController = [pscustomobject]@{name=$Name;port=$Port;baseUrl=$baseUrl;user=$Credential.UserName;token=$response.token} $script:nsController.psobject.TypeNames.Insert(0,'Ns.Controller') Write-Verbose ($script:nsController | fl | Out-String) Return $script:nsController } catch { if ($_.response.message -eq 'Bad credentials') { throw "Invalid Nexenta Credentials" } else { throw $_ } } finally { [System.Net.ServicePointManager]::CertificatePolicy = $CertificatePolicy [Net.ServicePointManager]::SecurityProtocol = $tlsBackup Remove-Variable login } } Function Invoke-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 ) #Define a hash of valid operations and their associated default action $OperationSet = @{} $OperationSet.add('auth/status','get') $OperationSet.add('storage/pools','get') $OperationSet.add('storage/filesystems','get') $OperationSet.add('jobStatus','get') $OperationSet.add('nas/smb','get') $OperationSet.add('nas/nfs','get') $OperationSet.add('hpr/services','get') $OperationSet.add('storage/snapshots','get') Write-Verbose $PSCmdlet.ParameterSetName #Validate Operation $OperationArray = ($Operation -split '\?')[0] -split '/' for ($x = $OperationArray.Count; $x -ge 1 ; $x -= 1) { #hunt map hash for closest match chomping the last operation each time $key = ($OperationArray | select -First $x) -join "/" if ($OperationSet.ContainsKey($key.ToLower())) { if (-Not $Method) { $Method = $OperationSet[$key] } break } if ($x -eq 1) { throw 'Operation could not be found in OperationSet' } } #Override default OpSet if a request is included if ($PSCmdlet.ParameterSetName -eq 'POST') { if ($Method -eq 'get') { $Method = 'post' } #Validate Json try { $Request | ConvertFrom-Json -ErrorAction stop | Out-Null } catch { throw 'Request is not a valid Json string' } } #Check we are connected to a controller If (-Not $Script:nsController) { throw 'Please connect to NexentaStor controller' } #Set type for ignoring ssl cert errors add-type @' using System.Net; using System.Security.Cryptography.X509Certificates; public class IDontCarePolicy : ICertificatePolicy { public IDontCarePolicy() {} public bool CheckValidationResult( ServicePoint sPoint, X509Certificate cert, WebRequest wRequest, int certProb) { return true; } } '@ try { # Disable SSL checks $CertificatePolicy = [System.Net.ServicePointManager]::CertificatePolicy [System.Net.ServicePointManager]::CertificatePolicy = new-object IDontCarePolicy #Set tls $tlsBackup = [Net.ServicePointManager]::SecurityProtocol [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 #Build request $baseUrl = $Script:nsController.baseUrl $token = $Script:nsController.token $requestUrl = "$baseUrl/$Operation" Write-Verbose $requestUrl Write-Verbose $Method $Header = @{'Authorization' = "Bearer $token";'Content-Type' = 'application/json'} # Make the request switch ($method) { {$_ -eq 'post' -or $_ -eq 'put'} { Write-Verbose ($Request | Out-String) $response = Invoke-RestMethod -Uri $requestUrl -Method $Method -Body $Request -Headers $Header -Verbose:$false -ErrorVariable RestError } default { $response = Invoke-RestMethod -Uri $requestUrl -Method $Method -Headers $Header -Verbose:$false -ErrorVariable RestError } } # Return the response Return $response } catch { Write-Verbose $_.Exception.Response.StatusCode Write-Verbose $_.Exception.Response.StatusCode.value__ Switch ($_.Exception.Response.StatusCode.value__) { 401 { # Unauthorized # Token timeout, try reconnecting. Write-Verbose "We are not connected to a NexentaStor controller..trying to reconnect" try { # Try reconnecting and resubmitting the request. Write-Verbose "Trying to reconnect to $($Script:nsController.name):$($Script:nsController.port)" Connect-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 } 400 { throw ($RestError.Message | ConvertFrom-Json).message } default { # Not an error we know how to handle, pass it up the stack throw $_ } } } finally { [System.Net.ServicePointManager]::CertificatePolicy = $CertificatePolicy [Net.ServicePointManager]::SecurityProtocol = $tlsBackup } } Function Get-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 "storage/pools?poolName=$([uri]::EscapeDataString($Name))" $returnValue = $pools.data } else { $pools = Invoke-Api -Operation "storage/pools" # Are we returning a filter view or all the pools? if ($All) { $returnValue = $pools.data } else { $returnValue = $pools.data | ? {$_.poolName -ne 'rpool'} } } $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.Pool')} Return $returnValue } catch { throw $_ } } Function Get-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 = "storage/filesystems?limit=300&path=$([uri]::EscapeDataString($Path))" } else { # using parent causes all sub filesystems to be returned. $operation = "storage/filesystems?limit=300&parent=$([uri]::EscapeDataString($Path))&recursive=$Recurse" } } 'none' { $operation = "storage/filesystems?limit=300" } } Write-Verbose $operation # Get the required filesystems $filesystems = Invoke-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 = "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 Get-Jobs { Param ( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)] [ValidateNotNullOrEmpty()] [ValidatePattern("^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$")] [string]$JobId ) Process { try { # If JobId is not given, we get all jobs if ($JobId) {$jobQuery="?jobId=$JobId"} $jobs = Invoke-Api -Operation "jobStatus$jobQuery" $jobs.data } catch { throw $_ } } } Function Get-JobResult { Param ( [Parameter(Mandatory=$true, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)] [ValidateNotNullOrEmpty()] [ValidatePattern("^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$")] [string]$JobId ) Process { try { $jobs = Invoke-Api -Operation "jobStatus/$JobId" return $true } 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 GUID of the creation task If the cmdlet waits for more then 30sec it will return the GUID of the task. .PARAMETER Path <String> The path for the filesystem to create .PARAMETER ReferencedQuota <String> The data quota (in [int]Mb/[int]Gb/[int]Tb) for the filesystem, .PARAMETER Quota <String> The hard (data+desendents) quota (in [int]Mb/[int]Gb/[int]Tb) for the filesystem, .PARAMETER CustomProperties <[String[]]> An Attray of strings to set as custom attributes for the filesystem in the format AttributeName:AttributeValue Eg "custatt1:yes","custatt2:Tuesday" .PARAMETER NoWait Invoke to make the cmdlet start the job and return the GUID of the JobID .EXAMPLE PS C:\> New-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")) { # Kick off the creation request $response = Invoke-Api -Operation "storage/filesystems" -Request ($request | ConvertTo-Json) # Grap the id from the result $JobId = $response.links.href | Split-Path -Leaf # If we've been told to wait for completion... if (-Not $NoWait) { # Limit ourselves to 30 sec before returning filesystem $count = 10 #Get job and check status while ((Get-Jobs -JobId $JobId).done -ne $true) { Write-Verbose "Filesystem not ready yet - sleeping" sleep 5 $count -= 1 # If we've waited too long, just return the jobid if ($count -eq 0) {Return $JobId} } # Check result Get-JobResult $JobId | Out-Null # Filesystem has been created so go get the details. Return Get-Filesystem -Path $Path -Detailed } else { # Not been asked to wait so just return the jobid Return $JobId } } } 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 PS C:\> Set-FileSystem -Path p2/top2 -ReferencedQuota 10Gb 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 filesystem exists if (($Path | Get-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,"Update Filesystem")) { # Kick off the creation request $request | ConvertTo-Json $response = Invoke-Api -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))" -Request ($request | ConvertTo-Json) -Method put # Grap the id from the result $JobId = $response.links.href | Split-Path -Leaf # If we've been told to wait for completion... if (-Not $NoWait) { # Limit ourselves to 30 sec before returning filesystem $count = 10 #Get job and check status while ((Get-Jobs -JobId $JobId).done -ne $true) { Write-Verbose "Filesystem not ready yet - sleeping" sleep 5 $count -= 1 # If we've waited too long, just return the jobid if ($count -eq 0) {Return $JobId} } # Check result Get-JobResult $JobId | Out-Null # Filesystem has been created so go get the details. Return Get-Filesystem -Path $Path -Detailed } else { # Not been asked to wait so just return the jobid Return $JobId } } } 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 if (($Path | Get-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 $response = Invoke-Api -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))/setOwner" -Request ($request | ConvertTo-Json) # Grap the id from the result $JobId = $response.links.href | Split-Path -Leaf # If we've been told to wait for completion... if (-Not $NoWait) { # Limit ourselves to 30 sec before returning filesystem $count = 10 #Get job and check status while ((Get-Jobs -JobId $JobId).done -ne $true) { Write-Verbose "Owner not ready yet - sleeping" sleep 5 $count -= 1 # If we've waited too long, just return the jobid if ($count -eq 0) {Return $JobId} } # Check result Get-JobResult $JobId | Out-Null # Filesystem has been created so go get the details. Return $true } else { # Not been asked to wait so just return the jobid Return $JobId } } } catch { throw $_ } } } Function Remove-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 if (($Path | Get-Filesystem) -eq $null) { throw 'Filesystem does not exists' } try { if ($pscmdlet.ShouldProcess($Path,"Remove Filesystem")) { $Operation = "storage/filesystems/$([uri]::EscapeDataString($Path))?force=$(-Not $NoForce)&snapshots=$(-Not $NoSnapshots)" Write-Verbose $Operation # Kick off the deletion request $response = Invoke-Api -Operation $Operation -Method 'delete' # Grap the id from the result $JobId = $response.links.href | Split-Path -Leaf # If we've been told to wait for completion... if (-Not $NoWait) { # Limit ourselves to 30 sec before returning filesystem $count = 10 #Get job and check status while ((Get-Jobs -JobId $JobId).done -ne $true) { Write-Verbose "Filesystem not deleted yet - sleeping" sleep 5 $count -= 1 # If we've waited too long, just return the jobid if ($count -eq 0) {Return $JobId} } # Check result Get-JobResult $JobId | Out-Null # Filesystem has been deleted so go get the details. Return $true } else { # Not been asked to wait so just return the jobid Return $JobId } } }catch { throw $_ } } } Function Get-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 = "nas/smb?limit=300&shareName=$([uri]::EscapeDataString($ShareName))" } 'ByPath' { $operation = "nas/smb?limit=300&filesystem=$([uri]::EscapeDataString($Path))" } 'none' { $operation = "nas/smb?limit=300" } } Write-Verbose $operation # Get the required filesystems $shares = Invoke-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 if (($Path | Get-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")) { # Kick off the creation request Invoke-Api -Operation "nas/smb" -Request ($request | ConvertTo-Json) | Out-Null 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 if ((Get-SmbShare -Path $Path) -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")) { # Kick off the update request Invoke-Api -Operation "nas/smb/$([uri]::EscapeDataString($Path))" -Request ($request | ConvertTo-Json) -Method 'put' | Out-Null 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 if ((Get-SmbShare -Path $Path) -eq $null) { throw 'The filesystem is not shared' } try { if ($pscmdlet.ShouldProcess($Path,"Remove Share")) { # Kick off the update request Invoke-Api -Operation "nas/smb/$([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 { try { $response = Invoke-Api -Operation "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 { #Remove any duplicate flags $Flags = $Flags | select -Unique #Remove and Duplicate permissions $Permissions = $Permissions | select -Unique #Build request $request = @{} if ($Flags -ne 'none') { $request.add('flags',$Flags) } else { $request.add('flags',@()) } $request.add('permissions',$Permissions) $request.add('principal',$Principal) $request.add('type',$Type) if (-Not $Replace) {$request.add('index',-1)} try { if ($pscmdlet.ShouldProcess($Path,"Add ACL")) { # Kick off the update request $response = Invoke-Api -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))/acl" -Request ($request | ConvertTo-Json) # Grap the id from the result $JobId = $response.links.href | Split-Path -Leaf # If we've been told to wait for compleation... if (-Not $NoWait) { # Limit ourselves to 30 sec before returning filesystem $count = 10 #Get job and check status while ((Get-Jobs -JobId $JobId).done -ne $true) { Write-Verbose "acl not ready yet - sleeping" sleep 5 $count -= 1 # If we've waited too long, just return the jobid if ($count -eq 0) {Return $JobId} } # Check result Get-JobResult $JobId | Out-Null # Filesystem has been updated so go get the new acls. Return Get-FilesystemAcl -Path $Path } else { # Not been asked to wait so just return the jobid Return $JobId } $response } else {Write-Verbose ($request | ConvertTo-Json)} } catch { throw $_ } } } Function Set-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 { #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")) { # Kick off the update request $response = Invoke-Api -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))/acl/$Index" -Request ($request | ConvertTo-Json) -Method put # Grap the id from the result $JobId = $response.links.href | Split-Path -Leaf # If we've been told to wait for completion... if (-Not $NoWait) { # Limit ourselves to 30 sec before returning filesystem $count = 10 #Get job and check status while ((Get-Jobs -JobId $JobId).done -ne $true) { Write-Verbose "acl not ready yet - sleeping" sleep 5 $count -= 1 # If we've waited too long, just return the jobid if ($count -eq 0) {Return $JobId} } # Check result Get-JobResult $JobId | Out-Null # Filesystem has been updated so go get the new acls. Return Get-FilesystemAcl -Path $Path } else { # Not been asked to wait so just return the jobid Return $JobId } $response } else {Write-Verbose ($request | ConvertTo-Json)} } catch { throw $_ } } } Function Remove-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 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")) { # Kick off the update request $response = Invoke-Api -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))/acl/$Index" -Method delete # Grap the id from the result $JobId = $response.links.href | Split-Path -Leaf # If we've been told to wait for completion... if (-Not $NoWait) { # Limit ourselves to 30 sec before returning filesystem $count = 10 #Get job and check status while ((Get-Jobs -JobId $JobId).done -ne $true) { Write-Verbose "acl not ready yet - sleeping" sleep 5 $count -= 1 # If we've waited too long, just return the jobid if ($count -eq 0) {Return $JobId} } # Check result Get-JobResult $JobId | Out-Null # Filesystem has been updated so go get the new acls. Return Get-FilesystemAcl -Path $Path } else { # Not been asked to wait so just return the jobid Return $JobId } $response } } 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 ot more NFS Shares .DESCRIPTION Get information about one ot more NFS Shares .OUTPUTS If nothing is specified, all NFS shares are returned. If a path is specified, the NFS share for that filesystem is retrieved. .PARAMETER Path <String> The path to the filesystem .EXAMPLE PS C:\> Get-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(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName='ByPath', HelpMessage='The path to the filesystem')] [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")] [string]$Path ) Process { try { # Set operation based on input switch ($PSCmdlet.ParameterSetName) { 'ByPath' { $operation = "nas/nfs?limit=300&filesystem=$([uri]::EscapeDataString($Path))" } 'none' { $operation = "nas/nfs?limit=300" } } Write-Verbose $operation # Get the required filesystems $shares = Invoke-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 $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 .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')] [ValidateSet('none','sys','dh','krb5','krb5i','krb5p')] [string[]]$SecurityModes = 'sys', [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Entities allowed access as root - Allowed values = FQDN,IP, Subnets in the format "@CIDR", Domains in the format "domain:domain.name"')] [ValidatePattern("((?=^.{1,254}$)(^(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(?=^.{1,254}$)(^domain:(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)|(^@\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d\d$))")] [string[]]$Root, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Entities allowed access to read/write - Allowed values = FQDN,IP, Subnets in the format "@CIDR", Domains in the format "domain:domain.name"')] [ValidatePattern("((?=^.{1,254}$)(^(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(?=^.{1,254}$)(^domain:(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)|(^@\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d\d$))")] [string[]]$ReadWrite, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Entities allowed access to read only - Allowed values = FQDN,IP, Subnets in the format "@CIDR", Domains in the format "domain:domain.name"')] [ValidatePattern("((?=^.{1,254}$)(^(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(?=^.{1,254}$)(^domain:(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)|(^@\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d\d$))")] [string[]]$ReadOnly, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Entities denied access - Allowed values = FQDN,IP, Subnets in the format "@CIDR", Domains in the format "domain:domain.name"')] [ValidatePattern("((?=^.{1,254}$)(^(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(?=^.{1,254}$)(^domain:(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)|(^@\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d\d$))")] [string[]]$NoAccess, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Effective user name for unknown users - Default is -1 disabled')] [ValidatePattern("^(\w+|-1)$")] [string]$Anonymous = '-1', [Parameter(ValueFromPipelineByPropertyName, HelpMessage='User identifier to remap root UID to')] [ValidatePattern("^\w+$")] [string]$RootMap, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Normally, if a server exports two filesystems one of which is mounted on the other, then the client will have to mount both filesystems explicitly to get access to them. If it just mounts the parent, it will see an empty directory at the place where the other filesystem is mounted. That filesystem is "hidden". Setting the nohide option on a filesystem causes it not to be hidden, and an appropriately authorised client will be able to move from the parent to that filesystem without noticing the change. However, some NFS clients do not cope well with this situation as, for instance, it is then possible for two files in the one apparent filesystem to have the same inode number. The nohide option is currently only effective on single host exports. It does not work reliably with netgroup, subnet, or wildcard exports.')] [switch]$NoHide, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Apply share properties to nested folders')] [switch]$Recursive ) #Test filesystem exists if (($Path | Get-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")) { # Kick off the creation request Invoke-Api -Operation "nas/nfs" -Request ($request | ConvertTo-Json -Depth 4) | Out-Null 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')] [ValidateSet('none','sys','dh','krb5','krb5i','krb5p')] [string[]]$SecurityModes, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Entities allowed access as root - Allowed values = FQDN,IP, FDQSubnets in the format "@CIDR", Domains in the format "domain:domain.name"')] [ValidatePattern("((?=^.{1,254}$)(^(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(?=^.{1,254}$)(^domain:(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)|(^@\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d\d$))")] [string[]]$Root, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Entities allowed access to read/write - Allowed values = FQDN,IP, FDQSubnets in the format "@CIDR", Domains in the format "domain:domain.name"')] [ValidatePattern("((?=^.{1,254}$)(^(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(?=^.{1,254}$)(^domain:(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)|(^@\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d\d$))")] [string[]]$ReadWrite, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Entities allowed access to read only - Allowed values = FQDN,IP, Subnets in the format "@CIDR", Domains in the format "domain:domain.name"')] [ValidatePattern("((?=^.{1,254}$)(^(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(?=^.{1,254}$)(^domain:(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)|(^@\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d\d$))")] [string[]]$ReadOnly, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Entities denied access - Allowed values = FQDN,IP, Subnets in the format "@CIDR", Domains in the format "domain:domain.name"')] [ValidatePattern("((?=^.{1,254}$)(^(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(?=^.{1,254}$)(^domain:(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)|(^@\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d\d$))")] [string[]]$NoAccess, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Effective user name for unknown users - Default is -1 disabled')] [ValidatePattern("^(\w+|-1)$")] [string]$Anonymous, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='User identifier to remap root UID to')] [ValidatePattern("^\w+$")] [string]$RootMap, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Normally, if a server exports two filesystems one of which is mounted on the other, then the client will have to mount both filesystems explicitly to get access to them. If it just mounts the parent, it will see an empty directory at the place where the other filesystem is mounted. That filesystem is "hidden". Setting the nohide option on a filesystem causes it not to be hidden, and an appropriately authorised client will be able to move from the parent to that filesystem without noticing the change. However, some NFS clients do not cope well with this situation as, for instance, it is then possible for two files in the one apparent filesystem to have the same inode number. The nohide option is currently only effective on single host exports. It does not work reliably with netgroup, subnet, or wildcard exports.')] [switch]$NoHide, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Apply share properties to nested folders')] [switch]$Recursive ) #Test filesystem exists if (($Path | Get-Filesystem) -eq $null) { throw 'Filesystem does not exists' } #Test the filesystem is already shared if ((Get-NfsShare -Path $Path) -eq $null) { throw 'The filesystem is not shared' } #Get existing settings $currentNfs = Get-NfsShare -Path $Path #Build request $request = @{} if ($Anonymous) {$request.add('anon',$Anonymous)} else {$request.add('anon',$currentNfs.anon)} if ($Nohide.IsPresent) {$request.add('nohide',$Nohide.IsPresent)} else {$request.add('nohide',$currentNfs.nohide)} $request.add('recursive',$Recursive.IsPresent) if ($RootMap) {$request.add('rootMapping',$RootMap)} else {if ($currentNfs.rootMapping) {$request.add('rootMapping',$currentNfs.rootMapping)}} #Now do securityContexts $securityContextsHash = @{} if ($SecurityModes) {$securityContextsHash.Add('securityModes',$SecurityModes)} $rootAcl = @($Root | % {ConvertTo-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")) { # Kick off the creation request Invoke-Api -Operation "nas/nfs/$([uri]::EscapeDataString($Path))" -Request ($request | ConvertTo-Json -Depth 4) -Method put | Out-Null 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, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='force all nested filesystems also to be unshared')] [switch]$Recursive ) Process { #Test the filesystem is shared if ((Get-NfsShare -Path $Path) -eq $null) { throw 'The filesystem is not shared' } try { if ($pscmdlet.ShouldProcess($Path,"Remove Share")) { # Kick off the update request Invoke-Api -Operation "nas/nfs/$([uri]::EscapeDataString($Path))?recursive=$($Recursive.IsPresent)" -Method 'delete' | Out-Null Return $true } } catch { throw $_ } } } Function Get-UserUtilization { <# .SYNOPSIS Retrieves the user userspace Utilization for a filesystem .DESCRIPTION Retrieves the user userspace Utilization for a filesystem .OUTPUTS If no user is specified, all userspaceUtilization objects for the filesystem Otherwise the userspaceUtilization object for the requested user on the filesystem .PARAMETER Path <String> The path to the filesystem to query .PARAMETER User <String> The username to return .EXAMPLE PS C:\> Get-UserUtilization p1/top6 | ft name type class quota used ---- ---- ----- ----- ---- root user POSIX 0 11776 user1@domain user SMB 0 955878912 Get all user userspace Utilization for a filesystem .EXAMPLE PS C:\> "user1@domain" | Get-UserUtilization p1/top6 name : user1@domain type : user class : SMB quota : 0 used : 955878912 Get a specific user userspace Utilization for a filesystem #> Param ( [Parameter(Mandatory=$true, Position = 0, ValueFromPipelineByPropertyName, HelpMessage='The path to the filesystem to query')] [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")] [alias('filesystem')] [string]$Path, [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, HelpMessage='Username to retrieve utilization data for')] [ValidateNotNullOrEmpty()] [string]$User ) Begin { Try { #Test filesystem exists if (($Path | Get-Filesystem) -eq $null) { throw 'Filesystem does not exists' } # We cant query for a specific user so do one big get and filter the results. # Kick off the update request $response = Invoke-Api -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))/userspaceUtilization" -Request "{}" # Grap the id from the result $JobId = $response.links.href | Split-Path -Leaf # Limit ourselves to 30 sec before returning filesystem $count = 10 #Get job and check status while ((Get-Jobs -JobId $JobId).done -ne $true) { Write-Verbose "query not ready yet - sleeping" sleep 5 $count -= 1 # If we've waited too long, just return the jobid if ($count -eq 0) {Return $JobId} } # Check result Get-JobResult $JobId | Out-Null # Filesystem has been queried so get the results. $response = Invoke-Api -Operation "jobStatus/$JobId" } catch {throw $_} } Process { if (-not $User) { $returnValue = $response.userspace | ? {$_.type -eq 'user'} } else { $returnValue = $response.userspace | ? {($_.name -eq $User) -and ($_.type -eq 'user')} } $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.UsUtil')} return $returnValue } } Function Get-GroupUtilization { <# .SYNOPSIS Retrieves the group userspace Utilization for a filesystem .DESCRIPTION Retrieves the group userspace Utilization for a filesystem .OUTPUTS If no group is specified, all userspaceUtilization objects for the filesystem Otherwise the userspaceUtilization object for the requested group on the filesystem .PARAMETER Path <String> The path to the filesystem to query .PARAMETER Group <String> The username to return .EXAMPLE PS C:\> Get-GroupUtilization p1/top6 | ft name type class quota used ---- ---- ----- ----- ---- root group POSIX 0 11776 Domain Users@domin group SMB 0 955878912 .EXAMPLE PS C:\> Get-GroupUtilization -Path p1/top6 -Group "Domain Users@domain" name : Domain Users@domain type : group class : SMB quota : 0 used : 955878912 #> Param ( [Parameter(Mandatory=$true, Position = 0, ValueFromPipelineByPropertyName, HelpMessage='The path to the filesystem to query')] [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")] [alias('filesystem')] [string]$Path, [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, HelpMessage='Username to retrive utilization data for')] [ValidateNotNullOrEmpty()] [string]$Group ) Begin { Try { #Test filesystem exists if (($Path | Get-Filesystem) -eq $null) { throw 'Filesystem does not exists' } # We cant query for a specific user so do one big get and filter the results. # Kick off the update request $response = Invoke-Api -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))/userspaceUtilization" -Request "{}" # Grap the id from the result $JobId = $response.links.href | Split-Path -Leaf # Limit ourselves to 30 sec before returning filesystem $count = 10 #Get job and check status while ((Get-Jobs -JobId $JobId).done -ne $true) { Write-Verbose "query not ready yet - sleeping" sleep 5 $count -= 1 # If we've waited too long, just return the jobid if ($count -eq 0) {Return $JobId} } # Filesystem has been queried so get the results. $response = Invoke-Api -Operation "jobStatus/$JobId" } catch {throw $_} } Process { if (-not $Group) { $returnValue = $response.userspace | ? {$_.type -eq 'group'} } else { $returnValue = $response.userspace | ? {($_.name -eq $Group) -and ($_.type -eq 'group')} } $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.UsUtil')} return $returnValue } } Function Get-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 set a quota for')] [ValidatePattern("^([\w.]+|S(-\d+)+)$")] [alias('sid')] [string]$User ) Process { Try { #Test filesystem exists if (($Path | Get-Filesystem) -eq $null) { throw 'Filesystem does not exists' } if ($User) { $operation = "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 Get-GroupQuota { <# .SYNOPSIS Retrieves group quotas for a filesystem .DESCRIPTION Retrieves group quotas for a filesystem .OUTPUTS If no group is specified, all quota objects for the filesystem Otherwise the quota object for the requested group on the filesystem .PARAMETER Path <String> The path to the filesystem to get the quota from .PARAMETER Group <String> The group to set a quota for .EXAMPLE PS C:\> Get-GroupQuota -Path p1/top1 Group Quota ----- ----- S-1-5-21-1643737065-1150890963-312552118-242249 524288000 Get all group quotas for a file system #> Param ( [Parameter(Mandatory=$true, Position = 0, ValueFromPipelineByPropertyName, HelpMessage='The path to the filesystem to set the quota on')] [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")] [string]$Path, [Parameter(Position = 1, ValueFromPipeline, ValueFromPipelineByPropertyName, HelpMessage='The group to set a quota for')] [ValidatePattern("^([\w.]+|S(-\d+)+)$")] [alias('sid')] [string]$Group ) Process { Try { #Test filesystem exists if (($Path | Get-Filesystem) -eq $null) { throw 'Filesystem does not exists' } if ($Group) { $operation = "storage/filesystems/$([uri]::EscapeDataString($Path))?fields=groupquota%40$Group" $response = Invoke-Api -Operation $Operation $returnValue = [pscustomobject]@{Group=$Group;Quota=$response."groupquota@$Group"} $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.GrQuota')} } else { $results = Get-Filesystem -Path $Path -Detailed $returnValue = $results | Get-Member | ? {$_.Name -like "groupquota@*"} | % {[pscustomobject]@{Group=($_.Name -split '@')[1];Quota=$results.($_.Name)}} $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.GrQuota')} } return $returnValue } catch { throw $_ } } } Function Set-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 if (($Path | Get-Filesystem) -eq $null) { throw 'Filesystem does not exists' } $QuotaBytes = $Quota /1 $request = @{} $request.add("userquota@$User",$QuotaBytes) if ($pscmdlet.ShouldProcess($Path,"Set quota")) { # Kick off the creation request $response = Invoke-Api -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))" -Request ($request | ConvertTo-Json) -Method put # Grap the id from the result $JobId = $response.links.href | Split-Path -Leaf # If we've been told to wait for completion... if (-Not $NoWait) { # Limit ourselves to 30 sec before returning filesystem $count = 10 #Get job and check status while ((Get-Jobs -JobId $JobId).done -ne $true) { Write-Verbose "quota not ready yet - sleeping" sleep 5 $count -= 1 # If we've waited too long, just return the jobid if ($count -eq 0) {Return $JobId} } #All done # Check result Get-JobResult $JobId | Out-Null Return $true } else { # Not been asked to wait so just return the jobid Return $JobId } } } catch { throw $_ } } } Function Set-GroupQuota { <# .SYNOPSIS Sets a group quota for the given filesystem .DESCRIPTION Sets a group quota for the given filesystem .OUTPUTS True if the quota as set correctly .PARAMETER Path <String> The path to the filesystem to set the quota on .PARAMETER Group <String> The group to set a quota for .PARAMETER Quota <String> The group quota (in [int]Mb/[int]Gb/[int]Tb) for the filesystem, .PARAMETER NoWait Invoke to make the cmdlet start the job and return the GUID of the JobID .EXAMPLE PS C:\> Set-GroupQuota -Path p1/top1 -Group "6525" -Quota 1Gb True Sets a quota for a unix gid .EXAMPLE PS C:\> Set-GroupQuota -Path p1/top1 -Group "S-1-5-21-1643337385113899993-312552118-56130" -Quota 1Gb True Sets a quota for an active directory group .EXAMPLE PS C:\> (Get-adgroup group1).sid | Set-GroupQuota p1/collab1 -Quota 500Mb True Pipe the sid of an AD group into the cmdlet #> [cmdletbinding( ConfirmImpact = 'low', SupportsShouldProcess )] Param ( [Parameter(Mandatory=$true, Position = 0, ValueFromPipelineByPropertyName, HelpMessage='The path to the filesystem to set the quota on')] [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")] [string]$Path, [Parameter(Mandatory=$true, Position = 1, ValueFromPipeline, ValueFromPipelineByPropertyName, HelpMessage='The group to set a quota for')] [ValidatePattern("^([\w.]+|S(-\d+)+)$")] [alias('sid')] [string]$Group, [Parameter(Mandatory=$true, Position=2, ValueFromPipelineByPropertyName, HelpMessage='The group quota (in [int]Mb/[int]Gb/[int]Tb) for the filesystem,')] [ValidatePattern("^\d+[MGTmgt][Bb]$")] [string]$Quota, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Makes the cmdlet wait for the creation to complete before returning true')] [switch]$NoWait ) Process { Try { #Test filesystem exists if (($Path | Get-Filesystem) -eq $null) { throw 'Filesystem does not exists' } $QuotaBytes = $Quota /1 $request = @{} $request.add("groupquota@$Group",$QuotaBytes) if ($pscmdlet.ShouldProcess($Path,"Set quota")) { # Kick off the creation request $response = Invoke-Api -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))" -Request ($request | ConvertTo-Json) -Method put # Grap the id from the result $JobId = $response.links.href | Split-Path -Leaf # If we've been told to wait for completion... if (-Not $NoWait) { # Limit ourselves to 30 sec before returning filesystem $count = 10 #Get job and check status while ((Get-Jobs -JobId $JobId).done -ne $true) { Write-Verbose "quota not ready yet - sleeping" sleep 5 $count -= 1 # If we've waited too long, just return the jobid if ($count -eq 0) {Return $JobId} } #All done # Check result Get-JobResult $JobId | Out-Null Return $true } else { # Not been asked to wait so just return the jobid Return $JobId } } } catch { throw $_ } } } Function Remove-UserQuota { <# .SYNOPSIS Remove a user quota from the given filesystem .DESCRIPTION Remove a user quota from the given filesystem .OUTPUTS True if the quota as removed correctly .PARAMETER Path <String> The path to the filesystem to remove the quota from .PARAMETER User <String> The user to remove the quota for .PARAMETER NoWait Invoke to make the cmdlet start the job and return the GUID of the JobID .EXAMPLE PS C:\> Remove-UserQuota p1/collab1 -User 6565 True Remove a quota for a unix uid #> [cmdletbinding( ConfirmImpact = 'medium', SupportsShouldProcess )] Param ( [Parameter(Mandatory=$true, Position = 0, ValueFromPipelineByPropertyName, HelpMessage='The path to the filesystem to remove the quota from')] [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")] [string]$Path, [Parameter(Mandatory=$true, Position = 1, ValueFromPipeline, ValueFromPipelineByPropertyName, HelpMessage='The user to remove a quota for')] [ValidatePattern("^([\w.]+|S(-\d+)+)$")] [alias('sid')] [string]$User, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Makes the cmdlet wait for the removal to complete before returning true')] [switch]$NoWait ) Process { Try { #Test filesystem exists if (($Path | Get-Filesystem) -eq $null) { throw 'Filesystem does not exists' } $QuotaBytes = 0 $request = @{} $request.add("userquota@$User",$QuotaBytes) if ($pscmdlet.ShouldProcess($Path,"Remove quota")) { # Kick off the creation request $response = Invoke-Api -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))" -Request ($request | ConvertTo-Json) -Method put # Grap the id from the result $JobId = $response.links.href | Split-Path -Leaf # If we've been told to wait for completion... if (-Not $NoWait) { # Limit ourselves to 30 sec before returning filesystem $count = 10 #Get job and check status while ((Get-Jobs -JobId $JobId).done -ne $true) { Write-Verbose "quota not ready yet - sleeping" sleep 5 $count -= 1 # If we've waited too long, just return the jobid if ($count -eq 0) {Return $JobId} } #All done # Check result Get-JobResult $JobId | Out-Null Return $true } else { # Not been asked to wait so just return the jobid Return $JobId } } } catch { throw $_ } } } Function Remove-GroupQuota { <# .SYNOPSIS Remove a group quota from the given filesystem .DESCRIPTION Remove a group quota from the given filesystem .OUTPUTS True if the quota as removed correctly .PARAMETER Path <String> The path to the filesystem to remove the quota from .PARAMETER Group <String> The group to remove the quota for .PARAMETER NoWait Invoke to make the cmdlet start the job and return the GUID of the JobID .EXAMPLE PS C:\> Remove-GroupQuota p1/collab1 -Group "S-1-5-21-1643737065-1150840763-312552118-242249" True Remove a quota for an active directory group #> [cmdletbinding( ConfirmImpact = 'medium', SupportsShouldProcess )] Param ( [Parameter(Mandatory=$true, Position = 0, ValueFromPipelineByPropertyName, HelpMessage='The path to the filesystem to remove the quota from')] [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")] [string]$Path, [Parameter(Mandatory=$true, Position = 1, ValueFromPipeline, ValueFromPipelineByPropertyName, HelpMessage='The group to remove a quota for')] [ValidatePattern("^([\w.]+|S(-\d+)+)$")] [alias('sid')] [string]$Group, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Makes the cmdlet wait for the removal to complete before returning true')] [switch]$NoWait ) Process { Try { #Test filesystem exists if (($Path | Get-Filesystem) -eq $null) { throw 'Filesystem does not exists' } $QuotaBytes = 0 $request = @{} $request.add("groupquota@$Group",$QuotaBytes) if ($pscmdlet.ShouldProcess($Path,"Remove quota")) { # Kick off the creation request $response = Invoke-Api -Operation "storage/filesystems/$([uri]::EscapeDataString($Path))" -Request ($request | ConvertTo-Json) -Method put # Grap the id from the result $JobId = $response.links.href | Split-Path -Leaf # If we've been told to wait for completion... if (-Not $NoWait) { # Limit ourselves to 30 sec before returning filesystem $count = 10 #Get job and check status while ((Get-Jobs -JobId $JobId).done -ne $true) { Write-Verbose "quota not ready yet - sleeping" sleep 5 $count -= 1 # If we've waited too long, just return the jobid if ($count -eq 0) {Return $JobId} } #All done # Check result Get-JobResult $JobId | Out-Null Return $true } else { # Not been asked to wait so just return the jobid Return $JobId } } } catch { throw $_ } } } Function Get-SnapshotProtectionService { <# .SYNOPSIS Get Snaphost based Protection Services .DESCRIPTION Get Snaphost based Protection Services .OUTPUTS Nothing if no matching filesystems are found. All snaphot protection services if no criteria given Otherwise, matching snaphot protection services .PARAMETER Path <String> The path of a filesystem to retrieve services for .PARAMETER Name <String> The name of a service to retrieve .PARAMETER byPath Invoke to resolve ambiguity between piped inputs .PARAMETER byName Invoke to resolve ambiguity between piped inputs .EXAMPLE PS C:\temp> Get-Filesystem p1/collab-01 | Get-SnapshotProtectionService -ByPath | ft name id managerNodes isManager runNumber recursive sourceDataset type state isLocked ---- -- ------------ --------- --------- --------- ------------- ---- ----- -------- collab-01-hourly 6c1da6c1-05a7-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 True p1/collab-01 scheduled enabled False twsset/sdf 7bacd8d1-05a8-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 False p1/collab-01 scheduled enabled False Get all services for a filesystem .EXAMPLE PS C:\temp> Get-SnapshotProtectionService | ft name id managerNodes isManager runNumber recursive sourceDataset type state isLocked ---- -- ------------ --------- --------- --------- ------------- ---- ----- -------- p1-top2 d32e58e0-ff86-11e7-9b3d-773bc8b1c3e3 @{primary=} True 0 True p1/top2 scheduled faulted False daily 4121b941-0046-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 True p1/top2 scheduled faulted False test-hpr-ss 58f73c10-0515-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 True p1/test scheduled enabled False collab-01-hourly 6c1da6c1-05a7-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 True p1/collab-01 scheduled enabled False twsset/sdf 7bacd8d1-05a8-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 False p1/collab-01 scheduled enabled False Get all services #> [cmdletbinding(DefaultParameterSetName='none')] Param ( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'byPath', HelpMessage='The path of a filesystem to retrieve services for')] [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")] [Alias('sourceDataset')] [string]$Path, [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'byName', HelpMessage='The name of a service to retrieve')] [ValidatePattern("^(\w|-|_|\.|\/)+$")] [string]$Name, [Parameter(ParameterSetName='byPath', HelpMessage='Invoke to resolve ambiguity between piped inputs')] [switch]$ByPath, [Parameter(ParameterSetName='byName', HelpMessage='Invoke to resolve ambiguity between piped inputs')] [switch]$ByName ) Process { try { switch ($PSCmdlet.ParameterSetName) { 'ByPath' { $operation = "hpr/services?type=scheduled&sourceDataset=$([uri]::EscapeDataString($Path))" } 'ByName' { $operation = "hpr/services?type=scheduled&name=$([uri]::EscapeDataString($Name))" } 'none' { $operation = "hpr/services?type=scheduled" } } Write-Verbose $operation # Get the required filesystems $services = Invoke-Api -Operation $operation $returnValue = $services.data | ? {$_.destinationDataset -eq $null} $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.HprSnapshot')} Return $returnValue } catch {throw $_} } } Function New-SnapshotProtectionService { <# .SYNOPSIS Creates a Snaphost based Protection Service for a filesystem .DESCRIPTION Creates a Snaphost based Protection Service for a filesystem .OUTPUTS The cmdlet will wait (30sec) for the task to complete and return an NsSnapshotProtectionService object for the created service If -NoWait was invoked it will return the GUID of the creation task If the cmdlet waits for more then 30sec it will return the GUID of the task. .PARAMETER Path <String> The path of a filesystem to create the service for .PARAMETER Name <String> The name of a service to create .PARAMETER Minutes <String> A valid minutes cron entry - defaults to 0 .PARAMETER Hours <String> A valid hours cron entry - defaults to * .PARAMETER DaysOfMonth <String> A valid days of the month cron entry - defaults to * .PARAMETER Months <String> A valid months cron entry - defaults to * .PARAMETER DaysOfWeek <String> A valid days of the week cron entry - defaults to * .PARAMETER Retain <Int> The number of snapshots to retain - defaults to 1 .PARAMETER Recurse Invoke to protect all child filesystems .PARAMETER NoWait Invoke to make the cmdlet start the job and return the GUID of the JobID .EXAMPLE PS C:\temp> New-SnapshotProtectionService -Path p1/collab-02 -Name collab2 -Hours 6-18/3 -Retain 5 -Recurse name : collab2 id : 15ae3e31-05cf-11e8-9b3d-773bc8b1c3e3 managerNodes : @{primary=} isManager : True runNumber : 0 recursive : True sourceDataset : p1/collab-02 type : scheduled state : disabled isLocked : False isSyncing : False kstatId : 0000000000000006 schedules : {@{cron=* 6-18/3 * * *; scheduleName=c9a56193-8cd4-48f2-be9d-ca6d4cf5cde9; keepSource=5; disabled=False; scheduleId=15ae3e30-05cf-11e8-9b3d-773bc8b1c3e3}} isRunning : False href : /hpr/services/collab2 Create a shecduled snaphot protection service running every 3 hours between 06:00 and 18:00 keeping 5 snaphosts and including child filesystems #> [cmdletbinding( ConfirmImpact = 'low', SupportsShouldProcess )] Param ( [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName, HelpMessage='The path of the filesystem to create service for')] [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")] [Alias('sourceDataset')] [string]$Path, [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName, HelpMessage='The name of the service')] [ValidatePattern("^(\w|-|_|\.|\/)+$")] [string]$Name, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Cron pattern defining the minutes')] [ValidateNotNullOrEmpty()] [string]$Minutes = '0', [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Cron pattern defining the hours')] [ValidateNotNullOrEmpty()] [string]$Hours = '1', [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Cron pattern defining the days of the month')] [ValidateNotNullOrEmpty()] [string]$DaysOfMonth = '*', [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Cron pattern defining the months')] [ValidateNotNullOrEmpty()] [string]$Months = '*', [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Cron pattern defining the days of the week')] [string]$DaysOfWeek = '*', [Parameter(ValueFromPipelineByPropertyName, HelpMessage='The number of snaphots to retain')] [int]$Retain = 1, [Parameter(ParameterSetName='byPath', HelpMessage='Invoke to snapshot ')] [switch]$Recurse, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Makes the cmdlet wait for the creation to complete before returning a service object.')] [switch]$NoWait ) try { #Test filesystem exists if (($Path | Get-Filesystem) -eq $null) { throw 'Filesystem does not exists' } #Test service name exists if (Get-SnapshotProtectionService -Name $Name) { throw 'A service with that name already exists' } $cron = "$Minutes $Hours $DaysOfMonth $Months $DaysOfWeek" if ($cron -eq "* * * * *") { throw "[$cron] is invalid" } #Build request $request = @{} $request.add('type','scheduled') $request.add('name',$Name) $request.add('recursive',$Recurse.IsPresent) $request.add('sourceDataset',$Path) $schedule = @{} $schedule.add('cron',$cron) $schedule.add('keepSource',$Retain) $schedule.add('scheduleName',[guid]::NewGuid()) $request.add('schedules',@($schedule)) if ($pscmdlet.ShouldProcess($Path,"Create service")) { # Kick off the creation request $response = Invoke-Api -Operation "hpr/services" -Request ($request | ConvertTo-Json) # Grap the id from the result $JobId = $response.links.href | Split-Path -Leaf # If we've been told to wait for completion... if (-Not $NoWait) { # Limit ourselves to 30 sec before returning filesystem $count = 10 #Get job and check status while ((Get-Jobs -JobId $JobId).done -ne $true) { Write-Verbose "service not ready yet - sleeping" sleep 5 $count -= 1 # If we've waited too long, just return the jobid if ($count -eq 0) {Return $JobId} } # Check result Get-JobResult $JobId | Out-Null # Filesystem has been created so go get the details. $service = Get-SnapshotProtectionService -Name $Name if (-Not $service) { throw "Service creation failed - $JobId" } return $service } else { # Not been asked to wait so just return the jobid Return $JobId } } else { Write-Verbose ($request | ConvertTo-Json) } } catch {throw $_} } Function Remove-SnapshotProtectionService { <# .SYNOPSIS Removes an acl from a file system .DESCRIPTION Removes an acl from a file system by index .OUTPUTS The cmdlet will wait (30sec) for the task to complete and return the updated acls, unless -NoWait was invoked If -NoWait was invoked the cmdlet will return the GUID of the update task If the cmdlet waits for more than 30sec it will return the GUID of the task. .PARAMETER Name <String> The name of the service to remove .PARAMETER Force Invoke to delete service forcibly .PARAMETER destroySnapshots Invoke to destroy snapshots .PARAMETER NoWait Invoke to make the cmdlet start the job ad return the JobID .EXAMPLE PS C:\temp> Remove-SnapshotProtectionService collab-03 -destroySnapshots True Remove the Snapshot Protection Service called collab-03 and destroy it's snapshots .EXMAPLE PS C:\temp> Get-SnapshotProtectionService -Name collab-04 | Disable-ProtectionService | Remove-SnapshotProtectionService -destroySnapshots True Get the Snapshot Protection Service called collab-04, disable it and then remove it. #> [cmdletbinding( ConfirmImpact = 'High', SupportsShouldProcess )] Param ( [Parameter(Mandatory=$true, Position = 0, ValueFromPipelineByPropertyName, HelpMessage='The name of the service to remove')] [ValidatePattern("^(\w|-|_|\.|\/)+$")] [string]$Name, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Delete service forcibly')] [switch]$Force, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Destroy snapshots')] [switch]$destroySnapshots, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Makes the cmdlet wait for the update to complete before returning')] [switch]$NoWait ) Process { try { #Test service exists if (($service = Get-SnapshotProtectionService -Name $Name) -eq $null) { throw 'The service does not exist' } if ($service.state -eq 'enabled') { throw 'Invalid service state "enabled" (allowed state(s): "disabled", "faulted"' } if ($pscmdlet.ShouldProcess($Name,"Remove Snapshot Protection Service")) { # Kick off the update request $response = Invoke-Api -Operation "hpr/services/$([uri]::EscapeDataString($Name))?force=$($Force.IsPresent)&destroySourceSnapshots=$($destroySnapshots.IsPresent)" -Method delete # Grap the id from the result $JobId = $response.links.href | Split-Path -Leaf # If we've been told to wait for completion... if (-Not $NoWait) { # Limit ourselves to 30 sec before returning true $count = 10 #Get job and check status while ((Get-Jobs -JobId $JobId).done -ne $true) { Write-Verbose "service not removed yet - sleeping" sleep 5 $count -= 1 # If we've waited too long, just return the jobid if ($count -eq 0) {Return $JobId} } # Check result Get-JobResult $JobId | Out-Null # Service has been removed so return true. Return $true } else { # Not been asked to wait so just return the jobid Return $JobId } $response } } catch { throw $_ } } } Function Get-ReplicationProtectionService { <# .SYNOPSIS Get Snaphost based Protection Services .DESCRIPTION Get Snaphost based Protection Services .OUTPUTS Nothing if no matching filesystems are found. All snaphot protection services if no criteria given Otherwise, matching snaphot protection services .PARAMETER Path <String> The path of a filesystem to retrieve services for .PARAMETER Name <String> The name of a service to retrieve .PARAMETER byPath Invoke to resolve ambiguity between piped inputs .PARAMETER byName Invoke to resolve ambiguity between piped inputs .EXAMPLE PS C:\temp> Get-Filesystem p1/collab-01 | Get-SnapshotProtectionService -ByPath | ft name id managerNodes isManager runNumber recursive sourceDataset type state isLocked ---- -- ------------ --------- --------- --------- ------------- ---- ----- -------- collab-01-hourly 6c1da6c1-05a7-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 True p1/collab-01 scheduled enabled False twsset/sdf 7bacd8d1-05a8-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 False p1/collab-01 scheduled enabled False Get all services for a filesystem .EXAMPLE PS C:\temp> Get-SnapshotProtectionService | ft name id managerNodes isManager runNumber recursive sourceDataset type state isLocked ---- -- ------------ --------- --------- --------- ------------- ---- ----- -------- p1-top2 d32e58e0-ff86-11e7-9b3d-773bc8b1c3e3 @{primary=} True 0 True p1/top2 scheduled faulted False daily 4121b941-0046-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 True p1/top2 scheduled faulted False test-hpr-ss 58f73c10-0515-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 True p1/test scheduled enabled False collab-01-hourly 6c1da6c1-05a7-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 True p1/collab-01 scheduled enabled False twsset/sdf 7bacd8d1-05a8-11e8-9b3d-773bc8b1c3e3 @{primary=} True 0 False p1/collab-01 scheduled enabled False Get all services #> [cmdletbinding(DefaultParameterSetName='none')] Param ( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'byPath', HelpMessage='The path of a filesystem to retrieve services for')] [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")] [Alias('sourceDataset')] [string]$Path, [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'byName', HelpMessage='The name of a service to retrieve')] [ValidatePattern("^(\w|-|_|\.|\/)+$")] [string]$Name, [Parameter(ParameterSetName='byPath', HelpMessage='Invoke to resolve ambiguity between piped inputs')] [switch]$ByPath, [Parameter(ParameterSetName='byName', HelpMessage='Invoke to resolve ambiguity between piped inputs')] [switch]$ByName ) Process { try { switch ($PSCmdlet.ParameterSetName) { 'ByPath' { $operation = "hpr/services?type=scheduled&sourceDataset=$([uri]::EscapeDataString($Path))" } 'ByName' { $operation = "hpr/services?type=scheduled&name=$([uri]::EscapeDataString($Name))" } 'none' { $operation = "hpr/services?type=scheduled" } } Write-Verbose $operation # Get the required filesystems $services = Invoke-Api -Operation $operation $returnValue = $services.data | ? {$_.destinationDataset -ne $null} $returnValue | % {$_.psobject.TypeNames.Insert(0,'Ns.HprSnapshot')} Return $returnValue } 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 NsSnapshotProtectionService object for the Enabled service If -NoWait was invoked it will return the GUID of the creation task If the cmdlet waits for more then 30sec it will return the GUID of the task. .PARAMETER Name <String> The name of a service to create .PARAMETER NoWait Invoke to make the cmdlet start the job and return the GUID of the JobID .EXAMPLE PS C:\temp> Enable-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 ((Get-SnapshotProtectionService -Name $Name) -eq $null) { if ((Get-ReplicationProtectionService -Name $Name) -eq $null) { throw 'The service does not exist' } } if ($pscmdlet.ShouldProcess($Name,"Enable service")) { # Kick off the creation request $response = Invoke-Api -Operation "hpr/services/$([uri]::EscapeDataString($Name))/enable" -Request "{}" # Grap the id from the result $JobId = $response.links.href | Split-Path -Leaf # If we've been told to wait for completion... if (-Not $NoWait) { # Limit ourselves to 30 sec before returning filesystem $count = 10 #Get job and check status while ((Get-Jobs -JobId $JobId).done -ne $true) { Write-Verbose "service not ready yet - sleeping" sleep 5 $count -= 1 # If we've waited too long, just return the jobid if ($count -eq 0) {Return $JobId} } # Check result Get-JobResult $JobId | Out-Null # Service has been enabled so go get the details. $service = Get-SnapshotProtectionService -Name $Name return $service } else { # Not been asked to wait so just return the jobid Return $JobId } } } } 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 NsSnapshotProtectionService object for the Disabled service If -NoWait was invoked it will return the GUID of the creation task If the cmdlet waits for more then 30sec it will return the GUID of the task. .PARAMETER Name <String> The name of a service to create .PARAMETER NoWait Invoke to make the cmdlet start the job and return the GUID of the JobID .EXAMPLE PS C:\temp> Disable-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 ((Get-SnapshotProtectionService -Name $Name) -eq $null) { if ((Get-ReplicationProtectionService -Name $Name) -eq $null) { throw 'The service does not exist' } } if ($pscmdlet.ShouldProcess($Name,"Disable service")) { # Kick off the creation request $response = Invoke-Api -Operation "hpr/services/$([uri]::EscapeDataString($Name))/disable" -Request "{}" # Grap the id from the result $JobId = $response.links.href | Split-Path -Leaf # If we've been told to wait for completion... if (-Not $NoWait) { # Limit ourselves to 30 sec before returning filesystem $count = 10 #Get job and check status while ((Get-Jobs -JobId $JobId).done -ne $true) { Write-Verbose "service not ready yet - sleeping" sleep 5 $count -= 1 # If we've waited too long, just return the jobid if ($count -eq 0) {Return $JobId} } # Check result Get-JobResult $JobId | Out-Null # Service has been disabled so go get the details. $service = Get-SnapshotProtectionService -Name $Name return $service } else { # Not been asked to wait so just return the jobid Return $JobId } } } } 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 NsSnapshotProtectionSchedule object for the service If -NoWait was invoked it will return the GUID of the creation task If the cmdlet waits for more then 30sec it will return the GUID of the task. .PARAMETER Name <String> The name of a service to create .PARAMETER Minutes <String> A valid minutes cron entry - defaults to 0 .PARAMETER Hours <String> A valid hours cron entry - defaults to * .PARAMETER DaysOfMonth <String> A valid days of the month cron entry - defaults to * .PARAMETER Months <String> A valid months cron entry - defaults to * .PARAMETER DaysOfWeek <String> A valid days of the week cron entry - defaults to * .PARAMETER Retain <Int> The number of snapshots to retain - defaults to 1 .PARAMETER NoWait Invoke to make the cmdlet start the job and return the GUID of the JobID .EXAMPLE PS C:\temp> Add-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(Mandatory=$true, ValueFromPipelineByPropertyName, HelpMessage='The name of the service')] [ValidatePattern("^(\w|-|_|\.|\/)+$")] [string]$Name, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Cron pattern defining the minutes')] [ValidateNotNullOrEmpty()] [string]$Minutes = '0', [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Cron pattern defining the hours')] [ValidateNotNullOrEmpty()] [string]$Hours = '1', [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Cron pattern defining the days of the month')] [ValidateNotNullOrEmpty()] [string]$DaysOfMonth = '*', [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Cron pattern defining the months')] [ValidateNotNullOrEmpty()] [string]$Months = '*', [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Cron pattern defining the days of the week')] [string]$DaysOfWeek = '*', [Parameter(ValueFromPipelineByPropertyName, HelpMessage='The number of snaphots to retain')] [int]$Retain = 1, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Makes the cmdlet wait for the creation to complete before returning a service object.')] [switch]$NoWait ) try { #Test service exists if ((Get-SnapshotProtectionService -Name $Name) -eq $null) { if ((Get-ReplicationProtectionService -Name $Name) -eq $null) { throw 'The service does not exist' } } $cron = "$Minutes $Hours $DaysOfMonth $Months $DaysOfWeek" if ($cron -eq "* * * * *") { throw "[$cron] is invalid" } #Build request $request = @{} $request.add('cron',$cron) $request.add('keepSource',$Retain) $scheduleName=[guid]::NewGuid() $request.add('scheduleName',$scheduleName) if ($pscmdlet.ShouldProcess($Name,"Add schedule")) { # Kick off the creation request $response = Invoke-Api -Operation "hpr/services/$([uri]::EscapeDataString($Name))/schedules" -Request ($request | ConvertTo-Json) # Grap the id from the result $JobId = $response.links.href | Split-Path -Leaf # If we've been told to wait for completion... if (-Not $NoWait) { # Limit ourselves to 30 sec before returning filesystem $count = 10 #Get job and check status while ((Get-Jobs -JobId $JobId).done -ne $true) { Write-Verbose "service not ready yet - sleeping" sleep 5 $count -= 1 # If we've waited too long, just return the jobid if ($count -eq 0) {Return $JobId} } # Check result Get-JobResult $JobId | Out-Null # Schedule has been added so go get the details. $schedule = Invoke-Api -Operation "hpr/services/$([uri]::EscapeDataString($Name))/schedules/$scheduleName" return $schedule } else { # Not been asked to wait so just return the jobid Return $JobId } } else { Write-Verbose ($request | ConvertTo-Json) } } catch {throw $_} } Function Remove-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 NsSnapshotProtectionSchedule object for the service If -NoWait was invoked it will return the GUID of the creation task If the cmdlet waits for more then 30sec it will return the GUID of the task. .PARAMETER ServiceName <String> The name of a service to remove the schedule from .PARAMETER ScheduleName <String> The name of a the schedule to remove .PARAMETER NoWait Invoke to make the cmdlet start the job and return the GUID of the JobID .EXAMPLE PS C:\temp> Remove-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 ((Get-SnapshotProtectionService -Name $Name) -eq $null) { if ((Get-ReplicationProtectionService -Name $Name) -eq $null) { throw 'The service does not exist' } } #Test the schedule exists $schedule = Invoke-Api -Operation "hpr/services/$([uri]::EscapeDataString($ServiceName))/schedules/$scheduleName" if ($schedule -eq $null) { throw "Failed to remove schedule - $JobId" } if ($pscmdlet.ShouldProcess($ServiceName,"Remove schedule")) { # Kick off the creation request $response = Invoke-Api -Operation "hpr/services/$([uri]::EscapeDataString($ServiceName))/schedules/$scheduleName" -Method delete # Grap the id from the result $JobId = $response.links.href | Split-Path -Leaf # If we've been told to wait for completion... if (-Not $NoWait) { # Limit ourselves to 30 sec before returning filesystem $count = 10 #Get job and check status while ((Get-Jobs -JobId $JobId).done -ne $true) { Write-Verbose "service not ready yet - sleeping" sleep 5 $count -= 1 # If we've waited too long, just return the jobid if ($count -eq 0) {Return $JobId} } # Check result Get-JobResult $JobId | Out-Null $service = Get-SnapshotProtectionService -Name $ServiceName return $service } else { # Not been asked to wait so just return the jobid Return $JobId } } } catch {throw $_} } Function Get-SnapshotProtectionServiceSnapshot { <# .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-SnapshotProtectionServiceSnapshot -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-SnapshotProtectionServiceSnapshot -Path p1/collab-02/pso-research_data_management | ft path pool parent name creationTime parentType bytesLogicalUsed bytesReferenced bytesUsed compressionRatio ---- ---- ------ ---- ------------ ---------- ---------------- --------------- --------- ---------------- p1/collab-02/pso-... p1 p1/collab-02/pso-... snapping-2018-01-... 2018-01-30T15:25:... filesystem 0 24576 0 1 p1/collab-02/pso-... p1 p1/collab-02/pso-... snapping-2018-01-... 2018-01-30T15:26:... filesystem 0 24576 0 1 p1/collab-02/pso-... p1 p1/collab-02/pso-... snapping-2018-01-... 2018-01-30T15:27:... filesystem 0 24576 0 1 Get snaphosts for the filesystem p1/collab-02/pso-research_data_management #> Param ( [Parameter(Position = 0, ValueFromPipelineByPropertyName, HelpMessage='The name of the snapshot to get')] [ValidateNotNullOrEmpty()] [string]$Name, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Get snaphosts assosiated with a HPR Service')] [ValidatePattern("^(\w|-|_|\.|\/)+$")] [Alias('hprService')] [string]$Service, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Get snapshots for a given path')] [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")] [Alias('parent')] [string]$Path ) Process { try { # Set operation $operation = "storage/snapshots?limit=300" if ($Name) { $operation = "$operation&name=$Name" } if ($Service) { $HprService = Get-SnapshotProtectionService -Name $Service $operation = "$operation&hprService=$($HprService.id)" } 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 Get-ReplicationProtectionServiceSnapshot { <# .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-ReplicationProtectionServiceSnapshot -Service collabs-dr | ft path pool parent name creationTime parentType bytesLogicalUsed bytesReferenced bytesUsed compressionR atio ---- ---- ------ ---- ------------ ---------- ---------------- --------------- --------- ------------ p1/collabs/collab-01/share-01@hpr-2018-02-08-09-50-00-888 p1 p1/collabs/collab-01/share-01 hpr-2018-02-08-09-50-00-888 2018-02-08T09:50:00.000Z filesystem 0 24576 0 1 dr1/collabs/collab-01/share-01@hpr-2018-02-08-09-50-00-888 dr1 dr1/collabs/collab-01/share-01 hpr-2018-02-08-09-50-00-888 2018-02-08T09:50:00.000Z filesystem 0 24576 0 1 dr1/collabs/collab-01@hpr-2018-02-08-09-50-00-888 dr1 dr1/collabs/collab-01 hpr-2018-02-08-09-50-00-888 2018-02-08T09:50:00.000Z filesystem 0 33917440 0 14.89 p1/collabs@hpr-2018-02-08-09-50-00-888 p1 p1/collabs hpr-2018-02-08-09-50-00-888 2018-02-08T09:50:00.000Z filesystem 0 24576 0 1 dr1/collabs@hpr-2018-02-08-09-50-00-888 dr1 dr1/collabs hpr-2018-02-08-09-50-00-888 2018-02-08T09:50:00.000Z filesystem 0 24576 0 1 p1/collabs/collab-01@hpr-2018-02-08-09-50-00-888 p1 p1/collabs/collab-01 hpr-2018-02-08-09-50-00-888 2018-02-08T09:50:00.000Z filesystem 0 33913344 0 14.89 dr1/collabs/collab-01/share-01@hpr-2018-02-08-09-55-00-896 dr1 dr1/collabs/collab-01/share-01 hpr-2018-02-08-09-55-00-896 2018-02-08T09:55:00.000Z filesystem 0 24576 0 1 p1/collabs/collab-01/share-01@hpr-2018-02-08-09-55-00-896 p1 p1/collabs/collab-01/share-01 hpr-2018-02-08-09-55-00-896 2018-02-08T09:55:00.000Z filesystem 0 24576 0 1 dr1/collabs@hpr-2018-02-08-09-55-00-896 dr1 dr1/collabs hpr-2018-02-08-09-55-00-896 2018-02-08T09:55:00.000Z filesystem 0 24576 0 1 p1/collabs/collab-01@hpr-2018-02-08-09-55-00-896 p1 p1/collabs/collab-01 hpr-2018-02-08-09-55-00-896 2018-02-08T09:55:00.000Z filesystem 0 33913344 0 14.89 dr1/collabs/collab-01@hpr-2018-02-08-09-55-00-896 dr1 dr1/collabs/collab-01 hpr-2018-02-08-09-55-00-896 2018-02-08T09:55:00.000Z filesystem 0 33917440 0 14.89 p1/collabs@hpr-2018-02-08-09-55-00-896 p1 p1/collabs hpr-2018-02-08-09-55-00-896 2018-02-08T09:55:00.000Z filesystem 0 24576 0 1 Get snaphosts assosiated with the collabs-dr HPR Service .EXAMPLE PS C:\temp> Get-ReplicationProtectionServiceSnapshot -Path dr1/collabs/collab-01 | ft path pool parent name creationTime parentType bytesLogicalUsed bytesReferenced bytesUsed compressionRa tio ---- ---- ------ ---- ------------ ---------- ---------------- --------------- --------- ------------- dr1/collabs/collab-01@hpr-2018-02-07-15-00-00-708 dr1 dr1/collabs/collab-01 hpr-2018-02-07-15-00-00-708 2018-02-07T15:00:02.000Z filesystem 0 33917440 0 14.89 dr1/collabs/collab-01@hpr-2018-02-07-15-10-00-436 dr1 dr1/collabs/collab-01 hpr-2018-02-07-15-10-00-436 2018-02-07T15:10:01.000Z filesystem 0 33917440 0 14.89 dr1/collabs/collab-01@hpr-2018-02-07-15-15-00-846 dr1 dr1/collabs/collab-01 hpr-2018-02-07-15-15-00-846 2018-02-07T15:15:01.000Z filesystem 0 33917440 0 14.89 Get snaphosts for the filesystem dr1/collabs/collab-01 #> Param ( [Parameter(Position = 0, ValueFromPipelineByPropertyName, HelpMessage='The name of the snapshot to get')] [ValidateNotNullOrEmpty()] [string]$Name, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Get snaphosts assosiated with a HPR Service')] [ValidatePattern("^(\w|-|_|\.|\/)+$")] [Alias('hprService')] [string]$Service, [Parameter(ValueFromPipelineByPropertyName, HelpMessage='Get snapshots for a given path')] [ValidatePattern("^\w+(|(\/[\w\-\:\.]+)){1,}$")] [Alias('parent')] [string]$Path ) Process { try { # Set operation $operation = "storage/snapshots?limit=300" if ($Name) { $operation = "$operation&name=$Name" } if ($Service) { $HprService = Get-ReplicationProtectionService -Name $Service $operation = "$operation&hprService=$($HprService.id)" } 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 $_ } } } |