Deploy/Classes/EnvironmentValidator/EnvironmentValidator.psm1

<###################################################
# #
# Copyright (c) Microsoft. All rights reserved. #
# #
##################################################>


#using module ..\Common\Role.psm1

$ErrorActionPreference = "Stop"
class Role
{
}

class EnvironmentValidator : Role
{
    static EnvironmentValidatorLite([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to run environment validator during bootstrap on seed node.
 
        .EXAMPLE
        [EnvironmentValidator]::EnvironmentValidatorLite($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


        $OperationType = 'Bootstrap'
        [EnvironmentValidator]::RunValidators($Parameters, $OperationType)
    }

    static EnvironmentValidatorFull([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to run environment validator and run pre-deployment
        when full ECE parameters are available
 
        .EXAMPLE
        [EnvironmentValidator]::EnvironmentValidatorFull($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


        $OperationType = 'Deployment'
        [EnvironmentValidator]::RunValidators($Parameters, $OperationType)
    }

    static EnvironmentValidatorUpgrade([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to run environment validator and run pre-upgrade
        when full ECE parameters are available
 
        .EXAMPLE
        [EnvironmentValidator]::EnvironmentValidatorUpgrade($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


        $OperationType = 'Upgrade'
        [EnvironmentValidator]::RunValidators($Parameters, $OperationType)
    }

    static EnvironmentValidatorPreUpdate([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to run environment validator and run pre-update
        when full ECE parameters are available
 
        .EXAMPLE
        [EnvironmentValidator]::EnvironmentValidatorPreUpdate($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


        $OperationType = 'PreUpdate'
        [EnvironmentValidator]::RunValidators($Parameters, $OperationType)
    }

    static EnvironmentValidatorPostUpdate([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to run environment validator and run post-update
        when full ECE parameters are available
 
        .EXAMPLE
        [EnvironmentValidator]::EnvironmentValidatorPostUpdate($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


        $OperationType = 'PostUpdate'
        [EnvironmentValidator]::RunValidators($Parameters, $OperationType)
    }

    static EnvironmentValidatorAddNode([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to run environment validator and run during AddNode
        when full ECE parameters are available
 
        .EXAMPLE
        [EnvironmentValidator]::EnvironmentValidatorAddNode($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


        $OperationType = 'AddNode'
        [EnvironmentValidator]::RunValidators($Parameters, $OperationType)
    }

    static EnvironmentValidatorPreAddNode([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to run environment validator and run before AddNode
        when full ECE parameters are available, but new node has not be updated in
        ECE.
 
        .EXAMPLE
        [EnvironmentValidator]::EnvironmentValidatorPreAddNode($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


        $OperationType = 'PreAddNode'
        [EnvironmentValidator]::RunValidators($Parameters, $OperationType)
    }

    static EnvironmentValidatorRecovery([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to run environment validator and run during Recovery
        when full ECE parameters are available
 
        .EXAMPLE
        [EnvironmentValidator]::EnvironmentValidatorRecovery($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


        $OperationType = 'Recovery'
        [EnvironmentValidator]::RunValidators($Parameters, $OperationType)
    }

    static EnvironmentValidatorReplayResult([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to replay environment validator results from any node after telemetry pipeline is up
 
        .EXAMPLE
        [EnvironmentValidator]::EnvironmentValidatorReplayResult()
 
        #>

        try {
            Trace-Execution "Searching for environment validator telemetry events to replay."
            $PsSession = [EnvironmentValidator]::NewPsSessionAllHosts($Parameters)
            $sb = {
                Write-Host ("{0} Importing Module" -f $ENV:COMPUTERNAME)
                Import-Module AzStackHci.EnvironmentChecker
                Write-Host ("{0} Reading Events" -f $ENV:COMPUTERNAME)
                $Events = Get-AzStackHciEnvironmentCheckerEvents -Source Telemetry
                Write-Host ("{0} Finished reading events" -f $ENV:COMPUTERNAME)
                if ($Events)
                {
                    Write-Host ("{0} Environment Validator Telemetry Events found on {1}. Replaying." -f $Events.Count, $ENV:COMPUTERNAME)
                    $Events | Foreach-Object {
                        $params = @{
                            Source = 'AzStackHciEnvironmentChecker/Telemetry'
                            LogName = 'AzStackHciEnvironmentChecker'
                            Message = $PSITEM.Message
                            EventId = $PSITEM.EventId
                            EventType = $PSITEM.EntryType
                        }
                        Write-ETWLog @params
                    }
                    Write-Host "Replay complete."
                }
                else
                {
                    Write-Host ("No Environment Validator Telemetry Events found on {0}. No Action." -f $ENV:COMPUTERNAME)
                }
            }
            Trace-Execution ("Calling script block")
            Invoke-Command -Session $PsSession -ScriptBlock $sb
            Trace-Execution ("Finished calling script block")
        }
        catch
        {
            Trace-Execution ("Error occurred trying to replay environment validator telemetry events. Error: {0}" -f $_)
        }
    }

    static ValidateBitlocker([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to run environment validator before deployment
        when full ECE parameters are available.
 
        .EXAMPLE
        [EnvironmentValidator]::ValidateBitlocker($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


        $OperationType = 'Deployment'
        $ValidatorName = 'AzStackHciBitlocker'
        [EnvironmentValidator]::RunSingleValidator($Parameters, $OperationType, $ValidatorName)
    }

    static ValidateConnectivity([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to run environment validator before deployment
        when full ECE parameters are available.
 
        .EXAMPLE
        [EnvironmentValidator]::ValidateConnectivity($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


        $OperationType = 'Deployment'
        $ValidatorName = 'AzStackHciConnectivity'
        [EnvironmentValidator]::RunSingleValidator($Parameters, $OperationType, $ValidatorName)
    }

    static ValidateExternalAD([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to run environment validator before deployment.
 
        .EXAMPLE
        [EnvironmentValidator]::ValidateExternalAD($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


        $OperationType = 'Deployment'
        $ValidatorName = 'AzStackHciExternalActiveDirectory'
        [EnvironmentValidator]::RunSingleValidator($Parameters, $OperationType, $ValidatorName)
    }
    static ValidateHardware([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to run environment validator before deployment.
 
        .EXAMPLE
        [EnvironmentValidator]::ValidateHardware($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


        $OperationType = 'Deployment'
        $ValidatorName = 'AzStackHciHardware'
        [EnvironmentValidator]::RunSingleValidator($Parameters, $OperationType, $ValidatorName)
    }

    static ValidateNetwork([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to run environment validator before deployment.
 
        .EXAMPLE
        [EnvironmentValidator]::ValidateNetwork($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


        $OperationType = 'Deployment'
        $ValidatorName = 'AzStackHciNetwork'
        [EnvironmentValidator]::RunSingleValidator($Parameters, $OperationType, $ValidatorName)
    }

    static ValidateObservability([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to run environment validator before deployment.
 
        .EXAMPLE
        [EnvironmentValidator]::ValidateObservability($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


        $OperationType = 'Deployment'
        $ValidatorName = 'AzStackHciObservability'
        [EnvironmentValidator]::RunSingleValidator($Parameters, $OperationType, $ValidatorName)
    }

    static ValidateSoftware([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to run environment validator before deployment.
 
        .EXAMPLE
        [EnvironmentValidator]::ValidateSoftware($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


        $OperationType = 'Deployment'
        $ValidatorName = 'AzStackHciSoftware'
        [EnvironmentValidator]::RunSingleValidator($Parameters, $OperationType, $ValidatorName)
    }

    static hidden RunSingleValidator([CloudEngine.Configurations.EceInterfaceParameters] $Parameters, [string] $OperationType, [string] $ValidatorName)
    {
        $ErrorActionPreference = "Stop"
        [EnvironmentValidator]::ImportEnvironmentValidator()
        $EnvCheckerModuleBase = Get-Module AzStackHci.EnvironmentChecker -ListAvailable | Select-Object -ExpandProperty ModuleBase
        Import-Module (Join-Path "$EnvCheckerModuleBase\$ValidatorName" "$ValidatorName.psm1")
        $cmdLetname = "Test-$ValidatorName"
        $splat = @{
            Parameters = $Parameters
            OperationType = $OperationType
            FailFast = $true
        }
        $ENV:EnvChkrOp = $OperationType
        Invoke-Expression "$cmdletName @splat"
    }

    static hidden [Array] BuildJobExecution([CloudEngine.Configurations.EceInterfaceParameters] $Parameters, [string] $OperationType)
    {
        $ErrorActionPreference = "Stop"
        [EnvironmentValidator]::ImportEnvironmentValidator()
        $EnvCheckerModuleBase = Get-Module AzStackHci.EnvironmentChecker -ListAvailable | Select-Object -ExpandProperty ModuleBase

        $validators = Get-ChildItem -Path $EnvCheckerModuleBase -Filter AzStackHci* -Directory
        Trace-Execution "Validators found: $($validators.Name -join ',')"

        # Gather validator list to run
        $executionJobs = @()
        class ExecutionJob {
            [string]$ModuleName
            [hashTable]$Params
            [string]$Command
            [string]$Name
            [string]$Description
            [string]$Result
            [datetime]$StartTime
            [datetime]$EndTime
            [int]$DurationSeconds
            [psobject[]]$FailedResult
            [string]$ExecutionDetail
        }
        foreach ($validator in $validators)
        {
            $validatorModulePath = Get-ChildItem -Path $validator.FullName -Filter "$($Validator.Name)*.psm1"
            if ($validatorModulePath)
            {
                Trace-Execution "Importing Validator $($validatorModulePath.Directory.Name)"
                Import-Module -Name $validatorModulePath.FullName -Force
                $module = Get-Module $validatorModulePath.Directory.Name
                $metaData = $module.ExportedVariables.MetaData.value
                if ($metaData)
                {
                    Trace-Execution "Checking if $OperationType is applicable to $($Module.Name)"
                    if ($metaData.OperationType -contains $OperationType)
                    {
                        $validatorCommand = Get-Command -Module $module.Name
                        if ($validatorCommand)
                        {
                            Trace-Execution "Found command $validatorCommand in module $($module.Name) for operation type $OperationType"
                            $executionJobs += New-Object ExecutionJob -Property @{
                                ModuleName = $module.Name
                                Params = @{
                                    Parameters = $Parameters
                                    OperationType = $OperationType
                                    FailFast = $false
                                }
                                Command = $validatorCommand.Name
                                Result = 'Queued'
                                Name = if ([string]::IsNullOrEmpty($metaData.UIName))
                                {
                                    'Azure Stack HCI {0}' -f ($validatorCommand.Name -replace 'Test-AzStackHci','')
                                }
                                else
                                {
                                    $metaData.UIName
                                }
                                Description = if ([string]::IsNullOrEmpty($metaData.UIDescription))
                                {
                                    "Test {0} requirements" -f ($validatorCommand.Name -replace 'Test-AzStackHci','')
                                }
                                else
                                {
                                    $metaData.UIDescription
                                }
                            }
                        }
                        else
                        {
                            Trace-Execution "No command found for $($validator.Name)"
                        }
                    }
                    else
                    {
                        Trace-Execution "$($Module.Name) not applicable for $OperationType. Continuing."
                    }
                }
                else
                {
                    Trace-Execution "No metadata found for $($module.Name)"
                }
            }
            else
            {
                Trace-Execution "No PS module found for $($validator.Name)"
            }
        }
        return $executionJobs
    }
    static hidden RunValidators([CloudEngine.Configurations.EceInterfaceParameters] $Parameters, [string] $OperationType)
    {
        # Archive files
        'AzStackHciEnvironmentChecker.log', 'AzStackHciEnvironmentProgress.json', 'AzStackHciEnvironmentReport.json', 'AzStackHciEnvironmentReport.xml' | ForEach-Object {
            Get-ChildItem -Path "$env:LocalRootFolderPath\MASLogs" -Filter $PSITEM -ErrorAction SilentlyContinue | ForEach-Object {
                Rename-Item -Path $PSITEM.FullName -NewName ($PSITEM.fullname -replace '(\.)', ('_{0}.' -f (Get-Date -Format yyyyMMdd-HHmmss)))
            }
        }

        # Determine which jobs need to run.
        $executionJobs = [EnvironmentValidator]::BuildJobExecution($Parameters,$OperationType)
        # Run validator list
        foreach ($work in $executionJobs)
        {
            Trace-Execution ("Running & {0} {1}" -f $work.Command,(($work.params.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join ';'))
            $work.StartTime = [System.DateTime]::UtcNow
            $work.Result = 'In Progress'
            [EnvironmentValidator]::WriteProgressFile($executionJobs)
            $splat = $work.Params
            $command = $work.Command
            # Execute validator
            try {
                $ENV:EnvChkrOp = $splat.OperationType
                $result = Invoke-Expression "$command @splat"
                $work.Result = $result.Result
                $work.FailedResult = $result.FailedResult | Sort-Object Severity
                $work.ExecutionDetail = $result.ExecutionDetail
            }
            catch
            {
                $work.Result = 'Error'
                $work.ExecutionDetail = "Exception occurred ($($work.Command)): $($_.exception.message)"
            }
            finally
            {
                $work.EndTime = [System.DateTime]::UtcNow
                $work.DurationSeconds = [System.Math]::Round(([system.datetime]$work.EndTime - [system.datetime]$work.StartTime).TotalSeconds,0)
                [EnvironmentValidator]::WriteProgressFile($executionJobs)
            }
        }

        Trace-Execution (Get-AzStackHciEnvironmentCheckerProgress -Path C:\MasLogs\AzStackHciEnvironmentProgress.json | Out-String)
        # First throw any exceptions
        $exceptions = $executionJobs | Where-Object { $_.ExecutionDetail -match 'Exception occurred' }
        if ($exceptions)
        {
            throw ("Failing action plan. Validator(s) threw exception(s):`n{0}`nLog file: {1}`nProgress file: {2}" -f `
                ($exceptions.ExecutionDetail -join "`r`n"),"$env:LocalRootFolderPath\MASLogs\AzStackHciEnvironmentChecker.log","$env:LocalRootFolderPath\MASLogs\AzStackHciEnvironmentProgress.json")
        }

        # Second throw if there are failed tests
        $terminateResult = @('Error','Failed','Critical')
        $terminatingValidators = $executionJobs | Where-Object {$_.Result -in $terminateResult }
        if ($terminatingValidators)
        {
            throw ("Failing action plan. Valdiator(s) failed test(s):`n{0}`nLog file: {1}`nReport file: {2}" -f `
                ([EnvironmentValidator]::FormatResultToString($terminatingValidators.FailedResult)), `
                 "$env:LocalRootFolderPath\MASLogs\AzStackHciEnvironmentChecker.log", `
                 "$env:LocalRootFolderPath\MASLogs\AzStackHciEnvironmentReport.json")
        }
    }

    static hidden WriteProgressFile([array] $ExecutionJobs)
    {
        # Write a progress file
        $progressPath = Join-Path -Path "$($env:LocalRootFolderPath)\MASLogs" -ChildPath AzStackHciEnvironmentProgress.json
        if (-not (Test-Path -Path (Split-Path $progressPath -Parent)))
        {
            New-Item -Path (Split-Path $progressPath -Parent) -ItemType Directory -Force | Out-Null
        }
        $ExecutionJobs | Select-Object Name, Description, Result, Command, ModuleName, StartTime, EndTime, DurationSeconds, FailedResult, ExecutionDetail | ConvertTo-Json -Depth 5 | Out-File -FilePath $progressPath
    }
    static hidden ImportEnvironmentValidator()
    {
        if (-not (Get-Module AzStackHci.EnvironmentChecker -ListAvailable))
        {
            $nugetname = [EnvironmentValidator]::InstallEnvironmentValidator()
            Trace-Execution "Importing $nugetname"
            $EnvCheckerNugetPath = Get-ASArtifactPath -NugetName $nugetname
            $EnvCheckerModulePath = Join-Path $EnvCheckerNugetPath "$nugetname.psd1"
            Import-Module $EnvCheckerModulePath -ErrorAction Stop -Verbose:$false -Force -Global | Out-Null
            if (-not (Get-Module $nugetname))
            {
                throw "Unable to import AzStackHci.EnvironmentChecker"
            }
        }
    }

    static hidden [string] InstallEnvironmentValidator()
    {
        $nugetSourceFolderPath = "$env:LocalRootFolderPath\CloudDeployment\NuGetStore"
        $nugetname = "AzStackHci.EnvironmentChecker"
        $nugetDestination = "$env:LocalRootFolderPath:\Nugetstore"
        if (-not (Test-Path "$nugetDestination\$nugetname.1*"))
        {
            $nugetExePath = "$env:LocalRootFolderPath\tools\nuget.exe"
            Trace-Execution "Unpacking $nugetname"
            Invoke-Expression "$nugetExePath install $nugetName -Source $nugetSourceFolderPath -OutputDirectory $nugetDestination -PackageSaveMode 'nuspec' -Prerelease"
        }
        return $nugetname
    }

    static hidden [PsObject] ParseResult([array] $Result, [string]$Title, [switch]$FailFast)
    {
        <#
            If no result, set Result to skipped and detail to 'no result'
            If skipped results exist, set Result to skipped and include
            If warning results exist, set Result to warning and include
            If critical results exist, set Result to failed
        #>

        $statusResult = ''
        $detail = @()
        $executionDetail = ''
        if ($null -eq $Result)
        {
            Trace-Warning ("Warning: checking {0} requirements. Review {1}. Returning." -f $Title, "$($env:LocalRootFolderPath)\MasLogs\AzStackHciEnvironmentChecker.log")
            $executionDetail = 'Validator generated no results.'
            $statusResult = 'Informational'
        }
        # Check for skips and write to log
        $skipped = @()
        $skipped = $Result | Where-Object { $_.Status -eq 'Skipped' }
        if ($skipped)
        {
            Trace-Warning ("{0} validation has skipped results: {1}" -f $Title, ($skipped | Format-List | Out-String))
            # if there are no more results then the overall Result is skipped
            if (-not ($Result | Where-Object { $_.Status -ne 'Skipped' }))
            {
                $statusResult = 'Informational'
                $detail += $skipped
                $executionDetail = 'All tests were skipped'
            }
        }
        # Check for warnings
        $warningFailures = @()
        $warningFailures = $Result | Where-Object { $_.Status -eq 'Failed' -and $_.Severity -eq 'Warning' }
        if ($warningFailures)
        {
            $warningResultString = [EnvironmentValidator]::FormatResultToString($warningFailures)
            Trace-Warning ("Warning (non-blocking) {0} failures found in validation. {1} Rules failed: {2}" -f $Title,$warningFailures.count,($warningResultString -join ""))
            $statusResult = 'Warning'
            $detail += $warningFailures
        }

        # Check for critical failures
        $criticalFailures = @()
        $criticalFailures = $Result | Where-Object { $_.Status -eq 'Failed' -and $_.Severity -eq 'Critical' }
        if ($criticalFailures)
        {
            Trace-Warning ("Critical (blocking) {0} failures found in validation. {1} Rules failed: " -f $Title, $criticalFailures.Count)
            $criticalResultString = [EnvironmentValidator]::FormatResultToString($criticalFailures)
            $terminalFailureMsg = ("{0} requirements not met. Review output and remediate: {1}" -f $Title, ($criticalResultString -join ""))
            if ($FailFast)
            {
                throw $terminalFailureMsg
            }
            Trace-Warning $terminalFailureMsg
            $statusResult = 'Critical'
            $detail += $criticalFailures
        }

        if (-not [string]::IsNullOrEmpty($Result) -and -not $criticalFailures -and -not $warningFailures)
        {
            Trace-Execution -Verbose "$Title prerequisites met."
            $statusResult = 'Succeeded'
        }

        return (New-Object -TypeName PSObject -Property @{
                Result = $statusResult
                FailedResult = if ($detail.count -gt 0) { $detail } else { $null }
                ExecutionDetail = $executionDetail
        })
    }

    static hidden [string] FormatResultToString([PSObject] $FailedResult)
    {
        $failureDetail = @()
        $failureDetail = $FailedResult | Foreach-Object {
            Write-Output "`nRule:"
            $PSITEM | Select-Object -Property * -ExcludeProperty AdditionalData | Format-List | Out-String
            Write-Output "AdditionalData:"
            $PSITEM.AdditionalData | Where-Object Status -eq 'Failed' | Format-List | Out-String
        }
        return $failureDetail
    }

    static hidden [PSCredential] GetLocalAdminCredential([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        $cloudRole = $Parameters.Roles["Cloud"].PublicConfiguration

        # Account info
        $securityInfo = $cloudRole.PublicInfo.SecurityInfo
        $localAdmin = $securityInfo.LocalUsers.User | ? Role -EQ $Parameters.Configuration.Role.PrivateInfo.Accounts.BuiltInAdminAccountID
        $localAdminCredential = $Parameters.GetCredential($localAdmin.Credential)
        Trace-Execution ("Found credential {0}" -f $localAdminCredential.Username )
        return $localAdminCredential
    }

    static hidden [PSCredential] GetDomainAdminCredential([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        $cloudRole = $Parameters.Roles["Cloud"].PublicConfiguration

        # Account info
        $securityInfo = $cloudRole.PublicInfo.SecurityInfo
        $domainFQDN = $Parameters.Roles["Domain"].PublicConfiguration.PublicInfo.DomainConfiguration.FQDN
        $domainAdminUser = $securityInfo.DomainUsers.User | ? Role -EQ $Parameters.Configuration.Role.PrivateInfo.Accounts.DomainAdminAccountID
        $Credential = $Parameters.GetCredential($domainAdminUser.Credential)
        $domainAdminCredential = New-Object pscredential("$domainFQDN\$($Credential.UserName)", $Credential.Password)
        return $domainAdminCredential
    }

    static hidden [string[]] GetAnswerFile()
    {
        # Return the deployment data
        $deploymentData = Get-Content "$($env:LocalRootFolderPath)\CloudDeployment\Unattended.json" | ConvertFrom-Json
        return $deploymentData
    }

    static hidden [string[]] GetAllHostNicIps([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        # Find the IP for the contect node for PSSession
        [System.Array]$NodesIps = Get-NetworkMgmtIPv4FromECEForAllHosts -Parameters $Parameters
        Trace-Execution ("Found host IPs {0}" -f ($NodesIps -join ','))
        return $NodesIps.Values
    }

    static hidden [string[]] GetHostNicIpByName([CloudEngine.Configurations.EceInterfaceParameters] $Parameters, [string]$NodeName)
    {
        # Find the IP for the contect node for PSSession
        [System.Array]$NodesIps = Get-NetworkMgmtIPv4FromECEForHost -Parameters $Parameters -HostName $NodeName
        Trace-Execution ("Found host IP {0} for host {1}" -f ($NodesIps -join ','),$NodeName)
        return $NodesIps
    }

    static hidden [System.Management.Automation.Runspaces.PSSession[]] NewPsSessionAllHosts([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        $Nodes = [EnvironmentValidator]::GetAllHostNicIps($Parameters)

        if ([string]::IsNullOrEmpty(($nodes | Select-Object -first 1)))
        {
            # As a back up try the local cluster e.g. during upgrade
            Trace-Execution "Unable to find nodes in ECE parameters, querying local cluster."
            $Nodes = Get-ClusterNode | Select-Object -ExpandProperty Name
        }

        [EnvironmentValidator]::SetTrustedHosts($Nodes)
        Trace-Execution ("Making PsSession to {0}" -f ($Nodes -join ','))
        [System.Array]$PsSession = $Nodes | ForEach-Object { [EnvironmentValidator]::NewPsSessionWithRetries($PSITEM,$Parameters) }
        return $PsSession
    }

    static hidden [System.Management.Automation.Runspaces.PSSession[]] NewPsSessionByHost([CloudEngine.Configurations.EceInterfaceParameters] $Parameters, [string[]]$Node, [switch]$IncludeLocalHost)
    {
        $Ips = @()
        $Node | ForEach-Object {
            $Ips += [EnvironmentValidator]::GetHostNicIpByName($Parameters,$PSITEM)
        }

        if ($IncludeLocalHost)
        {
            $Ips += 'localhost'
        }
        [EnvironmentValidator]::SetTrustedHosts($Ips)
        Trace-Execution ("Making PsSession to {0}" -f ($Ips -join ','))
        [System.Array]$PsSession = $Ips | ForEach-Object { [EnvironmentValidator]::NewPsSessionWithRetries($PSITEM,$Parameters) }
        return $PsSession
    }

    static hidden [System.Management.Automation.Runspaces.PSSession] NewPsSessionWithRetries([string]$Node, [CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        function New-PsSessionWithRetries
        {
            param (
                [string]$node,
                [pscredential]$Credential,
                [int]$retries = 3,
                [int]$waitSeconds = 30
            )
            For ($i=1; $i -le $retries; $i++)  {
                try {
                    Trace-Execution "Creating PsSession ($i/$retries) to $Node as $($Credential.UserName)..."
                    $PsSession = Microsoft.PowerShell.Core\New-PSSession -ComputerName $Node -Credential $Credential -ErrorAction Stop
                    $computerName = Microsoft.PowerShell.Core\Invoke-Command -Session $PsSession -ScriptBlock { $ENV:COMPUTERNAME } -ErrorAction Stop
                    break
                }
                catch
                {
                    Trace-Execution "Creating PsSession ($i/$retries) to $Node failed: $($_.exception.message)"
                    $errMsg = $_.tostring()
                    Start-Sleep -Seconds $waitSeconds
                }
            }
            if ($computerName -and $PsSession)
            {
                Trace-Execution ("PsSession to {0} created after {1} retries. (Remote machine name: {2})" -f $Node, ("$i/$retries"), $computerName)
                return $PsSession
            }
            else
            {
                throw "Unable to create a valid session to $Node`: $errMsg"
            }
        }


        # Check if we can connect with domain credentials
        # Otherwise connect with local credentials
        $domainCredential = [EnvironmentValidator]::GetDomainAdminCredential($Parameters)
        if (Test-WSMan -ComputerName $Node -Credential $domainCredential -Authentication Kerberos -ErrorAction SilentlyContinue)
        {
            Trace-Execution "Attempting PsSession $Node with domain credentials"
            $PsSession = New-PsSessionWithRetries -Node $Node -Credential $domainCredential
        }
        else
        {
            Trace-Execution "Attempting PsSession $Node with local credentials"
            $localCredential = [EnvironmentValidator]::GetLocalAdminCredential($Parameters)
            if ($null -eq $localCredential)
            {
                throw "Unable to create a valid session to $Node. No local admin credential found."
            }
            $PsSession = New-PsSessionWithRetries -Node $Node -Credential $localCredential
        }

        if ($PsSession)
        {
            return $PsSession
        }
        else
        {
            throw "Unable to create a valid session to $Node"
        }
    }

    static hidden [string[]] GetNodeContext([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        $nodeName = @()
        $executionRoleName = $Parameters.Context.ExecutionContext.Roles.Role.RoleName
        $nodeName = $Parameters.Context.ExecutionContext.Roles.Role.Nodes.Node.Name
        if ($null -eq $nodeName)
        {
            # Try runtimeParameters
            $runtimeParameters = $Parameters.RunInformation['RuntimeParameter']
            if ($runtimeParameters.ContainsKey('NodeName'))
            {
                $nodeName = $runtimeParameters['NodeName']
                Trace-Execution "Retrieved node(s): $($nodeName -join ', ') from runtimeParameters."
            }
            else
            {
                # If node names are not provided, get all nodes associated to the $executionRoleName
                Trace-Execution "Retrieving ExecutionContext with role name $executionRoleName and node(s): $($nodeName -join ', ')"
                $nodeName = ($Parameters.Roles[$executionRoleName].PublicConfiguration.Nodes.Node.Name) | Select-Object -Unique
            }
        }
        else
        {
            Trace-Execution "Retrieved node(s): $($nodeName -join ', ') from ExecutionContext."
        }
        return $nodeName
    }

    static hidden SetTrustedHosts([string[]]$Nodes)
    {
        $trustedHosts = (Get-Item -Path WSMan:\localhost\Client\TrustedHosts).Value
        foreach ($node in $nodes)
        {
            if ('*' -notin $TrustedHosts -and ($node -notin $TrustedHosts.Split(',')))
            {
                Trace-Execution "Adding $node to TrustedHosts"
                Set-Item WSMan:\localhost\Client\TrustedHosts -Value $node -Concatenate -Force
            }
            else
            {
                Trace-Execution "TrustedHosts already matches $node. Continuing."
            }
        }
    }
}
# SIG # Begin signature block
# MIInwgYJKoZIhvcNAQcCoIInszCCJ68CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDjUoBNZdBkfSmk
# n0nylrhmMVxDlKXpFf/AYCqKJIVwj6CCDXYwggX0MIID3KADAgECAhMzAAACy7d1
# OfsCcUI2AAAAAALLMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NTU5WhcNMjMwNTExMjA0NTU5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC3sN0WcdGpGXPZIb5iNfFB0xZ8rnJvYnxD6Uf2BHXglpbTEfoe+mO//oLWkRxA
# wppditsSVOD0oglKbtnh9Wp2DARLcxbGaW4YanOWSB1LyLRpHnnQ5POlh2U5trg4
# 3gQjvlNZlQB3lL+zrPtbNvMA7E0Wkmo+Z6YFnsf7aek+KGzaGboAeFO4uKZjQXY5
# RmMzE70Bwaz7hvA05jDURdRKH0i/1yK96TDuP7JyRFLOvA3UXNWz00R9w7ppMDcN
# lXtrmbPigv3xE9FfpfmJRtiOZQKd73K72Wujmj6/Su3+DBTpOq7NgdntW2lJfX3X
# a6oe4F9Pk9xRhkwHsk7Ju9E/AgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUrg/nt/gj+BBLd1jZWYhok7v5/w4w
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzQ3MDUyODAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAJL5t6pVjIRlQ8j4dAFJ
# ZnMke3rRHeQDOPFxswM47HRvgQa2E1jea2aYiMk1WmdqWnYw1bal4IzRlSVf4czf
# zx2vjOIOiaGllW2ByHkfKApngOzJmAQ8F15xSHPRvNMmvpC3PFLvKMf3y5SyPJxh
# 922TTq0q5epJv1SgZDWlUlHL/Ex1nX8kzBRhHvc6D6F5la+oAO4A3o/ZC05OOgm4
# EJxZP9MqUi5iid2dw4Jg/HvtDpCcLj1GLIhCDaebKegajCJlMhhxnDXrGFLJfX8j
# 7k7LUvrZDsQniJZ3D66K+3SZTLhvwK7dMGVFuUUJUfDifrlCTjKG9mxsPDllfyck
# 4zGnRZv8Jw9RgE1zAghnU14L0vVUNOzi/4bE7wIsiRyIcCcVoXRneBA3n/frLXvd
# jDsbb2lpGu78+s1zbO5N0bhHWq4j5WMutrspBxEhqG2PSBjC5Ypi+jhtfu3+x76N
# mBvsyKuxx9+Hm/ALnlzKxr4KyMR3/z4IRMzA1QyppNk65Ui+jB14g+w4vole33M1
# pVqVckrmSebUkmjnCshCiH12IFgHZF7gRwE4YZrJ7QjxZeoZqHaKsQLRMp653beB
# fHfeva9zJPhBSdVcCW7x9q0c2HVPLJHX9YCUU714I+qtLpDGrdbZxD9mikPqL/To
# /1lDZ0ch8FtePhME7houuoPcMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGaIwghmeAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAALLt3U5+wJxQjYAAAAAAsswDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIBphhY+LAIjVSyU27XldCA4O
# TvWqJurV5mMeHsZ/CjYcMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAootrTsfGlzcdPFNOe0T/UhSsdXwp6QmfdJD1R2apDtooki3/gLqVqgGy
# 2OFx6VG38uSLEZgj91gJ7+eMcR/cGlV1EuwFWGL6xirZLzxzyj2VRI1fIWX78QT4
# mV54/0VIN9D8wlftnlH9ClIrHjIyp3XeUraP8xE2H1WX2zSi50crQDjlKmJ9mtvB
# 9LkLzfs6kglVtXPeeQnFKrTdepTWECcYSQiIr9bSrUcP1d+6o3TDHQNnPqa9JI/4
# kCNYZDXOF6g5fqIGbhm813tNj2dxfmfVycseonuc35FIt1R1r0qNErGa3+iuLvfP
# UWaahFP5gRHBwcsT30Z7Z7jjelHR9qGCFywwghcoBgorBgEEAYI3AwMBMYIXGDCC
# FxQGCSqGSIb3DQEHAqCCFwUwghcBAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq
# hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCBuGNnqHhd6aaz5E04i2Ao13s19kMTMgt1J9g61hGBW3gIGZD/RCLC9
# GBMyMDIzMDQyMDIxMTU1Ni4xNjFaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO
# OkEyNDAtNEI4Mi0xMzBFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT
# ZXJ2aWNloIIRezCCBycwggUPoAMCAQICEzMAAAG4CNTBuHngUUkAAQAAAbgwDQYJ
# KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjIw
# OTIwMjAyMjE2WhcNMjMxMjE0MjAyMjE2WjCB0jELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl
# cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpBMjQwLTRC
# ODItMTMwRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJwbsfwRHERn5C95QPGn37tJ
# 5vOiY9aWjeIDxpgaXaYGiqsw0G0cvCK3YulrqemEf2CkGSdcOJAF++EqhOSqrO13
# nGcjqw6hFNnsGwKANyzddwnOO0jz1lfBIIu77TbfNvnaWbwSRu0DTGHA7n7PR0MY
# J9bC/HopStpbFf606LKcTWnwaUuEdAhx6FAqg1rkgugiuuaaxKyxRkdjFZLKFXEX
# L9p01PtwS0fG6vZiRVnEKgeal2TeLvdAIqapBwltPYifgqnp7Z4VJMcPo0TWmRNV
# FOcHRNwWHehN9xg6ugIGXPo7hMpWrPgg4moHO2epc0T36rgm9hlDrl28bG5TakmV
# 7NJ98kbF5lgtlrowT6ecwEVtuLd4a0gzYqhanW7zaFZnDft5yMexy59ifETdzpwA
# rj2nJAyIsiq1PY3XPm2mUMLlACksqelHKfWihK/Fehw/mziovBVwkkr/G0F19OWg
# R+MBUKifwpOyQiLAxrqvVnfCY4QjJCZiHIuS15HCQ/TIt/Qj4x1WvRa1UqjnmpLu
# 4/yBYWZsdvZoq8SXI7iOs7muecAJeEkYlM6iOkMighzEhjQK9ThPpoAtluXbL7qI
# HGrfFlHmX/4soc7jj1j8uB31U34gJlB2XphjMaT+E+O9SImk/6GRV9Sm8C88Fnmm
# 2VdwMluCNAUzPFjfvHx3AgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUxP1HJTeFwzNY
# o1njfucXuUfQaW4wHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD
# VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
# cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG
# CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw
# MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBAJ9uk8miwpMoKw3D
# 996piEzbegAGxkABHYn2vP2hbqnkS9U97s/6QlyZOhGFsVudaiLeRZZTsaG5hR0o
# CuBINZ/lelo5xzHc+mBOpBXpxSaW1hqoxaCLsVH1EBtz7in25Hjy+ejuBcilH6EZ
# 0ZtNxmWGIQz8R0AuS0Tj4VgJXHIlXP9dVOiyGo9Velrk+FGx/BC+iEuCaKd/Isyp
# HPiCUCh52DGc91s2S7ldQx1H4CljOAtanDfbvSejASWLo/s3w0XMAbDurWNns0Xi
# dAF2RnL1PaxoOyz9VYakNGK4F3/uJRZnVgbsCYuwNX1BmSwM1ZbPSnggNSGTZx/F
# Q20Jj/ulrK0ryAbvNbNb4kkaS4a767ifCqvUOFLlUT8PN43hhldxI6yHPMOWItJp
# EHIZBiTNKblBsYbIrghb1Ym9tfSsLa5ZJDzVZNndRfhUqJOyXF+CVm9OtVmFDG9k
# IwM6QAX8Q0if721z4VOzZNvD8ktg1lI+XjXgXDJVs3h47sMu9GXSYzky+7dtgmc3
# iRPkda3YVRdmPJtNFN0NLybcssE7vhFCij75eDGQBFq0A4KVG6uBdr6UTWwE0VKH
# xBz2BpGvn7BCs+5yxnF+HV6CUickDqqPi/II7Zssd9EbP9uzj4luldXDAPrWGtdG
# q+wK0odlGNVuCMxsL3hn8+KiO9UiMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ
# mQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh
# dGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1
# WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEB
# BQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjK
# NVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhg
# fWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJp
# rx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/d
# vI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka9
# 7aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKR
# Hh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9itu
# qBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyO
# ArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItb
# oKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6
# bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6t
# AgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQW
# BBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacb
# UzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYz
# aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnku
# aHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIA
# QwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2
# VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwu
# bWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEw
# LTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93
# d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYt
# MjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/q
# XBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6
# U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVt
# I1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis
# 9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTp
# kbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0
# sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138e
# W0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJ
# sWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7
# Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0
# dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQ
# tB1VM1izoXBm8qGCAtcwggJAAgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxh
# bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpB
# MjQwLTRCODItMTMwRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZaIjCgEBMAcGBSsOAwIaAxUAcGteVqFx/IbTKXHLeuXCPRPMD7uggYMwgYCk
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF
# AOfroLYwIhgPMjAyMzA0MjAxOTI5NThaGA8yMDIzMDQyMTE5Mjk1OFowdzA9Bgor
# BgEEAYRZCgQBMS8wLTAKAgUA5+ugtgIBADAKAgEAAgIaKwIB/zAHAgEAAgIRPDAK
# AgUA5+zyNgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB
# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAB6cBd/oYbZJbCUB
# m1LyM4Vme3IjcGo1wQrqMZhmpF+2O8/nC5TXiZypMcLBhd2JHU6xNvzSFx2Fr2HO
# XQ8ajAcEXtjtXukFKlJQC5uVT205FMioodahIYEPe+wlEU59E6rHvWCk9ClRQh1J
# 1LZw91VpJSqb425sb880hrD6ad47MYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTACEzMAAAG4CNTBuHngUUkAAQAAAbgwDQYJYIZIAWUD
# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B
# CQQxIgQgHvqjDAfybMfjIyz0ANFWQL8n8UeHxr1XI+3QhgMF9fAwgfoGCyqGSIb3
# DQEJEAIvMYHqMIHnMIHkMIG9BCAo69Y4oHA7Q4pS+Y1NsBfrpIYTeWsPeGTami0X
# 0PD7HzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB
# uAjUwbh54FFJAAEAAAG4MCIEIJ0ZR3BIroHGAR0gOReNT0y1LHXtnnGqi+GupPZa
# H7O5MA0GCSqGSIb3DQEBCwUABIICAGpOB6WJB4WxdK2rvtpjdKsCOvUyFgXmhLkw
# 8RUU6Rr3mduSdJn0yZlzYNnrHuZNznzTLFIS1ObLQ/eK1ws76/Okad7ivo8/j889
# +bbNAzk5LPmC1InRAA1oYT7WMvinj8W8HIPROvbFF42OdoatElFd9h7ZvCcsptRd
# QdNbAv6L3lryjwSNFaqT4n8m10VSS9HaJ2LMuZWN9py5SCpvZqpm6LKzfix8zxGt
# /srp3r2bgd98gorN+UppfgUS+JP5iPthLEG3pxESBhu/cpmlY1tZseaLoEmN/2fn
# P3ObB2VIuGnAMAuCZV6XbRU91NQINIauGxZjFogok2ydDiLaWx1NuT7+mJZILLna
# kV+cOCrfey/YgFjufEDhrSMikWSMWH5zxz3355SewgzJ43U3vCwAHQaAERR8nnK3
# XmT7NJOyGSYksvgbvt4mYdJNAbfC8JqHLhfqMY3moLG/5p5/8bh/XQyxNWbaycL2
# h0JrQ9N/BnxexIZ67CDtdGNu0rTywl/WJvJMYLYDVZDJ+rM56kN+Hno0n95UstLg
# S0e/tKn2VMoPCAfMAKXL0Eua+m+GxZZJKs/asmC4p7GGWIRvWBZH9OusqqS6NdcQ
# lHkFGfUwrGtODHe+77vCPRcM64TBzBAi5pFisy7gzhZEYUa/xp/snvVvX9HoVxN6
# Cq8SDBzk
# SIG # End signature block