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 Show-BootstrapStatus {
    param(
        [System.Object] $Response
    )

        Log-Info -Message "$($Response.Name) : $($Response.Status)" -ConsoleOut
        foreach($response in $Response.DetailedResponse)
        {
            Log-Info -Message " $($response.Name) : $($response.Status)" -ConsoleOut
        }
}



############################################################################################################
# Change the proxy settings for the environment
############################################################################################################
function Is-IpRange {
    param (
        [string]$IP
    )
    $ipRangeRegex = "^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?){1,3}(\.\*){1,3}$"
    return [regex]::IsMatch($IP, $ipRangeRegex)
}

function Convert-ToCidrAddress {
    param (
        [string]$ipRange
    )

    if (-not (Is-IpRange $ipRange)) {
        throw "IP range format exception: $ipRange"
    }
    
    $ipParts = $ipRange.Split('.')
    $cidrAddress = ""
    $maskLength = 0

    for ($i = 0; $i -lt 4; $i++) {
        if ($i -ge $ipParts.Length -or $ipParts[$i] -eq "*") {
            $cidrAddress += "0"
        } elseif ($ipParts[$i] -match '^\d+$') {
            $cidrAddress += $ipParts[$i]
            $maskLength++
        } else {
            throw "IP range format exception: $ipRange"
        }

        if ($i -lt 3) {
            $cidrAddress += "."
        }
    }

    $cidrAddress = "$cidrAddress/$($maskLength * 8)"
    return $cidrAddress
}

function Convert-BypassStringForEnv {
    param (
        [string] $bypassString
    )

    $formattedBypassList = ""
    $svcEntry = ".svc"

    $bypassList = $bypassString.Split(';')

    foreach ($entry in $bypassList) {
        $entry = $entry.Trim()
        if ($entry.StartsWith("*")) {
            $formattedBypassList += $entry.Substring(1) 
        } elseif (Is-IpRange $entry) {
            $formattedBypassList += (Convert-ToCidrAddress -ipRange $entry)
        } else {
            $formattedBypassList += $entry 
        }
         $formattedBypassList += ','
    }

    if ($formattedBypassList -notcontains $svcEntry) {
        $formattedBypassList += $svcEntry
    }

    $formattedBypassList = $formattedBypassList.Trim(',')

    Log-Info -Message "Formatted list: $($formattedBypassList)" -ConsoleOut
    return [string] $formattedBypassList
}

function Configure-EnvironmentProxy {
    param(
        [string] $NetworkProxy,
        [string] $Bypass
    )

    $urlRegex = "http://([^:]+):(\d+)"
    if (-not ($NetworkProxy -match $urlRegex)) {
        throw "Invalid proxy URL. Please provide a valid URL in the format http://<proxy>:<port>"
    }

    if ([string]::IsNullOrEmpty($Bypass))
    {
        throw "Bypass list cannot be empty. Please provide a valid bypass list in the format URL1;URL2;URL3 etc. The list needs to be separated by semicolon"
    }

    if ($Bypass -like "*,*") {
        throw "Invalid bypass list. Please provide a valid bypass list in the format URL1;URL2;URL3 etc. The list needs to be separated by semicolon"
    }

    if (-not (Get-Module -ListAvailable -Name WinInetProxy)) {
        Install-Module -Name WinInetProxy -Force -AllowClobber -Scope AllUsers -ErrorAction SilentlyContinue
    }

    Set-WinInetProxy -ProxySettingsPerUser 0 -ProxyServer "http=$($NetworkProxy);https=$($NetworkProxy)" -ProxyBypass $Bypass
    Set-winhttpproxy -proxyserver "http=$($NetworkProxy);https=$($NetworkProxy)" -BypassList $Bypass
    
    [Environment]::SetEnvironmentVariable("HTTP_PROXY", $NetworkProxy, "Machine")
    [Environment]::SetEnvironmentVariable("HTTPS_PROXY", $NetworkProxy, "Machine")

    if (-not [string]::IsNullOrEmpty($Bypass))
    {
        [Environment]::SetEnvironmentVariable("NO_PROXY", (Convert-BypassStringForEnv -bypassString $Bypass), "Machine")
    }

    Log-Info -Message "Successfully configured the host to route traffic via Customer's Network Proxy" -ConsoleOut

    # Read the environment variables to verify the changes
    $env:HTTP_PROXY = [System.Environment]::GetEnvironmentVariable("HTTP_PROXY", "Machine")
    $env:HTTPS_PROXY = [System.Environment]::GetEnvironmentVariable("HTTPS_PROXY", "Machine")
    $env:NO_PROXY = [System.Environment]::GetEnvironmentVariable("NO_PROXY", "Machine")
    
    $netshoutput = netsh winhttp show proxy | Out-String
    Log-Info -Message "WinHTTP: $($netshoutput)" -ConsoleOut
    Log-Info -Message "HTTP_PROXY: $($env:HTTP_PROXY), HTTPS_PROXY: $($env:HTTPS_PROXY), NO_PROXY: $($env:NO_PROXY) " -ConsoleOut
}

