CLI/HostManagement.psm1

####################################################################################
## © 2020,2021 Hewlett Packard Enterprise Development LP
##
## Permission is hereby granted, free of charge, to any person obtaining a
## copy of this software and associated documentation files (the "Software"),
## to deal in the Software without restriction, including without limitation
## the rights to use, copy, modify, merge, publish, distribute, sublicense,
## and/or sell copies of the Software, and to permit persons to whom the
## Software is furnished to do so, subject to the following conditions:
##
## The above copyright notice and this permission notice shall be included
## in all copies or substantial portions of the Software.
##
## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
## THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
## OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
## ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
## OTHER DEALINGS IN THE SOFTWARE.
##
## File Name: HostManagement.psm1
## Description: Host Management cmdlets
##
## Created: November 2019
## Last Modified: November 2019
## History: v3.0 - Created
#####################################################################################

$Info = "INFO:"
$Debug = "DEBUG:"
$global:VSLibraries = Split-Path $MyInvocation.MyCommand.Path
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

############################################################################################################################################
## FUNCTION Test-CLIObject
############################################################################################################################################
Function Test-CLIObject 
{
Param(     
    [string]$ObjectType, 
    [string]$ObjectName ,
    [string]$ObjectMsg = $ObjectType, 
    $SANConnection = $global:SANConnection
    )

    $IsObjectExisted = $True
    $ObjCmd = $ObjectType -replace ' ', '' 
    $Cmds = "show$ObjCmd $ObjectName"
    
    $Result = Invoke-CLICommand -Connection $SANConnection -cmds  $Cmds
    if ($Result -like "no $ObjectMsg listed")
    {
        $IsObjectExisted = $false
    }
    return $IsObjectExisted
    
} # End FUNCTION Test-CLIObject

############################################################################################################################################
################################################################## FUNCTION Get-Host #######################################################
############################################################################################################################################
Function Get-Host
{
<#
  .SYNOPSIS
    Lists hosts
  
  .DESCRIPTION
    Queries hosts
        
  .EXAMPLE
    Get-Host
    Lists all hosts

  .EXAMPLE
    Get-Host -hostName HV01A
    List host HV01A
    
  .EXAMPLE
    Get-Host -Domain scvmm
    
  .EXAMPLE
    Get-Host -D

  .EXAMPLE
    Get-Host -CHAP

  .EXAMPLE
    Get-Host -Descriptor

  .EXAMPLE
    Get-Host -Agent

  .EXAMPLE
    Get-Host -Pathsum

  .EXAMPLE
    Get-Host -Persona

  .EXAMPLE
    Get-Host -Listpersona

  .PARAMETER D
    Shows a detailed listing of host and path information. This option can
    be used with -agent and -domain options.

  .PARAMETER Verb
    Shows a verbose listing of all host information. This option cannot
    be used with -d.

  .PARAMETER CHAP
    Shows the CHAP authentication properties. This option cannot be used
    with -d.

  .PARAMETER Descriptor
    Shows the host descriptor information. This option cannot be used with
    -d.

  .PARAMETER Agent
    Shows information provided by host agent.

  .PARAMETER Pathsum
    Shows summary information about hosts and paths. This option cannot be
    used with -d.

  .PARAMETER Persona
    Shows the host persona settings in effect. This option cannot be used
    with -d.

  .PARAMETER Listpersona
    Lists the defined host personas. This option cannot be used with -d.

  .PARAMETER NoName
    Shows only host paths (WWNs and iSCSI names) not assigned to any host.
    This option cannot be used with -d.

  .PARAMETER Domain
    Shows only hosts that are in domains or domain sets that match one or
    more of the specifier <domainname_or_pattern> or set <domainset>
    arguments. The set name <domain_set> must start with "set:". This
    specifier does not allow listing objects within a domain of which the
    user is not a member.

  .PARAMETER CRCError
    Shows the CRC error counts for the host/port.
    
  .PARAMETER hostName
    Specify new name of the host
    
  .PARAMETER SANConnection
    Specify the SAN Connection object created with New-CLIConnection or New-PoshSshConnection
    
  .Notes
    NAME: Get-Host
    LASTEDIT: 19/11/2019
    KEYWORDS: Get-Host
   
  .Link
     http://www.hpe.com
 
 #Requires PS -Version 3.0

 #>

[CmdletBinding()]
    param(
        [Parameter(Position=0, Mandatory=$false)]
        [System.String]
        $Domain,
        
        [Parameter(Position=1, Mandatory=$false, ValueFromPipeline=$true)]
        [Switch]
        $D,
        
        [Parameter(Position=2, Mandatory=$false, ValueFromPipeline=$true)]
        [Switch]
        $Verb,
        
        [Parameter(Position=3, Mandatory=$false, ValueFromPipeline=$true)]
        [Switch]
        $CHAP,
        
        [Parameter(Position=4, Mandatory=$false, ValueFromPipeline=$true)]
        [Switch]
        $Descriptor,
        
        [Parameter(Position=5, Mandatory=$false, ValueFromPipeline=$true)]
        [Switch]
        $Agent,
        
        [Parameter(Position=6, Mandatory=$false, ValueFromPipeline=$true)]
        [Switch]
        $Pathsum,
        
        [Parameter(Position=7, Mandatory=$false, ValueFromPipeline=$true)]
        [Switch]
        $Persona,
        
        [Parameter(Position=8, Mandatory=$false, ValueFromPipeline=$true)]
        [Switch]
        $Listpersona,
        
        [Parameter(Position=9, Mandatory=$false, ValueFromPipeline=$true)]
        [Switch]
        $NoName,
        
        [Parameter(Position=10, Mandatory=$false, ValueFromPipeline=$true)]
        [Switch]
        $CRCError,
        
        [Parameter(Position=11, Mandatory=$false)]
        [System.String]
        $hostName,
        
        [Parameter(Position=12, Mandatory=$false, ValueFromPipeline=$true)]
        $SANConnection = $global:SANConnection        
    )        
    
    Write-DebugLog "Start: In Get-Host - validating input values" $Debug 
    #check if connection object contents are null/empty
    if (!$SANConnection)
    {        
        #check if connection object contents are null/empty
        $Validate1 = Test-CLIConnection $SANConnection
        if($Validate1 -eq "Failed")
        {
            #check if global connection object contents are null/empty
            $Validate2 = Test-CLIConnection $global:SANConnection
            if($Validate2 -eq "Failed")
            {
                Write-DebugLog "Connection object is null/empty or the array address (FQDN/IP Address) or user credentials in the connection object are either null or incorrect. Create a valid connection object using New-CLIConnection or New-PoshSshConnection" "ERR:"
                Write-DebugLog "Stop: Exiting Get-Host since SAN connection object values are null/empty" $Debug
                return "Unable to execute the cmdlet Get-Host since no active storage connection session exists. `nUse New-PoshSSHConnection or New-CLIConnection to start a new storage connection session."
            }
        }
    }
    
    $cliresult1 = Test-PARCli -SANConnection $SANConnection
    if($cliresult1 -match "FAILURE :")
    {
        write-debuglog "$cliresult1" "ERR:" 
        return $cliresult1
    }
    
    $CurrentId = $CurrentName = $CurrentPersona = $null
    $ListofvHosts = @()    
    
    $GetHostCmd = "showhost "
    
    if ($Domain)
    {
        $GetHostCmd +=" -domain $Domain"
    }
    if ($D)
    {
        $GetHostCmd +=" -d "
    }
    if ($Verb)
    {
        $GetHostCmd +=" -verbose "
    }
    if ($CHAP)
    {
        $GetHostCmd +=" -chap "
    }
    if ($Descriptor)
    {
        $GetHostCmd +=" -desc "
    }
    if ($Agent)
    {
        $GetHostCmd +=" -agent "
    }
    if ($Pathsum)
    {
        $GetHostCmd +=" -pathsum "
    }
    if ($Persona)
    {
        $GetHostCmd +=" -persona "
    }
    if ($Listpersona)
    {
        $GetHostCmd +=" -listpersona "
    }
    if ($NoName)
    {
        $GetHostCmd +=" -noname "
    }
    if ($CRCError)
    {
        $GetHostCmd +=" -lesb "
    }    
    if($hostName)
    {
        $objType = "host"
        $objMsg  = "hosts"
        
        ## Check Host Name
        ##
        if ( -not (Test-CLIObject -objectType $objType -objectName $hostName -objectMsg $objMsg -SANConnection $SANConnection))
        {
            write-debuglog "host $hostName does not exist. Nothing to List" "INFO:" 
            return "FAILURE : No host $hostName found"
        }
    }
    
    $GetHostCmd+=" $hostName"
    #write-host "$GetHostCmd"
    $Result = Invoke-CLICommand -Connection $SANConnection -cmds  $GetHostCmd    
    write-debuglog "Get list of Hosts" "INFO:" 
    if ($Result -match "no hosts listed")
    {
        return "Success : no hosts listed"
    }
    if ($Verb -or $Descriptor)
    {
        return $Result
    }
    
    $tempFile = [IO.Path]::GetTempFileName()
    $Header = $Result[0].Trim() -replace '-WWN/iSCSI_Name-' , ' Address' 
    
    set-content -Path $tempFile -Value $Header
    $Result_Count = $Result.Count - 3
    if($Agent)
    {
        $Result_Count = $Result.Count - 3            
    }
    if($Result.Count -gt 3)
    {    
        $CurrentId = $null
        $CurrentName = $null
        $CurrentPersona = $null        
        $address = $null
        $Port = $null
        
        $Flg = "false"
        
        foreach ($s in $Result[1..$Result_Count])
        {    
            if($Pathsum)
            {
                $s =  [regex]::Replace($s , "," , "|"  )  # Replace ',' with "|"
            }            
            if($Flg -eq "true")
            {
                $temp = $s.Trim()
                $temp1 = $temp.Split(',')
                if($temp1[0] -match "--")
                {
                    $temp =  [regex]::Replace($temp , "--" , ""  )  # Replace '--' with ""
                    $s = $temp
                }
            }
            $Flg = "true"
            
            $match = [regex]::match($s, "^ +")   # Match Line beginning with 1 or more spaces
            if (-not ($match.Success))
            {
                $s= $s.Trim()                
                $s= [regex]::Replace($s, " +" , "," )    # Replace spaces with comma (,)
                                
                $sTemp = $s.Split(',')
                $TempCnt = $sTemp.Count
                if($TempCnt -eq 2)
                {
                    $address = $sTemp[0]
                    $Port = $sTemp[1] # [regex]::Replace($sTemp[4] , "-+" , "" ) # Replace '----' with ""
                }
                else
                {
                    $CurrentId =  $sTemp[0]
                    $CurrentName = $sTemp[1]
                    $CurrentPersona = $sTemp[2]            
                    $address = $sTemp[3]
                    $Port = $sTemp[4] # [regex]::Replace($sTemp[4] , "-+" , "" ) # Replace '----' with ""
                }
                
                $vHost = New-Object -TypeName _vHost 
                $vHost.ID = $CurrentId
                $vHost.Persona = $currentPersona
                $vHost.Name = $CurrentName
                $vHost.Address = $address
                $vHost.Port= $port
            }            
            else
            {
                $s = $s.trim()
                $s= [regex]::Replace($s, " +" , "," )                                
                $sTemp = $s.Split(',')
                $TempCnt1 = $sTemp.Count
                
                if($TempCnt1 -eq 2)
                {
                    $address = $sTemp[0]
                    $Port = $sTemp[1] # [regex]::Replace($sTemp[4] , "-+" , "" ) # Replace '----' with ""
                }
                else
                {
                    $CurrentId =  $sTemp[0]
                    $CurrentName = $sTemp[1]
                    $CurrentPersona = $sTemp[2]            
                    $address = $sTemp[3]
                    $Port = $sTemp[4] # [regex]::Replace($sTemp[4] , "-+" , "" ) # Replace '----' with ""
                }
                
                $vHost = New-Object -TypeName _vHost 
                $vHost.ID = $CurrentId
                $vHost.Persona = $currentPersona
                $vHost.Name = $CurrentName
                $vHost.Address = $address
                $vHost.Port= $port
            }
            
            $ListofvHosts += $vHost        
        }    
    }    
    else
    {
        del $tempFile
        return "Success : No Data Available for Host Name :- $hostName"
    }
    del $tempFile
    $ListofvHosts    
    
} # ENd Get-Host

