SpectrumControlBase-Client.psm1

##############################################################################
# #
# Licensed Materials - Property of IBM #
# #
# IBM Storage Automation Plugin for PowerShell #
# #
# (C) COPYRIGHT International Business Machines Corp. 2013, 2022 #
# All Rights Reserved #
# #
# US Government Users Restricted Rights: Use, duplication or #
# disclosure restricted by GSA ADP Schedule Contract with IBM Corp. #
# #
##############################################################################
# requires -Version 3
#
# Module Definition for module 'SpectrumControlBase-Client'
#
# Generated by: Gino Lv (lvlch@cn.ibm.com)
#
# Generated on: Jun 20, 2016
#
#region Variable Definition
Set-Variable -Name API_ENTRY                -Value "api/v1"
Set-Variable -Name RT_CAPABILITY_VALUES     -Value "capability-values" -Option ReadOnly
Set-Variable -Name RT_CONTAINERS            -Value "containers" -Option ReadOnly
Set-Variable -Name RT_DISKS                 -Value "disks" -Option ReadOnly
Set-Variable -Name RT_DOWNLOADS             -Value "downloads" -Option ReadOnly
Set-Variable -Name RT_HOSTS                 -Value "hosts" -Option ReadOnly
Set-Variable -Name RT_HOST_INITIATORS       -Value "host_initiators" -Option ReadOnly
Set-Variable -Name RT_INTERFACES            -Value "interfaces" -Option ReadOnly
Set-Variable -Name RT_MAPPINGS              -Value "mappings" -Option ReadOnly
Set-Variable -Name RT_MODULES               -Value "modules" -Option ReadOnly
Set-Variable -Name RT_POOLS                 -Value "pools" -Option ReadOnly
Set-Variable -Name RT_PORTS                 -Value "ports" -Option ReadOnly
Set-Variable -Name RT_SERVICES              -Value "services" -Option ReadOnly
Set-Variable -Name RT_SETTINGS              -Value "settings" -Option ReadOnly
Set-Variable -Name RT_STORAGE_ATTRIBUTES    -Value "storage-attributes" -Option ReadOnly
Set-Variable -Name RT_STORAGE_RESOURCES     -Value "storage-resources" -Option ReadOnly
Set-Variable -Name RT_STORAGE_SYSTEMS       -Value "arrays" -Option ReadOnly
Set-Variable -Name RT_TASKS                 -Value "tasks" -Option ReadOnly
Set-Variable -Name RT_AUTH_TOKEN            -Value "users/get-auth-token" -Option ReadOnly
Set-Variable -Name RT_USERS                 -Value "users" -Option ReadOnly
Set-Variable -Name RT_VOLUMES               -Value "volumes" -Option ReadOnly
$Global:ModuleName="SpectrumControlBase-Client"
$Global:DefaultConnection=$null        #Handle to a most recently used SCBConnection;
#endregion

#region Classe Definition
try{
Add-Type @"
    using System;
    using System.Net;
    using System.Security;
    using System.Collections.Generic;
    using System.Security.Cryptography.X509Certificates;
    using System.Management.Automation;
    using System.Runtime.InteropServices;
 
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
        }
    }
 
    public class SCBConnection{
        private PSCredential _credential;
        public Uri ConnectionUri {get; set; }
        public string UserName {get{return _credential.UserName;} }
        public string Token {get;set;}
        private Dictionary<int,string> _capabilityValues= new Dictionary<int,string>();
        public void SetCapability(IDictionary<int,string> capValues){
            if(_capabilityValues==null)
                _capabilityValues= new Dictionary<int,string>();
            _capabilityValues.Clear();
            foreach(int key in capValues.Keys){
                _capabilityValues.Add(key,capValues[key]);
            }
        }
        public IDictionary<int,string> GetCapability(){
            return _capabilityValues;
        }
        public string GetTokenString(){
            if(string.IsNullOrEmpty(Token))
                return null;
            else
                return "Token " +Token;
        }
        public PSCredential GetCredential(){
            return _credential;
        }
        public void SetCredential(PSCredential credential){
            if(credential != null){
                _credential=credential;
            }
        }
        public void SetCredential(string username,string password){
            if(!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password)){
                var secureString=new SecureString();
                foreach(char ch in password)secureString.AppendChar(ch);
                _credential= new PSCredential(username,secureString);
            }
        }
        public string ResolvePassword(){
            if(_credential==null) return null;
            else{
                var ptrPassword=Marshal.SecureStringToBSTR(_credential.Password);
                return Marshal.PtrToStringBSTR(ptrPassword);
            }
        }
    }
 
    public class SCBMappingInfor{
        private Dictionary<string, string> _internalResourceType = new Dictionary<string, string>()
        {
            { "SCBToken","$RT_AUTH_TOKEN"},
            { "SCBSpace","$RT_CONTAINERS" },
            { "SCBService","$RT_SERVICES"},
            { "SCBVolume","$RT_VOLUMES"},
            { "SCBHost","$RT_HOSTS"},
            { "SCBHostInitiator","$RT_HOST_INITIATORS"},
            { "SCBCapabilityValue","$RT_CAPABILITY_VALUES"},
            { "SCBHostVolMapping","$RT_MAPPINGS"},
        };
        public string ObjectName{get;set;}
        public string ResourceType{get{if(ObjectName==null)return null;else return _internalResourceType[ObjectName];}}
        public Dictionary<string,string> FieldsToRenamed = new Dictionary<string, string>();
        public List<string> FieldsToRemoved = new List<string>();
        public List<string> FieldsToPostprocessed = new List<string>();
    }
 
    public class SCBRequest{
        public SCBConnection SCBConnection { get; set; }
        public SCBMappingInfor MappingInfor = new SCBMappingInfor();
        public string Method { get; set; }
        public string Key{get;set;}
        public Dictionary<string, string> Header = new Dictionary<string, string>();
        public Dictionary<string, string> UrlParams = new Dictionary<string, string>();
        public Dictionary<string, object> Body = new Dictionary<string, object>();
        public string Uri
        {
            get
            {
                string str="";
                if (!string.IsNullOrEmpty(Key))
                {
                    str=string.Format("{0}/{1}/{2}/{3}", SCBConnection.ConnectionUri.AbsoluteUri.Trim('/'), "$API_ENTRY", MappingInfor.ResourceType, Key);
                }
                else if (UrlParams.Count > 0)
                {
                    string queryStr = "";
                    foreach (string query in UrlParams.Keys)
                    {
                        if(string.IsNullOrEmpty(UrlParams[query]))
                            continue;
                        queryStr += "&" + string.Format("{0}={1}", query, UrlParams[query]);
                    }
                    str=string.Format("{0}/{1}/{2}?{3}", SCBConnection.ConnectionUri.AbsoluteUri.Trim('/'), "$API_ENTRY", MappingInfor.ResourceType, queryStr.Trim('&'));
                }
                else
                {
                    str=string.Format("{0}/{1}/{2}", SCBConnection.ConnectionUri.AbsoluteUri.Trim('/'), "$API_ENTRY", MappingInfor.ResourceType);
                }
                return (new Uri(str)).AbsoluteUri;
            }
        }
    }
"@

}
catch{
    Write-Warning "The classes are partially registered!"
}

#endregion

#region Initilization
#Will not check the certificate policy
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy

#Manually enable the high level protocols TLS1.1|TLS1.2
[System.Net.ServicePointManager]::SecurityProtocol=[System.Net.SecurityProtocolType]::Tls12,[System.Net.SecurityProtocolType]::Tls11,[System.Net.SecurityProtocolType]::Tls,[System.Net.SecurityProtocolType]::Ssl3
#endregion

#region Function Definition
function AnalyzeException{
    param(
        [Object] $Exception,
        [ref] $SCBConnectionRef,
        [String] $ErrorInCmdlet
        )
        $CmdletName="AnalyzeException"
        Write-Verbose "$CmdletName`: Exception: $($Exception.Message)"
        Write-Verbose "$CmdletName`: SCBConnection: $($SCBConnectionRef.Value|ConvertTo-Json -Compress)"
    
        $Message=""
        if($Exception -Is "System.Net.WebException"){
            Write-Verbose "$CmdletName`: The exception is a WebException."
            if($Exception.Response -ne $null){
                $statusCode=$Exception.Response.StatusCode
                $ErrorCode=[int]$Exception.Response.StatusCode
                $result = $Exception.Response.GetResponseStream()
                $reader = New-Object System.IO.StreamReader($result)
                $reader.BaseStream.Position = 0
                $reader.DiscardBufferedData()
                $response = $reader.ReadToEnd()
                Write-Verbose "$CmdletName`: Response: $response"
                if(![String]::IsNullOrEmpty($response)){
                    $msgPattern="{.*non_field_errors.*:[\S\s]*?\[""(?<Message>.*)""\]}|^{""detail"":[\S\s]*?""(?<Message>.*)""}$"
                    $volNamePattern="Volume is: (?<VolumeName>\w{1,})."
                    $match=[System.Text.RegularExpressions.Regex]::Match($response,$msgPattern)
                    if($match.Success){
                        $Message=$match.Groups["Message"].Value
                    }else{
                        $Message=$response
                    }
                    $match=[System.Text.RegularExpressions.Regex]::Match($response,$volNamePattern)
                    if($match.Success){
                        $volInfo=if([String]::IsNullOrEmpty($match.Groups["VolumeName"].Value)){""}else{" The volume is " + $match.Groups["VolumeName"].Value +"."}
                    }
                    switch($ErrorCode){
                        401{
                            if($response.Contains("Invalid token") -Or $response.Contains("Token has expired")){
                                $Message="Cannot process this command because this connection is invalid or has expired. Please re-issue the ""New-SCBConnection"" command to get a new connection."
                            }
                        }
                        500{
                            if($response.Contains("CMMVC8698E")){    #Failed for the volume has host mappings
                                $Message="The command failed for the volume has host mappings." + $volInfo+ " Please use $ErrorInCmdlet -Force."
                                break
                            }
                            if($response.Contains("CMMVC7019E")){
                                $Message="The command failed because the volume size is not a multiple of 512 bytes." + $volInfo
                                break
                            }
                            if($response.Contains("A volume with this name already exists")){
                                $Message="The command failed because a volume with this name already exists." + $volInfo
                                break
                            }
                        }
                        default{
                            Write-Verbose "Unhandled StatusCode: $StatusCode; Message: $response"
                        }
                    }
                }
            }
            if([String]::IsNullOrEmpty($Message)){$Message=$Exception.Message}
            $Exception=New-Object "System.Net.WebException" "$ErrorInCmdlet`: $Message",$Exception
        }else{
            $Message=$Exception.Message
            $Exception=New-Object Exception ("$ErrorInCmdlet`: $Message",$Exception)
        }
        Write-Verbose "$CmdletName`: Message: $ErrorInCmdlet`: $Message"
        $Exception.HelpLink = (Get-Module $Global:ModuleName).HelpInfoUri
        $Exception.Source = $Global:ModuleName
        return $Exception
}