############################################################################################################

############################################################################################################
# Configure to use Arc Proxy as proxy for all https host traffic and ARB & AKS traffic
############################################################################################################

function Get-ConfiguredBypassList {
    $proxySetting = netsh winhttp show proxy
    $proxyBypassString = ""
    foreach ($setting in $proxySetting) {
            if ($setting -like "*Bypass*"){
            $proxyBypassString = $setting
            break;
            }
            
    }
    $bypassList = $proxyBypassString -split ":", 2 | Select-Object -Last 1
    # Trim any leading or trailing whitespace
    $bypassList = $bypassList.Trim()
    return [string]$bypassList
}

function Configure-PortForwardToArcProxy {
param(
    [string] $ArcListenerPort
)
        $port_proxy_forward_port = "59031"

        try {
            netsh interface portproxy add v4tov4 listenaddress=* listenport=59031 connectaddress=localhost connectport=$ArcListenerPort
            New-NetFirewallRule -DisplayName "Arc Proxy Listener" -Direction Inbound -Profile Any -Action Allow -LocalPort $port_proxy_forward_port -Protocol TCP 
            $port_forward_output = netsh interface portproxy show all | Out-String
            Log-Info -Message "Port forward rule set to `n $($port_forward_output)" -ConsoleOut
           
            $firewallOutput = (Get-NetFirewallRule -DisplayName "Arc Proxy Listener" -ErrorAction Ignore) | Out-String
            Log-Info -Message "Firewall rule set to `n $($firewallOutput)" -ConsoleOut

            # This is a temporary workaround and will be removed once the crash root cause is identified.
            # Till Arc proxy is able to support configuring the listener, we are relying on port forwarding rule to
            # forward traffic to Arc proxy. We have seen crashes both in Arc proxy and iphlpsvc, following modifications
            # in recovery options ensure that these services are restarted more frequently to avoid any downtime.
            Log-Info -Message "Change the recovery options for iphlpsvc to reset failure counter every few mins" -ConsoleOut
            sc.exe failure iphlpsvc reset=310 actions= restart/30000/restart/30000/restart/30000/restart/30000/restart/30000/restart/30000/restart/30000/restart/30000/restart/30000/restart/30000/
            Log-Info "$(sc.exe qfailure iphlpsvc | Out-String)" -ConsoleOut
        }
        catch {
            Remove-NetFirewallRule -DisplayName "Arc Proxy Listener"  -ErrorAction Ignore
            Log-Info -Message "Failed to configure port forwarding to Arc Proxy" -ConsoleOut
            throw "Failed to configure port forwarding to Arc Proxy"
        }
}