#####################################################################################################################
## FUNCTION Get-HostSet
#####################################################################################################################
Function Get-HostSet
{
<#
  .SYNOPSIS
    show host set(s) information
  
  .DESCRIPTION
    The showhostset command lists the host sets defined on the storage system and their members.
        
  .EXAMPLE
    Get-HostSet
    List all host set information

  .EXAMPLE
    Get-HostSet -D myset
    Show the details of myset
    
  .EXAMPLE
    Get-HostSet -hostSetName "MyVVSet"
    List Specific HostSet name "MyVVSet"
    
  .EXAMPLE
    Get-HostSet -hostName "MyHost"
    Show the host sets containing host "MyHost"
    
  .EXAMPLE
    Get-HostSet -D
    Show a more detailed listing of each set
    
  .PARAMETER D
    Show a more detailed listing of each set.
    
  .PARAMETER hostSetName
    Specify name of the hostsetname to be listed.

  .PARAMETER hostName
    Show host sets that contain the supplied hostnames or patterns.

  .PARAMETER summary
    Shows host sets with summarized output with host set names and number of hosts in those sets.
    
  .PARAMETER SANConnection
    Specify the SAN Connection object created with New-CLIConnection or New-PoshSshConnection
    
  .Notes
    NAME: Get-HostSet
    LASTEDIT: 29/05/2021
    KEYWORDS: Get-HostSet
   
  .Link
     http://www.hpe.com
 
 #Requires PS -Version 3.0

 #>

[CmdletBinding()]
    param(
        [Parameter(Position=0, Mandatory=$false)]
        [System.String]
        $hostSetName,
        
        [Parameter(Position=1, Mandatory=$false)]
        [System.String]
        $hostName,

        [Parameter(Position=2, Mandatory=$false)]
        [Switch]
        $D,
        
        [Parameter(Position=3, Mandatory=$false)]
        [Switch]
        $summary,
        
        [Parameter(Position=4, Mandatory=$false, ValueFromPipeline=$true)]
        $SANConnection = $global:SANConnection 
       
    )        
    
    Write-DebugLog "Start: In Get-HostSet - validating input values" $Debug 
    #check if connection object contents are null/empty
    if(!$SANConnection)
    {            
        #check if connection object contents are null/empty
        $Validate1 = Test-CLIConnection $SANConnection
        if($Validate1 -eq "Failed")
        {
            #check if global connection object contents are null/empty
            $Validate2 = Test-CLIConnection $global:SANConnection
            if($Validate2 -eq "Failed")
            {
                Write-DebugLog "Connection object is null/empty or the array address (FQDN/IP Address) or user credentials in the connection object are either null or incorrect. Create a valid connection object using New-CLIConnection or New-PoshSshConnection" "ERR:"
                Write-DebugLog "Stop: Exiting Get-HostSet since SAN connection object values are null/empty" $Debug
                return "Unable to execute the cmdlet Get-HostSet since no active storage connection session exists. `nUse New-PoshSSHConnection or New-CLIConnection to start a new storage connection session."
            }
        }
    }

    $plinkresult = Test-PARCli -SANConnection $SANConnection
    if($plinkresult -match "FAILURE :")
    {
        write-debuglog "$plinkresult" "ERR:" 
        return $plinkresult
    }
    
    $GetHostSetCmd = "showhostset "
    if($D)
    {
        $GetHostSetCmd +=" -d"
    }
    if($summary)
    {
        $GetHostSetCmd +=" -summary"
    }    
    if ($hostName)
    {        
        $GetHostSetCmd +=" -host $hostName"
    }
    if ($hostSetName)
    {        
        $GetHostSetCmd +=" $hostSetName"
    }    
    else
    {
        write-debuglog "HostSet parameter is empty. Simply return all hostset information " "INFO:"
    }
        
    $Result = Invoke-CLICommand -Connection $SANConnection -cmds  $GetHostSetCmd
    $Result
    
    <#
    if($Result -match "total")
    {
        if($summary)
        {
            $ID = $null
            $Name = $null
            $HOST_Cnt = $null
            $VVOLSC = $null
            $Flashcache = $null
            $QoS = $null
            $RC_host = $null

            $ListofvHosts = @()
            
            $tempFile = [IO.Path]::GetTempFileName()
            $Header = $Result[0].Trim() -replace 'id' , ' ID'
            set-content -Path $tempFile -Value $Header
            
            $LastItem = $Result.Count -3
            #Write-Host " Result Count =" $Result.Count
            foreach ($s in $Result[1..$LastItem] )
            {
                $s= $s.Trim()
                $s= [regex]::Replace($s, " +" , "," ) # Replace spaces with comma (,)
                $sTemp = $s.Split(',')
                $TempCnt = $sTemp.Count
                
                if($TempCnt -gt 1)
                {
                    $ID = $sTemp[0]
                    $Name = $sTemp[1]
                    $HOST_Cnt = $sTemp[2]
                    $VVOLSC = $sTemp[3]
                    $Flashcache = $sTemp[4]
                    $QoS = $sTemp[5]
                    $RC_host = $sTemp[6]
                }
                $vHostSummary = New-Object -TypeName _vHostSetSummary
                $vHostSummary.ID = $ID
                $vHostSummary.Name = $Name
                $vHostSummary.HOST_Cnt = $HOST_Cnt
                $vHostSummary.VVOLSC = $VVOLSC
                $vHostSummary.Flashcache = $Flashcache
                $vHostSummary.QoS = $QoS
                $vHostSummary.RC_host = $RC_host
                
                $ListofvHosts += $vHostSummary
            }
        }
        else
        {
            $ID = $null
            $Name = $null
            $Membr = $null
            #$address = $null
            
            $ListofvHosts = @()
            
            $tempFile = [IO.Path]::GetTempFileName()
            $Header = $Result[0].Trim() -replace 'id' , ' ID'
            set-content -Path $tempFile -Value $Header
        
            $LastItem = $Result.Count -3
            #Write-Host " Result Count =" $Result.Count
            foreach ($s in $Result[1..$LastItem] )
            {
                $s= $s.Trim()
                $s= [regex]::Replace($s, " +" , "," ) # Replace spaces with comma (,)
                $sTemp = $s.Split(',')
                $TempCnt = $sTemp.Count
                
                if($TempCnt -gt 1)
                {
                    $ID = $sTemp[0]
                    $Name = $sTemp[1]
                    $Membr = $sTemp[2]
                }
                else
                {
                    $Membr = $sTemp[0]
                }
                
                $vHost = New-Object -TypeName _vHostSet
                $vHost.ID = $ID
                $vHost.Name = $Name
                $vHost.Members = $Membr
                
                $ListofvHosts += $vHost
            }
        }
    }
    else
    {
        return $Result
    }
    del $tempFile
    $ListofvHosts
    
    <#
    if($Result -match "total")
    {
        Return $Result + "`n `n Success : Executing Get-HostSet"
    }
    else
    {
        return $Result
    }
    #>

    
} # End Get-HostSet