#Verify the SCBConnection is well configured
function ValidateConnection([SCBConnection] $SCBConnection){
    $preSetting=$WarningPreference
    $WarningPreference="SilentlyContinue"            #Don't show the Warning information
    Write-Verbose "ValidateConnection: SCBConnection = $($SCBConnection|ConvertTo-Json -Compress)"
    if($SCBConnection -eq $null){
        $message="SCBConnection: Cannot process the command because it is null."
        throw new-object System.ArgumentNullException $message
    }
    $connUri= $SCBConnection.ConnectionUri
    $cred= $SCBConnection.GetCredential()
    $token= $SCBConnection.Token

    if($connUri -ne $null -And $cred -ne $null -And ![String]::IsNullOrEmpty($token)){
        Write-Verbose "ValidateConnection: No need to reconnect"
    }
    else
    {
        $parameter=if($connUri -eq $null){"ConnectionUri"}elseif($cred -eq $null){"Credential"}else{"Token"}
        $message="Cannot process the command because $parameter is null or empty. Please issue the ""New-SCBConnection"" command to get a new connection and retry."
        throw new-object [System.ArgumentNullException] $message
    }
    $Global:DefaultConnection=$SCBConnection
    $WarningPreference=$preSetting
}

function GetResponse([SCBRequest] $SCBRequest,[String]$CmdletName){
    Write-Verbose "$CmdletName.Request.Uri=$($SCBRequest.Uri)"
    Write-Verbose "$CmdletName.Request.ObjectName=$($SCBRequest.MappingInfor.ObjectName)"
    Write-Verbose "$CmdletName.Request.Method=$($SCBRequest.Method)"
    Write-Verbose "$CmdletName.Request.Header=$($SCBRequest.Header|ConvertTo-Json -Compress)"
    Write-Verbose "$CmdletName.Request.Body=$($SCBRequest.Body|ConvertTo-Json -Compress)"
    $ret=$null
    try{
        Write-Verbose("$CmdletName.BEGIN_REST_METHOD")
        if(!$request.Header.Keys.Contains("Authorization") -And ![String]::IsNullOrEmpty($request.SCBConnection.Token)){
            $request.Header.Add("Authorization",$request.SCBConnection.GetTokenString())
        }
        if($request.Body.Count -gt 0){
            $ret=Invoke-RestMethod -Method $request.Method -Uri $request.Uri -ContentType "application/json" -Header $request.Header -Body ($request.Body|ConvertTo-Json) -ErrorAction Stop
        }else{
            $ret=Invoke-RestMethod -Method $request.Method -Uri $request.Uri -ContentType "application/json" -Header $request.Header -ErrorAction Stop
        }
        #Write-Verbose "$CmdletName.Response=$($ret[0]|ConvertTo-Json -Compress)"
    }
    catch{
        $ex=AnalyzeException -Exception $_.Exception -SCBConnectionRef ([ref]$request.SCBConnection) -ErrorInCmdlet $CmdletName
        throw $ex
    }finally{
        Write-Verbose("$CmdletName.END_REST_METHOD")
    }
    $results=@()
    if($ret -ne $null){
        if($request.MappingInfor.FieldsToPostprocessed.Contains("space_name")){
            $spaces=Get-SCBSpace -SCBConnection $request.SCBConnection -Verbose:$False
        }
        if($request.MappingInfor.FieldsToPostprocessed.Contains("initiators")){
            $initiators=Get-SCBHostInitiator -SCBConnection $SCBConnection -Verbose:$False
        }
        if($request.MappingInfor.FieldsToPostprocessed.Contains("capability_values")){
            $CapabilityValues=$request.SCBConnection.GetCapability()
        }
        $isToRename=$request.MappingInfor.FieldsToRenamed.Count -gt 0
        $isToRemove=$request.MappingInfor.FieldsToRemoved.Count -gt 0
        $isToPostProcess=$request.MappingInfor.FieldsToPostprocessed.Count -gt 0
        foreach($obj in $ret){
            if($isToRename){
                foreach($key in $request.MappingInfor.FieldsToRenamed.Keys){
                    #Write-Verbose "$CmdletName.PostProcess: Rename the field $key to $($request.MappingInfor.FieldsToRenamed[$key])"
                    $obj|Add-Member -NotePropertyName $request.MappingInfor.FieldsToRenamed[$key] -NotePropertyValue $obj.$key -Force
                    $obj.PSObject.Properties.Remove($key)
                }
            }
            if($isToRemove){
                foreach($key in $request.MappingInfor.FieldsToRemoved){
                    #Write-Verbose "$CmdletName.PostProcess: Remove the field: $key"
                    $obj.PSObject.Properties.Remove($key)
                }
            }
            if($isToPostProcess){
                if($request.MappingInfor.FieldsToPostprocessed.Contains("space_name")){
                    $spname=($spaces|?{$_.ID -eq $obj.space_id})[0].Name
                    $obj|Add-Member -NotePropertyName space_name -NotePropertyValue $spname -Force
                }
                if($request.MappingInfor.FieldsToPostprocessed.Contains("capability_values")){
                    $caps=$obj.capability_values.Split(",")
                    $obj.PSObject.Properties.Remove("capability_values")
                    $obj|Add-Member -NotePropertyName capability_values -NotePropertyValue $CapabilityValues[$caps] -Force
                }
                if($request.MappingInfor.FieldsToPostprocessed.Contains("initiators")){
                    $ports=($initiators|?{$_.host -eq $obj.id}).identifier
                    $obj|Add-Member -NotePropertyName initiators -NotePropertyValue $ports -Force
                }
            }
            $results+=$obj
        }
    }
    return $results
}

#endregion

#region Cmdlet Definition
function New-SCBConnection{
    <#
.SYNOPSIS
    Set up the connection between the SCB server and the IBM Storage PowerShell Client.
    Before using the PowerShell client, create a PowerShell interface user.
.DESCRIPTION
    Setup the connection to the SCB server, and a SCBConnection object will be returned if successful.
.PARAMETER ConnectionUri
    Alias L; The connection URI for SCB server in the https://SCBIPAddress:ServicePort(8440) format.
.PARAMETER Credential
    User credentials for connection to the SCB server.
.PARAMETER Username
    Alias U; User name and password for the SCB server connection must be specified.
    The user can be created on the Spectrum Control BaseGUI, when creating a PowerShell interface.
    Other non-PowerShell-interface users will be limited to access some SCB functions
.PARAMETER Password
    Alias P; The corresponding password.
 
.EXAMPLE
$client=New-SCBConnection -ConnectionUri https://9.115.250.45:8440
If Credential or Username/Password is not provided, a Credential dialog will pop up to input username/password information.
 
.EXAMPLE
$client=New-SCBConnection -ConnectionUri https://9.115.250.45:8440 -UserName powershell -Password passw0rd!
Or in short form using parameter aliases: $client=New-SCBConnection -L https://9.115.250.45:8440 -U powershell -P passw0rd!
 
.EXAMPLE
$username="powershell"; $password="passw0rd"; $ssPsw=ConvertTo-SecureString -AsPlainText -Force $password; $cred=New-Object pscredential $username,$ssPsw; $client=New-SCBConnection -ConnectionUri https://9.115.236.61:8440 -Credential $cred
Create the PSCredential from cmdline, and pass it to New-SCBConnection
 
#>

        [CmdletBinding(DefaultParameterSetname="Credential")]
        Param(
            [Parameter(Mandatory = $true)][ValidateNotNullorEmpty()][Alias("L")][String] $ConnectionUri,
            [Parameter(Mandatory = $true,ParameterSetName="Credential",Position=1)][PSCredential]$Credential,
            [Parameter(Mandatory = $true,ParameterSetName="Password",Position=1)][ValidateNotNullorEmpty()][Alias("U")][String] $UserName,
            [Parameter(Mandatory = $true,ParameterSetName="Password",Position=2)][ValidateNotNullorEmpty()][Alias("P")][string] $Password
        )
        $CmdletName="New-SCBConnection"
        $SCBConnection=New-Object "SCBConnection"
        $SCBConnection.ConnectionUri=$ConnectionUri
        if(![String]::IsNullOrEmpty($Username)){
            $SCBConnection.SetCredential($UserName,$Password)
        }elseif($Credential -ne $null){
            $SCBConnection.SetCredential($Credential)
        }
        $request=New-Object SCBRequest;
        $request.Method="POST"
        $request.SCBConnection=$SCBConnection;
        $request.Header.Add("Referer",$SCBConnection.ConnectionUri.AbsoluteUri)
        $request.Header.Add("Host",$SCBConnection.ConnectionUri.Authority)
        $request.Body.Add("username",$SCBConnection.UserName)
        $request.Body.Add("password",$SCBConnection.ResolvePassword())
        $request.Body.Add("group","powershell")
        $request.MappingInfor.ObjectName="SCBToken"
        $Token=GetResponse $request $CmdletName
        $SCBConnection.Token=$Token.token
        if(![String]::IsNullOrEmpty($Token.token)){
            $capValues=Get-SCBCapabilityValue -SCBConnection $SCBConnection
            $CapabilityValues=New-Object "System.Collections.Generic.Dictionary[int,string]"
            $null=$CapabilityValues.Add(0,"")
            foreach($cv in $capValues){
                $null=$CapabilityValues.Add($cv.id,[String]::Format("{0}: {1}", $cv.capability, $cv.value))
            }
            $SCBConnection.SetCapability($CapabilityValues)
        }
        $Global:DefaultConnection=$SCBConnection
        return $SCBConnection
}