function Configure-VMOutboundToUseArcProxy {
param(
    [string] $ArcListenerPort
)
        # Remove the firewall rule if it exists
        Remove-NetFirewallRule -DisplayName "Arc Proxy Listener" -ErrorAction Ignore

        $supportedVersion = [System.Version]::Parse("1.45")
        $connectedMachineAgentVersion = & "$env:ProgramW6432\AzureConnectedMachineAgent\azcmagent.exe" --version 
        $arcproxyListenerConfigSupported = [regex]::Match($connectedMachineAgentVersion, '\d+(\.\d+)+').Value -ge $supportedVersion
        if ($arcproxyListenerConfigSupported)
        {
            # Configure the Arc proxy's listener and manage the firewall rule
            & "$env:ProgramW6432\AzureConnectedMachineAgent\azcmagent.exe" config set arcproxy.listenaddress 0.0.0.0
          
            $listenerIPConfigured = & "$env:ProgramW6432\AzureConnectedMachineAgent\azcmagent.exe" config get arcproxy.listenaddress
            if ($listenerIPConfigured -eq "0.0.0.0")
            {
                Log-Info -Message "Successfully configured Arc Proxy's listener" -ConsoleOut
                New-NetFirewallRule -DisplayName "Arc Proxy Listener" -Direction Inbound -Profile Any -Action Allow -LocalPort $ArcListenerPort -Protocol TCP
                $firewallOutput = (Get-NetFirewallRule -DisplayName "Arc Proxy Listener" -ErrorAction Ignore) | Out-String
                Log-Info -Message "Firewall rule set to `n $($firewallOutput)" -ConsoleOut
            }
            else
            {
                Log-Info -Message "Failed to configure Arc Proxy's listener" -ConsoleOut
                throw "Failed to configure Arc Proxy's listener"
            }
        }
        else
        {
            Log-Info -Message "Arc Proxy version does not support configuring the listener. Use port forwarding to route traffic via Arc Proxy" -ConsoleOut
            Configure-PortForwardToArcProxy -ArcListenerPort $ArcListenerPort
        }
}

function Configure-HostOutboundToUseArcProxy {
param(
    [string] $ArcURL,
    [string] $NetworkProxy,
    [string] $ProxyBypass
)

    $requiredBypasses = @("localhost","127.0.0.1")

    ## TBD - Might not need following URLs since we are configuring separate proxy for HTTP requests:
    # "*.tlu.dl.delivery.mp.microsoft.com", "ocsp.digicert.com", "crl3.digicert.com", "ctldl.windowsupdate.com"
    # Once we have Arc proxy that supports HTTP requests, we will not need to worry about separate HTTP setting

    # For environments with Proxy:
    # Read the bypass list first from parameter passed in. If that is null, read the system configured setting (to support backward compatibility).
    # If both of them are null, throw an error
    # For environment without Proxy: Ignore the Proxybypass and let it be empty
    $proxyServerNoProxy = $ProxyBypass
    if([string]::IsNullOrEmpty($proxyServerNoProxy) -and -not [string]::IsNullOrEmpty($NetworkProxy))
    {
        $proxyServerNoProxy = Get-ConfiguredBypassList
        if ([string]::IsNullOrEmpty($proxyServerNoProxy))
        {
            throw "Invalid bypass list.Please provide a valid bypass list in the format URL1;URL2;URL3 etc. The list needs to be separated by semicolon"
        }
    }

    # convert the comma-separated string into an array to make search easier below
    $bypasses = $proxyServerNoProxy.Split(';')
    foreach ($bypass in $requiredBypasses)
    {
        if ($bypasses -notcontains $bypass)
        {
            Trace-Execution "Adding $bypass to bypass list"
            $proxyServerNoProxy += ";$bypass"
        }
    }

    Log-Info -Message "Configuring bypass list as: $proxyServerNoProxy" -ConsoleOut
    $proxyServerNoProxy = $proxyServerNoProxy.Trim().Trim(";")

    # Set http proxy to customer's proxy as we don't want to route http traffic via Arc proxy &
    # Set https proxy to Arc proxy to route all https traffic via Arc proxy + Arc Gateway
    if (-not (Get-Module -ListAvailable -Name WinInetProxy)) {
        Install-Module -Name WinInetProxy -Force -AllowClobber -Scope AllUsers -ErrorAction SilentlyContinue
    }

    Set-WinInetProxy -ProxySettingsPerUser 0 -ProxyServer "http=$($NetworkProxy);https=$($ArcURL)" -ProxyBypass $proxyServerNoProxy
    Set-winhttpproxy -proxyserver "http=$($NetworkProxy);https=$($ArcURL)" -BypassList $proxyServerNoProxy
    
    # Similar to above: HTTP -> Customer's proxy & HTTPS -> Arc proxy
    # Do not set the environment variables to point to Arc proxy if no proxy is passed in.
    if (-not [string]::IsNullOrEmpty($NetworkProxy)) {
        # Setting this when no proxy was explicitly passed in causes ArcProxy to go in recursive connection to itself
        Log-Info -Message "Only set this when there was a network proxy passed in. " -ConsoleOut
        [Environment]::SetEnvironmentVariable("HTTP_PROXY", $NetworkProxy, "Machine")
        [Environment]::SetEnvironmentVariable("HTTPS_PROXY", $ArcURL, "Machine")

        # Workaround for the issue where ArcProxy's probe request (HTTP) ends up going to NetworkProxy and Arc proxy fails to start
        # when we configure Arc Proxy's listener to be on 0.0.0.0. This is a temporary workaround and can be removed once Arc Proxy brings in
        # the fix to handle this scenario.
        $env_no_proxy = (Convert-BypassStringForEnv -bypassString $proxyServerNoProxy) + ",0.0.0.0"
        [Environment]::SetEnvironmentVariable("NO_PROXY", $env_no_proxy, "Machine")

        # Restart the ArcProxy service to pick up the new environment variables
        Restart-Service ArcProxy
    }

    # Read the environment variables to verify the changes
    $env:HTTP_PROXY = [System.Environment]::GetEnvironmentVariable("HTTP_PROXY", "Machine")
    $env:HTTPS_PROXY = [System.Environment]::GetEnvironmentVariable("HTTPS_PROXY", "Machine")
    $env:NO_PROXY = [System.Environment]::GetEnvironmentVariable("NO_PROXY", "Machine")

    Log-Info -Message "Successfully configured the host to route traffic via the Arc Proxy" -ConsoleOut

    $netshoutput = netsh winhttp show proxy | Out-String
    Log-Info -Message "WinHTTP: $($netshoutput)" -ConsoleOut
    Log-Info -Message "HTTP_PROXY: $($env:HTTP_PROXY), HTTPS_PROXY: $($env:HTTPS_PROXY), NO_PROXY: $($env:NO_PROXY) " -ConsoleOut
}