############################################################################################################################################
## FUNCTION New-Host
############################################################################################################################################
Function New-Host
{
<#
  .SYNOPSIS
    Creates a new host.
  
  .DESCRIPTION
    Creates a new host.
        
  .EXAMPLE
    New-Host -HostName HV01A -Persona 2 -WWN 10000000C97B142E
    Creates a host entry named HV01A with WWN equals to 10000000C97B142E
    
  .EXAMPLE
    New-Host -HostName HV01B -Persona 2 -iSCSI
    Creates a host entry named HV01B with iSCSI equals to iqn.1991-06.com.microsoft:dt-391-xp.hq.3par.com
    
  .EXAMPLE
    New-Host -HostName HV01A -Persona 2

  .EXAMPLE New-Host -HostName Host3 -iSCSI

  .EXAMPLE New-Host -HostName Host4 -iSCSI -Domain ZZZ
    
  .PARAMETER HostName
    Specify new name of the host
    
  .PARAMETER Add
    Add the specified WWN(s) or iscsi_name(s) to an existing host (at least
    one WWN or iscsi_name must be specified). Do not specify host persona.

  .PARAMETER Domain
    Create the host in the specified domain or domain set. The default is to
    create it in the current domain, or no domain if the current domain is
    not set. The domain set name must start with "set:".

  .PARAMETER Forces
    Forces the tear down of lower priority VLUN exports if necessary.

  .PARAMETER Persona
    Sets the host persona that specifies the personality for all ports
    which are part of the host set. This selects certain variations in
    scsi command behavior which certain operating systems expect.
    <hostpersonaval> is the host persona id number with the desired
    capabilities. These can be seen with showhost -listpersona.

  .PARAMETER Location
    Specifies the host's location.

  .PARAMETER IPAddress
    Specifies the host's IP address.

  .PARAMETER OS
    Specifies the operating system running on the host.

  .PARAMETER Model
    Specifies the host's model.

  .PARAMETER Contact
    Specifies the host's owner and contact information.

  .PARAMETER Comment
    Specifies any additional information for the host.

  .PARAMETER NSP
    Specifies the desired relationship between the array port(s) and host
    for target-driven zoning. Multiple array ports can be specified by
    either using a pattern or a comma-separated list. This option is used
    only when the Smart SAN license is installed. At least one WWN needs
    to be specified with this option.
    
  .PARAMETER WWN
    Specifies the World Wide Name(WWN) to be assigned or added to an
    existing host. This specifier can be repeated to specify multiple WWNs.
    This specifier is optional.

  .PARAMETER IscsiName
    Host iSCSI name to be assigned or added to a host. This specifier is
    optional.

  .PARAMETER iSCSI
    when specified, it means that the address is an iSCSI address
    
  .PARAMETER SANConnection
    Specify the SAN Connection object created with New-CLIConnection or New-PoshSshConnection
    
  .Notes
    NAME: New-Host
    LASTEDIT: 19/11/2019
    KEYWORDS: New-Host
   
    .Link
     http://www.hpe.com
 
 #Requires PS -Version 3.0

 #>

[CmdletBinding()]
    param(
        [Parameter(Position=0, Mandatory=$false)]
        [System.String]
        $HostName,    
        
        [Parameter(Position=1, Mandatory=$false)]
        [switch]
        $Iscsi,
        
        [Parameter(Position=2, Mandatory=$false)]
        [switch]
        $Add,
        
        [Parameter(Position=3, Mandatory=$false)]
        [System.String]
        $Domain,
        
        [Parameter(Position=4, Mandatory=$false)]
        [switch]
        $Forces, 
        
        [Parameter(Position=5, Mandatory=$false)]
        [System.String]
        $Persona = 2,
        
        [Parameter(Position=6, Mandatory=$false)]
        [System.String]
        $Location,
        
        [Parameter(Position=7, Mandatory=$false)]
        [System.String]
        $IPAddress,
        
        [Parameter(Position=8, Mandatory=$false)]
        [System.String]
        $OS,
        
        [Parameter(Position=9, Mandatory=$false)]
        [System.String]
        $Model,
        
        [Parameter(Position=10, Mandatory=$false)]
        [System.String]
        $Contact,
        
        [Parameter(Position=11, Mandatory=$false)]
        [System.String]
        $Comment,
        
        [Parameter(Position=12, Mandatory=$false)]
        [System.String]
        $NSP,
        
        [Parameter(Position=13, Mandatory=$false)]
        [System.String]
        $WWN,
        
        [Parameter(Position=14, Mandatory=$false)]
        [System.String]
        $IscsiName,
        
        [Parameter(Position=15, Mandatory=$false, ValueFromPipeline=$true)]
        $SANConnection = $global:SANConnection        
    )        
    
    Write-DebugLog "Start: In New-Host - validating input values" $Debug 
    #check if connection object contents are null/empty
    if(!$SANConnection)
    {        
        #check if connection object contents are null/empty
        $Validate1 = Test-CLIConnection $SANConnection
        if($Validate1 -eq "Failed")
        {
            #check if global connection object contents are null/empty
            $Validate2 = Test-CLIConnection $global:SANConnection
            if($Validate2 -eq "Failed")
            {
                Write-DebugLog "Connection object is null/empty or the array address (FQDN/IP Address) or user credentials in the connection object are either null or incorrect. Create a valid connection object using New-CLIConnection or New-PoshSshConnection" "ERR:"
                Write-DebugLog "Stop: Exiting New-Host since SAN connection object values are null/empty" $Debug
                return "Unable to execute the cmdlet New-Host since no active storage connection session exists. `nUse New-PoshSSHConnection or New-CLIConnection to start a new storage connection session."
            }
        }
    }
    $plinkresult = Test-PARCli -SANConnection $SANConnection
    if($plinkresult -match "FAILURE :")
    {
        write-debuglog "$plinkresult" "ERR:" 
        return $plinkresult
    }
    
    $cmd ="createhost "
    
    if($Iscsi)
    {
        $cmd +="-iscsi "
    }
    if($Add)
    {
        $cmd +="-add "
    }
    if($Domain)
    {
        $cmd +="-domain $Domain "
    }
    if($Forces)
    {
        $cmd +="-f "
    }
    if($Persona)
    {
        $cmd +="-persona $Persona "
    }
    if($Location)
    {
        $cmd +="-loc $Location "
    }
    if($IPAddress)
    {
        $cmd +="-ip $IPAddress "
    }
    if($OS)
    {
        $cmd +="-os $OS "
    }
    if($Model)
    {
        $cmd +="-model $Model "
    }
    if($Contact)
    {
        $cmd +="-contact $Contact "
    }
    if($Comment)
    {
        $cmd +="-comment $Comment "
    }
    if($NSP)
    {
        $cmd +="-port $NSP "
    }
    if ($HostName)
    {
        $cmd +="$HostName "
    }
    else
    {
        write-debugLog "No name specified for new host. Skip creating host" "ERR:"
        Get-help New-Host
        return
    }
    if ($WWN)
    {
        $cmd +="$WWN "
    }
    if ($IscsiName)
    {
        $cmd +="$IscsiName "
    }
        
    $Result = Invoke-CLICommand -Connection $SANConnection -cmds $cmd    
        
    if([string]::IsNullOrEmpty($Result))
    {
        write-host""
        return "Success : New-Host command executed Host Name : $HostName is created."
    }
    else
    {
        write-host""
        return $Result
    }       
     
} # End New-Host

