AzSHCI.ARCInstaller.psm1

<#############################################################
 # #
 # Copyright (C) Microsoft Corporation. All rights reserved. #
 # #
 #############################################################>

Import-Module $PSScriptRoot\Classes\reporting.psm1 -Force -DisableNameChecking -Global
 

function Check-NodeArcRegistrationStateScriptBlock {
    if(Test-Path -Path "C:\Program Files\AzureConnectedMachineAgent\azcmagent.exe")
    {
        $arcAgentStatus = Invoke-Expression -Command "& 'C:\Program Files\AzureConnectedMachineAgent\azcmagent.exe' show -j"
        
        # Parsing the status received from Arc agent
        $arcAgentStatusParsed = $arcAgentStatus | ConvertFrom-Json

        # Throw an error if the node is Arc enabled to a different resource group or subscription id
        # Agent can be is "Connected" or disconnected state. If the resource name property on the agent is empty, that means, it is cleanly disconnected , and just the exe exists
        # If the resourceName exists and agent is in "Disconnected" state, indicates agent has temporary connectivity issues to the cloud
        if(-not ([string]::IsNullOrEmpty($arcAgentStatusParsed.resourceName)) -or
         -not ([string]::IsNullOrEmpty($arcAgentStatusParsed.subscriptionId))  -or 
         -not ([string]::IsNullOrEmpty($arcAgentStatusParsed.resourceGroup))
         )
        {
            
            $differentResourceExceptionMessage = "Node is already ARC Enabled and connected to Subscription Id: {0}, Resource Group: {1}" -f $arcAgentStatusParsed.subscriptionId, $arcAgentStatusParsed.resourceGroup
            Log-info -Message "$differentResourceExceptionMessage" -Type Error -ConsoleOut
            return [ErrorDetail]::NodeAlreadyArcEnabled
        }
        return [ErrorDetail]::Success
    }
}