function RouteAllTrafficViaArcProxy {
param(
    [string] $NetworkProxy,
    [string] $ProxyBypass
)
    $arcConnectionType =  & "$env:ProgramW6432\AzureConnectedMachineAgent\azcmagent.exe" config get connection.type
    if( $arcConnectionType -eq "gateway")
    {
        $urlRegex = "http://([^:]+):(\d+)"
        $arc_proxy_url = & "$env:ProgramW6432\AzureConnectedMachineAgent\azcmagent.exe" show "Using HTTPS Proxy"
        $arcproxy_listener_port = ""
        if ($arc_proxy_url -match $urlRegex) {
            $url = $matches[0]
            $arcproxy_listener_hostname = $matches[1]
            $arcproxy_listener_port = $matches[2]
            $arc_proxy_url = "http://$($arcproxy_listener_hostname):$($arcproxy_listener_port)"
            Log-Info -Message "Arc Proxy URL: $($arc_proxy_url), hostname:$($arcproxy_listener_hostname) Port: $($arcproxy_listener_port)" -ConsoleOut
        }
        
        Log-Info -Message "Configuring Host outbound Arc URL: $($arc_proxy_url), Customer proxy:$($NetworkProxy)" -ConsoleOut

        # Configure all host to route traffic via the Arc Proxy
        Configure-HostOutboundToUseArcProxy -ArcURL $arc_proxy_url -NetworkProxy $NetworkProxy -ProxyBypass $ProxyBypass
        Log-Info "Configuring port forwarding on $($arcproxy_listener_port)" -ConsoleOut

        Configure-VMOutboundToUseArcProxy -ArcListenerPort $arcproxy_listener_port

        # This is a temporary workaround and can be removed once Arc proxy brings in bug fix on race condition.
        # Also this change should not affect any functionality. It is just configuring the recovery options for ArcProxy to reset failure counter every few mins
        sc.exe failure ArcProxy reset=110 actions= restart/10000/restart/10000/restart/10000/restart/10000/restart/10000/restart/10000/restart/10000/restart/10000/restart/10000/restart/10000/

        Log-Info -Message "Change the recovery options for ArcProxy reset failure counter every few mins" -ConsoleOut
        Log-Info "$(sc.exe qfailure ArcProxy | Out-String)" -ConsoleOut
    }
}

