scripts/helpers.ps1

# HPE Nimble PowerShell Toolkit.
# File: helpers.ps1
# Description: This file contains common helper routines. These functions are called by generated SDK Cmdlet functions.
#
# © Copyright 2017 Hewlett Packard Enterprise Development LP.
function Connect-NSGroup {
<#
.SYNOPSIS
    Connects to a HPE Alletra 6000 and Nimble Storage group.
.DESCRIPTION
    Connect-NSGroup is an advanced function that provides the initial connection to a HPE Alletra 6000 and Nimble Storage
    array so that other subsequent commands can be run without having to authenticate individually.
    It is recommended to ignore the server certificate validation (-IgnoreServerCertificate param)
    since it uses an untrusted SSL certificate.
.PARAMETER Group
    The DNS name or IP address of the array group.
.PARAMETER Credential
    Specifies a user account that has permission to perform this action. Type a user name, such as User01
    or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a
    user name, this function prompts you for a password.
.PARAMETER IgnoreServerCertificate
    Ignore the server SSL certificate.
.EXAMPLE
     Connect-NSGroup -Group nimblegroup.yourdns.local -Credential admin -IgnoreServerCertificate
 
     *Note: IgnoreServerCertificate parameter is not available with PowerShell Core
.EXAMPLE
     Connect-NSGroup -Group 192.168.1.50 -Credential admin -IgnoreServerCertificate
 
     *Note: IgnoreServerCertificate parameter is not available with PowerShell Core
.EXAMPLE
     Connect-NSGroup -Group nimblegroup.yourdns.local -Credential admin -ImportServerCertificate
.EXAMPLE
     Connect-NSGroup -Group 192.168.1.50 -Credential admin -ImportServerCertificate
.INPUTS
    None
.OUTPUTS
    None
.NOTES
 
#>

    [cmdletbinding(DefaultParameterSetName='IgnoreServerCertificate')]
    param   (
        [Parameter(Mandatory,position=0)]
        [string]$Group,

        [Parameter(Mandatory,position=1)]
        $Credential=$null,
        

        [Parameter(ParameterSetName='ImportServerCertificate')]
        [switch]$ImportServerCertificate
    
    )

    DynamicParam {

            if ($PSEdition -ne 'Core'){ 

                 $IgnoreServerCertificateAttribute = New-Object System.Management.Automation.ParameterAttribute
                 $IgnoreServerCertificateAttribute.Mandatory = $false
                 #$IgnoreServerCertificateAttribute.Position = 3
                 $IgnoreServerCertificateAttribute.ParameterSetName = 'IgnoreServerCertificate'
                 $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
                 $attributeCollection.Add($IgnoreServerCertificateAttribute)  
                 $IgnoreServerCertificateParam = New-Object System.Management.Automation.RuntimeDefinedParameter('IgnoreServerCertificate', [Switch],$attributeCollection)
                 $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
                 $paramDictionary.Add('IgnoreServerCertificate', $IgnoreServerCertificateParam)
                 return $paramDictionary
                 }
            }

    Process{

        if ($Credential -is [String]) {
            $Credential = Get-Credential $Credential
         }
         $global:Group=$Group
         [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        if ($PSBoundParameters.IgnoreServerCertificate) { $Global:NimbleStorageIgnoreServerCertificate = $true; IgnoreServerCertificate}

        else 
            {
                 $Global:NimbleStorageIgnoreServerCertificate = $false
                 $Global:GlobalImportServerCertificate = $ImportServerCertificate
                 ValidateServerCertificate $group
            }

        Import-LocalizedData -BaseDirectory (Split-Path $PSScriptRoot -parent) -FileName "HPEAlletra6000andNimbleStoragePowerShellToolkit.psd1" -BindingVariable "ModuleData"
        $PSTKVersion = $ModuleData.moduleversion
        $Global:NimbleAppName = "HPEAlletra6000andNimbleStoragePowerShellToolkitV" + $PSTKVersion
        $Global:NimbleStoragePort = 5392
        $Global:BaseUri = "https://$($global:Group):$($NimbleStoragePort)"
        try{
            $Global:NimbleStorageTokenData = Invoke-RestMethod -Uri "$BaseUri/v1/tokens" -Method Post -Body ((@{data = @{username = $Credential.UserName;password = $Credential.GetNetworkCredential().password;app_name = $NimbleAppName}}) | ConvertTo-Json)
            Write-Host "Successfully connected to array $group `n`n"
        }
        catch{
            Write-error "Failed to connect with array $group `n`n $_.Exception.Message" -ErrorAction Stop
        }
        $Global:RestVersion = (Invoke-RestMethod -Uri "$BaseUri/versions").data.name
        $Global:NimbleStorageSession_token = $NimbleStorageTokenData.data.session_token
        $Global:NimbleStorageArray = $group
        $Global:NimbleStorageCommonPSParams=@('Debug','Verbose','ErrorAction','ErrorVariable','InformationAction','InformationVariable','OutBuffer','OutVariable','PipelineVariable','Verbose','WarningAction','WarningVariable','WhatIf','Confim','ItemType')
    }
}

function Disconnect-NSGroup {
<#
.SYNOPSIS
    Disconnects from a Nimble SAN.
.DESCRIPTION
    Disconnect-NSGroup is an advanced function that disconnects the established connection with Nimble Storage
    array.
.EXAMPLE
    Disconnect-NSGroup
.INPUTS
    None
.OUTPUTS
    None
.NOTES
 
#>

    [CmdletBinding()]
    param ( )
    if (Get-Variable NimbleStorageTokenData -Scope Global -ErrorAction SilentlyContinue)
    {
        Remove-NSToken -id $NimbleStorageTokenData.data.id
        remove-Variable -Scope "Global" NimbleStoragePort
        Remove-Variable -Scope "Global" BaseUri
        Remove-Variable -Scope "Global" NimbleStorageTokenData
        Remove-Variable -Scope "Global" RestVersion
        Remove-Variable -Scope "Global" NimbleStorageSession_token
        Remove-Variable -Scope "Global" NimbleStorageArray
        Remove-Variable -Scope "Global" NimbleStorageCommonPSParams
    }
}

function IgnoreServerCertificate {

    [CmdletBinding()]
    param()
    <#
  [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
  [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
 
  add-type @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
 
    public class IDontCarePolicy : ICertificatePolicy {
      public IDontCarePolicy() {}
      public bool CheckValidationResult(
        ServicePoint sPoint, X509Certificate cert,
        WebRequest wRequest, int certProb) {
        return true;
      }
    }
"@
 
  [System.Net.ServicePointManager]::CertificatePolicy = new-object IDontCarePolicy
    write-verbose 'Server certificate ignored'
    #>

     if (-not ([System.Management.Automation.PSTypeName]'CustomCertificateValidationCallback').Type)
    {

      add-type @"
      using System.Net;
    using System.Net.Security;
    using System.Security.Cryptography.X509Certificates;
 
    public static class CustomCertificateValidationCallback {
        public static void Install()
        {
            ServicePointManager.ServerCertificateValidationCallback += CustomCertificateValidationCallback.CheckValidationResult;
        }
 
 
        public static bool CheckValidationResult(
            object sender,
            X509Certificate certificate,
            X509Chain chain,
            SslPolicyErrors sslPolicyErrors)
        {
            // please don't do this. do some real validation with explicit exceptions.
            return true;
        }
    }
"@

    }

    [CustomCertificateValidationCallback]::Install()
}

function Invoke-NimbleStorageRestAPI ()
{
    param(
        [Parameter(Mandatory=$true)][string] $ResourcePath,
        [string] $APIVersion = 'v1',
        [string] $Method = 'GET',
        [hashtable] $RequestParams = @{}
    )

    if (!(Get-Variable NimbleStorageTokenData -Scope Global -ErrorAction SilentlyContinue)) {
       Write-Error -Message "Authentication Info missing. Please use Connect-NSGroup to login." -ErrorAction Stop
    }

    if ( $NimbleStorageIgnoreServerCertificate ) { IgnoreServerCertificate }

    # Form the parameters to Invoke-RestMethod call.
    $WebRequestParams = @{
        Uri = "$BaseUri/$APIVersion/$ResourcePath"
        Header = @{'X-Auth-Token' = $NimbleStorageSession_token}
        Method = $Method
    }

    # Copy request params to different variable. We may need to specifically process few of them.
    $RequestData = @{}
    foreach ($key in $RequestParams.keys)
    {
        # PowerShell serializes Booleans in JSON as True/False. We need all lowercase for Nimble Array's REST Server.
        if ($RequestParams.$key.getType() -eq [bool])
        {
            $RequestData.Add($key, $RequestParams.$key.ToString().ToLower())
        }
        elseif ($key -eq 'fields' -and $Method -eq 'GET')
        {
            # Array of fields. Convert to comma separated list.
            $RequestData.Add('fields', ($RequestParams['fields'] | Select-Object -unique) -join ',')
        }
        else
        {
            $RequestData.Add($key, $RequestParams.$key);
        }
    }

    switch($Method) {
        'GET' {
            # Hashmap supplied in Body for GET request gets converted to query params automatically.
            $WebRequestParams.Add('Body',$RequestData)
        }

        'POST' {
            # Encapsulate request payload in 'data'..
            $RequestDataForNimbleAPI = @{
                data = $RequestData
            }

            $WebRequestParams.Add('Body',($RequestDataForNimbleAPI | ConvertTo-Json -Depth 10))
        }

        'PUT'{
            $RequestDataForNimbleAPI = @{
                data = $RequestData
            }

            $WebRequestParams.Add('Body',($RequestDataForNimbleAPI | ConvertTo-Json -Depth 10))
        }

        'DELETE' {
            # Do nothing. No Body expected/required for delete request.
        }
    }

    Write-Verbose ($WebRequestParams | ConvertTo-Json -Depth 50)
    $max_retry_count = 5
    $retry_count = 0

    do {
        try
            {
          
              if ($retry_count -ne 0)
              {
                Start-Sleep -Milliseconds 30
              }
              $JsonResponse = (Invoke-RestMethod @WebRequestParams  | ConvertTo-Json -Depth 50)
              Write-Verbose "Server Response: $JsonResponse"

              #
              # The Invoke-Restmethod was successful we should exit the retry loop.
              # To do that we will force the retry count max surpass the max.
              #
              $retry_count  = $max_retry_count  + 1
            }
        catch
        {
            if ($_.Exception.Response -ne $null) 
            {
               APIExceptionHandler
            }
            else
            {     
                # if the Error response buffer is null then we will go for retries.
                # if we exhaust the retries we will thrown and error.
                $retry_count = $retry_count + 1
                
                if ($retry_count -gt $max_retry_count)
                {
                    Write-Verbose $_.exception
                    Write-Error "Error occoured while invoking restapi method, Please retry" -ErrorAction Stop 
                }

            }
        }
      
   }until ($retry_count -gt $max_retry_count) 
   
    return ($JsonResponse | ConvertFrom-Json)
}

function Get-NimbleStorageAPIObject()
{
    param(
        [Parameter(Mandatory=$true)][string] $ObjectName,
        [Parameter(Mandatory=$true)][string] $APIPath,
        [Parameter(Mandatory=$true)][string] $Id,
        [System.Collections.ArrayList] $Fields
    )

    $Params = @{
        ResourcePath = $APIPath + "/$Id"
        Method = 'GET'
    }

    if ($Fields)
    {
        $Params.Add('RequestParams', @{ fields = ($Fields | Select-Object) -join ','})
    }

    $APIObject = (Invoke-NimbleStorageRestAPI @Params).data
    $DataSetType = "NimbleStorage.$ObjectName"
    $APIObject.PSTypeNames.Insert(0,$DataSetType)
    $DataSetType = $DataSetType + ".TypeName"
    $APIObject.PSObject.TypeNames.Insert(0,$DataSetType)

    return $APIObject
}

function Get-NimbleStorageAPIObjectList()
{
    param(
        [Parameter(Mandatory=$true)][string] $ObjectName,
        [Parameter(Mandatory=$true)][string] $APIPath,
        [hashtable] $Filters,
        [System.Collections.ArrayList] $Fields
    )

    # First fetch all the objects (only id and name) matching the given filter.
    # Then for each of the objects, retrieve either all the details or given fields.
    $Params = @{
        ResourcePath = $APIPath
        Method = 'GET'
        RequestParams = $Filters
    }

    # Get the list of objects matching given criteria
    $JSONResponseObject = (Invoke-NimbleStorageRestAPI @Params)
    [System.Collections.ArrayList] $APIObjects = $JSONResponseObject.data

    # We are expecting a list. If total items/objects on the array for this query are more than 1024,
    # array will send back only first 1024 objects along with total count of objects in 'totalRows'.
    if ($JSONResponseObject.endRow -and $JSONResponseObject.totalRows -and ($JSONResponseObject.endRow -lt $JSONResponseObject.totalRows))
    {
        # There are more objects. Keep getting those until we reach the end.
        while ($JSONResponseObject.endRow -lt $JSONResponseObject.totalRows)
        {
            $Params.RequestParams.startRow = $JSONResponseObject.endRow
            $JSONResponseObject = Invoke-NimbleStorageRestAPI @Params
            $APIObjects.AddRange($JSONResponseObject.data) | out-null
        }
    }

    [System.Collections.ArrayList] $APIObjectsDetailed = @()

    # Fetch needed detailes of all the objects.
    foreach ($APIObject in $APIObjects)
    {
        $Params = @{
            ObjectName = $ObjectName
            APIPath = $APIPath
            Id = $APIObject.id
        }
        if ($Fields)
        {
            $Params.Add('Fields', $Fields)
        }

        $APIObject = (Get-NimbleStorageAPIObject @Params)
        $DataSetType = "NimbleStorage.$ObjectName"
        $APIObject.PSTypeNames.Insert(0,$DataSetType)
        $DataSetType = $DataSetType + ".TypeName"
        $APIObject.PSObject.TypeNames.Insert(0,$DataSetType)
        $APIObjectsDetailed.Add($APIObject) | out-null
    }
    Write-Verbose ("Found " + $APIObjectsDetailed.Count + " objects.")
    return ,$APIObjectsDetailed
}

function New-NimbleStorageAPIObject()
{
    param(
        [Parameter(Mandatory=$true)][string] $ObjectName,
        [Parameter(Mandatory=$true)][string] $APIPath,
        [Parameter(Mandatory=$true)][hashtable] $Properties
    )

    $Params = @{
        ResourcePath = $APIPath
        Method = 'POST'
        RequestParams = $Properties
    }

    $ResponseObject = (Invoke-NimbleStorageRestAPI @Params)
    $APIObject = $ResponseObject.data
    if ($APIObject)
    {$DataSetType = "NimbleStorage.$ObjectName"
        $APIObject.PSTypeNames.Insert(0,$DataSetType)
        $DataSetType = $DataSetType + ".TypeName"
        $APIObject.PSObject.TypeNames.Insert(0,$DataSetType)
    }
    else
    {
        $APIObject = $ResponseObject
        $DataSetType = "NimbleStorage.Messages"
        $APIObject.PSTypeNames.Insert(0,$DataSetType)
        $DataSetType = $DataSetType + ".TypeName"
        $APIObject.PSObject.TypeNames.Insert(0,$DataSetType)
    }

    return $APIObject
}

function Set-NimbleStorageAPIObject()
{
    param(
        [Parameter(Mandatory=$true)][string] $ObjectName,
        [Parameter(Mandatory=$true)][string] $APIPath,
        [Parameter(Mandatory=$true)][string] $Id,
        [Parameter(Mandatory=$true)][hashtable] $Properties
    )

    $Params = @{
        ResourcePath = $APIPath + "/$Id"
        Method = 'PUT'
        RequestParams = $Properties
    }

    $ResponseObject = (Invoke-NimbleStorageRestAPI @Params)
    $APIObject = $ResponseObject.data
    if ($APIObject)
    {$DataSetType = "NimbleStorage.$ObjectName"
        $APIObject.PSTypeNames.Insert(0,$DataSetType)
        $DataSetType = $DataSetType + ".TypeName"
        $APIObject.PSObject.TypeNames.Insert(0,$DataSetType)
    }
    else
    {
        $APIObject = $ResponseObject
        $DataSetType = "NimbleStorage.Messages"
        $APIObject.PSTypeNames.Insert(0,$DataSetType)
        $DataSetType = $DataSetType + ".TypeName"
        $APIObject.PSObject.TypeNames.Insert(0,$DataSetType)
    }

    return $APIObject
}

function Remove-NimbleStorageAPIObject()
{
    param(
        [Parameter(Mandatory=$true)][string] $ObjectName,
        [Parameter(Mandatory=$true)][string] $APIPath,
        [Parameter(Mandatory=$true)][string] $Id
    )

    $Params = @{
        ResourcePath = $APIPath + "/$Id"
        Method = 'DELETE'
    }

    $APIObject = (Invoke-NimbleStorageRestAPI @Params).data
}

function Invoke-NimbleStorageAPIAction()
{
    param(
        [Parameter(Mandatory=$true)][string] $APIPath,
        [Parameter(Mandatory=$true)][string] $Action,
        [Parameter(Mandatory=$true)][hashtable] $Arguments,
        [Parameter(Mandatory=$true)][string] $ReturnType
    )

    $Params = @{
        ResourcePath = $APIPath + "/actions/$Action"
        Method = 'POST'
        RequestParams = $Arguments
    }

    if ($Arguments.id)
    {
        $id = $($Arguments.id)
        $Params.ResourcePath = $APIPath + "/$id/actions/$Action"
        $Arguments.Remove('id')
    }

    $ResponseObject = (Invoke-NimbleStorageRestAPI @Params)
    if ($ReturnType -eq "void")
    {
        # Return empty object
        return $ResponseObject
    }
    $APIObject = $ResponseObject.data
    $DataSetType = "NimbleStorage.$ReturnType"
    $APIObject.PSTypeNames.Insert(0,$DataSetType)
    $DataSetType = $DataSetType + ".TypeName"
    $APIObject.PSObject.TypeNames.Insert(0,$DataSetType)

    return $APIObject
}


function ValidateServerCertificate() 
{

    param(
    [Parameter(Mandatory,Position=0)]
        [string]$Group )


    $Code = @'
        using System;
        using System.Collections.Generic;
        using System.Net.Http;
        using System.Net.Security;
        using System.Security.Cryptography.X509Certificates;
 
        namespace CertificateCapture
        {
            public class Utility
            {
                 public static Func<HttpRequestMessage,X509Certificate2,X509Chain,SslPolicyErrors,Boolean> ValidationCallback =
                    (message, cert, chain, errors) => {
                        var newCert = new X509Certificate2(cert);
                        var newChain = new X509Chain();
                        newChain.Build(newCert);
                        CapturedCertificates.Add(new CapturedCertificate(){
                            Certificate = newCert,
                            CertificateChain = newChain,
                            PolicyErrors = errors,
                            URI = message.RequestUri
                        });
                        return true;
                    };
                public static List<CapturedCertificate> CapturedCertificates = new List<CapturedCertificate>();
            }
 
            public class CapturedCertificate
            {
                public X509Certificate2 Certificate { get; set; }
                public X509Chain CertificateChain { get; set; }
                public SslPolicyErrors PolicyErrors { get; set; }
                public Uri URI { get; set; }
            }
        }
'@


if ($PSEdition -ne 'Core'){
    $webrequest=[net.webrequest]::Create("https://$Group")
    try { $response=$webrequest.getresponse() } catch {}
    $cert=$webrequest.servicepoint.certificate
    if($cert -ne $null){
            $Thumbprint = $webrequest.ServicePoint.Certificate.GetCertHashString()
            $bytes=$cert.export([security.cryptography.x509certificates.x509contenttype]::cert)
            $tfile=[system.io.path]::getTempFileName()
            set-content -value $bytes -encoding byte -path $tfile
            $certdetails = $cert | select * | ft -AutoSize | Out-String
            if ($($GlobalImportServerCertificate))  
            {   
                try{
                    $output =import-certificate -filepath $tfile -certStoreLocation 'Cert:\localmachine\Root'
                    $certdetails = $output | select -Property Thumbprint,subject | ft -AutoSize | Out-String
                    }
                catch{
                    Write-Error "Failed to import the server certificate `n`n $_.Exception.Message"  -ErrorAction Stop
                }
                Write-Host "Successfully imported the server certificate `n $certdetails"
            }
            else{
                 if((Get-ChildItem -Path Cert:\LocalMachine\root | Where-Object {$_.Thumbprint -eq $Thumbprint})){ }                
                 else{
                        write-Error "The security certificate presented by host $Group was not issued by a trusted certificate authority. Please verify the certificate details shown below and use ImportServerCertificate command line parameter to proceed. `n $certdetails `n`n" -ErrorAction Stop 
                     }
            }

             ResolveIPtoHost $cert.subject $Group


        }
    else{
            Write-Error "Failed to import the server certificate `n`n"  -ErrorAction Stop

        }  
}else { 
        Add-Type $Code
        $Certs = [CertificateCapture.Utility]::CapturedCertificates
        $Handler = [System.Net.Http.HttpClientHandler]::new()
        $Handler.ServerCertificateCustomValidationCallback = [CertificateCapture.Utility]::ValidationCallback
        $Client = [System.Net.Http.HttpClient]::new($Handler)
        $Url = "https://$Group"
        $Result = $Client.GetAsync($Url).Result
        $cert= $Certs[-1].Certificate
        if($certs -ne $null){
            $certdetails = $cert | select -Property Thumbprint,subject | ft -AutoSize | Out-String
            if ($($GlobalImportServerCertificate))  
            {
                $bytes=$cert.export([security.cryptography.x509certificates.x509contenttype]::cert)
                $OpenFlags = [System.Security.Cryptography.X509Certificates.OpenFlags]
                $store = new-object system.security.cryptography.X509Certificates.X509Store -argumentlist "Root","LocalMachine"
                try{
                        $Store.Open($OpenFlags::ReadWrite)
                        $Store.Add($Cert)
                        $Store.Close()
                        Write-Host "Successfully imported the server certificate `n $certdetails" 
                    }
                catch{
                        Write-Error "Failed to import the server certificate `n`n $_.Exception.Message"  -ErrorAction Stop
                }
            }
            else
            {
               if((Get-ChildItem -Path Cert:\LocalMachine\root | Where-Object {$_.Thumbprint -eq $cert.Thumbprint})){ }                
                 else{
                        write-Error "The security certificate presented by host $Group was not issued by a trusted certificate authority. Please verify the certificate details shown below and use ImportServerCertificate command line parameter to proceed. `n $certdetails `n`n" -ErrorAction Stop 
                     }
            }
             ResolveIPtoHost $cert.subject $Group
        }
        else{
            Write-Error "Failed to import the server certificate `n`n"  -ErrorAction Stop
        }


}
}
function ResolveIPtoHost{

 param(
    [Parameter(Mandatory)]
        [string]$CertSubject, 
        
    [Parameter(Mandatory)]
        [string]$Group
        )

            # we will check if the host name given as input matches the host name in the certificate.
            # if IP is given as input and the certificate has hostname (FQDN) then we will use the hostname name but
            # before that we will ensurelookup from hostname to IP works.
            
            $cert_hostname = ($CertSubject.Substring(($CertSubject.IndexOf("=")+1),($CertSubject.IndexOf(",")-3))).trim()
            
            # check if the input and cert hostname matches, if yes we are good to go
            # Else the input must have ben the IP address, we will if cert hostname can be resolved to match the input hostname (IP).
            if ($Group -ne $cert_hostname)
            {
                # we will look up the DNS to resolve the cert_hostname.
                # we could do this with either cert hostname or ibput hostname(IP), but cert hostname has better chance of getting resolved.
                
                try
                {
                    $resolved_name = [System.Net.DNS]::GetHostEntry($cert_hostname).AddressList 
                    $resolved_name = $resolved_name | select -ExpandProperty IPAddressToString
                   # $Group = $resolved_name
                    # will come here if the host got resolved.
                    
                     Write-Verbose " $cert_hostname is host-name for provided input $Group IP address"
                    
                    if ($resolved_name -ne $Group)             
                    {
                        # most probably this will not happen, just to be defensive adding this code.
                        # this is the same host as the certificate hostname got resolved to the input hostname(IP)
                        # we will start using the cert hostname for all the calls from this point in this session.
                        
                        Write-Error "Unable to resolve the certificate hostname to match the provided input hostname/IP. `n`n $_.Exception.Message"  -ErrorAction Stop
                    }
                    else
                    {
                        # we are good to go.
                        Write-Verbose " Resolved name and input name matches: $Group IP address"
                        $global:Group = $cert_hostname
                    }

                }
                catch 
                {
                    # unable to resolve the cert hostname. Host not reachable. Error out!.
                    Write-Error "Unable to resolve the certificate hostname. Host not reachable. `n`n $_.Exception.Message"  -ErrorAction Stop
                }

            }
            else
            {
                # we are good to go.
                
                Write-Verbose "Host-name given as input matches with certificate name"
            }
             


}

Function APIExceptionHandler
{
   #Exception message handle differently for core and non core environment
   #GetResponseStream method does not work in core environment

   if(Get-Member -inputobject $_.Exception.Response -name "GetResponseStream" -Membertype Method)
   {
       $JsonResponse = $_.Exception.Response.GetResponseStream()
       $reader = New-Object System.IO.StreamReader($JsonResponse)
       $reader.BaseStream.Position = 0
       $reader.DiscardBufferedData()
       $responseBody = $reader.ReadToEnd();
       if (($responseBody | ConvertFrom-Json).messages -ne $null)
       {
           foreach( $errorMsg in ($responseBody | ConvertFrom-Json).messages) {
           if($errorMsg.text -ne $null) {$exceptionString += $errorMsg.text + " "}
       }
       throw [System.Exception] $exceptionString
       }
       else
       {
           throw $_.Exception
       }
   }
   else
   {
        
        $responseBody = $_.ErrorDetails
       if (($responseBody | ConvertFrom-Json).messages -ne $null)
       {
           foreach( $errorMsg in ($responseBody | ConvertFrom-Json).messages) {
           if($errorMsg.text -ne $null) {$exceptionString += $errorMsg.text + " "}
       }
       throw [System.Exception] $exceptionString
       }
       else
       {
           throw $_.Exception
       }
   }       
}
# SIG # Begin signature block
# MIIhEwYJKoZIhvcNAQcCoIIhBDCCIQACAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCVQTsTa3mrUpqd
# okXJZmMqbB9nn1lA6qbWGLXRvpzg3qCCEKwwggUqMIIEEqADAgECAhEAvNU51iSY
# 0pIemSd4RhoKzjANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJHQjEbMBkGA1UE
# CBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRgwFgYDVQQK
# Ew9TZWN0aWdvIExpbWl0ZWQxJDAiBgNVBAMTG1NlY3RpZ28gUlNBIENvZGUgU2ln
# bmluZyBDQTAeFw0yMTA1MjgwMDAwMDBaFw0yMjA1MjgyMzU5NTlaMIGQMQswCQYD
# VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJUGFsbyBBbHRv
# MSswKQYDVQQKDCJIZXdsZXR0IFBhY2thcmQgRW50ZXJwcmlzZSBDb21wYW55MSsw
# KQYDVQQDDCJIZXdsZXR0IFBhY2thcmQgRW50ZXJwcmlzZSBDb21wYW55MIIBIjAN
# BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyo5MH8CMlPL4CA+tkPZO/A7zvMst
# 2DmdLgU7GJoMsXv8PYnYJzxb/ILnmaCIlCCimzZ7YmtuS1F0kMQLedMu0CyY92SW
# 0CCqJRMICtIE/ahCIPAHcN3dHjc/CNAezTGvMoqh3oSOGW4KbDk8buzIyVp6O4E8
# Q4SBKjo3Ly+yzBT63Oak+C7GTu7en0r50BPel7STQEaAPLEQbBJCafvCyZwHzF1l
# NzPWcnSITN7x9FIJ5H1quYnMhxWaDXY0GXZLW9UoNG0u87Emz3gBCxNrQf6y89qu
# wEF4IGDFL0l/PmHN70HXCOHWJhydRjAm7JER80NaBSqKWuDX+BPE63pQ/QIDAQAB
# o4IBkDCCAYwwHwYDVR0jBBgwFoAUDuE6qFM6MdWKvsG7rWcaA4WtNA4wHQYDVR0O
# BBYEFBLk4qaHNH/WpWNTozuiAfRQ3keeMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMB
# Af8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMBEGCWCGSAGG+EIBAQQEAwIEEDBK
# BgNVHSAEQzBBMDUGDCsGAQQBsjEBAgEDAjAlMCMGCCsGAQUFBwIBFhdodHRwczov
# L3NlY3RpZ28uY29tL0NQUzAIBgZngQwBBAEwQwYDVR0fBDwwOjA4oDagNIYyaHR0
# cDovL2NybC5zZWN0aWdvLmNvbS9TZWN0aWdvUlNBQ29kZVNpZ25pbmdDQS5jcmww
# cwYIKwYBBQUHAQEEZzBlMD4GCCsGAQUFBzAChjJodHRwOi8vY3J0LnNlY3RpZ28u
# Y29tL1NlY3RpZ29SU0FDb2RlU2lnbmluZ0NBLmNydDAjBggrBgEFBQcwAYYXaHR0
# cDovL29jc3Auc2VjdGlnby5jb20wDQYJKoZIhvcNAQELBQADggEBAHrs/rf97Zyr
# AqyaXhXV58W3q38egR7o5Dxyd8cIDBunhxY1v3e4syOmVU+APjU+49XThv1EHmt1
# Tbhi/NR+ZLBKwVH6rls7WiIQXGT4idWaFFItOlC5SaW0HLbEBLpCK/gva9aZzXfs
# EbgIgzBTqxmfpdIseptvdN5F6WIoPLRMaLJH4oCm0V2E5joqYawXunj0TNWzPoah
# Otq9x+Q8cinHNOXeqFVAfsQg8DdxX/xsVGyNl/TDU59+/VFZynHWneXi8ND8I6om
# iFuzPzKpr7vMiOveAs2wjrdxnaU+4HBL4E2g2WitRi890cmUaTLQrvNM52afdDEk
# 538pYKjmCUgwggWBMIIEaaADAgECAhA5ckQ6+SK3UdfTbBDdMTWVMA0GCSqGSIb3
# DQEBDAUAMHsxCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0
# ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVk
# MSEwHwYDVQQDDBhBQUEgQ2VydGlmaWNhdGUgU2VydmljZXMwHhcNMTkwMzEyMDAw
# MDAwWhcNMjgxMjMxMjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5l
# dyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNF
# UlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh
# dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCA
# EmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7
# NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTb
# f6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/Fp0YvVGO
# NaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2VN3I5xI6
# Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq/nRO
# acdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6l
# ZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8l
# iM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0A
# vzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+
# /XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeEHg9j1uli
# utZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo4HyMIHvMB8GA1Ud
# IwQYMBaAFKARCiM+lvEH7OKvKe+CpX/QMKS0MB0GA1UdDgQWBBRTeb9aqitKz1SA
# 4dibwJ3ysgNmyzAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zARBgNV
# HSAECjAIMAYGBFUdIAAwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21v
# ZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEE
# KDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZI
# hvcNAQEMBQADggEBABiHUdx0IT2ciuAntzPQLszs8ObLXhHeIm+bdY6ecv7k1v6q
# H5yWLe8DSn6u9I1vcjxDO8A/67jfXKqpxq7y/Njuo3tD9oY2fBTgzfT3P/7euLSK
# 8JGW/v1DZH79zNIBoX19+BkZyUIrE79Yi7qkomYEdoiRTgyJFM6iTckys7roFBq8
# cfFb8EELmAAKIgMQ5Qyx+c2SNxntO/HkOrb5RRMmda+7qu8/e3c70sQCkT0ZANMX
# XDnbP3sYDUXNk4WWL13fWRZPP1G91UUYP+1KjugGYXQjFrUNUHMnREd/EF2JKmuF
# MRTE6KlqTIC8anjPuH+OdnKZDJ3+15EIFqGjX5UwggX1MIID3aADAgECAhAdokgw
# b5smGNCC4JZ9M9NqMA0GCSqGSIb3DQEBDAUAMIGIMQswCQYDVQQGEwJVUzETMBEG
# A1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoT
# FVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBD
# ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xODExMDIwMDAwMDBaFw0zMDEyMzEy
# MzU5NTlaMHwxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0
# ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEk
# MCIGA1UEAxMbU2VjdGlnbyBSU0EgQ29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG
# 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhiKNMoV6GJ9J8JYvYwgeLdx8nxTP4ya2JWYp
# QIZURnQxYsUQ7bKHJ6aZy5UwwFb1pHXGqQ5QYqVRkRBq4Etirv3w+Bisp//uLjMg
# +gwZiahse60Aw2Gh3GllbR9uJ5bXl1GGpvQn5Xxqi5UeW2DVftcWkpwAL2j3l+1q
# cr44O2Pej79uTEFdEiAIWeg5zY/S1s8GtFcFtk6hPldrH5i8xGLWGwuNx2YbSp+d
# gcRyQLXiX+8LRf+jzhemLVWwt7C8VGqdvI1WU8bwunlQSSz3A7n+L2U18iLqLAev
# Rtn5RhzcjHxxKPP+p8YU3VWRbooRDd8GJJV9D6ehfDrahjVh0wIDAQABo4IBZDCC
# AWAwHwYDVR0jBBgwFoAUU3m/WqorSs9UgOHYm8Cd8rIDZsswHQYDVR0OBBYEFA7h
# OqhTOjHVir7Bu61nGgOFrTQOMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAG
# AQH/AgEAMB0GA1UdJQQWMBQGCCsGAQUFBwMDBggrBgEFBQcDCDARBgNVHSAECjAI
# MAYGBFUdIAAwUAYDVR0fBEkwRzBFoEOgQYY/aHR0cDovL2NybC51c2VydHJ1c3Qu
# Y29tL1VTRVJUcnVzdFJTQUNlcnRpZmljYXRpb25BdXRob3JpdHkuY3JsMHYGCCsG
# AQUFBwEBBGowaDA/BggrBgEFBQcwAoYzaHR0cDovL2NydC51c2VydHJ1c3QuY29t
# L1VTRVJUcnVzdFJTQUFkZFRydXN0Q0EuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8v
# b2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBDAUAA4ICAQBNY1DtRzRKYaTb
# 3moqjJvxAAAeHWJ7Otcywvaz4GOz+2EAiJobbRAHBE++uOqJeCLrD0bs80ZeQEaJ
# EvQLd1qcKkE6/Nb06+f3FZUzw6GDKLfeL+SU94Uzgy1KQEi/msJPSrGPJPSzgTfT
# t2SwpiNqWWhSQl//BOvhdGV5CPWpk95rcUCZlrp48bnI4sMIFrGrY1rIFYBtdF5K
# dX6luMNstc/fSnmHXMdATWM19jDTz7UKDgsEf6BLrrujpdCEAJM+U100pQA1aWy+
# nyAlEA0Z+1CQYb45j3qOTfafDh7+B1ESZoMmGUiVzkrJwX/zOgWb+W/fiH/AI57S
# HkN6RTHBnE2p8FmyWRnoao0pBAJ3fEtLzXC+OrJVWng+vLtvAxAldxU0ivk2zEOS
# 5LpP8WKTKCVXKftRGcehJUBqhFfGsp2xvBwK2nxnfn0u6ShMGH7EezFBcZpLKewL
# PVdQ0srd/Z4FUeVEeN0B3rF1mA1UJP3wTuPi+IO9crrLPTru8F4XkmhtyGH5pvEq
# CgulufSe7pgyBYWe6/mDKdPGLH29OncuizdCoGqC7TtKqpQQpOEN+BfFtlp5MxiS
# 47V1+KHpjgolHuQe8Z9ahyP/n6RRnvs5gBHN27XEp6iAb+VT1ODjosLSWxr6MiYt
# aldwHDykWC6j81tLB9wyWfOHpxptWDGCD70wgg+5AgEBMIGRMHwxCzAJBgNVBAYT
# AkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZv
# cmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEkMCIGA1UEAxMbU2VjdGlnbyBS
# U0EgQ29kZSBTaWduaW5nIENBAhEAvNU51iSY0pIemSd4RhoKzjANBglghkgBZQME
# AgEFAKB8MBAGCisGAQQBgjcCAQwxAjAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3
# AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEi
# BCD881SdjopR71UWj3hn2+Ehxr7InbkRiROF/dVJNLfeWDANBgkqhkiG9w0BAQEF
# AASCAQCVh05tg6/4EdvT6kVW4y22QOWdOyH32X1e3oNjAbmr/DU3+EAfG6oo059F
# iucR9NGWOooLTwiw8W3LkCIs4B0nzCYTIl39S9nwACtFRbUxk5WtyPGHdvVjpDMZ
# kr603Nn8DljVH0LYWZrw813tu6OU/MYd9Iv+H08k4ukpB9gULuvZfawfkohkJ6wz
# qJSBDgPgLX+G/GM0NXFrJZSbZ4NhUkVXP5HlCWkf1I/uToF87KGV52NRYTaTwDcK
# +aBPmeb1rb8IjO7c3B/lDMNC8QxAZqVMRkkvJvqYuMtvwUPyB6ERaumFhP+/xfwu
# vozRvQAMhkksB/uxSl2J5HsEXFHjoYINfjCCDXoGCisGAQQBgjcDAwExgg1qMIIN
# ZgYJKoZIhvcNAQcCoIINVzCCDVMCAQMxDzANBglghkgBZQMEAgEFADB4BgsqhkiG
# 9w0BCRABBKBpBGcwZQIBAQYJYIZIAYb9bAcBMDEwDQYJYIZIAWUDBAIBBQAEID1B
# +J82T88uk3PaoZ3/21pAWNNimTsZqd9/nJf0hOhfAhEAjTeAOGVN5CSl5kNzv01y
# DhgPMjAyMjAyMTEwMjQ0MDFaoIIKNzCCBP4wggPmoAMCAQICEA1CSuC+Ooj/YEAh
# zhQA8N0wDQYJKoZIhvcNAQELBQAwcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERp
# Z2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMo
# RGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQTAeFw0yMTAx
# MDEwMDAwMDBaFw0zMTAxMDYwMDAwMDBaMEgxCzAJBgNVBAYTAlVTMRcwFQYDVQQK
# Ew5EaWdpQ2VydCwgSW5jLjEgMB4GA1UEAxMXRGlnaUNlcnQgVGltZXN0YW1wIDIw
# MjEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDC5mGEZ8WK9Q0IpEXK
# Y2tR1zoRQr0KdXVNlLQMULUmEP4dyG+RawyW5xpcSO9E5b+bYc0VkWJauP9nC5xj
# /TZqgfop+N0rcIXeAhjzeG28ffnHbQk9vmp2h+mKvfiEXR52yeTGdnY6U9HR01o2
# j8aj4S8bOrdh1nPsTm0zinxdRS1LsVDmQTo3VobckyON91Al6GTm3dOPL1e1hyDr
# Do4s1SPa9E14RuMDgzEpSlwMMYpKjIjF9zBa+RSvFV9sQ0kJ/SYjU/aNY+gaq1ux
# HTDCm2mCtNv8VlS8H6GHq756WwogL0sJyZWnjbL61mOLTqVyHO6fegFz+BnW/g1J
# hL0BAgMBAAGjggG4MIIBtDAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAW
# BgNVHSUBAf8EDDAKBggrBgEFBQcDCDBBBgNVHSAEOjA4MDYGCWCGSAGG/WwHATAp
# MCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHwYDVR0j
# BBgwFoAU9LbhIB3+Ka7S5GGlsqIlssgXNW4wHQYDVR0OBBYEFDZEho6kurBmvrwo
# LR1ENt3janq8MHEGA1UdHwRqMGgwMqAwoC6GLGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0
# LmNvbS9zaGEyLWFzc3VyZWQtdHMuY3JsMDKgMKAuhixodHRwOi8vY3JsNC5kaWdp
# Y2VydC5jb20vc2hhMi1hc3N1cmVkLXRzLmNybDCBhQYIKwYBBQUHAQEEeTB3MCQG
# CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wTwYIKwYBBQUHMAKG
# Q2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJBc3N1cmVk
# SURUaW1lc3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggEBAEgc3LXpmiO8
# 5xrnIA6OZ0b9QnJRdAojR6OrktIlxHBZvhSg5SeBpU0UFRkHefDRBMOG2Tu9/kQC
# Zk3taaQP9rhwz2Lo9VFKeHk2eie38+dSn5On7UOee+e03UEiifuHokYDTvz0/rdk
# d2NfI1Jpg4L6GlPtkMyNoRdzDfTzZTlwS/Oc1np72gy8PTLQG8v1Yfx1CAB2vIEO
# +MDhXM/EEXLnG2RJ2CKadRVC9S0yOIHa9GCiurRS+1zgYSQlT7LfySmoc0NR2r1j
# 1h9bm/cuG08THfdKDXF+l7f0P4TrweOjSaH6zqe/Vs+6WXZhiV9+p7SOZ3j5Npjh
# yyjaW4emii8wggUxMIIEGaADAgECAhAKoSXW1jIbfkHkBdo2l8IVMA0GCSqGSIb3
# DQEBCwUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAX
# BgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3Vy
# ZWQgSUQgUm9vdCBDQTAeFw0xNjAxMDcxMjAwMDBaFw0zMTAxMDcxMjAwMDBaMHIx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJ
# RCBUaW1lc3RhbXBpbmcgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC90DLuS82Pf92puoKZxTlUKFe2I0rEDgdFM1EQfdD5fU1ofue2oPSNs4jkl79j
# IZCYvxO8V9PD4X4I1moUADj3Lh477sym9jJZ/l9lP+Cb6+NGRwYaVX4LJ37AovWg
# 4N4iPw7/fpX786O6Ij4YrBHk8JkDbTuFfAnT7l3ImgtU46gJcWvgzyIQD3XPcXJO
# Cq3fQDpct1HhoXkUxk0kIzBdvOw8YGqsLwfM/fDqR9mIUF79Zm5WYScpiYRR5oLn
# RlD9lCosp+R1PrqYD4R/nzEU1q3V8mTLex4F0IQZchfxFwbvPc3WTe8GQv2iUypP
# hR3EHTyvz9qsEPXdrKzpVv+TAgMBAAGjggHOMIIByjAdBgNVHQ4EFgQU9LbhIB3+
# Ka7S5GGlsqIlssgXNW4wHwYDVR0jBBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8w
# EgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYI
# KwYBBQUHAwgweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz
# cC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2lj
# ZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgw
# OqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJ
# RFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdp
# Q2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwUAYDVR0gBEkwRzA4BgpghkgBhv1sAAIE
# MCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCwYJ
# YIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4IBAQBxlRLpUYdWac3v3dp8qmN6s3jP
# BjdAhO9LhL/KzwMC/cWnww4gQiyvd/MrHwwhWiq3BTQdaq6Z+CeiZr8JqmDfdqQ6
# kw/4stHYfBli6F6CJR7Euhx7LCHi1lssFDVDBGiy23UC4HLHmNY8ZOUfSBAYX4k4
# YU1iRiSHY4yRUiyvKYnleB/WCxSlgNcSR3CzddWThZN+tpJn+1Nhiaj1a5bA9Fhp
# DXzIAbG5KHW3mWOFIoxhynmUfln8jA/jb7UBJrZspe6HUSHkWGCbugwtK22ixH67
# xCUrRwIIfEmuE7bhfEJCKMYYVs9BNLZmXbZ0e/VWMyIvIjayS6JKldj1po5SMYIC
# hjCCAoICAQEwgYYwcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IElu
# YzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQg
# U0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQQIQDUJK4L46iP9gQCHOFADw
# 3TANBglghkgBZQMEAgEFAKCB0TAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQw
# HAYJKoZIhvcNAQkFMQ8XDTIyMDIxMTAyNDQwMVowKwYLKoZIhvcNAQkQAgwxHDAa
# MBgwFgQU4deCqOGRvu9ryhaRtaq0lKYkm/MwLwYJKoZIhvcNAQkEMSIEIGemY+VN
# OFEgrOc8PricSpZp7FocUJMrXJEfTPVk4jjKMDcGCyqGSIb3DQEJEAIvMSgwJjAk
# MCIEILMQkAa8CtmDB5FXKeBEA0Fcg+MpK2FPJpZMjTVx7PWpMA0GCSqGSIb3DQEB
# AQUABIIBAHMj2pkOdnO8UVwR5+qbFQejBBLBcD9F7BLW1rgf3jLWLmB+9a0T2ppi
# nDFi6xzpMgBNEMet5AsHUyuqZ62PZPfkTKHK2lPg90FKcBjxCT1uxLeohxxx97yw
# 3DCo21G4H4kAH9xpvae+oOsZDnKtXodNWHF4WjVCcEit98F2JUj1zJGifDwNCMJM
# 09WfLpAvYKQXMmw0d/gKMym7buxpxRsx0mHeJYTuLhqGQqeZekiCb8Zd2ae05lFU
# J8sKlWgxMJonkXpQnvokaPJETFGkB2K08fTLdZ6FmT861r25z9de/yrRFf5VJ990
# LfLUcb5qE85YdMOlxczoq4oWTtLqEIk=
# SIG # End signature block