############################################################################################################################################
## FUNCTION New-HostSet
############################################################################################################################################
Function New-HostSet
{
<#
  .SYNOPSIS
    Creates a new host set.
  
  .DESCRIPTION
    Creates a new host set.
        
  .EXAMPLE
    New-HostSet -HostSetName xyz
    Creates an empty host set named "xyz"

  .EXAMPLE
    To create an empty hostset:
    New-HostSet hostset

  .EXAMPLE
    To add a host to the set:
    New-HostSet -Add -HostSetName hostset -HostName hosta

  .EXAMPLE
    To create a host set with hosts in it:
    New-HostSet -HostSetName hostset -HostName "host1 host2"
    or
    New-HostSet -HostSetName set:hostset -HostName "host1 host2"

  .EXAMPLE
    To create a host set with a comment and a host in it:
    New-HostSet -Comment "A host set" -HostSetName hostset -HostName hosta
    
  .EXAMPLE
    New-HostSet -HostSetName xyz -Domain xyz
    Create the host set in the specified domain
    
  .EXAMPLE
    New-HostSet -hostSetName HV01C-HostSet -hostName "MyHost"
    Creates an empty host set and named "HV01C-HostSet" and adds host "MyHost" to hostset
            (or)
    Adds host "MyHost" to hostset "HV01C-HostSet" if hostset already exists
    
  .PARAMETER HostSetName
    Specify new name of the host set

  .PARAMETER hostName
    Specify new name of the host

  .PARAMETER Add
    Specifies that the hosts listed should be added to an existing set.
    At least one host must be specified.

  .PARAMETER Comment
    Specifies any comment or additional information for the set. The
    comment can be up to 255 characters long. Unprintable characters are
    not allowed.

  .PARAMETER Domain
    Create the host set in the specified domain. For an empty set the
    default is to create it in the current domain, or no domain if the
    current domain is not set. A host set must be in the same domain as
    its members; if hosts are specified as part of the creation then
    the set will be created in their domain. The -domain option should
    still be used to specify which domain to use for the set when the
    hosts are members of domain sets. A domain cannot be specified
    when adding a host to an existing set with the -add option.
    
  .PARAMETER SANConnection
    Specify the SAN Connection object created with New-CLIConnection or New-PoshSshConnection
    
  .Notes
    NAME: New-HostSet
    LASTEDIT:19/11/2019
    KEYWORDS: New-HostSet
   
  .Link
     http://www.hpe.com
 
 #Requires PS -Version 3.0

 #>

[CmdletBinding()]
    param(
        [Parameter(Position=0, Mandatory=$false)]
        [System.String]
        $HostSetName,

        [Parameter(Position=1, Mandatory=$false)]
        [System.String]
        $hostName,
        
        [Parameter(Position=2, Mandatory=$false)]
        [switch]
        $Add,
        
        [Parameter(Position=3, Mandatory=$false)]
        [System.String]
        $Comment,
        
        [Parameter(Position=4, Mandatory=$false)]
        [System.String]
        $Domain,                
        
        [Parameter(Position=5, Mandatory=$false, ValueFromPipeline=$true)]
        $SANConnection = $global:SANConnection        
    )        
    
    Write-DebugLog "Start: In New-HostSet - validating input values" $Debug 
    #check if connection object contents are null/empty
    if(!$SANConnection)
    {    
        #check if connection object contents are null/empty
        $Validate1 = Test-CLIConnection $SANConnection
        if($Validate1 -eq "Failed")
        {
            #check if global connection object contents are null/empty
            $Validate2 = Test-CLIConnection $global:SANConnection
            if($Validate2 -eq "Failed")
            {
                Write-DebugLog "Connection object is null/empty or the array address (FQDN/IP Address) or user credentials in the connection object are either null or incorrect. Create a valid connection object using New-CLIConnection or New-PoshSshConnection" "ERR:"
                Write-DebugLog "Stop: Exiting New-HostSet since SAN connection object values are null/empty" $Debug
                return "Unable to execute the cmdlet New-HostSet since no active storage connection session exists. `nUse New-PoshSSHConnection or New-CLIConnection to start a new storage connection session."
            }
        }
    }
    $plinkresult = Test-PARCli -SANConnection $SANConnection
    if($plinkresult -match "FAILURE :")
    {
        write-debuglog "$plinkresult" "ERR:" 
        return $plinkresult
    }
    
    
    $cmdCrtHostSet =" createhostset "
    
    if($Add)
    {
        $cmdCrtHostSet +="-add "
    }
    if($Comment)
    {
        $cmdCrtHostSet +="-comment $Comment "
    }
    if($Domain)
    {
        $cmdCrtHostSet +="-domain $Domain "
    }    
    if ($HostSetName)
    {
        $cmdCrtHostSet +=" $HostSetName "
    }
    else
    {
        write-debugLog "No name specified for new host set. Skip creating host set" "ERR:"
        Get-help New-HostSet
        return    
    }
    if($hostName)
    {
        $cmdCrtHostSet +=" $hostName "
    }
    
    $Result = Invoke-CLICommand -Connection $SANConnection -cmds  $cmdCrtHostSet
    if($Add)
    {
        if([string]::IsNullOrEmpty($Result))
        {
            write-host""
            return "Success : New-HostSet command executed Host Name : $hostName is added to Host Set : $HostSetName"
        }
        else
        {
            write-host""
            return $Result
        }
    }    
    else
    {
        if([string]::IsNullOrEmpty($Result))
        {
            write-host""
            return "Success : New-HostSet command executed Host Set : $HostSetName is created with Host : $hostName"
        }
        else
        {
            write-host""
            return $Result
        }            
    }    
         
} # End New-HostSet