function Get-SCBSpace{
<#
.SYNOPSIS
    Retrieves all the spaces or show the detailed information via space ID or space name.
.PARAMETER SpaceID
    Alias ID; Get the spaces via the space ID.
.PARAMETER SpaceName
    Alias Name; Get the spaces via the space name.
.PARAMETER SCBConnection
    The client handle for SCB server. It can be created via New-SCBConnection. If the SCBConnection is not specified, a most recently used SCBConnection will be utilized by default. The connection can be found via the global variable $Global:DefaultConnection. If multiple SCB servers are connected, you must explicitly specify the SCBConnection parameter.
 
.EXAMPLE
Get-SCBSpace -SCBConnection $client |ft ID,Name
id name
-- ----
efbd54ac-70c8-4693-ad7f-3df1cf56c187 Default_Space
12fb661e-e9e6-45c7-a5f4-62b86d66e238 space_vwc
7af7432d-172d-495b-a287-a1df2d2f4b77 recovered_space_vwc
Retrieve all the spaces on the SCB server specified by the SCBConnection $client;
 
.EXAMPLE
Get-SCBSpace -SCBConnection $client -SpaceID efbd54ac-70c8-4693-ad7f-3df1cf56c187
id : efbd54ac-70c8-4693-ad7f-3df1cf56c187
num_services : 3
name : Default_Space
description : Space used as the default.
storage_array_metadata : {}
Get the detailed information via Space ID
 
.EXAMPLE
lsspace -SCBConnection $client -Name DemoSpace
id : 1ba5a6eb-c652-436f-a90c-2bdba732fdb3
num_services : 2
name : DemoSpace
description :
storage_array_metadata : {}
Use the aliases for short; For this cmdlet, it supports the alias for cmdlet and the alias for parameters
 
#>

    [CmdletBinding(DefaultParameterSetname="ID")]
    param(
        [parameter(ParameterSetName="ID",Position=0)][Alias("ID")][String] $SpaceID,
        [parameter(ParameterSetName="Name",Position=0)][Alias("Name")][String] $SpaceName,
        [SCBConnection] $SCBConnection=$Global:DefaultConnection
    )
    $CmdletName="Get-SCBSpace"
    Write-Verbose "$CmdletName.ENTER: SCBConnection=$($SCBConnection|ConvertTo-Json -Compress), SpaceID=$SpaceID, SpaceName=$SpaceName"
    ValidateConnection $SCBConnection 
    if(![String]::IsNullOrWhiteSpace($SpaceName)){
        Write-Verbose("$CmdletName.SpaceName:" + $SpaceName)
        $ret=Get-SCBSpace -SCBConnection $SCBConnection|?{$_.Name -eq $SpaceName}
        if([String]::IsNullOrEmpty($ret)){
            Write-Warning ([String]::Format("$CmdletName`: Instance with {0} equal to {1} not found!","Name",$SpaceName))
        }
        return $ret
    }
    $request=New-Object SCBRequest;
    $request.Method="GET"
    $request.SCBConnection=$SCBConnection;
    $request.MappingInfor.ObjectName="SCBSpace"
    $request.MappingInfor.FieldsToRemoved.Add("unique_identifier")
    if(![String]::IsNullOrWhiteSpace($SpaceID)){
        $request.Key=$SpaceID
    }
    $ret=GetResponse $request $CmdletName
    Write-Verbose "$CmdletName.EXIT"
    return $ret
}

function Get-SCBService{
<#
.SYNOPSIS
    Retrieves the services from SCB server. Services are a set of storage capabilities and capacity for a tier or application. For PowerShell, all the services are attached to PowerShell interfaces.
.PARAMETER ServiceID
    Alias ID; The service ID or Unique Identifier(id).
.PARAMETER ServiceName
    Alias Name; The service name.
.PARAMETER SpaceID
    Get the services via the space ID.
.PARAMETER SpaceName
    Get the services via the space Name.
.PARAMETER SCBConnection
    The client handle for SCB server. It can be created via New-SCBConnection. If the SCBConnection is not specified, a most recently used SCBConnection will be utilized by default. The connection can be found via the global variable $Global:DefaultConnection. If multiple SCB servers are connected, you must explicitly specify the SCBConnection parameter.
 
.EXAMPLE
Get-SCBService -SCBConnection $client|ft id,description,type,physical_size
 
id description type physical_size
-- ----------- ---- -------------
97b8ca86-a01e-4143-97c1-89d33003944a ThinProvisionService regular 214748364800
e7df033a-6367-4f51-a7b9-fb3bd04472e4 CompressionService regular 322122547200
Retrieves all the service instances
 
.EXAMPLE
Get-SCBService e7df033a-6367-4f51-a7b9-fb3bd04472e4 -SCBConnection $client
 
id : e7df033a-6367-4f51-a7b9-fb3bd04472e4
name : CompressionService
description : CompressionService
type : regular
physical_size : 322122547200
logical_size : 322122547200
physical_free : 317827579904
logical_free : 317827579904
total_capacity : 322122547200
used_capacity : 4294967296
max_resource_logical_free : 317827579904
max_resource_free_size_for_provisioning : 10594252663466
num_volumes : 4
has_admin : True
qos_max_iops : 0
qos_max_mbps : 0
space_id : 1ba5a6eb-c652-436f-a90c-2bdba732fdb3
space_name : DemoSpace
capability_values : {Compression: Enabled, Deduplication: Disabled}
Retrieves the detailed information about a service via its ID
 
.EXAMPLE
Get-SCBService -SpaceName DemoSpace -SCBConnection $sc|ft name,id,space_id
 
name id space_id
---- -- ---------
ThinProvisionService 8d4b6c91-670f-40bd-8d69-901e4a1219f9 4cfc8298-965f-4f90-b23f-4d04fe70d9ee
CompressionService d7cd2c6f-cb96-4c27-8c87-8dba3fb9820c 4cfc8298-965f-4f90-b23f-4d04fe70d9ee
Retrieves all the services under a space
 
#>

    [CmdletBinding(DefaultParameterSetname="ID")]
    param(
        [parameter(ParameterSetName="ID",Position=0)][Alias("ID")][String] $ServiceID,
        [parameter(ParameterSetName="Name")][Alias("Name")][String] $ServiceName,
        [parameter(ParameterSetName="SpaceID")][String] $SpaceID,
        [parameter(ParameterSetName="SpaceName")][String] $SpaceName,
        [SCBConnection] $SCBConnection=$Global:DefaultConnection
    )
    $CmdletName="Get-SCBService"
    Write-Verbose "$CmdletName.ENTER: SCBConnection=($($SCBConnection|ConvertTo-Json -Compress)), ServiceID=$ServiceID, ServiceName=$ServiceName, SpaceID=$SpaceID,SpaceName=$SpaceName"
    ValidateConnection $SCBConnection
    if(![String]::IsNullOrWhiteSpace($SpaceName)){
        $SpaceID=(Get-SCBSpace -SCBConnection $SCBConnection -SpaceName $SpaceName)[0].id
        if([String]::IsNullOrEmpty($SpaceID)){
            Write-Warning ([String]::Format("$CmdletName`: SCBSpace instance with {0} equal to {1} not found!","Name",$SpaceName))
            Write-Verbose("$CmdletName.EXIT")
            return $null
        }else{
            Write-Verbose "$CmdletName`: SCBSpace found with ID equal to $SpaceID"
        }
    }
    $request=New-Object SCBRequest;
    $request.MappingInfor.ObjectName="SCBService"
    $request.Method="GET"
    $request.SCBConnection=$SCBConnection;
    $request.MappingInfor.FieldsToRenamed.Add("container","space_id")
    $request.MappingInfor.FieldsToRemoved.Add("unique_identifier")
    $request.MappingInfor.FieldsToPostprocessed.Add("space_name")
    $request.MappingInfor.FieldsToPostprocessed.Add("capability_values")
    if(![String]::IsNullOrWhiteSpace($ServiceID)){
        $request.Key=$ServiceID
    }
    if(![String]::IsNullOrWhiteSpace($ServiceName)){
        $request.UrlParams.Add("name",$ServiceName)
    }
    if(![String]::IsNullOrWhiteSpace($SpaceID)){
        $request.UrlParams.Add("container",$SpaceID)
    }
    $ret=GetResponse $request $CmdletName
    Write-Verbose("$CmdletName.EXIT")
    return $ret
}