function Register-ResourceProviderIfRequired{
    param(
        [string] $ProviderNamespace
    )
        $rpState = Get-AzResourceProvider -ProviderNamespace $ProviderNamespace
        $notRegisteredResourcesForRP = ($rpState.Where({$_.RegistrationState  -ne "Registered"}) | Measure-Object ).Count
        if ($notRegisteredResourcesForRP -eq 0 )
        { 
            Log-Info -Message "$ProviderNamespace RP already registered, skipping registration" -ConsoleOut
        } 
        else
        {
            try
            {
                Register-AzResourceProvider -ProviderNamespace $ProviderNamespace | Out-Null
                Log-Info -Message "registered Resource Provider: $ProviderNamespace " -ConsoleOut
            }
            catch
            {
                Log-Info -Message "Exception occured while registering $ProviderNamespace RP, $_" -ConsoleOut   
                throw 
            }
        }
    }

 function Invoke-AzStackHciArcInitialization
 {
     <#
     .SYNOPSIS
         Perform AzStackHci ArcIntegration Initialization
     .DESCRIPTION
         Initializes ARC integration on Azure Stack HCI node
     .EXAMPLE
         PS C:\> Connect-AzAccount -Tenant $tenantID -Subscription $subscriptionID -DeviceCode
         PS C:\> $nodeNames = [string[]]("host1","host2","host3","host4")
         PS C:\> Invoke-AzStackHciArcIntegrationValidation -SubscriptionID $subscriptionID -ArcResourceGroupName $resourceGroupName -NodeNames $nodeNames
     .PARAMETER SubscriptionID
         Specifies the Azure Subscription to create the resource. Is Mandatory Paratmer
     .PARAMETER ResourceGroup
         Specifies the resource group to which ARC resources should be projected. Is Mandatory Paratmer
    .PARAMETER TenantID
         Specifies the Azure TenantId.Required only if ARMAccessToken is used.
    .PARAMETER Cloud
         Specifies the Azure Environment. Valid values are AzureCloud, AzureChinaCloud, AzureUSGovernment. Required only if ARMAccessToken is used.
    .PARAMETER Region
        Specifies the Region to create the resource. Region is a Mandatory parameter.
    .PARAMETER ArmAccessToken
         Specifies the ARM access token. Specifying this along with AccountId will avoid Azure interactive logon. If not specified, Azure Context is expected to be setup.
    .PARAMETER AccountID
         Specifies the Account Id. Specifying this along with ArmAccessToken will avoid Azure interactive logon. Required only if ARMAccessToken is used.
    .PARAMETER SpnCredential
        Specifies the Service Principal Credential. Required only if ARMAccessToken is not used.
    .PARAMETER Tag
        Specifies the resource tags for the resource in Azure in the form of key-value pairs in a hash table. For example: @{key0="value0";key1=$null;key2="value2"}
    .PARAMETER EnvironmentCheckerVersion
        Specifies the Version of Environment Checker to be used. Default is latest version.
    .PARAMETER OutputPath
         Directory path for log and report output.
    .PARAMETER Proxy
         Specify proxy server.
    .PARAMETER ArcGatewayID
         Specify Arc Gateway resource Id.
     #>

     [CmdletBinding(DefaultParametersetName='AZContext')]
     param (
         [Parameter(ParameterSetName='SPN', Mandatory = $true, HelpMessage = "Azure Subscription ID to project ARC resource ")]
         [Parameter(ParameterSetName='ARMToken', Mandatory = $true, HelpMessage = "Azure Subscription ID to project ARC resource ")]
         [string]
         $SubscriptionID,
         
         #TODO: should we do a validation of if the resource group is created or should we create the RG ?
         [Parameter(ParameterSetName='SPN', Mandatory = $true, HelpMessage = "Azure Resource group used for HCI ARC Integration")]
         [Parameter(ParameterSetName='ARMToken', Mandatory = $true, HelpMessage = "Azure Resource group used for HCI ARC Integration")]
         [string]
         $ResourceGroup,
         
         [Parameter(ParameterSetName='SPN', Mandatory = $true, HelpMessage = "Azure Tenant used for HCI ARC Integration")]
         [Parameter(ParameterSetName='ARMToken', Mandatory = $true, HelpMessage = "Azure Tenant used for HCI ARC Integration")]
         [string]
         $TenantID,
         
        # AzureCloud , AzureUSGovernment , AzureChinaCloud
         [Parameter(ParameterSetName='SPN', Mandatory = $true, HelpMessage = "Azure Cloud type used for HCI ARC Integration. Valid values are : AzureCloud , AzureUSGovernment , AzureChinaCloud")]
         [Parameter(ParameterSetName='ARMToken', Mandatory = $true, HelpMessage = "Azure Cloud type used for HCI ARC integration. Valid values are : AzureCloud , AzureUSGovernment , AzureChinaCloud")]
        [string] 
        $Cloud,
        
         [Parameter(ParameterSetName='SPN', Mandatory = $true, HelpMessage = "Azure Region used for HCI ARC Integration")]
         [Parameter(ParameterSetName='ARMToken', Mandatory = $true, HelpMessage = "Azure Region used for HCI ARC Integration")]
         [string] 
         $Region,


         [Parameter(ParameterSetName='ARMToken', Mandatory = $true, HelpMessage = "ARM Access Token used for HCI ARC Integration")]
         [string]
         $ArmAccessToken,

         [Parameter(ParameterSetName='ARMToken', Mandatory = $true, HelpMessage = "Account ID used for HCI ARC Integration")]
         [string]
         $AccountID,
         
         [Parameter(ParameterSetName='SPN', Mandatory = $true, HelpMessage = "SPN credential used for onboarding AR")]
         [System.Management.Automation.PSCredential] 
         $SpnCredential,
         
         [Parameter(ParameterSetName='SPN', Mandatory=$false)]
         [Parameter(ParameterSetName='ARMToken', Mandatory = $false, HelpMessage = "Return PSObject result.")]
         [Parameter(Mandatory = $false)]
         [System.Collections.Hashtable] $Tag,

         [Parameter(ParameterSetName='SPN', Mandatory=$false)]
         [Parameter(ParameterSetName='ARMToken', Mandatory = $false, HelpMessage = "Return PSObject result.")]
         [Parameter(Mandatory = $false)]
         [System.Version]$EnvironmentCheckerVersion,

         [Parameter(ParameterSetName='SPN', Mandatory=$false)]
         [Parameter(ParameterSetName='ARMToken', Mandatory = $false, HelpMessage = "Directory path for log and report output")]
         [string]$OutputPath,
         
         [Parameter(ParameterSetName='SPN', Mandatory=$false, HelpMessage = "Specify proxy server.")]
         [Parameter(ParameterSetName='ARMToken', Mandatory=$false, HelpMessage = "Specify proxy server.")]
         [string]
         $Proxy,

         [Parameter(ParameterSetName='SPN', Mandatory=$false, HelpMessage = "Specify Gateway resource Id.")]
         [Parameter(ParameterSetName='ARMToken', Mandatory=$false, HelpMessage = "Specify Gateway resource Id.")]
         [string]
         $ArcGatewayID,

         [Parameter(Mandatory = $false)]
         [Switch] $Force

         
     )

     $tempConfigPath = "$env:TEMP\bootstrap.json"
 
     try
     {
        $script:ErrorActionPreference = 'Stop'
        $ProgressPreference = 'SilentlyContinue'
        Set-AzStackHciOutputPath -Path $OutputPath

        Log-Info -Message "Starting AzStackHci ArcIntegration Initialization" -ConsoleOut

        if (-not (Get-Command -Name Start-ArcBootstrap -ErrorAction SilentlyContinue))
        {
            Log-Info -Message "Installing and Running Azure Stack HCI Environment Checker" -ConsoleOut
            [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072;
            $environmentValidatorResult = RunEnvironmentValidator -EnvironmentCheckerVersion $EnvironmentCheckerVersion
            if ($environmentValidatorResult -ne [ErrorDetail]::Success -and (-Not $Force) ) {
                Log-Info -Message "Environment Validator failed so not installing the ARC agent" -Type Error -ConsoleOut
                throw "Environment Validator failed, so skipping ARC integration"
            }
            install-HypervModules -SkipErrors $Force

            Log-Info -Message "Starting AzStackHci ArcIntegration Initialization" -ConsoleOut  
            $scrubbedParams = @{}
            foreach($psbp in $PSBoundParameters.GetEnumerator())
            {
                if($psbp.Key -eq "ArmAccessToken")
                {
                    continue
                }
                $scrubbedParams[$psbp.Key] = $psbp.Value
            }

            Write-AzStackHciHeader -invocation $MyInvocation -params $scrubbedParams -PassThru:$PassThru

            $ArcConnectionState = Check-NodeArcRegistrationStateScriptBlock

            #TODO: other validations related to OS Type and Version should happen here.
            # If the agent is already installed and not connected, we will re-install the agent again. This is like upgrade operation
            & "$PSScriptRoot\Classes\install_aszmagent_hci.ps1" -AltDownload "https://download.microsoft.com/download/c/c/e/cce7456c-e998-4fa1-9566-f43f4a2f6a6f/AzureConnectedMachineAgent.msi";
            if ($LASTEXITCODE -ne 0) { exit 1; }

            # Run connect command
            $CorrelationID = New-Guid
            $machineName = [System.Net.Dns]::GetHostName()
            if (-not [string]::IsNullOrEmpty($Proxy))
            {
                Log-Info -Message "Configuring proxy on agent : $($Proxy)" -ConsoleOut
                & "$env:ProgramW6432\AzureConnectedMachineAgent\azcmagent.exe" config set proxy.url $Proxy ;
            }

            $gatewaySupportedVersion = [System.Version]::Parse("1.40")
            $connectedMachineAgentVersion = & "$env:ProgramW6432\AzureConnectedMachineAgent\azcmagent.exe" --version 
            $isGatewaySupported = [regex]::Match($connectedMachineAgentVersion, '\d+(\.\d+)+').Value -ge $gatewaySupportedVersion

            #TODO: Replace the check with parameter test instead of version check once the gateway id parameter is no longer hidden
            #$connectCmdHelpOutput = & "$env:ProgramW6432\AzureConnectedMachineAgent\azcmagent.exe" connect -h 2>&1;
            #$isGatewaySupported = $connectCmdHelpOutput -match "--gateway-id";

            if ($PSCmdlet.ParameterSetName -eq "SPN")
            {
                Log-Info -Message "Connecting to Azure using SPN Credentials" -ConsoleOut
                Connect-AzAccount -ServicePrincipal -TenantId $TenantId -Credential $SpnCredential | out-null

                Log-Info -Message "Connected to Azure successfully" -ConsoleOut

                Register-ResourceProviderIfRequired -ProviderNamespace "Microsoft.HybridCompute"
                Register-ResourceProviderIfRequired -ProviderNamespace "Microsoft.GuestConfiguration"
                Register-ResourceProviderIfRequired -ProviderNamespace "Microsoft.HybridConnectivity"
                Register-ResourceProviderIfRequired -ProviderNamespace "Microsoft.AzureStackHCI"
                if ($ArcConnectionState -ne [ErrorDetail]::NodeAlreadyArcEnabled) {
                    Log-Info -Message "Connecting to Azure ARC agent " -ConsoleOut

                    if ($isGatewaySupported -and -not [string]::IsNullOrEmpty($ArcGatewayID)) 
                    {
                        & "$env:ProgramW6432\AzureConnectedMachineAgent\azcmagent.exe" connect --service-principal-id "$SpnCredential.UserName" --service-principal-secret "$SpnCredential.GetNetworkCredential().Password" --resource-group "$ResourceGroup" --resource-name "$machineName" --tenant-id "$TenantID" --location "$Region" --subscription-id "$SubscriptionID" --cloud "$Cloud" --correlation-id "$CorrelationID" --gateway-id "$ArcGatewayID";
                    }
                    else
                    {
                        & "$env:ProgramW6432\AzureConnectedMachineAgent\azcmagent.exe" connect --service-principal-id "$SpnCredential.UserName" --service-principal-secret "$SpnCredential.GetNetworkCredential().Password" --resource-group "$ResourceGroup" --resource-name "$machineName" --tenant-id "$TenantID" --location "$Region" --subscription-id "$SubscriptionID" --cloud "$Cloud" --correlation-id "$CorrelationID";
                    }

                    if ($LASTEXITCODE -ne 0) {
                        Log-Info -Message "Azure ARC agent onboarding failed " -ConsoleOut
                        throw "Arc agent onboarding failed, so erroring out, logs are present in C:\ProgramData\AzureConnectedMachineAgent\Log\azcmagent.log"
                    }

                    Log-Info -Message "Connected Azure ARC agent successfully " -ConsoleOut
                }
                else {
                    Log-Info -Message "Node Already Arc Enabled, so skipping the arc registration" -ConsoleOut
                }

            }
            elseif ($PSCmdlet.ParameterSetName -eq "ARMToken")
            {
                Log-Info -Message "Connecting to Azure using ARM Access Token" -ConsoleOut

                Connect-AzAccount -Environment $Cloud -Tenant $TenantID  -AccessToken $ArmAccessToken -AccountId $AccountId -Subscription $SubscriptionID | out-null

                Log-Info -Message "Connected to Azure successfully" -ConsoleOut

                Register-ResourceProviderIfRequired -ProviderNamespace "Microsoft.HybridCompute"
                Register-ResourceProviderIfRequired -ProviderNamespace "Microsoft.GuestConfiguration"
                Register-ResourceProviderIfRequired -ProviderNamespace "Microsoft.HybridConnectivity"
                Register-ResourceProviderIfRequired -ProviderNamespace "Microsoft.AzureStackHCI"
            
                if ($ArcConnectionState -ne [ErrorDetail]::NodeAlreadyArcEnabled) {
            
                    if ($isGatewaySupported -and -not [string]::IsNullOrEmpty($ArcGatewayID))
                    {
                        & "$env:ProgramW6432\AzureConnectedMachineAgent\azcmagent.exe" connect --resource-group "$ResourceGroup" --resource-name "$machineName" --tenant-id "$TenantID" --location "$Region" --subscription-id "$SubscriptionID" --cloud "$Cloud" --correlation-id "$CorrelationID" --access-token "$ArmAccessToken"  --gateway-id "$ArcGatewayID";
                    } 
                    else
                    {
                        & "$env:ProgramW6432\AzureConnectedMachineAgent\azcmagent.exe" connect --resource-group "$ResourceGroup" --resource-name "$machineName" --tenant-id "$TenantID" --location "$Region" --subscription-id "$SubscriptionID" --cloud "$Cloud" --correlation-id "$CorrelationID" --access-token "$ArmAccessToken";
                    }
                    if ($LASTEXITCODE -ne 0) {
                        Log-Info -Message "Azure ARC agent onboarding failed " -ConsoleOut
                        throw "Arc agent onboarding failed, so erroring out, logs are present in C:\ProgramData\AzureConnectedMachineAgent\Log\azcmagent.log" 
                    }

                    Log-Info -Message "Connected Azure ARC agent successfully " -ConsoleOut
                }
                else {
                    Log-Info -Message "Node is already arc enabled so skipping ARC registration" -ConsoleOut
                }

            }

            Log-Info -Message "Installing AzureEdgeTelemetryAndDiagnostics Extension " -ConsoleOut

            $Settings = @{ "CloudName" = $Cloud; "RegionName" = $Region; "DeviceType" = "AzureEdge" }
            New-AzConnectedMachineExtension -Name "AzureEdgeTelemetryAndDiagnostics"  -ResourceGroupName $ResourceGroup -MachineName $env:COMPUTERNAME -Location $Region -Publisher "Microsoft.AzureStack.Observability" -Settings $Settings -ExtensionType "TelemetryAndDiagnostics" -EnableAutomaticUpgrade -NoWait | out-null

            Log-Info -Message "Successfully triggered AzureEdgeTelemetryAndDiagnostics Extension installation " -ConsoleOut
            Start-Sleep -Seconds 60

            Log-Info -Message "Installing DeviceManagement Extension " -ConsoleOut
            New-AzConnectedMachineExtension -Name "AzureEdgeDeviceManagement"  -ResourceGroupName $ResourceGroup -MachineName $env:COMPUTERNAME -Location $Region -Publisher "Microsoft.Edge" -ExtensionType "DeviceManagementExtension" -NoWait | out-null

            Log-Info -Message "Successfully triggered DeviceManagementExtension installation " -ConsoleOut
            Start-Sleep -Seconds 60

            Log-Info -Message "Installing LcmController Extension " -ConsoleOut
            New-AzConnectedMachineExtension -Name "AzureEdgeLifecycleManager"  -ResourceGroupName $ResourceGroup -MachineName $env:COMPUTERNAME -Location $Region -Publisher "Microsoft.AzureStack.Orchestration" -ExtensionType "LcmController" -NoWait | out-null 

            Log-Info -Message "Successfully triggered LCMController Extension installation " -ConsoleOut
            Start-Sleep -Seconds 60

            Log-Info -Message "Installing EdgeRemoteSupport Extension " -ConsoleOut
            New-AzConnectedMachineExtension -Name "AzureEdgeRemoteSupport"  -ResourceGroupName $ResourceGroup -MachineName $env:COMPUTERNAME -Location $Region -Publisher "Microsoft.AzureStack.Observability" -ExtensionType "EdgeRemoteSupport" -EnableAutomaticUpgrade -NoWait | out-null
            Log-Info -Message "Successfully triggered EdgeRemoteSupport Extension installation " -ConsoleOut

            Log-Info -Message "ARC Successfully enabled on the device." -ConsoleOut
            Log-Info -Message "ARC Extension installations Successfully triggered on the device." -ConsoleOut
        }
        else 
        {
            $scrubbedParams = @{}
            foreach($psbp in $PSBoundParameters.GetEnumerator())
            {
                if($psbp.Key -eq "ArmAccessToken")
                {
                    continue
                }
                $scrubbedParams[$psbp.Key] = $psbp.Value
            }

            Write-AzStackHciHeader -invocation $MyInvocation -params $scrubbedParams -PassThru:$PassThru

            [Collections.Hashtable] $configHash = @{}

            if ($PSCmdlet.ParameterSetName -eq "SPN")
            {
                Log-Info -Message "Constructing node config using SPN Credentials" -ConsoleOut

                $configHash["ArcConfiguration"] = @{
                    "ArcMultiServerConfiguration" = @{
                        "resourceGroup" = $ResourceGroup
                        "tenantId" = $TenantID
                        "location" = $Region
                        "subscriptionId" = $SubscriptionID
                        "cloud" = $Cloud
                        "gatewayId" = $ArcGatewayID
                        "servicePrincipalClientId"= $SpnCredential.UserName
                        "servicePrincipalSecret" = $SpnCredential.GetNetworkCredential().Password
                    }
                }
            }
            elseif ($PSCmdlet.ParameterSetName -eq "ARMToken")
            {
                Log-Info -Message "Constructing node config using ARM Access Token" -ConsoleOut

                $configHash["ArcConfiguration"] = @{
                    "ArcMultiServerConfiguration" = @{
                        "resourceGroup" = $ResourceGroup
                        "tenantId" = $TenantID
                        "location" = $Region
                        "subscriptionId" = $SubscriptionID
                        "cloud" = $Cloud
                        "gatewayId" = $ArcGatewayID
                        "armAccessToken" = $ArmAccessToken
                        "accountId" = $AccountID
                    }
                }
            }

            $configHash | ConvertTo-Json | Out-File $tempConfigPath -Force

            $timeoutDuration = 1800 # 30 minutes

            # Set the polling interval (in seconds)
            $pollingInterval = 15

            $elapsed = 0

            try {

                $result = Start-ArcBootstrap -ConfigFilePath $tempConfigPath | ConvertFrom-Json

                do {
                    # Check the Response Status
                    $status = $result.Response.Status

                    # If the status is Failed or Success, print the result and break the loop
                    Log-Info -Message "Waiting for bootstrap to complete: $status" -ConsoleOut  

                    if ($status -eq "Succeeded") {
                        return $result
                    }

                    if ($status -eq "Failed") {
                        Log-Info -Message "Bootstrap failed with: $($result.Response.Errors[0].ErrorMessage)" -ConsoleOut
                        throw $result.Response.Errors[0].ErrorMessage
                    }

                    Start-Sleep -Seconds $pollingInterval

                    $elapsed += $pollingInterval

                    $result = Get-ArcBootstrapStatus

                } while ($elapsed -lt $timeoutDuration)
            }
            catch
            {
                Log-Info -Message "" -ConsoleOut
                Log-Info -Message "Elapsed time: $elapsed" -ConsoleOut
                if ($status -ne $null) {
                    Log-Info -Message "Most recent status: $status" -ConsoleOut
                }
                if ($result -ne $null) {
                    Log-Info -Message "Most recent status response: $result" -ConsoleOut
                }
                Log-Info -Message "Bootstrap failed: $($_.Exception.Message)" -ConsoleOut
                throw $_
            }
        }
     }
     catch
     {
         Log-Info -Message "" -ConsoleOut
         Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error
         Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error
         $cmdletFailed = $true
         throw $_
     }
     finally
     {
        Disconnect-AzAccount -ErrorAction SilentlyContinue | out-null
        $Script:ErrorActionPreference = 'SilentlyContinue'
        Remove-Item -Path $tempConfigPath -Force -ErrorAction SilentlyContinue | out-null
        Write-AzStackHciFooter -invocation $MyInvocation -Failed:$cmdletFailed -PassThru:$PassThru
        if ((Get-Command -Name Start-ArcBootstrap -ErrorAction SilentlyContinue)){
            CollectBootstrapLogs
        }
     }
 }

 function CollectBootstrapLogs {
    $RetryDelaySec = 10
    $maxWaitTime = 600 # 600 seconds = 10 mins
    $startTime = Get-Date
    $retryCount = -1
    try
    {
        Start-ArcBootstrapSupportLogs -IncludeType Latest

        Log-Info -Message "Successfully triggered Arc boostrap support log collection. Waiting for $maxWaitTime seconds to complete." -ConsoleOut

        $arcBootstrapSupportLogsComplete = $false
        while ($((Get-Date) - $startTime).TotalSeconds -le $maxWaitTime)
        {
            try
            {
                $retryCount = $retryCount + 1

                Log-Info -Message "Waiting for Arc bootstrap support logs to complete on '$NodeIP', retry count: $retryCount." -ConsoleOut
                $s = Get-ArcBootstrapSupportLogsStatus

                if ($s.Status -eq "Succeeded")
                {
                    Log-Info -Message "Arc boostrap support log collection completed successfully." -ConsoleOut
                    $arcBootstrapSupportLogsComplete = $true
                    break
                }
                elseif ($s.Status -eq "Failed")
                {
                    Log-Info -Message "Arc bootstrap support log collection failed" -ConsoleOut
                    $arcBootstrapSupportLogsComplete = $true
                    break
                }
                Log-Info -Message "Arc bootstrap support log collection status is $($s.Status). Sleep for $RetryDelaySec seconds." -ConsoleOut
                Start-Sleep -Seconds $RetryDelaySec
            }
            catch
            {
                $issue = ($_ | FL * | Out-String)
                Trace-Execution "Unable to query Arc bootstrap support log collection status due to $issue. Sleep for $RetryDelaySec seconds."
                Start-Sleep -Seconds $RetryDelaySec
            }
        }

        if (-not ($arcBootstrapSupportLogsComplete))
        {
            throw "Arc bootstrap support log collection timed out after $maxWaitTime seconds."
        }
    }
    finally
    {}
 }

 function Remove-AzStackHciArcInitialization
 {
     <#
     .SYNOPSIS
         Perform AzStackHci ArcIntegration Initialization
     .DESCRIPTION
         Initializes ARC integration on Azure Stack HCI node
     .EXAMPLE
         PS C:\> Connect-AzAccount -Tenant $tenantID -Subscription $subscriptionID -DeviceCode
         PS C:\> $nodeNames = [string[]]("host1","host2","host3","host4")
         PS C:\> Invoke-AzStackHciArcIntegrationValidation -SubscriptionID $subscriptionID -ArcResourceGroupName $resourceGroupName -NodeNames $nodeNames
     .PARAMETER SubscriptionID
         Specifies the Azure Subscription to create the resource. Is Mandatory Paratmer
     .PARAMETER ResourceGroup
        TODO: This is not used anywhere. Remove it
     .PARAMETER TenantID
         Specifies the Azure TenantId.Required only if ARMAccessToken is used.
     .PARAMETER Cloud
         Specifies the Azure Environment. Valid values are AzureCloud, AzureChinaCloud, AzureUSGovernment. Required only if ARMAccessToken is used.
     .PARAMETER ArmAccessToken
         Specifies the ARM access token. Specifying this along with AccountId will avoid Azure interactive logon. If not specified, Azure Context is expected to be setup.
     .PARAMETER AccountID
         Specifies the Account Id. Specifying this along with ArmAccessToken will avoid Azure interactive logon. Required only if ARMAccessToken is used.
      
    .PARAMETER PassThru
         Return PSObject result.
     .PARAMETER OutputPath
         Directory path for log and report output.
     .PARAMETER CleanReport
         Remove all previous progress and create a clean report.
     .INPUTS
         Inputs (if any)
     .OUTPUTS
         Output (if any)
     #>

     [CmdletBinding(DefaultParametersetName='AZContext')]
     param (
         [Parameter(ParameterSetName='SPN', Mandatory = $true, HelpMessage = "Azure Environment used for HCI ARC Integration")]
         [Parameter(ParameterSetName='ARMToken', Mandatory = $true, HelpMessage = "Azure Environment used for HCI ARC Integration")]
         [string]
         $SubscriptionID,
         
         #TODO: should we do a validation of if the resource group is created or should we create the RG ?
         [Parameter(ParameterSetName='SPN', Mandatory = $true, HelpMessage = "Azure Environment used for HCI ARC Integration")]
         [Parameter(ParameterSetName='ARMToken', Mandatory = $true, HelpMessage = "Azure Tenant used for HCI ARC Integration")]
         [string]
         $ResourceGroup,
         
         [Parameter(ParameterSetName='SPN', Mandatory = $true, HelpMessage = "Azure Environment used for HCI ARC Integration")]
         [Parameter(ParameterSetName='ARMToken', Mandatory = $true, HelpMessage = "Azure Subscription used for HCI ARC Integration")]
         [string]
         $TenantID,
         # AzureCloud , AzureUSGovernment , AzureChinaCloud
         [Parameter(ParameterSetName='SPN', Mandatory = $true, HelpMessage = "Specifies the Azure Environment. Azure Valid values are AzureCloud, AzureChinaCloud, AzureUSGovernment")]
         [Parameter(ParameterSetName='ARMToken', Mandatory = $true, HelpMessage = "Specifies the Azure Environment. Azure Valid values are AzureCloud, AzureChinaCloud, AzureUSGovernment")]
         [string] 
         $Cloud,

         [Parameter(ParameterSetName='ARMToken', Mandatory = $true, HelpMessage = "ARM Access Token used for HCI ARC Integration")]
         [string]
         $ArmAccessToken,

         [Parameter(ParameterSetName='ARMToken', Mandatory = $true, HelpMessage = "Account ID used for HCI ARC Integration")]
         [string]
         $AccountID,
         

         [Parameter(ParameterSetName='SPN', Mandatory = $true, HelpMessage = "SPN credential used for onboarding ARC machine")]
         [System.Management.Automation.PSCredential] 
         $SpnCredential,

        [Parameter(ParameterSetName='SPN', Mandatory=$false)]
        [Parameter(ParameterSetName='ARMToken', Mandatory = $false, HelpMessage = "Use to force clean the device , even if the cloud side clean up fails")]
        [switch]
        $Force,
 
         [Parameter(ParameterSetName='SPN', Mandatory=$false)]
         [Parameter(ParameterSetName='ARMToken', Mandatory = $false, HelpMessage = "Directory path for log and report output")]
         [string]$OutputPath
     )
 
     try
     {
        $script:ErrorActionPreference = 'Stop'
        Set-AzStackHciOutputPath -Path $OutputPath
        [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072;

        Log-Info -Message "Starting Arc Cleanup" -ConsoleOut

        $ArcConnectionState = Check-NodeArcRegistrationStateScriptBlock

        if ($PSCmdlet.ParameterSetName -eq "SPN")
        {
            Log-info -Message "Connecting to Azure with SPN" -ConsoleOut
            Connect-AzAccount -ServicePrincipal -TenantId $TenantId -Credential $SpnCredential
            
            Log-info -Message "Successfully connected to Azure with SPN" -ConsoleOut
            if ($ArcConnectionState -eq [ErrorDetail]::NodeAlreadyArcEnabled) {
                try {
                    Log-Info -Message "Removing Arc Extensions" -ConsoleOut
                    #TODO: enable Debug logs on Azure cmdlets
                    Get-AzConnectedMachineExtension -ResourceGroupName  $ResourceGroup -MachineName $ENV:COMPUTERNAME | Remove-AzConnectedMachineExtension -NoWait
                
                    Log-Info -Message "Removed Arc Extensions successfully" -ConsoleOut
                
                    & "$env:ProgramW6432\AzureConnectedMachineAgent\azcmagent.exe" disconnect --service-principal-id "$SpnCredential.UserName" --service-principal-secret "$SpnCredential.GetNetworkCredential().Password" ;

                    Log-Info -Message "successfully disconnected ARC agent" -ConsoleOut

                }
                catch {
                    & "$env:ProgramW6432\AzureConnectedMachineAgent\azcmagent.exe" disconnect --force-local-only;
                    #TODO: delete all the extension folders
                }
            }
            else{
                Log-Info -Message "Node was not ARC enabled so not disconnecting from ARC" -ConsoleOut
            }
  
        }
        elseif ($PSCmdlet.ParameterSetName -eq "ARMToken")
        {
            Log-Info -Message "Connecting to Azure with ARMAccess Token" -ConsoleOut
            Connect-AzAccount -Environment $Cloud -Tenant $TenantID  -AccessToken $ArmAccessToken -AccountId $AccountId -Subscription $SubscriptionID | out-null
            
            Log-Info -Message "Successfully connected to Azure with ARM Token" -ConsoleOut
            if ($ArcConnectionState -eq [ErrorDetail]::NodeAlreadyArcEnabled) {
                try {
                
                    Log-Info -Message "Removing Arc Extensions" -ConsoleOut
                    Get-AzConnectedMachineExtension -ResourceGroupName  $ResourceGroup -MachineName $ENV:COMPUTERNAME | Remove-AzConnectedMachineExtension -NoWait
            
                    & "$env:ProgramW6432\AzureConnectedMachineAgent\azcmagent.exe" disconnect  --access-token "$ArmAccessToken";
                
                    Log-Info -Message "successfully disconnected ARC agent" -ConsoleOut
                
                }
                catch {
                    & "$env:ProgramW6432\AzureConnectedMachineAgent\azcmagent.exe" disconnect --force-local-only;
                    #TODO: delete all the extension folders
                }
            }
            else{
                Log-Info -Message "Node was not ARC enabled, so not removing ARC agent" -ConsoleOut
            }

        }

     }
     catch
     {
         Log-Info -Message "" -ConsoleOut
         Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error
         Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error
         $cmdletFailed = $true
         throw $_
     }
     finally
     {
         Disconnect-AzAccount -ErrorAction SilentlyContinue | out-null
         $Script:ErrorActionPreference = 'SilentlyContinue'
     }
 }

 


function install-HypervModules{
    param
    (
        [bool] $SkipErrors
    )

    $status = Get-WindowsOptionalFeature -Online -FeatureName:Microsoft-Hyper-V
    if ($status.State -ne "Enabled") {
        if($SkipErrors)
        {
            Log-Info -Message "Hyper-v feature is not enabled. Continuing since 'Force' is configured." -ConsoleOut
        }
        else
        {
            throw "Windows Feature 'Microsoft-Hyper-V' is not enabled. Cannot proceed."                
        }
    }
    if (($state.RestartRequired -eq "Possible") -or ($state.RestartRequired -eq "Required"))
    {
        if($SkipErrors)
        {
            Log-Info -Message "Hyper-v feature requires a node restart, please restart the node using Restart-Computer -Force" -ConsoleOut
        }
        else
        {
            throw "Windows Feature 'Microsoft-Hyper-V' requires a node restart to be enabled. Please run Restart-Computer -Force"                
        }
    }  
    try
    {
        Log-Info -Message "Installing Hyper-V Management Tools" -ConsoleOut
        Install-WindowsFeature -Name Hyper-V -IncludeManagementTools | Out-Null
        Log-Info -Message "Successfully installed Hyper-V Management Tools"                
    }
    catch
    {
        Log-Info -Message "" -ConsoleOut
        Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error
        Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error
    }
 }
 # Method to Get the object id from the ARC Imds endpoint
 function GetObjectIdFromArcMachine {
    try {
        $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
        $headers.Add("metadata", "true")
        $headers.Add("UseDefaultCredentials","true")
        $response = Invoke-WebRequest -Uri "http://localhost:40342/metadata/instance/compute?api-version=2020-06-01" -Method GET -Headers $headers -UseBasicParsing
        $content = $response.Content | ConvertFrom-Json
        Log-Info -Message "Successfully got the content from IMDS endpoint" -ConsoleOut
        $arcResource = Get-AzResource -ResourceId $content.resourceId
        $objectId = $arcResource.Identity.PrincipalId
        Log-Info -Message "Successfully got Object Id for Arc Installation $objectId" -ConsoleOut
        return $objectId
    }
    catch {
        Log-Info -Message "" -ConsoleOut
        Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error
        Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error
    }
    
 }

 function RunEnvironmentValidator {
    [CmdletBinding()]
    param
    (
        [System.Version] $EnvironmentCheckerVersion
    )
    try {
        if ([string]::IsNullOrEmpty($EnvironmentCheckerVersion)) {
            Install-Module -Name AzStackHci.EnvironmentChecker -Repository PSGallery -Force
        }
        else {
            Install-Module -Name AzStackHci.EnvironmentChecker -Repository PSGallery -Force -RequiredVersion $EnvironmentCheckerVersion
        }        
        $ENV:EnvChkrOp = 'ArcInitialization'
        $res = Invoke-AzStackHciConnectivityValidation -PassThru
        $successfulTests = $res | Where-Object { $_.Status -in @("Succeeded","SUCCESS")}
        if ($res.Count -eq $successfulTests.Count){
            Log-Info -Message "All the environment validation checks succeeded" -ConsoleOut
            return [ErrorDetail]::Success
        }
        else {
            $failedTests = $res | Where-Object { $_.Status -notin @("Succeeded","SUCCESS")}
            $criticalFailedTests =  $failedTests | Where-Object { $_.Severity -eq "Critical"}
            if( $criticalFailedTests.Count -gt 0)
            {
                Log-Info -Message "Critical environment validations failed, Failed Tests are shown below" -ConsoleOut
                $criticalFailedTests | Where-Object { $msg = $_ | Format-List | Out-String ; Log-Info -Message $msg -ConsoleOut }
                return [ErrorDetail]::EnvironmentValidationFailed
                
            }else
            {
                Log-Info -Message "Non-Critical environment validations failed, Failed Tests are shown below" -ConsoleOut
                $failedTests | Where-Object { $msg = $_ | Format-List | Out-String ; Log-Info -Message $msg -ConsoleOut }
                return [ErrorDetail]::Success
            }

        }
    }
    catch {
        Log-Info -Message "" -ConsoleOut
        Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error
        Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error
        return [ErrorDetail]::EnvironmentValidationFailed
    }
    return [ErrorDetail]::EnvironmentValidationFailed
 }
enum ErrorDetail
{
    Unused;
    PermissionsMissing;
    Success;
    NodeAlreadyArcEnabled;
    EnvironmentValidationFailed
}

Export-ModuleMember -Function Invoke-AzStackHciArcInitialization
Export-ModuleMember -Function Remove-AzStackHciArcInitialization
# SIG # Begin signature block
# MIIoLQYJKoZIhvcNAQcCoIIoHjCCKBoCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCB2FvOEbQZF/Djf
# ph5AM73pqLwGUJ4L1ijxuC1ToNEZ/aCCDXYwggX0MIID3KADAgECAhMzAAADrzBA
# 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
# /Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIO/wCr1wjgZVafsNRuEbzSUO
# MyWXGXpPmiG+k2Um5Jt6MEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAXP7phB5m3kzNeiJexMml/h542MxMJt0TMl+rX/1musn8Yncg2+PEPF3b
# JWGxJbo92cU3jbrcU9Cy31XpWuSCBqJANFgITZbyrgMVmeQuig9aE2/n75aoDZDd
# ZZjDdKi+IrQL0y/9KJuRRjXvdiXnFPyYS8ouxK+0F8KHNdmdJZN1gMOAVJKgt6vA
# MIHfHSbcBH90lPpu3Vhxf2ra12rV7poIyQ8WBmqDtW/p9vbmFklo3hGuCrQ/dCJ7
# nlgERFCxCk8jiu1pROZ38YflzTkeOHggpLHlU6KPELI3I02vu4O1fPKB4S6XUt0s
# M2wBbcWh8aH4/46MrNKPtJLKLyH3c6GCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC
# F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq
# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCB8ycDrTmD5+f0H5ZBN0wc7VryOycTvzMX3BjhdDnEctgIGZjOaYDM4
# GBMyMDI0MDUxNDE4MDEyNS4yNTlaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046RTAwMi0w
# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg
# ghHtMIIHIDCCBQigAwIBAgITMwAAAe4F0wIwspqdpwABAAAB7jANBgkqhkiG9w0B
# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzEyMDYxODQ1
# NDRaFw0yNTAzMDUxODQ1NDRaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z
# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046RTAwMi0wNUUwLUQ5NDcxJTAjBgNV
# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQC+8byl16KEia8xKS4vVL7REOOR7LzYCLXEtWgeqyOV
# lrzuEz+AoCa4tBGESjbHTXECeMOwP9TPeKaKalfTU5XSGjpJhpGx59fxMJoTYWPz
# zD0O2RAlyBmOBBmiLDXRDQJL1RtuAjvCiLulVQeiPI8V7+HhTR391TbC1beSxwXf
# dKJqY1onjDawqDJAmtwsA/gmqXgHwF9fZWcwKSuXiZBTbU5fcm3bhhlRNw5d04Ld
# 15ZWzVl/VDp/iRerGo2Is/0Wwn/a3eGOdHrvfwIbfk6lVqwbNQE11Oedn2uvRjKW
# EwerXL70OuDZ8vLzxry0yEdvQ8ky+Vfq8mfEXS907Y7rN/HYX6cCsC2soyXG3OwC
# tLA7o0/+kKJZuOrD5HUrSz3kfqgDlmWy67z8ZZPjkiDC1dYW1jN77t5iSl5Wp1HK
# Bp7JU8RiRI+vY2i1cb5X2REkw3WrNW/jbofXEs9t4bgd+yU8sgKn9MtVnQ65s6QG
# 72M/yaUZG2HMI31tm9mooH29vPBO9jDMOIu0LwzUTkIWflgd/vEWfTNcPWEQj7fs
# WuSoVuJ3uBqwNmRSpmQDzSfMaIzuys0pvV1jFWqtqwwCcaY/WXsb/axkxB/zCTdH
# SBUJ8Tm3i4PM9skiunXY+cSqH58jWkpHbbLA3Ofss7e+JbMjKmTdcjmSkb5oN8qU
# 1wIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFBCIzT8a2dwgnr37xd+2v1/cdqYIMB8G
# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG
# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy
# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w
# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy
# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG
# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD
# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQB3ZyAva2EKOWSVpBnYkzX8f8GZjaOs577F
# 9o14Anh9lKy6tS34wXoPXEyQp1v1iI7rJzZVG7rpUznay2n9csfn3p6y7kYkHqtS
# ugCGmTiiBkwhFfSByKPI08MklgvJvKTZb673yGfpFwPjQwZeI6EPj/OAtpYkT7IU
# XqMki1CRMJKgeY4wURCccIujdWRkoVv4J3q/87KE0qPQmAR9fqMNxjI3ZClVxA4w
# iM3tNVlRbF9SgpOnjVo3P/I5p8Jd41hNSVCx/8j3qM7aLSKtDzOEUNs+ZtjhznmZ
# gUd7/AWHDhwBHdL57TI9h7niZkfOZOXncYsKxG4gryTshU6G6sAYpbqdME/+/g1u
# er7VGIHUtLq3W0Anm8lAfS9PqthskZt54JF28CHdsFq/7XVBtFlxL/KgcQylJNni
# a+anixUG60yUDt3FMGSJI34xG9NHsz3BpqSWueGtJhQ5ZN0K8ju0vNVgF+Dv05si
# rPg0ftSKf9FVECp93o8ogF48jh8CT/B32lz1D6Truk4Ezcw7E1OhtOMf7DHgPMWf
# 6WOdYnf+HaSJx7ZTXCJsW5oOkM0sLitxBpSpGcj2YjnNznCpsEPZat0h+6d7ulRa
# WR5RHAUyFFQ9jRa7KWaNGdELTs+nHSlYjYeQpK5QSXjigdKlLQPBlX+9zOoGAJho
# Zfrpjq4nQDCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI
# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy
# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg
# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF
# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6
# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp
# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu
# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E
# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0
# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q
# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ
# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA
# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw
# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG
# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV
# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj
# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK
# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX
# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI
# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG
# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x
# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC
# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449
# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM
# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS
# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d
# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn
# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs
# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL
# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL
# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQ
# MIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn
# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkUwMDItMDVFMC1EOTQ3MSUwIwYDVQQD
# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCI
# o6bVNvflFxbUWCDQ3YYKy6O+k6CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6e3qRTAiGA8yMDI0MDUxNDEzNDg1
# M1oYDzIwMjQwNTE1MTM0ODUzWjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDp7epF
# AgEAMAoCAQACAgGeAgH/MAcCAQACAhOsMAoCBQDp7zvFAgEAMDYGCisGAQQBhFkK
# BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ
# KoZIhvcNAQELBQADggEBAISpvO5kUXZVxhUhVblBITckBI65SsOYEac/MEp7gnrO
# /HfwDXCLo97iYSuQWXcuf3w0ZzMiiJDOXi0szCiPzJLHN5cWwXfAS7HmuEJ6y5av
# 1Kp9oJKMEoagu0FgKHbq5dU3KIzqx0mD58WHxkvIkrc5n/0Cw1EkIofwBWZ0iqHa
# x1aPmqsM7GVIWVLdh4lwDqJl0FwLrRxxncRvk93O7TNtpFVDxdxlSQc1/pU9cNVO
# PV8TjDjvF9aqf5MEzp76IcLn34RB2XckW5QkVE7Nr/ecJeuJDTVPo/ygy6o0DNHJ
# ugAh0/5/v+RF8DZQloq8kXCmXZ25ZzrAE1YBtNoi3B8xggQNMIIECQIBATCBkzB8
# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N
# aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAe4F0wIwspqdpwABAAAB
# 7jANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE
# MC8GCSqGSIb3DQEJBDEiBCDVmliMvG4EXrWzJ52exmySDJPPJedxm8ewOdWF6kz7
# FjCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIE9QdxSVhfq+Vdf+DPs+5EIk
# Bz9oCS/OQflHkVRhfjAhMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB
# IDIwMTACEzMAAAHuBdMCMLKanacAAQAAAe4wIgQgl7UjrU+AA6vfu9FjVcDM09oX
# rnSEM+LXLYumSfjGVRMwDQYJKoZIhvcNAQELBQAEggIAALbyhNqoBrJ7Dly7vj85
# PpdkCoHCwoMS2BuR8Tq+yDQ7moAA0yArLadDcjOVdPus3BSz+JDqfhzY1Zwh0J7v
# DR3n9O1lbErERrBW+pX0L9ZTh6L+UB5X0JnqHr4QnawF5JymHAtNfAsUFCuUCQZd
# lAuAmz1tRmUixuHBL/tzIpPX4+mNr2YZgkjBSIyHo7sc2hjnJ4gwjkqya1MLmhex
# DpMwo8j0O8OsjwNPk6tmTiQLKQ/FDeTjFy0DZDDnoOPNd8/ITmmyaL28FBehG7yO
# 1SNIA3SsQW0eXuJi5Ftra36B5YTF5AwdWPglixPMLZDRrQSDvAZbtJ17lgamK3zn
# WSWptLMv9WkC8F6iCSDxJyQMTPCmlymx1UG+5RpfM9T7AAxN8kD1C+R4UWnAHCG+
# KTyQGdB/X4BfT6BXMEoi/xV2oknBzZkLtZ2OGXIY/AzjTg5UdcBVNvKGMsdu5TQA
# lwcPbWkMRrLE0/+OY2mI46mGycbTRFSYId4xLVGlV6zRxt0VyFNmpyykzLSILGKM
# MsPoe1V27f6Ff1RzrOE9QSlexLxGF9jj8r2moEHAdLorNBXBBBKeVqeB1UaondRn
# 7Gy+qimE4xQG8o3Xk3jn/YutUP+yOh2aFjZUjrLxvMIyaBJLQecehF8MXyX2Klz1
# MEGhhlLNVjB7SsVJlTx9/WY=
# SIG # End signature block