############################################################################################################################################
## FUNCTION Remove-Host
############################################################################################################################################
Function Remove-Host
{
<#
  .SYNOPSIS
    Removes a host.
  
  .DESCRIPTION
    Removes a host.
 
  .EXAMPLE
    Remove-Host -hostName HV01A
    Remove the host named HV01A
    
  .EXAMPLE
    Remove-Host -hostName HV01A -address 10000000C97B142E
    Remove the WWN address of the host named HV01A
    
  .EXAMPLE
    Remove-Host -hostName HV01B -iSCSI -Address iqn.1991-06.com.microsoft:dt-391-xp.hq.3par.com
    Remove the iSCSI address of the host named HV01B
    
  .PARAMETER hostName
    Specify name of the host.

  .PARAMETER Address
    Specify the list of addresses to be removed.
    
  .PARAMETER Rvl
    Remove WWN(s) or iSCSI name(s) even if there are VLUNs exported to the host.

  .PARAMETER iSCSI
    Specify twhether the address is WWN or iSCSI
    
  .PARAMETER Pat
    Specifies that host name will be treated as a glob-style pattern and that all hosts matching the specified pattern are removed. T

  .PARAMETER Port
    Specifies the NSP(s) for the zones, from which the specified WWN will be removed in the target driven zoning.
    
  .PARAMETER SANConnection
    Specify the SAN Connection object created with New-CLIConnection or New-PoshSshConnection
    
  .Notes
    NAME: Remove-Host
    LASTEDIT: 19/11/2019
    KEYWORDS: Remove-Host
   
  .Link
     http://www.hpe.com
 
 #Requires PS -Version 3.0

 #>

[CmdletBinding()]
    param(
        [Parameter(Position=0, Mandatory=$false)]
        [System.String]
        $hostName,
        
        [Parameter(Position=1, Mandatory=$false, ValueFromPipeline=$true)]
        [switch] $Rvl,
                
        [Parameter(Position=2, Mandatory=$false, ValueFromPipeline=$true)]
        [switch] $ISCSI = $false,
        
        [Parameter(Position=3, Mandatory=$false, ValueFromPipeline=$true)]
        [switch] $Pat = $false,
        
        [Parameter(Position=4, Mandatory=$false)]
        [System.String]
        $Port,
        
        [Parameter(Position=5, Mandatory=$false, ValueFromPipeline=$true)]
        [System.String[]]
        $Address,
        
        [Parameter(Position=6, Mandatory=$false, ValueFromPipeline=$true)]
        $SANConnection = $global:SANConnection        
    )        
    
    Write-DebugLog "Start: In Remove-Host - validating input values" $Debug 
    #check if connection object contents are null/empty
    if(!$SANConnection)
    {    
        #check if connection object contents are null/empty
        $Validate1 = Test-CLIConnection $SANConnection
        if($Validate1 -eq "Failed")
        {
            #check if global connection object contents are null/empty
            $Validate2 = Test-CLIConnection $global:SANConnection
            if($Validate2 -eq "Failed")
            {
                Write-DebugLog "Connection object is null/empty or the array address (FQDN/IP Address) or user credentials in the connection object are either null or incorrect. Create a valid connection object using New-CLIConnection or New-PoshSshConnection" "ERR:"
                Write-DebugLog "Stop: Exiting Remove-Host since SAN connection object values are null/empty" $Debug
                return "Unable to execute the cmdlet Remove-Host since no active storage connection session exists. `nUse New-PoshSSHConnection or New-CLIConnection to start a new storage connection session."
            }
        }
    }
    $plinkresult = Test-PARCli -SANConnection $SANConnection
    if($plinkresult -match "FAILURE :")
    {
        write-debuglog "$plinkresult" "ERR:" 
        return $plinkresult
    }      
    if ($hostName)
    {
        $objType = "host"
        $objMsg  = "hosts"
        
        ## Check Host Name
        if ( -not ( Test-CLIObject -objectType $objType -objectName $hostName -objectMsg $objMsg -SANConnection $SANConnection)) 
        {
            write-debuglog " Host $hostName does not exist. Nothing to remove"  "INFO:"  
            return "FAILURE : No host $hostName found"
        }
        else
        {
            $RemoveCmd = "removehost "            
            if ($address)
            {            
                if($Rvl)
                {
                    $RemoveCmd += " -rvl "
                }    
                if($ISCSI)
                {
                    $RemoveCmd += " -iscsi "
                }
                if($Pat)
                {
                    $RemoveCmd += " -pat "
                }
                if($Port)
                {
                    $RemoveCmd += " -port $Port "
                }
            }            
            $Addr = [string]$address 
            $RemoveCmd += " $hostName $Addr"
            $Result1 = Get-HostSet -hostName $hostName -SANConnection $SANConnection
            
            if(($Result1 -match "No host set listed"))
            {
                $Result2 = Invoke-CLICommand -Connection $SANConnection -cmds  $RemoveCmd
                write-debuglog "Removing host with the command --> $RemoveCmd" "INFO:" 
                if([string]::IsNullOrEmpty($Result2))
                {
                    return "Success : Removed host $hostName"
                }
                else
                {
                    return "FAILURE : While removing host $hostName"
                }                
            }
            else
            {
                $Result3 = Invoke-CLICommand -Connection $SANConnection -cmds  $RemoveCmd
                return "FAILURE : Host $hostName is still a member of set"
            }            
        }                
    }
    else
    {
        write-debuglog  "No host name mentioned to remove" "INFO:"
        Get-help Remove-Host            
    }
} # End of Remove-Host

#####################################################################################################################
## FUNCTION Remove-HostSet
#####################################################################################################################

Function Remove-HostSet
{
<#
  .SYNOPSIS
    Remove a host set or remove hosts from an existing set
  
  .DESCRIPTION
    Remove a host set or remove hosts from an existing set
        
  .EXAMPLE
    Remove-HostSet -hostsetName "MyHostSet" -force
    Remove a hostset "MyHostSet"
    
  .EXAMPLE
    Remove-HostSet -hostsetName "MyHostSet" -hostName "MyHost" -force
    Remove a single host "MyHost" from a hostset "MyHostSet"
    
  .PARAMETER hostsetName
    Specify name of the hostsetName

  .PARAMETER hostName
    Specify name of a host to remove from hostset
 
  .PARAMETER force
    If present, perform forcible delete operation
    
  .PARAMETER Pat
    Specifies that both the set name and hosts will be treated as glob-style patterns.
    
  .PARAMETER SANConnection
    Specify the SAN Connection object created with New-CLIConnection or New-PoshSshConnection
              
  .Notes
    NAME: Remove-HostSet
    LASTEDIT: 19/11/2019
    KEYWORDS: Remove-HostSet
   
  .Link
     http://www.hpe.com
 
 #Requires PS -Version 3.0

 #>

[CmdletBinding()]
    param(
        [Parameter(Position=0, Mandatory=$true)]
        [System.String]
        $hostsetName,
        
        [Parameter(Position=1, Mandatory=$false)]
        [System.String]
        $hostName,
        
        [Parameter(Position=2, Mandatory=$false)]
        [switch]
        $force,
        
        [Parameter(Position=3, Mandatory=$false)]
        [switch]
        $Pat,
        
        [Parameter(Position=4, Mandatory=$false, ValueFromPipeline=$true)]
        $SANConnection = $global:SANConnection        
    )        

    Write-DebugLog "Start: In Remove-HostSet - validating input values" $Debug 
    #check if connection object contents are null/empty
    if(!$SANConnection)
    {        
        #check if connection object contents are null/empty
        $Validate1 = Test-CLIConnection $SANConnection
        if($Validate1 -eq "Failed")
        {
            #check if global connection object contents are null/empty
            $Validate2 = Test-CLIConnection $global:SANConnection
            if($Validate2 -eq "Failed")
            {
                Write-DebugLog "Connection object is null/empty or the array address (FQDN/IP Address) or user credentials in the connection object are either null or incorrect. Create a valid connection object using New-CLIConnection or New-PoshSshConnection" "ERR:"
                Write-DebugLog "Stop: Exiting Remove-HostSet since SAN connection object values are null/empty" $Debug
                return "Unable to execute the cmdlet Remove-HostSet since no active storage connection session exists. `nUse New-PoshSSHConnection or New-CLIConnection to start a new storage connection session."
            }
        }
    }
    $plinkresult = Test-PARCli -SANConnection $SANConnection
    if($plinkresult -match "FAILURE :")
    {
        write-debuglog "$plinkresult" "ERR:" 
        return $plinkresult
    }
    
    $RemovehostsetCmd = "removehostset "
    if ($hostsetName)
    {
        if (!($force))
        {
            write-debuglog "no force option selected to remove hostset, Exiting...." "INFO:"
            return "FAILURE : no -force option selected to remove hostset"
        }
        $objType = "hostset"
        $objMsg  = "host set"
        
        ## Check hostset Name
        ##
        if ( -not ( Test-CLIObject -objectType $objType -objectName $hostsetName -objectMsg $objMsg -SANConnection $SANConnection)) 
        {
            write-debuglog " hostset $hostsetName does not exist. Nothing to remove"  "INFO:"  
            return "FAILURE : No hostset $hostsetName found"
        }
        else
        {    
            if($force)
            {
                $RemovehostsetCmd += " -f "
            }
            if($Pat)
            {
                $RemovehostsetCmd += " -pat "
            }
            
            $RemovehostsetCmd += " $hostsetName "
            
            if($hostName)
            {
                $RemovehostsetCmd +=" $hostName"
            }
        
            $Result2 = Invoke-CLICommand -Connection $SANConnection -cmds  $RemovehostsetCmd
            
            write-debuglog "Removing hostset with the command --> $RemovehostsetCmd" "INFO:"
            if([string]::IsNullOrEmpty($Result2))
            {
                if($hostName)
                {
                    return "Success : Removed host $hostName from hostset $hostsetName "
                }
                else
                {
                    return "Success : Removed hostset $hostsetName "
                }
            }
            else
            {
                return "FAILURE : While removing hostset $hostsetName"
            }            
        }
    }
    else
    {
            write-debuglog  "No hostset name mentioned to remove" "INFO:"
            Get-help Remove-HostSet
    }
} # End of Remove-HostSet

