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)
    }

    # TO DO: Remove PreAddNode
    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 ValidatePreAddNode([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]::ValidatePreAddNode($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


        $OperationType = 'AddNode'
        $ValidatorName = 'AzStackHciAddNode'
        [EnvironmentValidator]::RunSingleValidator($Parameters, $OperationType, $ValidatorName)
    }

    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 ValidateScaleOutConnectivity([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to run environment validator before AddNode
        when full ECE parameters are available.
 
        .EXAMPLE
        [EnvironmentValidator]::ValidateScaleOutConnectivity($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


        $OperationType = 'AddNode'
        $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 ValidateScaleOutHardware([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to run environment validator before AddNode.
 
        .EXAMPLE
        [EnvironmentValidator]::ValidateScaleOutHardware($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


        $OperationType = 'AddNode'
        $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 ValidateScaleOutNetwork([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to run environment validator before AddNode.
 
        .EXAMPLE
        [EnvironmentValidator]::ValidateScaleOutNetwork($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


        $OperationType = 'AddNode'
        $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 ValidateScaleOutObservability([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to run environment validator before AddNode.
 
        .EXAMPLE
        [EnvironmentValidator]::ValidateScaleOutObservability($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


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

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


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

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


        $OperationType = 'AddNode'
        $ValidatorName = 'AzStackHciMOCStack'
        [EnvironmentValidator]::RunSingleValidator($Parameters, $OperationType, $ValidatorName)
    }

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


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

    static [CloudEngine.Actions.ConditionalActionDescription] EvaluateValidateArcIntegrationAction([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        $ErrorActionPreference = "Stop"

        return [EnvironmentValidator]::EvaluateValidatorAction($Parameters, "ValidateArcIntegration")
    }

    static [CloudEngine.Actions.ConditionalActionDescription] EvaluateValidatorAction([CloudEngine.Configurations.EceInterfaceParameters] $Parameters, [string] $actionName)
    {
        $ErrorActionPreference = "Stop"

        function Get-ArcAEndpoint {
            [CmdletBinding()]
            Param
            (
                [Parameter(Mandatory = $true)]
                [CloudEngine.Configurations.EceInterfaceParameters]
                $Parameters
            )
            $cloudRole = $Parameters.Roles["Cloud"].PublicConfiguration
            $registrationArcAEndpoint = $cloudRole.PublicInfo.RegistrationArcAEndpoint

            return $registrationArcAEndpoint
        }

        function Test-IsArcADeployment {
            [CmdletBinding()]
            Param
            (
                [Parameter(Mandatory = $true)]
                [CloudEngine.Configurations.EceInterfaceParameters]
                $Parameters
            )
            $registrationArcAEndpoint = Get-ArcAEndpoint -Parameters $Parameters
            if ($null -eq $registrationArcAEndpoint -or $registrationArcAEndpoint.Length -eq 0) {
                return $false
            }

            return $true
        }

        # For ArcA-Managed ASZ deployment, tenantId is retrieved from ArcA, skip validation...
        if (-not (Test-IsArcADeployment -Parameters $Parameters))
        {
            Trace-Execution "EvaluateValidatorActionForArcA: Execute $actionName"
            return [CloudEngine.Actions.ConditionalActionDescription]::CreateWithDefinedAction("Cloud\Infrastructure\EnvironmentValidator", $actionName)
        }

        Trace-Execution "EvaluateValidatorActionForArcA: Skip executing $actionName for ArcA deployment" -Verbose
        return [CloudEngine.Actions.ConditionalActionDescription]::CreateNegative()
    }

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


        $OperationType = 'Deployment'
        $ValidatorName = 'AzStackHciSBEHealth'
        [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 ValidateScaleOutSoftware([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        <#
        .SYNOPSIS
        This function is used to run environment validator before AddNode.
 
        .EXAMPLE
        [EnvironmentValidator]::ValidateScaleOutSoftware($Parameters)
 
        .PARAMETER Parameters
        The ECE role parameters for this interface.
        #>


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

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


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

    static hidden RunSingleValidator([CloudEngine.Configurations.EceInterfaceParameters] $Parameters, [string] $OperationType, [string] $ValidatorName)
    {
        $ErrorActionPreference = "Stop"
        [EnvironmentValidator]::ImportEnvironmentValidator($Parameters)
        if (Get-Module -Name AzStackHci.EnvironmentChecker)
        {
            $EnvCheckerModuleBase = Get-Module -Name AzStackHci.EnvironmentChecker | Sort-Object Version -Descending | Select-Object -First 1 | Select-Object -ExpandProperty ModuleBase
        }
        else
        {
            $EnvCheckerModuleBase = Get-Module AzStackHci.EnvironmentChecker -ListAvailable | Where-Object {$_.Path -notlike "*\$($_.Version)*"} | Sort-Object Version -Descending | Select-Object -First 1 | Select-Object -ExpandProperty ModuleBase
        }
        Trace-Execution "EnvCheckerModuleBase: $EnvCheckerModuleBase"
        Import-Module (Join-Path "$EnvCheckerModuleBase\$ValidatorName" "$ValidatorName.psm1") -Force
        $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($Parameters)
        if (Get-Module -Name AzStackHci.EnvironmentChecker)
        {
            $EnvCheckerModuleBase = Get-Module -Name AzStackHci.EnvironmentChecker | Sort-Object Version -Descending | Select-Object -First 1 | Select-Object -ExpandProperty ModuleBase
        }
        else
        {
            $EnvCheckerModuleBase = Get-Module AzStackHci.EnvironmentChecker -ListAvailable | Where-Object {$_.Path -notlike "*\$($_.Version)*"} | Sort-Object Version -Descending | Select-Object -First 1 | Select-Object -ExpandProperty ModuleBase
        }
        Trace-Execution "EnvCheckerModuleBase: $EnvCheckerModuleBase"
        $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]$Status
            [datetime]$StartTime
            [datetime]$EndTime
            [int]$DurationSeconds
            [psobject[]]$FailedResult
            [psobject[]]$AllResults
            [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
                                Status = '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.Status = 'In Progress'
            [EnvironmentValidator]::WriteProgressFile($executionJobs)
            $splat = $work.Params
            $command = $work.Command
            # Execute validator
            try {
                $ENV:EnvChkrOp = $splat.OperationType
                $result = Invoke-Expression "$command @splat"
                $work.Status = $result.Status
                $work.FailedResult = $result.FailedResult | Sort-Object Severity
                $work.ExecutionDetail = $result.ExecutionDetail
                $work.AllResults = $result.AllResults
            }
            catch
            {
                $work.Status = '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 $env:LocalRootFolderPath\MASLogs\AzStackHciEnvironmentProgress.json | Out-String)
        # First throw any exceptions
        $exceptions = $executionJobs | Where-Object { $_.ExecutionDetail -match 'Exception occurred' }
        if ($exceptions)
        {
            throw ("Failing action plan. {0}:`n{1}`nLog file: {2}`nProgress file: {3}" -f `
                ($exceptions.ModuleName | Foreach-Object { "{0} validator threw and exception`r`n" -f $_ -replace 'AzStackHci',''}),
                ($exceptions.ExecutionDetail -join "`r`n"),"$env:LocalRootFolderPath\MASLogs\AzStackHciEnvironmentChecker.log","$env:LocalRootFolderPath\MASLogs\AzStackHciEnvironmentProgress.json")
        }

        # Second throw if there are failed tests
        $terminateResult = @('Error','FAILURE','CRITICAL')
        $terminatingValidators = $executionJobs | Where-Object {$_.Status -in $terminateResult }

        # if this is update, there's a runtimeparamter to put the file
        try
        {
            $runtimeParameters = $Parameters.RunInformation["RuntimeParameter"]
            $resultPath = $null
            if ($runtimeParameters -ne $null -and -not ([string]::IsNullOrEmpty($runtimeParameters["EnvironmentCheckerResultPath"])))
            {
                $resultPath = $runtimeParameters["EnvironmentCheckerResultPath"]
                if (-not (Test-Path $resultPath -IsValid))
                {
                    throw "Invalid result path: $resultPath"
                }
                $executionJobs.Allresults | ConvertTo-Json -Depth 5 | Out-File -FilePath $resultPath
            }
            else
            {
                Trace-Execution "HealthCheckResultPath runtime parameter was not specified, so result will not be written to file."
            }
        }
        catch
        {
            Trace-Warning "Error occurred trying to write the environment checker result file. Error: $_"
        }

        # Throw on any terminating validators
        if ($terminatingValidators)
        {
            throw ("Failing action plan. {0}:`n{1}`nLog file: {2}`nReport file: {3}" -f `
                ($terminatingValidators.ModuleName | Foreach-Object { "{0} requirements not met`r`n" -f $_ -replace 'AzStackHci',''}),
                ([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, Status, Command, ModuleName, StartTime, EndTime, DurationSeconds, FailedResult, ExecutionDetail | ConvertTo-Json -Depth 5 | Out-File -FilePath $progressPath
    }
    static hidden ImportEnvironmentValidator([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        [EnvironmentValidator]::CleanEnvironmentValidator()
        $fromUpdateIsTrue = [EnvironmentValidator]::ImportEnvironmentValidatorFromUpdatePackage($Parameters)
        if (!$fromUpdateIsTrue -and -not (Get-Module AzStackHci.EnvironmentChecker -ListAvailable | Where-Object {$_.Path -notlike "*\$($_.Version)*"}))
        {
            $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 [bool] ImportEnvironmentValidatorFromUpdatePackage([CloudEngine.Configurations.EceInterfaceParameters] $Parameters)
    {
        try {

            # Gets the file share directory containing expanded contents of a currently in-progress update package.
            function Get-InProgressUpdatePackagePath
            {
                [CmdletBinding()]
                param (
                    [Parameter(Mandatory = $true)]
                    [CloudEngine.Configurations.EceInterfaceParameters]
                    $Parameters
                )
                $runtimeParameters = $Parameters.RunInformation['RuntimeParameter']
                $packagePath = $runtimeParameters["UpdatePackagePath"]
                if (-not $packagePath)
                {
                    Trace-Execution "The UpdatePackagePath runtime parameter is not present."
                    return $null
                }

                Trace-Execution "Upgrade package @: [$packagePath]"

                # The package path is expected to be a full path to an update package file (e.g. a self-
                # extracting .exe, or a metadata.xml file). The path where the contents are extracted is
                # the directory containing this file.
                return (Split-Path $packagePath)
            }

            # Gets the location of the folder containing nugets in the currently in-progress update package.
            function Get-InProgressUpdateNugetStore
            {
                [CmdletBinding()]
                param (
                    [Parameter(Mandatory = $true)]
                    [CloudEngine.Configurations.EceInterfaceParameters]
                    $Parameters
                )

                $updatePackagePath = Get-InProgressUpdatePackagePath -Parameters $Parameters
                if ($updatePackagePath)
                {
                    $urpRole = $Parameters.Roles["URP"].PublicConfiguration
                    $nugetSourceFolderName = ($urpRole.PublicInfo.UpdatePackagePaths.ContentMappings.ContentMapping | Where-Object { $_.Name -eq "Nugets" }).SourceFolder

                    $nugetStorePath = Join-Path $updatePackagePath $nugetSourceFolderName
                    if (Test-Path $nugetStorePath)
                    {
                        Trace-Execution "Found update nuget store at $nugetStorePath."
                        return $nugetStorePath
                    }

                    # Fallback: check for legacy nuget store path.
                    $nugetSourceFolderName = ($urpRole.PublicInfo.UpdatePackagePaths.ContentMappings.ContentMapping | Where-Object { $_.Name -eq "NugetsLegacy" }).SourceFolder
                    $nugetStorePath = Join-Path $updatePackagePath $nugetSourceFolderName
                    if (Test-Path $nugetStorePath)
                    {
                        Trace-Execution "Found legacy update nuget store at $nugetStorePath."
                        return $nugetStorePath
                    }

                    Trace-Execution "No nuget store was found at update package path: $updatePackagePath."
                }
                else
                {
                    Trace-Execution "No update is currently in progress."
                }

                return $null
            }

            $nugetAndModuleName = "AzStackHci.EnvironmentChecker"
            Trace-Execution "Trying Get-InprogressUpdateNugetStore"
            $updatePackageNugetStore = Get-InProgressUpdateNugetStore -Parameters $Parameters
            Trace-Execution "InprogressUpdateNugetStorePath: $updatePackageNugetStore"
            if (-not $updatePackageNugetStore)
            {
                throw "Update package nuget store was not specified in runtime parameters"
            }

            Trace-Execution "Checking for new $nugetAndModuleName nuget in the update package at $updatePackageNugetStore"
            $nuget = Find-Package -Source $updatePackageNugetStore -Name $nugetAndModuleName -ErrorAction Ignore

            if (-not $nuget)
            {
                throw "The $nugetAndModuleName nuget was not found."
            }

            $tempFolder = Join-Path (Get-Item $env:TEMP | % FullName) ([IO.Path]::GetRandomFileName())
            if (-not (Test-Path $tempFolder -PathType Container))
            {
                $null = New-Item -Path $tempFolder -Type Directory
            }

            Trace-Execution "Installing $($nuget.Name) to $tempFolder."
            $nuget | Install-Package -Destination $tempFolder | Out-Null
            $installedPath = Get-Package -Destination $tempFolder -Name $nuget.Name | % Source | Split-Path

            $validatorModulePath = Join-Path $installedPath "$nugetAndModuleName.psd1"
            Trace-Execution "$nugetAndModuleName module installed to $validatorModulePath."
            if (-not (Test-Path $validatorModulePath))
            {
                throw "Unable to find the validator module from the installed location: $validatorModulePath."
            }
            Trace-Execution "Importing $validatorModulePath."
            Import-Module $validatorModulePath -ErrorAction Stop -Verbose:$false -Force -Global | Out-Null
            return $true
        }
        catch
        {
            Trace-Execution "Get-InprogressUpdateNugetStore failed. $_."
            return $false
        }
    }

    static hidden [string] InstallEnvironmentValidator()
    {
        [EnvironmentValidator]::CleanEnvironmentValidator()
        $nugetname = "AzStackHci.EnvironmentChecker"
        $nugetSourceFolderPath = "$env:LocalRootFolderPath\CloudDeployment\NuGetStore"
        $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 CleanEnvironmentValidator()
    {
        $nugetname = "AzStackHci.EnvironmentChecker"
        # Unload any previously loaded modules
        $loadedModule = Get-Module -Name $nugetname | Where-Object {$_.Path -like "*$($_.Version)*"}
        if ($loadedModule)
        {
            Trace-Execution "Loaded module(s) found for $($loadedModule.Name):"
            Trace-Execution ($loadedModule | Format-List Path, Name, Version | Out-String)
            Trace-Execution "Unloading"
            $loadedModule | Remove-Module -Force
        }

        # Uninstall any modules installed from external
        $installedModule = Get-Module -Name $nugetname -ListAvailable | Where-Object {$_.Path -like "*$($_.Version)*"}
        if ($installedModule)
        {
            Trace-Execution "Externally installed module(s) found for $($installedModule.Name):"
            Trace-Execution ($installedModule | Format-List Path, Name, Version | Out-String)
            Trace-Execution "Uninstalling"
            $installedModule | Uninstall-Module -Force
        }
    }


    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 'FAILURE' -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 'FAILURE' -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 = 'SUCCESS'
        }

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

    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 'FAILURE' | 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
        $domainName = $Parameters.Roles["Domain"].PublicConfiguration.PublicInfo.DomainConfiguration.DomainName
        $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
                    $IsAdmin = Microsoft.PowerShell.Core\Invoke-Command -Session $PsSession -ScriptBlock {
                        ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')
                    } -ErrorAction Stop
                    if (-not $IsAdmin)
                    {
                        throw ("PsSession was successful but user: {0} is not an administrator on computer {1} " -f $PsSession.Runspace.ConnectionInfo.Credential.Username, $computerName)
                    }
                    break
                }
                catch
                {
                    Trace-Execution "Creating PsSession ($i/$retries) to $Node failed: $($_.exception.message)"
                    $errMsg = $_.tostring()
                    Start-Sleep -Seconds $waitSeconds
                }
            }
            if ($PsSession -and $ComputerName -and $IsAdmin)
            {
                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 Default -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
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCw0fx0mos7XWxw
# OaXIFdRkSPI7551sad9nR5ZQ6zmWdKCCDXYwggX0MIID3KADAgECAhMzAAADrzBA
# DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA
# hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG
# 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN
# xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL
# go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB
# tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd
# mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ
# 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY
# 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp
# XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn
# TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT
# e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG
# OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O
# PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk
# ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx
# HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt
# CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# 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
# Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIKyIzQEp3nSEaa3kdEXFxppD
# 9DNR32CwcBl1Qbq8D7BTMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAZahThdliRaIDtQcCqJGOrJ9iCYwebDw24oniAHHDhRSSVZZcXnmcDSHw
# UQuVdF1CIo3Bzd8X90sYOKJmJPFi54mBp9A1TKLvY3ew0sCf+jYs5aE+Dspg7AF1
# 2ew1zpbcG0TCioFb5mXcaNpFwfSU7xtS9VZoXr41Aa71bWKhgToLS+tlbUuW/Mq0
# Ef9P4Zbq8klwmgUYbQWcJkRWFtH7feI/xI+pu3t2lAX/BfUAmtkx7PsYZEnqfADu
# ey1QJdddcGcvxCmBQygjEXttAgHde97PnkaoL70e1BW+q8Hjw91A/6yY+ieFAg0d
# JaWKJC4jVNShHyqYva5UZbQrorFdxKGCFywwghcoBgorBgEEAYI3AwMBMYIXGDCC
# FxQGCSqGSIb3DQEHAqCCFwUwghcBAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq
# hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCAue56J3zqPC1hF4u3/1KCfXK5OHUVhz8+39qHyuYA67gIGZpfHenkf
# GBMyMDI0MDcyMzExMDA1Ny42MTRaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO
# OjNCRDQtNEI4MC02OUMzMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT
# ZXJ2aWNloIIRezCCBycwggUPoAMCAQICEzMAAAHlj2rA8z20C6MAAQAAAeUwDQYJ
# KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjMx
# MDEyMTkwNzM1WhcNMjUwMTEwMTkwNzM1WjCB0jELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl
# cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjozQkQ0LTRC
# ODAtNjlDMzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKl74Drau2O6LLrJO3HyTvO9
# aXai//eNyP5MLWZrmUGNOJMPwMI08V9zBfRPNcucreIYSyJHjkMIUGmuh0rPV5/2
# +UCLGrN1P77n9fq/mdzXMN1FzqaPHdKElKneJQ8R6cP4dru2Gymmt1rrGcNe800C
# cD6d/Ndoommkd196VqOtjZFA1XWu+GsFBeWHiez/PllqcM/eWntkQMs0lK0zmCfH
# +Bu7i1h+FDRR8F7WzUr/7M3jhVdPpAfq2zYCA8ZVLNgEizY+vFmgx+zDuuU/GChD
# K7klDcCw+/gVoEuSOl5clQsydWQjJJX7Z2yV+1KC6G1JVqpP3dpKPAP/4udNqpR5
# HIeb8Ta1JfjRUzSv3qSje5y9RYT/AjWNYQ7gsezuDWM/8cZ11kco1JvUyOQ8x/JD
# kMFqSRwj1v+mc6LKKlj//dWCG/Hw9ppdlWJX6psDesQuQR7FV7eCqV/lfajoLpPN
# x/9zF1dv8yXBdzmWJPeCie2XaQnrAKDqlG3zXux9tNQmz2L96TdxnIO2OGmYxBAA
# ZAWoKbmtYI+Ciz4CYyO0Fm5Z3T40a5d7KJuftF6CToccc/Up/jpFfQitLfjd71cS
# +cLCeoQ+q0n0IALvV+acbENouSOrjv/QtY4FIjHlI5zdJzJnGskVJ5ozhji0YRsc
# v1WwJFAuyyCMQvLdmPddAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQU3/+fh7tNczEi
# fEXlCQgFOXgMh6owHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD
# VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
# cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG
# CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw
# MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBADP6whOFjD1ad8Gk
# EJ9oLBuvfjndMyGQ9R4HgBKSlPt3pa0XVLcimrJlDnKGgFBiWwI6XOgw82hdolDi
# MDBLLWRMTJHWVeUY1gU4XB8OOIxBc9/Q83zb1c0RWEupgC48I+b+2x2VNgGJUsQI
# yPR2PiXQhT5PyerMgag9OSodQjFwpNdGirna2rpV23EUwFeO5+3oSX4JeCNZvgyU
# OzKpyMvqVaubo+Glf/psfW5tIcMjZVt0elswfq0qJNQgoYipbaTvv7xmixUJGTbi
# xYifTwAivPcKNdeisZmtts7OHbAM795ZvKLSEqXiRUjDYZyeHyAysMEALbIhdXgH
# Eh60KoZyzlBXz3VxEirE7nhucNwM2tViOlwI7EkeU5hudctnXCG55JuMw/wb7c71
# RKimZA/KXlWpmBvkJkB0BZES8OCGDd+zY/T9BnTp8si36Tql84VfpYe9iHmy7Pqq
# xqMF2Cn4q2a0mEMnpBruDGE/gR9c8SVJ2ntkARy5SfluuJ/MB61yRvT1mUx3lypp
# O22ePjBjnwoEvVxbDjT1jhdMNdevOuDeJGzRLK9HNmTDC+TdZQlj+VMgIm8ZeEIR
# NF0oaviF+QZcUZLWzWbYq6yDok8EZKFiRR5otBoGLvaYFpxBZUE8mnLKuDlYobjr
# xh7lnwrxV/fMy0F9fSo2JxFmtLgtMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ
# 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
# bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjoz
# QkQ0LTRCODAtNjlDMzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZaIjCgEBMAcGBSsOAwIaAxUA942iGuYFrsE4wzWDd85EpM6RiwqggYMwgYCk
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF
# AOpJhYcwIhgPMjAyNDA3MjMwOTI3MzVaGA8yMDI0MDcyNDA5MjczNVowdzA9Bgor
# BgEEAYRZCgQBMS8wLTAKAgUA6kmFhwIBADAKAgEAAgIE9gIB/zAHAgEAAgISXDAK
# AgUA6krXBwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB
# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAKXq6tSuyrlwxkg/
# J9kYj751iJXUpCRlXIEIvlV/6kJEWJGuEOZGbIIbq8p2E6Ey7ixup6RoCvhC/KB4
# 8YfDionhUDmuCr6M3+T+RXeyRSN4q3ZN+slYdzprDL9FrvpqXoQd+2NNeSpB2rI+
# vrR5WoiNnCFIgVK4IDHKXQgst/kgMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTACEzMAAAHlj2rA8z20C6MAAQAAAeUwDQYJYIZIAWUD
# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B
# CQQxIgQgEEbYmNq/ia6r6NsLPCKYuexDQeUbmqIsvjBMZDV8ijEwgfoGCyqGSIb3
# DQEJEAIvMYHqMIHnMIHkMIG9BCAVqdP//qjxGFhe2YboEXeb8I/pAof01CwhbxUH
# 9U697TCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB
# 5Y9qwPM9tAujAAEAAAHlMCIEIN2DXzqqPnfDPq8kn3VhILvJ/1JGplHAaWk/ZRf8
# 66epMA0GCSqGSIb3DQEBCwUABIICAA8OtQAwWvYQ4fna841MdsAgwEKrKPrhjvD/
# 81tanuWcRVVOCS8tD4uIPwL2OjEiC62fmzOw6yxh2Qqe2esPZfv77pkaHTL6t1DL
# Llid2HydC2cPmHS59i1QE9RRERrYx1xlnRO7R3Q2SQ66NhMsIGdWOaZqbeirLR6k
# VODkX88vYgDiUaoDbZQ3is69f8RcTII0hJ4Pgd3BGYSPNRp5yXLlVm7f+4orj9jh
# E1A/CR+q8Joe5GIWHLrXmP7RlOlXaKU7Uv5kF27KVlkm0WB2KFmcVT3xH206Eg2I
# 9b0GHMFWFVjQO5B4wbtQxnB0sKJbA3Y++jFCP9oc1TyktEt9+v2MfHZJQORvLNna
# /Fb0Zpp+zxIN0gceHGDChWnsTFIJ82JVj0PnE3IwVL/bnp4P5Ex8Z3fm+eRDMN69
# ChFQo7/0BukBtq+VoLef34Rb+HHaquTO8QfC4Jw8bL3oYffWtF4wO6X+QYXqPCa1
# /wgtTYilUSJOAzp7duZcKHWARGqSfI9rNTxT75MDYowYAc74O8ag940tglkGe/jJ
# pEDEn4K4FapVp3Abg1eiHIuN4dmF4iS0Wpghqtdyjk9h5nXCOgMOcETiSaEbfjXG
# YvswF21cbHCFAs9rzQItX3WlM0AAp3MmxZ3pchrzcH3hHrVAnmrTEB3UnKCTH0tK
# ooSKWiNd
# SIG # End signature block