function Get-SCBHostInitiator{
<#
.SYNOPSIS
    Retrieves initiator ports information from the SCB server.
 
.PARAMETER HostID
    The host ID from the SCB server.
 
.PARAMETER ArrayID
    The ID of the managed storage array.
 
.PARAMETER HostName
    Specifies on which host to retrieve its initiator ports.
 
.PARAMETER ConnectionType
    Specifies the type of the connection. Acceptable values are: FibreChannel, and iSCSI.
 
.PARAMETER SCBConnection
    The client handle for SCB server. It can be created via New-SCBConnection. If the SCBConnection is not specified, a most recently used SCBConnection will be utilized by default. The connection can be found via the global variable $Global:DefaultConnection. If multiple SCB servers are connected, you must explicitly specify the SCBConnection parameter.
 
.EXAMPLE
Get-SCBHostInitiator -ConnectionType FibreChannel -SCBConnection $client|ft
id identifier port_type host physical_initiator
-- ---------- --------- ---- ------------------
 4 21000024FF2DC5EE FC 4
 5 21000024FF2DA3F6 FC 3
 6 21000024FF2FADC5 FC 2
 7 21000024FF2FADC4 FC 1
 8 21000024FF523307 FC 6
 9 21000024FF523306 FC 4
Retrieves all the FC initiators from all managed storage arrays
 
.EXAMPLE
Get-SCBHostInitiator -ArrayID $array -HostName fakeFCHost -SCBConnection $client|ft
d identifier port_type host physical_initiator
- ---------- --------- ---- ------------------
2 21000024FF2DAFF5 FC 6
1 21000024FF2DAFF6 FC 6
Shows all the initiators of a host on a managed array
#>


    [CmdletBinding(DefaultParameterSetname="ID")]
    param(
        [parameter(ParameterSetName="ID",Position=0)][String] $HostID,
        [parameter(Mandatory=$true,ParameterSetName="Name",Position=0)][String] $ArrayID,
        [parameter(ParameterSetName="Name",Position=1)][String] $HostName,
        [ValidateSet("iSCSI","FibreChannel")][String] $ConnectionType,
        [SCBConnection] $SCBConnection=$Global:DefaultConnection
    )
    $CmdletName="Get-SCBHostInitiator"
    Write-Verbose ("$CmdletName.ENTER: SCBConnection=($($SCBConnection|ConvertTo-Json -Compress)), HostID=$HostID, HostName=$HostName, ArrayID=$ArrayID ConnectionType=$ConnectionType, FromHostSide=$FromHostSide")
    ValidateConnection $SCBConnection
    $request=New-Object SCBRequest;
    $request.Method="GET"
    $request.SCBConnection=$SCBConnection;
    $request.MappingInfor.ObjectName="SCBHostInitiator"
    if(![String]::IsNullOrEmpty($HostName) -And ![String]::IsNullOrEmpty($ArrayID)){
        $HostID=(Get-SCBHost -SCBConnection $SCBConnection -Array $ArrayID -Name $HostName).id
    }
    if(![String]::IsNullOrEmpty($HostID)){
        $request.UrlParams.Add("host",$HostID)
    }
    $portType=""
    if($ConnectionType -eq "FibreChannel"){
        $portType="FC"
    }elseif($ConnectionType -eq "iSCSI"){
        $portType="iSCSI"
    }
    $ret=GetResponse $request $CmdletName
    Write-Verbose("$CmdletName.EXIT")
    if([String]::IsNullOrEmpty($portType)){
        return $ret
    }else{
        return ($ret|where {$_.port_type -eq $portType})
    }
}

function Get-SCBCapabilityValue{
<#
.SYNOPSIS Retrieves the capabilities of services.
.PARAMETER ID
    The ID or Unique Identifier(id) of the capabilities values.
.EXAMPLE
Get-SCBCapabilityValue -SCBConnection $Client|ft
 
id capability capability_constraints value value_type
-- ---------- ---------------------- ----- ----------
 1 Qos {} Max Independent Performance string
 2 Qos {} Max Shared Performance string
 3 Compression {1} Enabled string
 4 Compression {} Disabled string
...
Retrieves all the capability values
 
.EXAMPLE
Get-SCBCapabilityValue 3 -SCBConnection $client
 
id : 3
capability : Compression
capability_constraints : {1}
value : Enabled
value_type : string
Retrieve the capability values via ID
#>

[CmdletBinding()]
    param(
        [String] $ID,
        [SCBConnection] $SCBConnection=$Global:DefaultConnection
    )
    $CmdletName="Get-SCBCapabilityValue"
    Write-Verbose("$CmdletName.ENTER: SCBConnection=($($SCBConnection|ConvertTo-Json -Compress)), ID=$ID")
    ValidateConnection $SCBConnection 
    $request=New-Object SCBRequest;
    $request.Method="GET"
    $request.SCBConnection=$SCBConnection;
    $request.MappingInfor.ObjectName="SCBCapabilityValue"
    if(![String]::IsNullOrWhiteSpace($ID)){
        $request.Key=$ID
    }
    $ret=GetResponse $request $CmdletName
    Write-Verbose("$CmdletName.EXIT")
    return $ret
}

function Get-SCBHost{
<#
.SYNOPSIS
    Retrieves the information of host instances on the storage arrays managed by SCB Server.
 
.PARAMETER HostID
    Alias ID; The host ID.
.PARAMETER HostName
    Alias Name; The host name(case sensitive). The host name is not unique, especially when more storage arrays are managed by the same SCB server; To get the correct host, the ArrayID parameter must be specified.
.PARAMETER ArrayID
    The ID of the managed storage array.
.PARAMETER Initiators
    The host endpoints (either FC or iSCSI); System.Collections.ArrayList object or just a Object[] is accepted here for multiple host endpoints.
.PARAMETER SCBConnection
    The client handle for SCB server. It can be created via New-SCBConnection. If the SCBConnection is not specified, a most recently used SCBConnection will be utilized by default. The connection can be found via the global variable $Global:DefaultConnection. If multiple SCB servers are connected, you must explicitly specify the SCBConnection parameter.
 
.EXAMPLE
Get-SCBHost -SCBConnection $scbclient|ft id,name,array,initiators -AutoSize
id name array initiators
-- ---- ----- ----------
 1 win2012r2x215 00000200AE62F4E4 iqn.1991-05.com.microsoft:win2012r2x215.vmm.com
17 host_memeber5 00000200AE62F4E4 iqn.1991-05.com.microsoft:host5
 7 WIN2012X66_FC 00000200AE62F4E4 21000024FF2DC5FB
 2 win2012x73_ISCSI 00000200AE62F4E4 iqn.1991-05.com.microsoft:win2012x73.vmm.com
 ...
 6 fakeFCHost 00000200AE62F4E4 {21000024FF2DAFF6, 21000024FF2DAFF5}
Gets all the host information for the connected SCB Server.
 
.EXAMPLE
Get-SCBHost -HostID 6 -SCBConnection $scbclient(or Get-SCBHost 6, or Get-SCBHost -ID 6)
id : 6
array_type : 2145
array : 00000200AE62F4E4
name : fakeFCHost
port_count : 2
iogroup_count : 4
status : offline
host_type : generic
iogroups : {2, 3, 4, 1}
initiators : {21000024FF2DAFF6, 21000024FF2DAFF5}
Gets the information for the specified host with ID 6.
 
.EXAMPLE
Get-SCBHost -ArrayID $array -HostName fakeISCSIHost -SCBConnection $scbclient
id : 5
array_type : 2145
array : 00000200AE62F4E4
name : fakeISCSIHost
port_count : 1
iogroup_count : 4
status : offline
host_type : generic
iogroups : {2, 3, 4, 1}
initiators : iqn.1991-05.com.microsoft:agino
...
Retrieves the detailed information by host name.
 
.EXAMPLE
$ports="iqn.1991-05.com.microsoft:win2012x72.vmm.com","21000024FF2DC5FB","21000024FF2DAFF6"; Get-SCBHost -ArrayID $array -Initiators $ports|ft name,array,id
name array id
---- ----- --
WIN2012X66_FC 00000200AE62F4E4 7
fakeFCHost 00000200AE62F4E4 6
win2012x72_ISCSI 00000200AE62F4E4 3
Retrieves the hosts via their initiators.
 
.EXAMPLE
$ports=New-Object "System.Collections.ArrayList"; $ports.Add("21000024FF2DC5FB"); $ports.Add("iqn.1991-05.com.microsoft:win2012x72.vmm.com"); Get-SCBHost -ArrayID $array -Initiators $ports |ft name,array,id
name array id
---- ----- --
fakeFCHost 00000200AE62F4E4 6
win2012x72_ISCSI 00000200AE62F4E4 3
Retrieves the hosts via their initiators.
 
#>

    [CmdletBinding(DefaultParameterSetname="ID")]
    param(
        [parameter(ParameterSetName="ID",Position=0)][Alias("ID")][String] $HostID,
        [parameter(ParameterSetName="ArrayID",Position=0,Mandatory= $True)][parameter(ParameterSetName="Initiators",Position=0,Mandatory= $True)][parameter(ParameterSetName="Name",Position=0,Mandatory= $True)][String] $ArrayID,
        [parameter(ParameterSetName="Name",Position=1,Mandatory= $True)][Alias("Name")][String] $HostName,
        [parameter(ParameterSetName="Initiators",Position=1,Mandatory= $True)][Object] $Initiators,
        [SCBConnection] $SCBConnection=$Global:DefaultConnection
    )
    $CmdletName="Get-SCBHost"
    Write-Verbose("$CmdletName.ENTER: SCBConnection=($($SCBConnection|ConvertTo-Json -Compress)), ID=$HostID,Name=$HostName,ArrayID=$ArrayID, Initiators=$($Initiators|ConvertTo-Json -Compress)")
    ValidateConnection $SCBConnection 
    $request=New-Object SCBRequest;
    $request.Method="GET"
    $request.SCBConnection=$SCBConnection;
    $request.MappingInfor.ObjectName="SCBHost"
    $request.MappingInfor.FieldsToRemoved.Add("host_id")
    $request.MappingInfor.FieldsToRemoved.Add("physical_host")
    $request.MappingInfor.FieldsToRemoved.Add("storage_cluster")
    $request.MappingInfor.FieldsToPostprocessed.Add("initiators")
    if(![String]::IsNullOrWhiteSpace($HostID)){
        $request.Key=$HostID
    }
    if (![String]::IsNullOrWhiteSpace($ArrayID)) {
        $request.UrlParams.Add("array",$ArrayID)
    }
    if (![String]::IsNullOrWhiteSpace($HostName)) {
        $request.UrlParams.Add("name",$HostName)
    }
    if ($Initiators -ne $null) {
        if($Initiators -Is "System.Collections.ArrayList" -Or $Initiators.GetType().IsArray){    # [1,2] =>"1,2"
            $request.UrlParams.Add("match_all_ports","$Initiators".Replace(" ",","))
        }else{
            $request.UrlParams.Add("match_all_ports",$Initiators)
        }
    }
    try{
        $ret=GetResponse $request $CmdletName
    }
    catch{
        if($_.Exception.Message.Contains("descriptor 'union' of 'set' object needs an argument")){
            $ret=$null
        }else{
            throw $_.Exception
        }
    }
    finally{
        Write-Verbose("$CmdletName.EXIT")
    }
    return $ret
}