##########################################################################
######################### FUNCTION Update-HostSet ####################
##########################################################################
Function Update-HostSet()
{
<#
  .SYNOPSIS
   Update-HostSet - set parameters for a host set

  .DESCRIPTION
   The Update-HostSet command sets the parameters and modifies the properties of a host set.

  .EXAMPLE

  .PARAMETER Setname
    Specifies the name of the host set to modify.
  
  .PARAMETER Comment
   Specifies any comment or additional information for the set. The comment can be up to 255 characters long. Unprintable characters are not allowed.

  .PARAMETER NewName
   Specifies a new name for the host set, using up to 27 characters in length.

  .Notes
    NAME: Update-HostSet
    LASTEDIT 19/11/2019
    KEYWORDS: Update-HostSet
  
  .Link
    http://www.hpe.com

 #Requires PS -Version 3.0
#>

[CmdletBinding()]
 param(
    [Parameter(Position=0, Mandatory=$false, ValueFromPipeline=$true)]
    [System.String]
    $Comment,

    [Parameter(Position=1, Mandatory=$false, ValueFromPipeline=$true)]
    [System.String]
    $NewName,

    [Parameter(Position=2, Mandatory=$true, ValueFromPipeline=$true)]
    [System.String]                         
    $Setname,

    [Parameter(Position=3, Mandatory=$false, ValueFromPipeline=$true)]
    $SANConnection = $global:SANConnection
 )

 Write-DebugLog "Start: In Update-HostSet - validating input values" $Debug 
 #check if connection object contents are null/empty
 if(!$SANConnection)
 {
    #check if connection object contents are null/empty
    $Validate1 = Test-CLIConnection $SANConnection
    if($Validate1 -eq "Failed")
    {
        #check if global connection object contents are null/empty
        $Validate2 = Test-CLIConnection $global:SANConnection
        if($Validate2 -eq "Failed")
        {
            Write-DebugLog "Connection object is null/empty or the array address (FQDN/IP Address) or user credentials in the connection object are either null or incorrect. Create a valid connection object using New-CLIConnection or New-PoshSshConnection" " ERR: "
            Write-DebugLog "Stop: Exiting Update-HostSet since SAN connection object values are null/empty" $Debug 
            Return "Unable to execute the cmdlet Update-HostSet since no active storage connection session exists. `nUse New-PoshSSHConnection or New-CLIConnection to start a new storage connection session."
        }
    }
 }

 $plinkresult = Test-PARCli -SANConnection $SANConnection
 if($plinkresult -match "FAILURE :")
 {
   write-debuglog "$plinkresult"
   Return $plinkresult
 }

    $Cmd = " sethostset "
    
 if($Comment)
 {
    $Cmd += " -comment $Comment "
 }

 if($NewName)
 {
    $Cmd += " -name $NewName "
 } 

 if($Setname)
 {
    $Cmd += " $Setname "
 } 
 else
 {
    return "Setname is mandatory Please enter..."
 } 
 
 $Result = Invoke-CLICommand -Connection $SANConnection -cmds  $Cmd
 Write-DebugLog "Executing Function : Update-HostSet Command -->" INFO: 
 
 if ([string]::IsNullOrEmpty($Result))
 {
    Get-HostSet -hostSetName $NewName
 }
 else
 { 
    Return $Result
 }
} ## End-of Update-HostSet