function ValidateCloudFqdn() {
param(
    [string] $CloudFqdn
)
    try 
    {
        if ([string]::IsNullOrEmpty($CloudFqdn))
        {
            return $false
        }
        $Url = "armmanagement.$CloudFqdn"
        $response = Test-Connection -ComputerName $Url -Count 1 -ErrorAction Stop
        return $true
    }
    catch  
    {
        return $false
    }
}

############################################################################################################
 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, Azure.local. 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 ProxyBypass
         List of endpoints that bypass the 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")]
         [ValidateScript({
            if ($_ -match '^[a-zA-Z0-9_()\-\.]{1,89}[a-zA-Z0-9_()\-]$') {
                $true
            } else {
                throw "ResourceGroup is invalid. The requirements for resource group names are:
                       1.Between 1 and 90 characters long.
                       2.Alphanumeric characters, underscores, parentheses, hyphens and periods.
                       3.Cannot end with a period."

            }
         })]
         [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 , Azure.local
         [Parameter(ParameterSetName='SPN', Mandatory = $true, HelpMessage = "Azure Cloud type used for HCI ARC Integration. Valid values are : AzureCloud , AzureUSGovernment , AzureChinaCloud, Azure.local")]
         [Parameter(ParameterSetName='ARMToken', Mandatory = $true, HelpMessage = "Azure Cloud type used for HCI ARC integration. Valid values are : AzureCloud , AzureUSGovernment , AzureChinaCloud, Azure.local")]
        [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 = "Bypass proxy server.")]
         [Parameter(ParameterSetName='ARMToken', Mandatory=$false, HelpMessage = "Bypass proxy server.")]
         [string]
         $ProxyBypass,

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

         [Parameter(ParameterSetName='SPN', Mandatory=$false, HelpMessage = "Local FQDN for Azure.local")]
         [Parameter(ParameterSetName='ARMToken', Mandatory=$false, HelpMessage = "Local FQDN for Azure.local")]
         [string]
         $CloudFqdn,

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

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

     )

     $tempConfigPath = "$env:TEMP\bootstrap.json"
     $cmdletFailed = $false

     try
     {
        $script:ErrorActionPreference = 'Stop'
        $ProgressPreference = 'SilentlyContinue'
        Set-AzStackHciOutputPath -Path $OutputPath
        
        if ($Cloud -eq "Azure.local")
        {
            if (-not (ValidateCloudFqdn -CloudFqdn $CloudFqdn -ErrorAction SilentlyContinue))
            {
                Log-Info -Message "Invalid Configuration - A valid CloudFqdn value is required when the ‘Cloud’ parameter is set to ‘Azure.local’" -Type Error -ConsoleOut
                throw "Invalid Configuration - A valid CloudFqdn value is required when the ‘Cloud’ parameter is set to ‘Azure.local’"
            }
        }

        Log-Info -Message "Starting AzStackHci ArcIntegration Initialization" -ConsoleOut
        
        if (-not (Get-Command -Name Start-ArcBootstrap -ErrorAction SilentlyContinue))
        {
            # Configure environment proxy settings only if both Proxy and Bypass are provided as parameters
            # otherwise assume it is older path and customer already configured the proxy settings before calling this cmdlet
            if (-not [string]::IsNullOrEmpty($Proxy) -and (-not [string]::IsNullOrEmpty($ProxyBypass)))
            {
                Log-Info -Message "Configuring Environment proxy settings" -ConsoleOut
                Log-Info -Message "Warning!! All environment proxy settings will be overwritten with the parameters passed in" -ConsoleOut
                Configure-EnvironmentProxy -NetworkProxy $Proxy -Bypass $ProxyBypass
            }

            Log-Info -Message "Installing and Running Azure Stack HCI Environment Checker" -ConsoleOut
            [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072;

            $environmentValidatorResult = [ErrorDetail]::Success
            if (-not [string]::IsNullOrEmpty($ArcGatewayID))
            {
                $environmentValidatorResult = RunEnvironmentValidator -EnvironmentCheckerVersion $EnvironmentCheckerVersion -ArcGateway
            }
            else
            {
                $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
            
            if ($Cloud -eq "Azure.local")
            {
                $AltDownload = "https://artifacts.blob.$CloudFqdn/arc4server/AzureConnectedMachineAgent.msi"
                $AltHisEndpoint = "https://his.$CloudFqdn"
                $EndpointResourceManager = "https://armmanagement.$CloudFqdn"
            }
            else
            {
                $AltDownload = "https://download.microsoft.com/download/6/c/0/6c0775bc-9ed7-49af-9637-79653f783062/AzureConnectedMachineAgent.msi"
                $AltHisEndpoint = ""
            }

            #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 $AltDownload -AltHisEndpoint $AltHisEndpoint;
            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

            if ($PSCmdlet.ParameterSetName -eq "SPN")
            {
                Log-Info -Message "Connecting to Azure using SPN Credentials" -ConsoleOut
                Connect-AzAccount -ServicePrincipal -TenantId $TenantId -Credential $SpnCredential | out-null
            }
            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) {
                Log-Info -Message "Connecting to Azure ARC agent " -ConsoleOut

                $argList = @{
                    "--resource-group" = $ResourceGroup;
                    "--resource-name" = $machineName;
                    "--tenant-id" = $TenantID;
                    "--location" = $Region;
                    "--subscription-id" = $SubscriptionID;
                    "--cloud" = $Cloud;
                    "--correlation-id" = $CorrelationID;
                }

                if ($isGatewaySupported -and -not [string]::IsNullOrEmpty($ArcGatewayID)) 
                {
                    $argList["--gateway-id"] = $ArcGatewayID;
                }

                if ($Cloud -eq "Azure.local")
                {
                    $argList["--cloud"] = "AzureStackCloud";
                    $argList["--endpoint-resource-manager"] = $EndpointResourceManager;
                }

                if ($PSCmdlet.ParameterSetName -eq "SPN") 
                {
                    $argList["--service-principal-id"] = $SpnCredential.UserName;
                    $argList["--service-principal-secret"] = $SpnCredential.GetNetworkCredential().Password;
                }
                elseif ($PSCmdlet.ParameterSetName -eq "ARMToken")
                {
                    $argList["--access-token"] = $ArmAccessToken;
                }

                $argListAsArray = [System.Collections.ArrayList]@()
                foreach ($arg in $argList.GetEnumerator()) {
                    $argListAsArray.Add($arg.Name)
                    $argListAsArray.Add($arg.Value)
                }
                
                & "$env:ProgramW6432\AzureConnectedMachineAgent\azcmagent.exe" connect $argListAsArray;

                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
            }

            if(-Not $SkipExtensionInstall)
            {
                Log-Info -Message "Configure to route all traffic via Arc proxy, if gateway setting is enabled " -ConsoleOut

                RouteAllTrafficViaArcProxy -NetworkProxy  $(If ($Proxy) { $Proxy } else { "" }) -ProxyBypass $ProxyBypass

                Log-Info -Message "Installing AzureEdgeTelemetryAndDiagnostics Extension " -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

                if ($Cloud -ne "Azure.local") #EdgeRemoteSupport extension is not supported on Azure.local
                {
                    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 Extension installations Successfully triggered on the device." -ConsoleOut
            }

            Log-Info -Message "ARC Successfully enabled 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 (-not [string]::IsNullOrEmpty($Proxy))
            {
                Log-Info -Message "Constructing node config with proxy : $($Proxy) and bypass list: $($ProxyBypass)" -ConsoleOut

                $bypassList = @();
                if ([string]::IsNullOrEmpty($ProxyBypass))
                {
                    # if the bypass list is not provided, read the system configured setting
                    $ProxyBypass = Get-ConfiguredBypassList

                    # if the system configured bypass setting is empty too, throw an error
                    if ([string]::IsNullOrEmpty($ProxyBypass))
                    {
                        throw "Bypass list cannot be empty. Please provide a valid bypass list in the format URL1;URL2;URL3 etc. The list needs to be separated by semicolon"
                    }
                }

                # the bypass list should not contain any commas, it should only be separated by semicolon
                if ($ProxyBypass -like "*,*") {
                    throw "Invalid bypass list. Please provide a valid bypass list in the format URL1;URL2;URL3 etc. The list needs to be separated by semicolon"
                }

                $bypassList = $ProxyBypass.Split(";")
                $configHash["WebProxy"] = @{
                    "ConnectionUri" = $Proxy
                    "BypassList" = $bypassList
                }
            }

            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
                        "AzureLocalCloudFQDN" = $CloudFqdn
                    }
                }
            }
            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
                        "AzureLocalCloudFQDN" = $CloudFqdn
                    }
                }
            }

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

            $timeoutDuration = 1800 # 30 minutes

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

            $elapsed = 0

            try {
                Log-Info -Message "Triggering bootstrap on the device" -ConsoleOut
                $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 "" -ConsoleOut
                    Log-Info -Message "Waiting for bootstrap to complete, Status: $status" -ConsoleOut
                    Log-Info -Message "" -ConsoleOut

                    # Display the response to the console
                    foreach($response in $result.Response.DetailedResponse)
                    {
                        Show-BootstrapStatus -Response $response
                    }
                    Log-Info -Message "" -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
     {
        Log-Info -Message "Disconnect from Azure"
        Disconnect-AzAccount -ErrorAction SilentlyContinue | out-null
        $Script:ErrorActionPreference = 'SilentlyContinue'
        Remove-Item -Path $tempConfigPath -Force -ErrorAction SilentlyContinue | out-null
        Write-AzStackHciArcInstallerFooter -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")]
         [ValidateScript({
            if ($_ -match '^[a-zA-Z0-9_()\-\.]{1,89}[a-zA-Z0-9_()\-]$') {
                $true
            } else {
                throw "ResourceGroup is invalid. The requirements for resource group names are:
                       1.Between 1 and 90 characters long.
                       2.Alphanumeric characters, underscores, parentheses, hyphens and periods.
                       3.Cannot end with a period."

            }
         })]
         [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,
        [switch] $ArcGateway
    )
    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'
        $isGatewayParameterSupported = (Get-Command  -Name Invoke-AzStackHciConnectivityValidation).Parameters.ContainsKey('ARCGateway')
        $res = @()
        if ($isGatewayParameterSupported -and $ArcGateway)
        {
            Log-Info -Message "Running Environment Validation checks with ARC Gateway parameter, $($EnvironmentCheckerVersion)" -ConsoleOut
            $res = Invoke-AzStackHciConnectivityValidation -PassThru -ARCGateway
        }
        else
        {
            Log-Info -Message "Running Environment Validation checks without ARC Gateway parameter, $($EnvironmentCheckerVersion)" -ConsoleOut
            $res = Invoke-AzStackHciConnectivityValidation -PassThru
            $res = $res | Where-object Name -ne 'AzStackHci_Connectivity_Proxy_Settings_Consistency'
        }

        $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
# MIIoKgYJKoZIhvcNAQcCoIIoGzCCKBcCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDwzhwNz1YXQAUp
# bPt0y8qHXgvBc0FnDooaWcPVvMuWq6CCDXYwggX0MIID3KADAgECAhMzAAADrzBA
# 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
# /Xmfwb1tbWrJUnMTDXpQzTGCGgowghoGAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIJiWu+6N10qS06B+LsR7xqt0
# R8K3VL12aem1KecXT7wbMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAXYl5X19Xp3d8QS0nI7SiqJQ71KBzYZSQry78zHUZYwZ6z7pROvQtIYEL
# ep1xYc30wedq1I+GgIhSUf7UoBXXpUXSo46wikskVcSHT2mS8AYCOagdwm5xD2mI
# KExbU0tqXIUPQxBnhy/9AnYF+JRJVI3c6E4xzfl5EaIwCbWdqXoL67b1vigblnAT
# KfAcUO1zEdIxpEZ8Wih1C1ZPY2y+Vt/CkynPUjnEFfs4O5FGcgEcKBi1bH5qT0Ch
# o7nJYWxL9JMxaOpoHjWo6a9g5PPmozX/q3SEHUlPZsJhaz3BkNgYJyE5YUECu5kH
# sc4lpKcx05ZpGv1sQM4uL3BiqvlR96GCF5QwgheQBgorBgEEAYI3AwMBMYIXgDCC
# F3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq
# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCD3QHiZS0JcNoDnLMgrTi2aEOn0jB/JyEcPLHe/rf5z3wIGZwgPj2xU
# GBMyMDI0MTAxNDIyNDQ1OC4xMzlaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046RTAwMi0w
# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg
# ghHqMIIHIDCCBQigAwIBAgITMwAAAe4F0wIwspqdpwABAAAB7jANBgkqhkiG9w0B
# 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/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNN
# MIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn
# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkUwMDItMDVFMC1EOTQ3MSUwIwYDVQQD
# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCI
# o6bVNvflFxbUWCDQ3YYKy6O+k6CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6rfTvDAiGA8yMDI0MTAxNDE3MzAz
# NloYDzIwMjQxMDE1MTczMDM2WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDqt9O8
# AgEAMAcCAQACAgomMAcCAQACAhOnMAoCBQDquSU8AgEAMDYGCisGAQQBhFkKBAIx
# KDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZI
# hvcNAQELBQADggEBAHzINoZAJi9oeJZmlEX50LK7ujp55bgqRVOWswFX0cVXVIQo
# LQQYeRRLsaAUnDgsX7Zxher8+zF9ajbp2ynN7BFvngP19JaKHIJUII+mbBWXPphP
# n7fdTuSYhkrEGOOY/AwZmyeiiRswkVPxjEKKzrgp+lE5MD3vAn0oh/UbObqL4AiG
# V+T21dQT6oBYpD6Y86zqmINEnYWb7tZO4Pvc/LXVoA3GTIsCmdXd1dqUvYZGLKoL
# 0FA88mk87ztLODiAjoHknVvA1N8aRHEY4eZg3HwflrnZfGoGpNtYnN1ZnmuM80NQ
# eXd9uT7WEHcmF4Qh5Xsg1n+yh5RkOFgz638dsvAxggQNMIIECQIBATCBkzB8MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy
# b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAe4F0wIwspqdpwABAAAB7jAN
# BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G
# CSqGSIb3DQEJBDEiBCBQMwmno3m1JX3BuyHd149hXRnN/E7GIgi3IwBmfnlrUDCB
# +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIE9QdxSVhfq+Vdf+DPs+5EIkBz9o
# CS/OQflHkVRhfjAhMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw
# MTACEzMAAAHuBdMCMLKanacAAQAAAe4wIgQgycOH8EwPv/ttcv5fBaNKzDm57Dw1
# I7vGzFGGT2DUknEwDQYJKoZIhvcNAQELBQAEggIATMpPAlQhB6X3SziiKvCf5+Op
# Zj1OcjMxeeXbIzSTmacUT5wCuqhH5JvVh6FhgjqQp69lO4RlZ9RYRzxiJG9U7S7P
# 6+ODtK1C4Zu2+pv5gD0Emq1YYkG7qHMDilCAmlqrA2BIKdqNXFGTDFMa8Qj1jcp0
# 4sihY/69Nz0J43LJ5f2g/uPGjVdN1PUYOo9oH5dtLnZ9cqf27iTjnlBa9VpX5AJS
# u2JGCrzQwroCUH9qcEpUUGISqpuBXQLgcmzOef2uiX7XOGYh2I5lw7xSM1bdlWsF
# hjqQ+IDbvpWAJ43RPghVQSm64bofra/kD4W2Bd866M/X42cuFHosv5R1kmPITNQ2
# xncibKCx+fjyQz8eSygifDXpLJ+0H/oCsngZ3omhgi+NzF+44byt4CXoKCkXsE+T
# Py+i+if3aBVGx8nhzfwfvOLy1cvfEbDg0h/npkxfD82lKoEyilCssD6IcAVQXBXO
# WZftTwssN/vXZYEcsTKtRt43ShGN7mHxe9Rwn3GD7pgbtPjgV3hoszxpJa7t1+8Y
# he3o8loKnMoXVoFsz16cXabhM6eA9PRIZ2h81Nu/j7BmW81pGwK9FJ7/3XarvMqT
# bX686nHwizZwC/NihzsBBX3HaW7Gjbt5eVB2gav2Ml49wSlO0/FkY1jikwYgGBmY
# 0oKigetYx379NDw55Hk=
# SIG # End signature block