#region SCBVolume
function Get-SCBVolume{
<#
.SYNOPSIS
    Retrieves all the volumes managed by the PowerShell interfaces; Detailed information can be retrieved if the VolumeID/VolumeName parameter is specified.
 
.PARAMETER VolumeID
    Alias ID; The volume ID or Unique Identifier(id).
.PARAMETER VolumeName
    Alias Name; The volume Name; The volume name is not unique, especially when more storage arrays are managed by the same SCB server; To get the correct volume, the ArrayID parameter must be specified.
.PARAMETER ArrayID
    The ID of the managed storage array. This parameter can be used seperately, or with VolumeName together.
.PARAMETER ServiceName
    Get the volumes via the service name.
.PARAMETER SCBConnection
    The client handle for SCB server. It can be created via New-SCBConnection. If the SCBConnection is not specified, a most recently used SCBConnection will be utilized by default. The connection can be found via the global variable $Global:DefaultConnection. If multiple SCB servers are connected, you must explicitly specify the SCBConnection parameter.
 
.EXAMPLE
Get-SCBVolume -SCBConnection $client |?{$_.name -match ".*demo.*"}|ft name,id,pool_name,array
name id pool_name array
---- -- --------- -----
SCBDemo004 6005076801A707416800000000000B85 DemoPool 0000020069C1D05A
DemoThin04 6005076801A707416800000000000B76 DemoPool 0000020069C1D05A
DemoThin05 6005076801A707416800000000000B75 DemoPool 0000020069C1D05A
Gets the volumes whose names contain "demo" on the array(id=0000020069C1D05A)
 
.EXAMPLE
Get-SCBVolume -VolumeID 6005076802B98BD390000000000000AD -SCBConnection $client|fl name,id,logical_capacity
name : SCBVOL-TCK2-636180625453455304
id : 6005076802B98BD390000000000000AD
logical_capacity : 10737418240
Retrieves the detailed information via volume ID
 
.EXAMPLE
Get-SCBVolume -VolumeName SCBVOL304 -ArrayID 00000200AE62F4E4 -SCBConnection $client|fl id,name,array,service_name,space_name,logical_capacity
id : 6005076802B98BD390000000000000AD
array : 00000200AE62F4E4
name : SCBVOL304
service_name : ThinProvisionService
space_name : DemoSpace
logical_capacity : 10737418240
Retrieves the detailed information of the volume(SCBVOL304) on a managed array
#>

    [CmdletBinding(DefaultParameterSetName="ID")]
    param(
        [parameter(ParameterSetName="ID",Position=0)][Alias("ID")][String] $VolumeID,
        [parameter(ParameterSetName="Name",Position=0,Mandatory=$true)][Alias("Name")][String] $VolumeName,
        [parameter(ParameterSetName="Name",Position=1)][parameter(ParameterSetName="Array",Position=0,Mandatory=$true)][String] $ArrayID,
        [parameter(ParameterSetName="Service",Position=0,Mandatory=$true)][String] $ServiceID,
        [parameter(ParameterSetName="ScsiID",Position=0,Mandatory=$true)][String] $ScsiID,
        [SCBConnection]$SCBConnection=$Global:DefaultConnection
    )
    $CmdletName="Get-SCBVolume"
    Write-Verbose ("$CmdletName.ENTER: SCBConnection=($($SCBConnection|ConvertTo-Json -Compress)), ID=$VolumeID,Name=$VolumeName,ServiceID=$ServiceID,ArrayID=$ArrayID,ScsiID=$ScsiID")
    ValidateConnection $SCBConnection 
    $request=New-Object SCBRequest;
    $request.Method="GET"
    $request.SCBConnection=$SCBConnection;
    $request.MappingInfor.ObjectName="SCBVolume"
    $request.MappingInfor.FieldsToRenamed.Add("volume_id","volume_id_on_array")
    $request.MappingInfor.FieldsToRenamed.Add("container_name","space_name")
    $request.MappingInfor.FieldsToRenamed.Add("container_id","space_id")
    if (![String]::IsNullOrWhiteSpace($VolumeID)) {
        $request.Key=$VolumeID
    }
    if (![String]::IsNullOrWhiteSpace($ArrayID)) {
        $request.UrlParams.Add("array","$ArrayID")
    }
    if (![String]::IsNullOrWhiteSpace($VolumeName)){
        $request.UrlParams.Add("name","$VolumeName")
    }
    if (![String]::IsNullOrWhiteSpace($ServiceID)) {
        $request.UrlParams.Add("service","$ServiceID")
    }
    if (![String]::IsNullOrWhiteSpace($ScsiID)) {
        $request.UrlParams.Add("scsi_identifier","$ScsiID")
    }
    $ret=GetResponse $request $CmdletName
    Write-Verbose ("$CmdletName.EXIT")
    return $ret
}

function New-SCBVolume{
<#
.SYNOPSIS
    Create volumes via the services attached to the PowerShell interface user; If the Initiators parameter is specified, the volume will be automatically mapped; A SCBVolume instance will be returned if succeed.
 
.PARAMETER VolumeName
    Alias Name; Specify the name for the volume.
.PARAMETER ServiceName
    The service name. The service will determine what kind of volumes will be created based on its capabilities.
.PARAMETER Size
    Specify the volume size. Its unit can be specified via the SizeUnit parameter. If the SizeUnit param is not specified, the default unit is GB(10^9Byte).
.PARAMETER SizeUnit
    Specify the unit of Size. If not specified, the unit GB will be used by default.
    The following units are available:
    TiB = 1024^4 Byte
    GiB = 1024^3 Byte
    GB = 1000^3 Byte
    Byte
.PARAMETER Initiators
    The host endpoints (either FC or iSCSI); System.Collections.ArrayList object or just a Object[] is accepted here for multiple host endpoints.
.PARAMETER SCBConnection
    The client handle for SCB server. It can be created via New-SCBConnection. If the SCBConnection is not specified, a most recently used SCBConnection will be utilized by default. The connection can be found via the global variable $Global:DefaultConnection. If multiple SCB servers are connected, you must explicitly specify the SCBConnection parameter.
 
.EXAMPLE
New-SCBVolume -Name DemoThin04 -Size 1 -SizeUnit GiB -ServiceID $service.id |fl array_name,id,name,pool_name,service_name,space_name,logical_capacity
array_name : cim75
id : 6005076801A707416800000000000B76
name : DemoThin04
pool_name : SCBReserved
service_name : SVCThinService
space_name : DemoSpace
logical_capacity : 1073741824
Creates a new volume from service.
 
.EXAMPLE
$vol=New-SCBVolume -Name $volName -Size 1 -SizeUnit TiB -ServiceID $service.ID -Initiators 21000024FF2DA3F6,21000024FF2DD3FA; Get-SCBHostVolMapping -VolumeID $vol.id|ft
 id volume host lun_number
 -- ------ ---- ----------
317 6005076802B98BD39000000000000177 6 3
318 6005076802B98BD39000000000000177 7 3
News a volume via Service, and register it to specified FC/iSCSI initiators.
 
.EXAMPLE
$service=(Get-SCBService -SCBConnection $sc|?{$_.name.Contains("Compress")})[0]; New-SCBVolume -VolumeName CompVol001 -Size 10 -ServiceID $service.id|fl Name,ID,logical_capacity
name : CompVol001
id : 6005076802B98BD39000000000000136
logical_capacity : 10000000000
Specifies the SizeUnit when creating volume.
#>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true,Position=0)][Alias("Name")][String] $VolumeName,
        [Parameter(Mandatory = $true,Position=1)][Long] $Size,
        [Parameter(Position=2)][ValidateSet("TiB","GiB","GB","Byte")][String] $SizeUnit="GB",
        [Parameter(Mandatory = $true,Position=3)][String] $ServiceID,
        [Object] $Initiators,
        [SCBConnection] $SCBConnection=$Global:DefaultConnection
    )
    $CmdletName="New-SCBVolume"
    Write-Verbose ("$CmdletName.ENTER: SCBConnection=($($SCBConnection|ConvertTo-Json -Compress)), Name=$VolumeName,Size=$Size,SizeUnit=GB,ServiceID=$ServiceID,Initiators=$($Initiators|ConvertTo-Json -Compress)")
    ValidateConnection $SCBConnection 
    $request=New-Object SCBRequest;
    $request.Method="Post"
    $request.SCBConnection=$SCBConnection;
    $request.MappingInfor.ObjectName="SCBVolume"
    $request.MappingInfor.FieldsToRenamed.Add("volume_id","volume_id_on_array")
    $request.MappingInfor.FieldsToRenamed.Add("container_name","space_name")
    $request.MappingInfor.FieldsToRenamed.Add("container_id","space_id")
    $request.Body.Add("name",$VolumeName)
    if($SizeUnit.Equals("GB")){
        $request.Body.Add("size",$Size)
    }else{
        [long]$finalSize=$Size
        switch($SizeUnit.ToUpper()){
            "TIB"{$finalSize=1024*$Size; $SizeUnit="gib";}
            "GIB"{$finalSize=$Size;$SizeUnit="gib";}
            "BYTE"{$finalSize=$Size;$SizeUnit="byte";}    
        }
        $request.Body.Add("size",$finalSize)
        $request.Body.Add("size_unit",$SizeUnit)
    }
    if(![String]::IsNullOrWhiteSpace($ServiceID)){
        $request.Body.Add("service",$ServiceID)
    }
    if($Initiators -ne $Null){
        if($Initiators -Is "System.Collections.ArrayList" -Or $Initiators.GetType().IsArray){
            $request.Body.Add("host_initiators","$Initiators".Replace(" ",","))
        }
        else{
            $request.Body.Add("host_initiators",$Initiators)
        }
    }
    $ret=GetResponse $request $CmdletName
    Write-Verbose ("$CmdletName.EXIT")
    return $ret
}