Export-ModuleMember Get-Host , Get-HostSet , New-Host , New-HostSet , Remove-Host , Remove-HostSet , Update-HostSet
# SIG # Begin signature block
# MIIhEQYJKoZIhvcNAQcCoIIhAjCCIP4CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCFuc6/3ipRw4wN
# k5gFfAtzmMp8ealYUS3Lg7DlMEKtt6CCEKswggUpMIIEEaADAgECAhB4Lu4fcD9z
# xUgD+jf1OoqlMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAkdCMRswGQYDVQQI
# ExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoT
# D1NlY3RpZ28gTGltaXRlZDEkMCIGA1UEAxMbU2VjdGlnbyBSU0EgQ29kZSBTaWdu
# aW5nIENBMB4XDTIxMDUyODAwMDAwMFoXDTIyMDUyODIzNTk1OVowgZAxCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlQYWxvIEFsdG8x
# KzApBgNVBAoMIkhld2xldHQgUGFja2FyZCBFbnRlcnByaXNlIENvbXBhbnkxKzAp
# BgNVBAMMIkhld2xldHQgUGFja2FyZCBFbnRlcnByaXNlIENvbXBhbnkwggEiMA0G
# CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDmclZSXJBXA55ijwwFymuq+Y4F/quF
# mm2vRdEmjFhzRvTpnGjIYtVcG11ka4JGCROmNVDZGAelnqcXn5DKO710j5SICTBC
# 5gXOLwga7usifs21W+lVT0BsZTiUnFu4hEhuFTlahJIEvPGVgO1GBcuItD2QqB4q
# 9j15GDI5nGBSzIyJKMctcIalxsTSPG1kiDbLkdfsIivhe9u9m8q6NRqDUaYYQTN+
# /qGCqVNannMapH8tNHqFb6VdzUFI04t7kFtSk00AkdD6qUvA4u8mL2bUXAYz8K5m
# nrFs+ckx5Yqdxfx68EO26Bt2qbz/oTHxE6FiVzsDl90bcUAah2l976ebAgMBAAGj
# ggGQMIIBjDAfBgNVHSMEGDAWgBQO4TqoUzox1Yq+wbutZxoDha00DjAdBgNVHQ4E
# FgQUlC56g+JaYFsl5QWK2WDVOsG+pCEwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB
# /wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEQYJYIZIAYb4QgEBBAQDAgQQMEoG
# A1UdIARDMEEwNQYMKwYBBAGyMQECAQMCMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8v
# c2VjdGlnby5jb20vQ1BTMAgGBmeBDAEEATBDBgNVHR8EPDA6MDigNqA0hjJodHRw
# Oi8vY3JsLnNlY3RpZ28uY29tL1NlY3RpZ29SU0FDb2RlU2lnbmluZ0NBLmNybDBz
# BggrBgEFBQcBAQRnMGUwPgYIKwYBBQUHMAKGMmh0dHA6Ly9jcnQuc2VjdGlnby5j
# b20vU2VjdGlnb1JTQUNvZGVTaWduaW5nQ0EuY3J0MCMGCCsGAQUFBzABhhdodHRw
# Oi8vb2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAY+1n2UUlQU6Z
# VoEVaZKqZf/zrM/d7Kbx+S/t8mR2E+uNXStAnwztElqrm3fSr+5LMRzBhrYiSmea
# w9c/0c7qFO9mt8RR2q2uj0Huf+oAMh7TMuMKZU/XbT6tS1e15B8ZhtqOAhmCug6s
# DuNvoxbMpokYevpa24pYn18ELGXOUKlqNUY2qOs61GVvhG2+V8Hl/pajE7yQ4diz
# iP7QjMySms6BtZV5qmjIFEWKY+UTktUcvN4NVA2J0TV9uunDbHRt4xdY8TF/Clgz
# Z/MQHJ/X5yX6kupgDeN2t3o+TrColetBnwk/SkJEsUit0JapAiFUx44j4w61Qanb
# Zmi0tr8YGDCCBYEwggRpoAMCAQICEDlyRDr5IrdR19NsEN0xNZUwDQYJKoZIhvcN
# AQEMBQAwezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3Rl
# cjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQx
# ITAfBgNVBAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0xOTAzMTIwMDAw
# MDBaFw0yODEyMzEyMzU5NTlaMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3
# IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VS
# VFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0
# aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIAS
# ZRc2DsPbCLPQrFcNdu3NJ9NMrVCDYeKqIE0JLWQJ3M6Jn8w9qez2z8Hc8dOx1ns3
# KBErR9o5xrw6GbRfpr19naNjQrZ28qk7K5H44m/Q7BYgkAk+4uh0yRi0kdRiZNt/
# owbxiBhqkCI8vP4T8IcUe/bkH47U5FHGEWdGCFHLhhRUP7wz/n5snP8WnRi9UY41
# pqdmyHJn2yFmsdSbeAPAUDrozPDcvJ5M/q8FljUfV1q3/875PbcstvZU3cjnEjpN
# rkyKt1yatLcgPcp/IjSufjtoZgFE5wFORlObM2D3lL5TN5BzQ/Myw1Pv26r+dE5p
# x2uMYJPexMcM3+EyrsyTO1F4lWeL7j1W/gzQaQ8bD/MlJmszbfduR/pzQ+V+DqVm
# sSl8MoRjVYnEDcGTVDAZE6zTfTen6106bDVc20HXEtqpSQvf2ICKCZNijrVmzyWI
# zYS4sT+kOQ/ZAp7rEkyVfPNrBaleFoPMuGfi6BOdzFuC00yz7Vv/3uVzrCM7LQC/
# NVV0CUnYSVgaf5I25lGSDvMmfRxNF7zJ7EMm0L9BX0CpRET0medXh55QH1dUqD79
# dGMvsVBlCeZYQi5DGky08CVHWfoEHpPUJkZKUIGy3r54t/xnFeHJV4QeD2PW6WK6
# 1l9VLupcxigIBCU5uA4rqfJMlxwHPw1S9e3vL4IPAgMBAAGjgfIwge8wHwYDVR0j
# BBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQwHQYDVR0OBBYEFFN5v1qqK0rPVIDh
# 2JvAnfKyA2bLMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MBEGA1Ud
# IAQKMAgwBgYEVR0gADBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsLmNvbW9k
# b2NhLmNvbS9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDA0BggrBgEFBQcBAQQo
# MCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmNvbW9kb2NhLmNvbTANBgkqhkiG
# 9w0BAQwFAAOCAQEAGIdR3HQhPZyK4Ce3M9AuzOzw5steEd4ib5t1jp5y/uTW/qof
# nJYt7wNKfq70jW9yPEM7wD/ruN9cqqnGrvL82O6je0P2hjZ8FODN9Pc//t64tIrw
# kZb+/UNkfv3M0gGhfX34GRnJQisTv1iLuqSiZgR2iJFODIkUzqJNyTKzuugUGrxx
# 8VvwQQuYAAoiAxDlDLH5zZI3Ge078eQ6tvlFEyZ1r7uq7z97dzvSxAKRPRkA0xdc
# Ods/exgNRc2ThZYvXd9ZFk8/Ub3VRRg/7UqO6AZhdCMWtQ1QcydER38QXYkqa4Ux
# FMToqWpMgLxqeM+4f452cpkMnf7XkQgWoaNflTCCBfUwggPdoAMCAQICEB2iSDBv
# myYY0ILgln0z02owDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpOZXcgSmVyc2V5MRQwEgYDVQQHEwtKZXJzZXkgQ2l0eTEeMBwGA1UEChMV
# VGhlIFVTRVJUUlVTVCBOZXR3b3JrMS4wLAYDVQQDEyVVU0VSVHJ1c3QgUlNBIENl
# cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTE4MTEwMjAwMDAwMFoXDTMwMTIzMTIz
# NTk1OVowfDELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3Rl
# cjEQMA4GA1UEBxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMSQw
# IgYDVQQDExtTZWN0aWdvIFJTQSBDb2RlIFNpZ25pbmcgQ0EwggEiMA0GCSqGSIb3
# DQEBAQUAA4IBDwAwggEKAoIBAQCGIo0yhXoYn0nwli9jCB4t3HyfFM/jJrYlZilA
# hlRGdDFixRDtsocnppnLlTDAVvWkdcapDlBipVGREGrgS2Ku/fD4GKyn/+4uMyD6
# DBmJqGx7rQDDYaHcaWVtH24nlteXUYam9CflfGqLlR5bYNV+1xaSnAAvaPeX7Wpy
# vjg7Y96Pv25MQV0SIAhZ6DnNj9LWzwa0VwW2TqE+V2sfmLzEYtYbC43HZhtKn52B
# xHJAteJf7wtF/6POF6YtVbC3sLxUap28jVZTxvC6eVBJLPcDuf4vZTXyIuosB69G
# 2flGHNyMfHEo8/6nxhTdVZFuihEN3wYklX0Pp6F8OtqGNWHTAgMBAAGjggFkMIIB
# YDAfBgNVHSMEGDAWgBRTeb9aqitKz1SA4dibwJ3ysgNmyzAdBgNVHQ4EFgQUDuE6
# qFM6MdWKvsG7rWcaA4WtNA4wDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYB
# Af8CAQAwHQYDVR0lBBYwFAYIKwYBBQUHAwMGCCsGAQUFBwMIMBEGA1UdIAQKMAgw
# BgYEVR0gADBQBgNVHR8ESTBHMEWgQ6BBhj9odHRwOi8vY3JsLnVzZXJ0cnVzdC5j
# b20vVVNFUlRydXN0UlNBQ2VydGlmaWNhdGlvbkF1dGhvcml0eS5jcmwwdgYIKwYB
# BQUHAQEEajBoMD8GCCsGAQUFBzAChjNodHRwOi8vY3J0LnVzZXJ0cnVzdC5jb20v
# VVNFUlRydXN0UlNBQWRkVHJ1c3RDQS5jcnQwJQYIKwYBBQUHMAGGGWh0dHA6Ly9v
# Y3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggIBAE1jUO1HNEphpNve
# aiqMm/EAAB4dYns61zLC9rPgY7P7YQCImhttEAcET7646ol4IusPRuzzRl5ARokS
# 9At3WpwqQTr81vTr5/cVlTPDoYMot94v5JT3hTODLUpASL+awk9KsY8k9LOBN9O3
# ZLCmI2pZaFJCX/8E6+F0ZXkI9amT3mtxQJmWunjxucjiwwgWsatjWsgVgG10Xkp1
# fqW4w2y1z99KeYdcx0BNYzX2MNPPtQoOCwR/oEuuu6Ol0IQAkz5TXTSlADVpbL6f
# ICUQDRn7UJBhvjmPeo5N9p8OHv4HURJmgyYZSJXOSsnBf/M6BZv5b9+If8AjntIe
# Q3pFMcGcTanwWbJZGehqjSkEAnd8S0vNcL46slVaeD68u28DECV3FTSK+TbMQ5Lk
# uk/xYpMoJVcp+1EZx6ElQGqEV8aynbG8HArafGd+fS7pKEwYfsR7MUFxmksp7As9
# V1DSyt39ngVR5UR43QHesXWYDVQk/fBO4+L4g71yuss9Ou7wXheSaG3IYfmm8SoK
# C6W59J7umDIFhZ7r+YMp08Ysfb06dy6LN0KgaoLtO0qqlBCk4Q34F8W2WnkzGJLj
# tXX4oemOCiUe5B7xn1qHI/+fpFGe+zmAEc3btcSnqIBv5VPU4OOiwtJbGvoyJi1q
# V3AcPKRYLqPzW0sH3DJZ84enGm1YMYIPvDCCD7gCAQEwgZAwfDELMAkGA1UEBhMC
# R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9y
# ZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMSQwIgYDVQQDExtTZWN0aWdvIFJT
# QSBDb2RlIFNpZ25pbmcgQ0ECEHgu7h9wP3PFSAP6N/U6iqUwDQYJYIZIAWUDBAIB
# BQCgfDAQBgorBgEEAYI3AgEMMQIwADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIB
# BDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg
# 2c1eZmUdk6990oh7jKAyluauUXdxW+/peWO/f0NUo1wwDQYJKoZIhvcNAQEBBQAE
# ggEA2XMftm+4Wrdd4NeqFEBU+vdwBeKKNSVyJDOHR2wA+1bWUlan0e3+mba51+Ua
# owR95mkqwKlelWNjEsQuxMd1+KuPXmwvRiJZx67/BHubxMPGmHiVuRlq12F+7rau
# iIasbL80+1Z33Po+seHK5l8wi/ru9WhZ1ALRoLaa752RsfMAbZT+BTk1UsaJxbBr
# TDr+uegbyVju+7XMHP+1lYea1KJqFB7Ng88yCMOGJeowVMckSMvnUKzo8jFUGT9y
# cFR6DvAxWM8TRqw0BwhxQtBdd8iBuvJHxpSRXcbIHmhstY2cAb516d9mjgEISHOi
# Kp37GHzHwzOABboKhTGaSnHdGaGCDX4wgg16BgorBgEEAYI3AwMBMYINajCCDWYG
# CSqGSIb3DQEHAqCCDVcwgg1TAgEDMQ8wDQYJYIZIAWUDBAIBBQAweAYLKoZIhvcN
# AQkQAQSgaQRnMGUCAQEGCWCGSAGG/WwHATAxMA0GCWCGSAFlAwQCAQUABCACDnsu
# H0d8Oe9BUQR+qOPYCQNL91Ezlx1MAskQPL2FQAIRAOSAbJ+3877c2IUiN/eP0CQY
# DzIwMjEwNjE5MDQxMTI1WqCCCjcwggT+MIID5qADAgECAhANQkrgvjqI/2BAIc4U
# APDdMA0GCSqGSIb3DQEBCwUAMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdp
# Q2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERp
# Z2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBUaW1lc3RhbXBpbmcgQ0EwHhcNMjEwMTAx
# MDAwMDAwWhcNMzEwMTA2MDAwMDAwWjBIMQswCQYDVQQGEwJVUzEXMBUGA1UEChMO
# RGlnaUNlcnQsIEluYy4xIDAeBgNVBAMTF0RpZ2lDZXJ0IFRpbWVzdGFtcCAyMDIx
# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwuZhhGfFivUNCKRFymNr
# Udc6EUK9CnV1TZS0DFC1JhD+HchvkWsMlucaXEjvROW/m2HNFZFiWrj/ZwucY/02
# aoH6KfjdK3CF3gIY83htvH35x20JPb5qdofpir34hF0edsnkxnZ2OlPR0dNaNo/G
# o+EvGzq3YdZz7E5tM4p8XUUtS7FQ5kE6N1aG3JMjjfdQJehk5t3Tjy9XtYcg6w6O
# LNUj2vRNeEbjA4MxKUpcDDGKSoyIxfcwWvkUrxVfbENJCf0mI1P2jWPoGqtbsR0w
# wptpgrTb/FZUvB+hh6u+elsKIC9LCcmVp42y+tZji06lchzun3oBc/gZ1v4NSYS9
# AQIDAQABo4IBuDCCAbQwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwFgYD
# VR0lAQH/BAwwCgYIKwYBBQUHAwgwQQYDVR0gBDowODA2BglghkgBhv1sBwEwKTAn
# BggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMB8GA1UdIwQY
# MBaAFPS24SAd/imu0uRhpbKiJbLIFzVuMB0GA1UdDgQWBBQ2RIaOpLqwZr68KC0d
# RDbd42p6vDBxBgNVHR8EajBoMDKgMKAuhixodHRwOi8vY3JsMy5kaWdpY2VydC5j
# b20vc2hhMi1hc3N1cmVkLXRzLmNybDAyoDCgLoYsaHR0cDovL2NybDQuZGlnaWNl
# cnQuY29tL3NoYTItYXNzdXJlZC10cy5jcmwwgYUGCCsGAQUFBwEBBHkwdzAkBggr
# BgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tME8GCCsGAQUFBzAChkNo
# dHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyQXNzdXJlZElE
# VGltZXN0YW1waW5nQ0EuY3J0MA0GCSqGSIb3DQEBCwUAA4IBAQBIHNy16ZojvOca
# 5yAOjmdG/UJyUXQKI0ejq5LSJcRwWb4UoOUngaVNFBUZB3nw0QTDhtk7vf5EAmZN
# 7WmkD/a4cM9i6PVRSnh5Nnont/PnUp+Tp+1DnnvntN1BIon7h6JGA0789P63ZHdj
# XyNSaYOC+hpT7ZDMjaEXcw3082U5cEvznNZ6e9oMvD0y0BvL9WH8dQgAdryBDvjA
# 4VzPxBFy5xtkSdgimnUVQvUtMjiB2vRgorq0Uvtc4GEkJU+y38kpqHNDUdq9Y9Yf
# W5v3LhtPEx33Sg1xfpe39D+E68Hjo0mh+s6nv1bPull2YYlffqe0jmd4+TaY4cso
# 2luHpoovMIIFMTCCBBmgAwIBAgIQCqEl1tYyG35B5AXaNpfCFTANBgkqhkiG9w0B
# AQsFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk
# IElEIFJvb3QgQ0EwHhcNMTYwMTA3MTIwMDAwWhcNMzEwMTA3MTIwMDAwWjByMQsw
# CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
# ZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQg
# VGltZXN0YW1waW5nIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
# vdAy7kvNj3/dqbqCmcU5VChXtiNKxA4HRTNREH3Q+X1NaH7ntqD0jbOI5Je/YyGQ
# mL8TvFfTw+F+CNZqFAA49y4eO+7MpvYyWf5fZT/gm+vjRkcGGlV+Cyd+wKL1oODe
# Ij8O/36V+/OjuiI+GKwR5PCZA207hXwJ0+5dyJoLVOOoCXFr4M8iEA91z3FyTgqt
# 30A6XLdR4aF5FMZNJCMwXbzsPGBqrC8HzP3w6kfZiFBe/WZuVmEnKYmEUeaC50ZQ
# /ZQqLKfkdT66mA+Ef58xFNat1fJky3seBdCEGXIX8RcG7z3N1k3vBkL9olMqT4Ud
# xB08r8/arBD13ays6Vb/kwIDAQABo4IBzjCCAcowHQYDVR0OBBYEFPS24SAd/imu
# 0uRhpbKiJbLIFzVuMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMBIG
# A1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsG
# AQUFBwMIMHkGCCsGAQUFBwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3Au
# ZGlnaWNlcnQuY29tMEMGCCsGAQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2Vy
# dC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3J0MIGBBgNVHR8EejB4MDqg
# OKA2hjRodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURS
# b290Q0EuY3JsMDqgOKA2hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNl
# cnRBc3N1cmVkSURSb290Q0EuY3JsMFAGA1UdIARJMEcwOAYKYIZIAYb9bAACBDAq
# MCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAsGCWCG
# SAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAQEAcZUS6VGHVmnN793afKpjerN4zwY3
# QITvS4S/ys8DAv3Fp8MOIEIsr3fzKx8MIVoqtwU0HWqumfgnoma/Capg33akOpMP
# +LLR2HwZYuhegiUexLoceywh4tZbLBQ1QwRostt1AuByx5jWPGTlH0gQGF+JOGFN
# YkYkh2OMkVIsrymJ5Xgf1gsUpYDXEkdws3XVk4WTfraSZ/tTYYmo9WuWwPRYaQ18
# yAGxuSh1t5ljhSKMYcp5lH5Z/IwP42+1ASa2bKXuh1Eh5Fhgm7oMLSttosR+u8Ql
# K0cCCHxJrhO24XxCQijGGFbPQTS2Zl22dHv1VjMiLyI2skuiSpXY9aaOUjGCAoYw
# ggKCAgEBMIGGMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0IFNI
# QTIgQXNzdXJlZCBJRCBUaW1lc3RhbXBpbmcgQ0ECEA1CSuC+Ooj/YEAhzhQA8N0w
# DQYJYIZIAWUDBAIBBQCggdEwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwG
# CSqGSIb3DQEJBTEPFw0yMTA2MTkwNDExMjVaMCsGCyqGSIb3DQEJEAIMMRwwGjAY
# MBYEFOHXgqjhkb7va8oWkbWqtJSmJJvzMC8GCSqGSIb3DQEJBDEiBCBi/J/PU+CU
# x+twv3FrpCcPe/t7zMmV0ab5YpbYgVT1DTA3BgsqhkiG9w0BCRACLzEoMCYwJDAi
# BCCzEJAGvArZgweRVyngRANBXIPjKSthTyaWTI01cez1qTANBgkqhkiG9w0BAQEF
# AASCAQAcSmbMDJyiV8wUPyN57Rmp9B5PV48ugirhcHkmuMEXBQkDnqzVj3eLYFvL
# FfeYEtAXh0Gg97/brLU/y135h+xr7QYjvjRnG+zKcX+ICXj5vfZPiQrNmtsDJfbD
# 0jQmvOaX8YhVmoerXdqFvrGMAlOMEwPNMl9rBOom0JC1ZOAjM1OIV2cNZqrCdfB7
# /Gj9gy+zVxih3Lhd+4kCVCVAr4UIugzyBYunoZnw6mvBoLYK+1uEhC4QhtpZok1L
# fzZuVJsjHgHkkc6N0peUF+w0+UNZCJ0KtJocTniaiAoLN8pfLrlLPCo6rpPrrAzV
# oH2g3NszMk9j9H8KmnmU7P8qClLk
# SIG # End signature block