function Remove-SCBVolume{
<#
.SYNOPSIS
    Removes the volume or multiple volumes; For the operation on single volume, it will return $True if the volume is successfully removed, otherwize it will return $False; For multiple volumes, a dictionary, with VolumeID as the key and operation result as the value, will be returned.
 
.PARAMETER VolumeID
    Alias ID; The volume ID or Unique Identifier(id).
.PARAMETER Force
    Indicates whether to remove the mapping relationships if exist.
.PARAMETER SCBConnection
    The client handle for SCB server. It can be created via New-SCBConnection. If the SCBConnection is not specified, a most recently used SCBConnection will be utilized by default. The connection can be found via the global variable $Global:DefaultConnection. If multiple SCB servers are connected, you must explicitly specify the SCBConnection parameter.
 
.EXAMPLE
Remove-SCBVolume -VolumeID 6005076801A707416800000000000B85 -SCBConnection $client
Removes a volume via its ID.
 
.EXAMPLE
$ids=(Get-SCBVolume|?{$_.Name.StartsWith("SCBVOL")}).id; Remove-SCBVolume -VolumeID $ids -SCBConnection $client -Force
Key Value
--- -----
6005076802B98BD390000000000019B0 True
6005076802B98BD39000000000001996 True
6005076802B98BD39000000000001A22 True
....
6005076802B98BD39000000000001A82 True
Remove all the volumes whose name starts with "SCBVOL".
#>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true,ValueFromPipelineByPropertyName=$true,Position=0)][Alias("ID")][Object] $VolumeID,
        [switch] $Force,
        [SCBConnection] $SCBConnection=$Global:DefaultConnection
    )
    $CmdletName="Remove-SCBVolume"
    Write-Verbose ("$CmdletName.ENTER: SCBConnection=($($SCBConnection|ConvertTo-Json -Compress)), ID=$VolumeID, Force=$Force")
    ValidateConnection $SCBConnection
    $request=New-Object SCBRequest;
    $request.Method="Delete"
    $request.SCBConnection=$SCBConnection;
    $request.MappingInfor.ObjectName="SCBVolume"
    if($VolumeID -Is "System.Collections.ArrayList" -Or $VolumeID.GetType().IsArray){
        $rets=New-Object "System.Collections.Generic.Dictionary[String,String]"
        foreach($volID in $VolumeID){
            try{
                $ret=Remove-SCBVolume -SCBConnection $SCBConnection -VolumeID $volID -Force:$Force
            }catch{
                Write-Warning $_.Exception.Message
                $ret=$False
            }
            $rets.Add($volID,$ret)
        }
        return $rets
    }
    $request.Key=$VolumeID
    if($Force){
        $mappings=Get-SCBHostVolMapping -SCBConnection $SCBConnection -VolumeID $VolumeID
        if($mappings -ne $null){
            foreach($mapping in $mappings){
                Write-Verbose ("$CmdletName`: Begin to Unmap the Volume(ID=$VolumeID) from the Host($($mapping.host))")
                $rr=Remove-SCBHostVolMapping -SCBConnection $SCBConnection -VolumeID $VolumeID -HostID $mapping.host
                if($rr){
                Write-Verbose ("$CmdletName`: Succeed to unmap the volume(ID=$VolumeID) from the Host($($mapping.host))")
                }else{
                Write-Warning ("$CmdletName`: Fail to unmap the volume(ID=$VolumeID) from the Host($($mapping.host))")
                }
            }
        }
    }
    try{
        $ret=GetResponse $request $CmdletName
    }catch{
        $ex=([System.Net.WebException]$_.Exception)
        if($ex.Message.Contains("Not found.")){
            Write-Warning "$CmdletName`: The volume with ID equal to $VolumeID is not found."
            Write-Verbose ("$CmdletName.EXIT")
            return $true
        }else{
            throw $ex
        }
    }
    if([String]::IsNullOrEmpty("$ret")){$ret=$True}else{$ret=$False}
    Write-Verbose ("$CmdletName.EXIT")
    return $ret
}

function Resize-SCBVolume{
<#
.SYNOPSIS
    Resizes the specified volume; Returns a already-resized SCBVolume instance if succeed while $Null if fail
 
.PARAMETER VolumeID
    Alias ID; The ID for the volume to be resized.
.PARAMETER Size
    The size for the volume to be resized.
.PARAMETER SizeUnit
    The unit of Size. If not specified, GB will be used by default. The following units are available:
    TiB = 1024^4 Byte
    GiB = 1024^3 Byte
    GB = 1000^3 Byte
    Byte
.PARAMETER SCBConnection
    The client handle for SCB server. It can be created via New-SCBConnection. If the SCBConnection is not specified, a most recently used SCBConnection will be utilized by default. The connection can be found via the global variable $Global:DefaultConnection. If multiple SCB servers are connected, you must explicitly specify the SCBConnection parameter.
 
.EXAMPLE
Resize-SCBVolume -ID 6005076801A707416800000000000B76 -NewSize 2 |ft name,id,logical_capacity
name id logical_capacity
---- -- ----------------
DemoThin03 6005076801A707416800000000000B76 2000000000
Resize the volume to 2GB(from 1GB)
#>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName=$true,Position=0)][Alias("ID")][String] $VolumeID,
        [Parameter(Mandatory = $true,Position=1 )][long] $NewSize,
        [Parameter(Position=2 )][ValidateSet("TiB","GiB","GB","Byte")][String] $SizeUnit="GB",
        [SCBConnection]$SCBConnection=$Global:DefaultConnection
    )
    $CmdletName="Resize-SCBVolume"
    Write-Verbose ("$CmdletName.ENTER: SCBConnection=($($SCBConnection|ConvertTo-Json -Compress)), ID=$VolumeID, NewSize=$NewSize")
    ValidateConnection $SCBConnection
    $request=New-Object SCBRequest;
    $request.Method="Patch"
    $request.SCBConnection=$SCBConnection;
    $request.MappingInfor.ObjectName="SCBVolume"
    $request.MappingInfor.FieldsToRenamed.Add("volume_id","volume_id_on_array")
    $request.MappingInfor.FieldsToRenamed.Add("container_name","space_name")
    $request.MappingInfor.FieldsToRenamed.Add("container_id","space_id")
    $size=(Get-SCBVolume -SCBConnection $SCBConnection -ID $VolumeID).logical_capacity
    Write-Verbose ("$CmdletName.OldSize: $size")
    $request.Key=$VolumeID
    if($SizeUnit.Equals("GB")){
        $request.Body.Add("new_size",$newSize)
    }else{
        [long]$finalSize=$newSize
        switch($SizeUnit.ToUpper()){
            "TIB"{$finalSize=1024*$newSize; $SizeUnit="gib";}
            "GIB"{$finalSize=$newSize;$SizeUnit="gib";}
            "BYTE"{$finalSize=$newSize;$SizeUnit="byte";}
        }
        $request.Body.Add("new_size",$finalSize)
        $request.Body.Add("size_unit",$SizeUnit)
    }
    $ret=GetResponse $request $CmdletName
    Write-Verbose ("$CmdletName.END_REST_METHOD")
    return $ret
}
#endregion

#region SCBMapping
function Get-SCBHostVolMapping{
<#
.SYNOPSIS
    Retrieves the Mapping relationships between volumes and hosts on the Storage arrays managed by the PowerShell interface user.
 
.PARAMETER HostVolMappingID
    Alias ID; The ID or Unique Identifier(id) for the mapping relationship; Alias ID can be used instead;
 
.PARAMETER HostID
    Show only the mapping relationships of hosts with specified HostID
 
.PARAMETER VolumeID
    Show only the mapping relationships of the Volume
 
.PARAMETER SCBConnection
    The client handle for SCB server. It can be created via New-SCBConnection. If the SCBConnection is not specified, a most recently used SCBConnection will be utilized by default. The connection can be found via the global variable $Global:DefaultConnection. If multiple SCB servers are connected, you must explicitly specify the SCBConnection parameter.
 
.EXAMPLE
Get-SCBHostVolMapping -SCBConnection $sc|ft
 id volume host lun_number
 -- ------ ---- ----------
122 6005076802B98BD390000000000000B2 6 10
117 6005076802B98BD390000000000000AF 6 9
114 6005076802B98BD390000000000000AD 6 8
.....
Retrieves all the mapping relationships.
   
.EXAMPLE
Get-SCBHostVolMapping 122 -SCBConnection $sc|ft
 
 id volume host lun_number
 -- ------ ---- ----------
122 6005076802B98BD390000000000000B2 6 10
Gets mapping information via the mapping ID.
 
.EXAMPLE
Get-SCBHostVolMapping -HostID 5 -SCBConnection $sc|ft
 id volume host lun_number
 -- ------ ---- ----------
113 6005076802B98BD390000000000000AC 5 2
  3 6005076802B98BD39000000000000018 5 1
  4 6005076802B98BD39000000000000017 5 0
...
Gets the mapping relationships for the given host.
#>

    [CmdletBinding(DefaultParameterSetname="ID")]
    param(
        [parameter(ParameterSetName="ID",Position=0,ValueFromPipelineByPropertyName=$true)][Alias("ID")][String] $HostVolMappingID,
        [parameter(Mandatory = $true,ParameterSetName="HostID",Position=1)][String] $HostID,
        [parameter(Mandatory = $true,ParameterSetName="Volume",Position=2)][String] $VolumeID,
        [SCBConnection]$SCBConnection=$Global:DefaultConnection
    )
    $CmdletName="Get-SCBHostVolMapping"
    Write-Verbose("$CmdletName.ENTER: SCBConnection=($($SCBConnection|ConvertTo-Json -Compress)), ID=$HostVolMappingID, HostID=$HostID, VolumeID=$VolumeID")
    ValidateConnection $SCBConnection
    $request=New-Object SCBRequest;
    $request.Method="Get"
    $request.SCBConnection=$SCBConnection;
    $request.MappingInfor.ObjectName="SCBHostVolMapping"
    if(![String]::IsNullOrWhiteSpace($HostVolMappingID)){
        $request.Key=$HostVolMappingID
    }
    if (![String]::IsNullOrWhiteSpace($HostID)) {
        $request.UrlParams.Add("host","$HostID")
    }
    if (![String]::IsNullOrWhiteSpace($VolumeID)) {
        $request.UrlParams.Add("volume","$VolumeID")
    }
    $ret=GetResponse $request $CmdletName
    Write-Verbose ("$CmdletName.EXIT")
    return $ret
}

function New-SCBHostVolMapping{
<#
.SYNOPSIS
    Maps the volume. The hosts can be specified via HostID, HostName or their endpoints. Returns mapping relationships if succeed while $null if fail.
 
.PARAMETER VolumeID
    The ID of the volume to be mapped.
 
.PARAMETER HostID
    The ID(s) of the host(s) that the specified volume will be mapped to. System.Collections.ArrayList object or just a Object[] is accepted here for multiple host IDs;
 
.PARAMETER HostName
    The Name of the host that the specified volume will be mapped to.
 
.PARAMETER Initiator
    The host endpoint(s) (either FC or iSCSI); System.Collections.ArrayList object or just a Object[] is accepted here for multiple host endpoints.
 
.PARAMETER SCBConnection
    The client handle for SCB server. It can be created via New-SCBConnection. If the SCBConnection is not specified, a most recently used SCBConnection will be utilized by default. The connection can be found via the global variable $Global:DefaultConnection. If multiple SCB servers are connected, you must explicitly specify the SCBConnection parameter.
 
.EXAMPLE
    $host=Get-SCBHost -ArrayID 0000020069C1D05A -Initiator 21000024FF2DA3F7; New-SCBHostVolMapping -VolumeID 6005076801A707416800000000000B76 -HostID $host.id
    {@{id=20; volume=6005076801A707416800000000000B76; host=21; lun_number=4}}
    Create a mapping relationship by spedifying HostID
 
.EXAMPLE
    New-SCBHostVolMapping -VolumeID 6005076801A707416800000000000B76 -ArrayID 0000020069C1D05A -Initiator iqn.1991-05.com.microsoft:agino,21000024FF2DAFF6,21000024FF2DC5FB
    {@{id=21; volume=6005076801A707416800000000000B76; host=22; lun_number=4}, @{id=20; volume=6005076801A707416800000000000B76; host=21; lun_number=4}, @{id=19; volume=6005076801A...
    Map a volume to multiple hosts by specifying host endpoints
      
.EXAMPLE
    New-SCBHostVolMapping -VolumeID 6005076802B98BD39000000000001B09 -HostID 12,6,3 -SCBConnection $client
    {@{id=614; volume=6005076802B98BD39000000000001B09; host=12; lun_number=7}, @{id=615; volume=6005076802B98BD39000000000001B09; host=6; lun_number=7}, @{id=616; volume=600507680...
    Map a volume to multiple hosts by their IDs
      
.EXAMPLE
    New-SCBHostVolMapping -VolumeID 6005076801A707416800000000000B76 -HostName win2012r2x215
    {@{id=21; volume=6005076801A707416800000000000B76; host=22; lun_number=1}}
    Create the mapping relationship using host name
 
#>

    [CmdletBinding(DefaultParameterSetName="Host")]
    param(
        [parameter(Mandatory = $true,Position=0)][String] $VolumeID,
        [parameter(Mandatory = $true,ParameterSetName="Host", Position=1)][Object] $HostID,
        [parameter(Mandatory = $true,ParameterSetName="HostName", Position=1)][String] $HostName,
        [parameter(Mandatory = $true,ParameterSetName="Array", Position=1)][Object] $Initiators,
        [SCBConnection]$SCBConnection=$Global:DefaultConnection
    )
    $CmdletName="New-SCBHostVolMapping"
    Write-Verbose ("$CmdletName.ENTER: SCBConnection=($($SCBConnection|ConvertTo-Json -Compress)), VolumeID=$VolumeID,HostName=$HostName,HostID=$HostID,Initiators=$Initiators")
    ValidateConnection $SCBConnection
    $request=New-Object SCBRequest;
    $request.Method="Post"
    $request.SCBConnection=$SCBConnection;
    $request.MappingInfor.ObjectName="SCBHostVolMapping"
    $arrayID=(Get-SCBVolume -SCBConnection $SCBConnection -ID $VolumeID).array
    $request.Body.Add("volume_id",$VolumeID)
    if(![String]::IsNullOrWhiteSpace($HostName)){    #HostName=hostname1
        $HostID=(Get-SCBHost -SCBConnection $SCBConnection -Name $HostName -ArrayID $ArrayID).id
    }
    if($Initiators -ne $Null){    #-Initiators 21000024FF2DA3F7,21000024FF2DFFFE,iqn.1991-05.com.microsoft:win2012r2x214.vmm.org
        $HostID=((Get-SCBHost -SCBConnection $SCBConnection -ArrayID $arrayID -Initiators $Initiators).id)
    }
    if($HostID -ne $Null){        #-HostIDs 22,21,1 || 21 ||@(21)
        $hosts = @()
        if($HostID -Is "System.Collections.ArrayList" -Or $HostID.GetType().IsArray){
            foreach($idTemp in $HostID){
                $hosts+=$idTemp.ToString()
            }
            $request.Body.Add("host_id",$hosts)        #=>["21","22","1"]
        }else{
            $request.Body.Add("host_id",$HostID.ToString())
        }
    }
    $ret=GetResponse $request $CmdletName
    Write-Verbose ("$CmdletName.EXIT")
    return $ret
}

function Remove-SCBHostVolMapping{
<#
.SYNOPSIS
    Unmaps the volume from the selected host; $True if succeed while $False if fail.
 
.PARAMETER VolumeID
    The ID(s) of the volume(s) to be unmapped.
 
.PARAMETER HostID
    The ID(s) of the host(s) that the specified volume will be unmapped from. System.Collections.ArrayList object or just a Object[] is accepted here for multiple host IDs.
 
.PARAMETER HostName
    The Name of the host that the specified volume will be unmapped from.
 
.PARAMETER Initiators
    The host endpoint(s) (either FC or iSCSI); System.Collections.ArrayList object or just a Object[] is accepted here for multiple host endpoints.
 
.PARAMETER SCBConnection
    The client handle for SCB server. It can be created via New-SCBConnection. If the SCBConnection is not specified, a most recently used SCBConnection will be utilized by default. The connection can be found via the global variable $Global:DefaultConnection. If multiple SCB servers are connected, you must explicitly specify the SCBConnection parameter.
 
.EXAMPLE
Remove-SCBHostVolMapping -VolumeID 6005076801A707416800000000000B76 -HostName fakeFCHost -SCBConnection $client
Remove the mapping relationships of the volume from host with name fakeFCHost
 
.EXAMPLE
Remove-SCBHostVolMapping -VolumeID 6005076801A707416800000000000B76 -Initiators 21000024FF2DA3F7,iqn.1991-05.com.microsoft:win2012r2x214.vmm.org,21000024FF2DFFFE -SCBConnection $client
Remove the mapping relationships of the volume from hosts with the specified initiators
 
.EXAMPLE
Remove-SCBHostVolMapping -VolumeID 6005076801A707416800000000000B76 -HostIDs 22,21 -SCBConnection $client
Remove the mapping relationships of the volume from hosts 22 and 21
 
#>

    [CmdletBinding(DefaultParameterSetName="Host")]
    param(
        [parameter(Mandatory = $true,Position=0)][String] $VolumeID,
        [parameter(Mandatory = $true,ParameterSetName="Host", Position=1)][Object] $HostID,
        [parameter(Mandatory = $true,ParameterSetName="HostName", Position=1)][String] $HostName,
        [parameter(Mandatory = $true,ParameterSetName="Array", Position=1)][Object] $Initiators,
        [SCBConnection]$SCBConnection=$Global:DefaultConnection
    )
    $CmdletName="Remove-SCBHostVolMapping"
    Write-Verbose ("$CmdletName.ENTER: SCBConnection=($($SCBConnection|ConvertTo-Json -Compress)), VolumeID=$VolumeID,HostID=$HostID,ArrayID=$ArrayID,Initiators=$Initiators,HostName=$HostName")
    ValidateConnection $SCBConnection 
    $request=New-Object SCBRequest;
    $request.Method="Delete"
    $request.SCBConnection=$SCBConnection;
    $request.MappingInfor.ObjectName="SCBHostVolMapping"
    $arrayID=(Get-SCBVolume -SCBConnection $SCBConnection -ID $VolumeID).array
    $request.Body.Add("volume_id",$VolumeID)
    $hosts = @()
    if(![String]::IsNullOrWhiteSpace($HostName)){    #HostName=hostname1
        $HostID=(Get-SCBHost -SCBConnection $SCBConnection -Name $HostName -ArrayID $ArrayID).id
    }
    if($Initiators -ne $Null){ #-Initiators 21000024FF2DA3F7,21000024FF2DFFFE,iqn.1991-05.com.microsoft:win2012r2x214.vmm.org
        $HostID=((Get-SCBHost -SCBConnection $SCBConnection -ArrayID $arrayID -Initiators $Initiators).id)
    }
    if($HostID -ne $Null){        #-HostIDs 22,21,1 || 21 ||@(21)
        if($HostID -Is "System.Collections.ArrayList" -Or $HostID.GetType().IsArray){
            foreach($idTemp in $HostID){
                $hosts+=$idTemp.ToString()
            }
            $request.Body.Add("host_id",$hosts)        #=>["21","22","1"]
        }else{
            $request.Body.Add("host_id",$HostID.ToString())
        }
    }
    $ret=GetResponse $request $CmdletName
    if([String]::IsNullOrEmpty("$ret")){$ret=$True}else{$ret=$False}
    Write-Verbose ("$CmdletName.EXIT")
    return $ret
}
#endregion

Set-Alias -Name    mkclient     -Value    New-SCBConnection
Set-Alias -Name    lsspace      -Value    Get-SCBSpace 
Set-Alias -Name    lsservice    -Value    Get-SCBService
Set-Alias -Name    lshost       -Value    Get-SCBHost 
Set-Alias -Name    lsvol        -Value    Get-SCBVolume 
Set-Alias -Name    mkvol        -Value    New-SCBVolume 
Set-Alias -Name    rmvol        -Value    Remove-SCBVolume 
Set-Alias -Name    chvol        -Value    Resize-SCBVolume
Set-Alias -Name    lsmapping    -Value    Get-SCBHostVolMapping 
Set-Alias -Name    mkmapping    -Value    New-SCBHostVolMapping  
Set-Alias -Name    rmmapping    -Value    Remove-SCBHostVolMapping

Export-ModuleMember -Function New-SCBConnection,Get-SCBSpace,Get-SCBService,Get-SCBHost,Get-SCBVolume
Export-ModuleMember -Function New-SCBVolume,Remove-SCBVolume,Resize-SCBVolume,Get-SCBHostVolMapping,New-SCBHostVolMapping,Remove-SCBHostVolMapping
Export-ModuleMember -Alias mkclient,lsspace,lsservice,lshost,lsvol,mkvol,rmvol,chvol,lsmapping,mkmapping,rmmapping
#endregion

# SIG # Begin signature block
# MIINMwYJKoZIhvcNAQcCoIINJDCCDSACAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUGmdv8qRKDpeo0Ag6txBTb1b7
# viCgggpoMIIFBzCCA++gAwIBAgIQO50nymBTQHmVgRJdr11LljANBgkqhkiG9w0B
# AQsFADB/MQswCQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRp
# b24xHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxMDAuBgNVBAMTJ1N5
# bWFudGVjIENsYXNzIDMgU0hBMjU2IENvZGUgU2lnbmluZyBDQTAeFw0xNzExMTAw
# MDAwMDBaFw0yMDEyMDkyMzU5NTlaMIGgMQswCQYDVQQGEwJDTjEQMA4GA1UECAwH
# QmVpamluZzEQMA4GA1UEBwwHQmVpamluZzEvMC0GA1UECgwmSUJNIChDaGluYSkg
# SW52ZXN0bWVudCBDb21wYW55IExpbWl0ZWQxCzAJBgNVBAsMAklUMS8wLQYDVQQD
# DCZJQk0gKENoaW5hKSBJbnZlc3RtZW50IENvbXBhbnkgTGltaXRlZDCCASAwDQYJ
# KoZIhvcNAQEBBQADggENADCCAQgCggEBAPLD5ch7kY3fK7NRk6kPkWafTZzGveXm
# ZCIqDJs4Et5wt5IhZzWIeMsZ3bu+M++lhEJieQCMnPATx/UVlaKxZNHS2Lhp2AlO
# nTiNCoPsmpPMeX93dwgvNPut0Hnd+jQdAmi2SCECboAmC6nOdC6oDrvqUkZ6Dk7Y
# n8g+R3GMenDc8ytkEO/Ou8+ySznHD/4JCfHyP0AuaBdejJ7fXcoXG5Z7wptKp5e4
# 0+cmbU0I6bUf8vgC5GD7xtwauN/aA3CqIuOX1u/gk0b5vR51YKYkF6HKJyrn2JlG
# 5z17FRMLVGAKYXWpaTD6Ib0GnsbLsTed80VYqxpAkpZ3g2ut+/Tz9AMCAQOjggFd
# MIIBWTAJBgNVHRMEAjAAMA4GA1UdDwEB/wQEAwIHgDArBgNVHR8EJDAiMCCgHqAc
# hhpodHRwOi8vc3Yuc3ltY2IuY29tL3N2LmNybDBhBgNVHSAEWjBYMFYGBmeBDAEE
# ATBMMCMGCCsGAQUFBwIBFhdodHRwczovL2Quc3ltY2IuY29tL2NwczAlBggrBgEF
# BQcCAjAZDBdodHRwczovL2Quc3ltY2IuY29tL3JwYTATBgNVHSUEDDAKBggrBgEF
# BQcDAzBXBggrBgEFBQcBAQRLMEkwHwYIKwYBBQUHMAGGE2h0dHA6Ly9zdi5zeW1j
# ZC5jb20wJgYIKwYBBQUHMAKGGmh0dHA6Ly9zdi5zeW1jYi5jb20vc3YuY3J0MB8G
# A1UdIwQYMBaAFJY7U/B5M5evfYPvLivMyreGHnJmMB0GA1UdDgQWBBSCjmKRqxXa
# sDjI6emU05joKoNBczANBgkqhkiG9w0BAQsFAAOCAQEAUV14IxCZrkkGCKFpn048
# G7O8OYESMkJPzBUt35Rn2eXtZI0HUgDEZ4iKi8xCrZ6ssz+eATSm4m22Fc+0wRVf
# o1Uq4VfGyFPkXLAPhvQUFEsevFK9ps9buaWGTwSixRjpPOwfymcEz/6p7MIiXteL
# mIlfPDY4+ie3IWCo2PFEvrVzJObcuuEjEylraxkcVvQte7f4i3QHGQvkLhWA0OGY
# WT5YDJD6s229ujueHBkvvDGmWP7dmFWe2j2jpgJKOxZFPQ/+YyrAlRohzDAcYgGj
# 6jxuXLUoFgKiRyJ+PDmAYI695AE6QWJBC4EwmTNZNFNnu5JbFCzipRQSD122QsLh
# wzCCBVkwggRBoAMCAQICED141/l2SWCyYX308B7KhiowDQYJKoZIhvcNAQELBQAw
# gcoxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UE
# CxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDIwMDYgVmVy
# aVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8
# VmVyaVNpZ24gQ2xhc3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1
# dGhvcml0eSAtIEc1MB4XDTEzMTIxMDAwMDAwMFoXDTIzMTIwOTIzNTk1OVowfzEL
# MAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYD
# VQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMTAwLgYDVQQDEydTeW1hbnRlYyBD
# bGFzcyAzIFNIQTI1NiBDb2RlIFNpZ25pbmcgQ0EwggEiMA0GCSqGSIb3DQEBAQUA
# A4IBDwAwggEKAoIBAQCXgx4AFq8ssdIIxNdok1FgHnH24ke021hNI2JqtL9aG1H3
# ow0Yd2i72DarLyFQ2p7z518nTgvCl8gJcJOp2lwNTqQNkaC07BTOkXJULs6j20Tp
# Uhs/QTzKSuSqwOg5q1PMIdDMz3+b5sLMWGqCFe49Ns8cxZcHJI7xe74xLT1u3LWZ
# Qp9LYZVfHHDuF33bi+VhiXjHaBuvEXgamK7EVUdT2bMy1qEORkDFl5KK0VOnmVuF
# NVfT6pNiYSAKxzB3JBFNYoO2untogjHuZcrf+dWNsjXcjCtvanJcYISc8gyUXsBW
# UgBIzNP4pX3eL9cT5DiohNVGuBOGwhud6lo43ZvbAgMBAAGjggGDMIIBfzAvBggr
# BgEFBQcBAQQjMCEwHwYIKwYBBQUHMAGGE2h0dHA6Ly9zMi5zeW1jYi5jb20wEgYD
# VR0TAQH/BAgwBgEB/wIBADBsBgNVHSAEZTBjMGEGC2CGSAGG+EUBBxcDMFIwJgYI
# KwYBBQUHAgEWGmh0dHA6Ly93d3cuc3ltYXV0aC5jb20vY3BzMCgGCCsGAQUFBwIC
# MBwaGmh0dHA6Ly93d3cuc3ltYXV0aC5jb20vcnBhMDAGA1UdHwQpMCcwJaAjoCGG
# H2h0dHA6Ly9zMS5zeW1jYi5jb20vcGNhMy1nNS5jcmwwHQYDVR0lBBYwFAYIKwYB
# BQUHAwIGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIBBjApBgNVHREEIjAgpB4wHDEa
# MBgGA1UEAxMRU3ltYW50ZWNQS0ktMS01NjcwHQYDVR0OBBYEFJY7U/B5M5evfYPv
# LivMyreGHnJmMB8GA1UdIwQYMBaAFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqG
# SIb3DQEBCwUAA4IBAQAThRoeaak396C9pK9+HWFT/p2MXgymdR54FyPd/ewaA1U5
# +3GVx2Vap44w0kRaYdtwb9ohBcIuc7pJ8dGT/l3JzV4D4ImeP3Qe1/c4i6nWz7s1
# LzNYqJJW0chNO4LmeYQW/CiwsUfzHaI+7ofZpn+kVqU/rYQuKd58vKiqoz0EAeq6
# k6IOUCIpF0yH5DoRX9akJYmbBWsvtMkBTCd7C6wZBSKgYBU/2sn7TUyP+3Jnd/0n
# lMe6NQ6ISf6N/SivShK9DbOXBd5EDBX6NisD3MFQAfGhEV0U5eK9J0tUviuEXg+m
# w3QFCu+Xw4kisR93873NQ9TxTKk/tYuEr2Ty0BQhMYICNTCCAjECAQEwgZMwfzEL
# MAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYD
# VQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMTAwLgYDVQQDEydTeW1hbnRlYyBD
# bGFzcyAzIFNIQTI1NiBDb2RlIFNpZ25pbmcgQ0ECEDudJ8pgU0B5lYESXa9dS5Yw
# CQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcN
# AQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUw
# IwYJKoZIhvcNAQkEMRYEFKTJWEg4YGnRb7NBbHPMWOMTOn+AMA0GCSqGSIb3DQEB
# AQUABIIBAGYBLpRlxwaCdv3s2z8V8AYCwFCiihM/E9p8+3TyYPiNTy3T5A8a8oRk
# IKFBtG72O45x01ZeBQhnhchuN6/fSmEJmr4lNqUXU3vrwWzkNlWJVUZiJ6+QONpI
# KNSG+SAexlEvOppMd3+gUquib/UisAxzwK/X/hD8wVB4/PrM2WcgPovz8QzuPfEH
# XfssJjbuGCxlB4Lt/moFwS3DwK7l7c8TtQ7emq1zfileSQOMssEpq10p8y1ungza
# M/zkY+lPi37iKZ1N55B+KqX4dxLXVrtwgKQZZBAYzemIOjxqJUEvZi+nOqGzHiDi
# beCVwgNHxK3m/DuliUqKBSMhLQAIso0=
# SIG # End signature block