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  -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 OutputPath
         Directory path for log and report output.
    .PARAMETER Proxy
         Specify proxy server.
     #>

     [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 = "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(Mandatory = $false)]
         [Switch] $Force

         
     )
 
     try
     {

        $script:ErrorActionPreference = 'Stop'
        $ProgressPreference = 'SilentlyContinue'
        Set-AzStackHciOutputPath -Path $OutputPath
        Log-Info -Message "Installing and Running Azure Stack HCI Environment Checker" -ConsoleOut
        [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072;
        $environmentValidatorResult = RunEnvironmentValidator
        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/5/e/9/5e9081ed-2ee2-4b3a-afca-a8d81425bcce/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 ;
        }

        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

                & "$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
            }

            PerformRoleAssignmentsOnArcMSI $ResourceGroup
               
        }
        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) {
                & "$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
            }

            PerformRoleAssignmentsOnArcMSI $ResourceGroup
        }

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

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

        Log-Info -Message "Successfully triggered TelemetryAndDiagnostics 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 "EdgeRemoteSupport"  -ResourceGroupName $ResourceGroup -MachineName $env:COMPUTERNAME -Location $Region -Publisher "Microsoft.AzureStack.Observability" -ExtensionType "EdgeRemoteSupport" -NoWait | out-null

        Log-Info -Message "Successfully triggered EdgeRemoteSupport Extension installation " -ConsoleOut

        Log-Info -Message "Please verify that the extensions are successfully installed before continuing..." -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'
        Write-AzStackHciFooter -invocation $MyInvocation -Failed:$cmdletFailed -PassThru:$PassThru
     }
 }


 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
            RemoveRoleAssignmentsOnArcMSI $ResourceGroup
            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
            RemoveRoleAssignmentsOnArcMSI $ResourceGroup
            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'
     }
 }

 # Method to assign role assignments on ARC MSI
function PerformRoleAssignmentsOnArcMSI {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string] $ResourceGroup
    )
    try {
        $objectId = GetObjectIdFromArcMachine
        if ($null -ne $objectId) {
            $setEdgeDevicesRolesResult = AssignRoleToAnObjectUsingRetries -ObjectId $objectId -RoleName "Azure Stack HCI Device Management Role" -ResourceGroup $ResourceGroup -Verbose
            if ($setEdgeDevicesRolesResult -ne [ErrorDetail]::Success) {
                Log-Info -Message "Failed to assign Edge devices create role on the resource group" -ConsoleOut -Type Error
            }
            else{
                Log-Info -Message "Successfully assigned permission Azure Stack HCI Device Management Service Role to create or update Edge Devices on the resource group" -ConsoleOut
            }
            
            # Temporary assignment till the Observability role removes the extension installation call
            $arcManagerRoleStatus = AssignRoleToAnObjectUsingRetries -ObjectId $objectId -RoleName "Azure Connected Machine Resource Manager" -ResourceGroup $ResourceGroup
            if ($arcManagerRoleStatus -ne [ErrorDetail]::Success) {
                Log-Info -Message "Failed to assign the Azure Connected Machine Resource Manager role on the resource group" -ConsoleOut -Type Error
            }
            else{
                Log-Info -Message "Successfully assigned the Azure Connected Machine Resource Manager role on the resource group" -ConsoleOut
            }
            # Temporary assignment till the "Azure Stack HCI Device Management Role" gets the ResourceGroup Read permission
            $readerRoleStatus = AssignRoleToAnObjectUsingRetries -ObjectId $objectId -RoleName "Reader" -ResourceGroup $ResourceGroup
            if ($readerRoleStatus -ne [ErrorDetail]::Success) {
                Log-Info -Message "Failed to assign the reader role on the resource group" -ConsoleOut -Type Error
            }
            else{
                Log-Info -Message "Successfully assigned the reader Resource Nanager role on the resource group" -ConsoleOut
            }
        }    
    }
    catch {
        Log-Info -Message "" -ConsoleOut
        Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error
        Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error
    }
}

# Method to remove role assignments on ARC MSI
function RemoveRoleAssignmentsOnArcMSI {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string] $ResourceGroup
    )
    try {
        $objectId = GetObjectIdFromArcMachine
        if ($null -ne $objectId) {
            $edgeDevicesRoleAssignment = Get-AzRoleAssignment -ObjectId $objectId -RoleDefinitionName "Azure Stack HCI Device Management Service Role" -ResourceGroupName $ResourceGroup
            if ($null -ne $edgeDevicesRoleAssignment){
                Remove-AzRoleAssignment -ObjectId $objectId -RoleDefinitionName "Azure Stack HCI Device Management Service Role" -ResourceGroupName $ResourceGroup
                Log-Info -Message "Successfully removed permission Azure Stack HCI Device Management Service Role to create or update Edge Devices on the resource group" -ConsoleOut
            }
            else{
                Log-Info -Message "Already Azure Stack HCI Device Management Service Role role assignment is removed" -ConsoleOut
            }
        }
    }
    catch {
        Log-Info -Message "" -ConsoleOut
        Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error
        Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error
    }
}

# Set Role On An Object Id with retries
function AssignRoleToAnObjectUsingRetries {
    param(
        [String] $ObjectId,
        [String] $ResourceGroup,
        [string] $RoleName
    )
    $stopLoop = $false
    [int]$retryCount = "0"
    [int]$maxRetryCount = "5"

    Log-Info -Message $"Checking if $RoleName is assigned already for SPN with Object ID: $ObjectId" -ConsoleOut
    $arcSPNRbacRoles = Get-AzRoleAssignment -ObjectId $ObjectId -ResourceGroupName $ResourceGroup
    $alreadyFoundRole = $false
    $arcSPNRbacRoles | ForEach-Object {
        $roleFound = $_.RoleDefinitionName
        if ($roleFound -eq $RoleName)
        {
            $alreadyFoundRole=$true
            Log-Info -Message $"Already Found $RoleName Not Assigning" -ConsoleOut
        }
    }
    if( -not $alreadyFoundRole)
    {
        Log-Info -Message "Assigning $RoleName to Object : $ObjectId" -ConsoleOut
        do
        {
            try
            {
                New-AzRoleAssignment -ObjectId $ObjectId -ResourceGroupName $ResourceGroup -RoleDefinitionName $RoleName | Out-Null
                Log-Info -Message $"Sucessfully assigned $RoleName to Object Id $ObjectId" -ConsoleOut
                $stopLoop = $true
            }
            catch
            {
                # 'Conflict' can happen when either the RoleAssignment already exists or the limit for number of role assignments has been reached.
                if ($_.Exception.Response.StatusCode -eq 'Conflict')
                {
                    $roleAssignment  = Get-AzRoleAssignment -ObjectId $ObjectId -ResourceGroupName $ResourceGroup -RoleDefinitionName $RoleName
                    if ($null -ne $roleAssignment)
                    {
                        Log-Info -Message $"Sucessfully assigned $RoleName to Object Id $ObjectId" -ConsoleOut
                        return [ErrorDetail]::Success
                    }
                    Log-Info -Message $"Failed to assign roles to service principal with object Id $($ObjectId). ErrorMessage: " + $_.Exception.Message + " PositionalMessage: " + $_.InvocationInfo.PositionMessage -ConsoleOut -Type Error
                    return [ErrorDetail]::PermissionsMissing
                }
                if ($retryCount -ge $maxRetryCount)
                {
                    # Timed out.
                    Log-Info -Message $"Failed to assign roles to service principal with object Id $($ObjectId). ErrorMessage: " + $_.Exception.Message + " PositionalMessage: " + $_.InvocationInfo.PositionMessage -ConsoleOut -Type Error
                    return [ErrorDetail]::PermissionsMissing
                }
                Log-Info -Message $"Could not assign roles to service principal with Object Id $($ObjectId). Retrying in 10 seconds..." -ConsoleOut
                Start-Sleep -Seconds 10
                $retryCount = $retryCount + 1
            }
        }
        While(-Not $stopLoop)
    }
    return [ErrorDetail]::Success
}

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 {
    try {
        Install-Module -Name AzStackHci.EnvironmentChecker -Repository PSGallery -Force
        $res = Invoke-AzStackHciConnectivityValidation -PassThru
        $successfulTests = $res | Where-Object { $_.Status -eq "Succeeded"}
        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 -ne "Succeeded"}
            $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
# MIInvgYJKoZIhvcNAQcCoIInrzCCJ6sCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDE579jZ11UWMLY
# zwa6LDk+qkY7AII1Z1O6i6Bc5uGZgKCCDXYwggX0MIID3KADAgECAhMzAAADTrU8
# esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU
# p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1
# 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm
# WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa
# +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq
# jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk
# mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31
# TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2
# kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d
# hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM
# pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh
# JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX
# UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir
# IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8
# 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A
# Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H
# tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# 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
# /Xmfwb1tbWrJUnMTDXpQzTGCGZ4wghmaAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIFF0Or4mF4scj5MzRUpUYzHX
# FtzGop1eHRd4YFsvVAd9MEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAQOxlC/Ms758nH0c55X1AeTHhLMuWJcSCHcFwAxEw7GBetubJ6VVeEEbJ
# zQ3PtojUbOctV0h1o7/RPOAZqbfwNryyWS7hHwNLj0rO3fNzaKsfPjPvMz+oIKmK
# dKhHdgaA61nosPdSw38a9dNsMs777Yw2Hzonv5fb0ZYrXTzy+GBYPCPqajYBHN6K
# na78A48GKQTqKxfnGN3dk3VetUwjkOXUYCuKMex4dOlqxdijfzshR9BDmu8ySeEG
# 9Rc+ZDJmf0vjKr7lp31Sg0xHSpiyorkMfnY4r6CGW/khCPgWHu8WEjh1/o9gnQh6
# fkrCuOa8D3lWXHwQedqvkC5SZi6rwaGCFygwghckBgorBgEEAYI3AwMBMYIXFDCC
# FxAGCSqGSIb3DQEHAqCCFwEwghb9AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFYBgsq
# hkiG9w0BCRABBKCCAUcEggFDMIIBPwIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCBcv4JWlO7d64zzfJ6pmiBNFkAmvrknzBeP0R2DnOMuZwIGZWdR3z4U
# GBIyMDIzMTIwODEwNTU0Mi4xOVowBIACAfSggdikgdUwgdIxCzAJBgNVBAYTAlVT
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVs
# YW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046
# RDA4Mi00QkZELUVFQkExJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl
# cnZpY2WgghF4MIIHJzCCBQ+gAwIBAgITMwAAAdzB4IzCX1hejgABAAAB3DANBgkq
# hkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ
# MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
# MSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzEw
# MTIxOTA3MDZaFw0yNTAxMTAxOTA3MDZaMIHSMQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVy
# YXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOkQwODItNEJG
# RC1FRUJBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIC
# IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAi8izIDWyOD2RIonN6WtRYXlK
# GphYvzdqafdITknIhU9QLsXqpNwumGEdn2J1/bV/RFoatTwQfJ0Xw3E8xHYpU2IC
# 0IY8lryRXUIa+fdt4YHabaW2aolqcbvWYDLCuQoBNieLAos9AsnTQSRfDlNLB+Yl
# dt2BAsWUfJ8DkqD6lSwlfOq6aQi8SvQNc++m0AaqR0UsrCjgFOUSCe/N5N9e6TNf
# y9C1MAt9Um5NSBFTvOg/9EVa3dZqBqFnpSWgjQULxeUFANUNfkl4wSzHuOAkN0Sc
# rjhjyAe4RZEOr5Ib1ejQYg6OK5NYPm6/e+USYgDJH/utIW9wufACox2pzL+KpA8y
# UM5x3QBueI/yJrUFARSd9lPdTHIr2ssH9JGIo/IcOWDyhbBfKK/f5sYHp2Z0zrW6
# vqdS18N/nWU9wqErhWjzek4TX+eJaVWcQdBX00nn8NtRKpbZGpNRrY7Yq6+zJEYw
# SCMYkDXb9KqtGqW8TZ+I3lmZlW2pI9ZohqzHtrQYH591PD6B5GfoyjZLr79tkTBL
# /QgnmBwoaKc1t/JDXGu9Zc+1fMo5+OSHvmJG5ei6sZU9GqSbPlRjP5HnJswlaP6Z
# 9warPaFdXyJmcJkMGuudmK+cSsIyHkWV+Dzj3qlPSmGNRMfYYKEci8ThINKTaHBY
# /+4cH2ASzyn/097+a30CAwEAAaOCAUkwggFFMB0GA1UdDgQWBBToc9IF3Q58Rfe4
# 1ax2RKtpQZ7d2zAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNV
# HR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Ny
# bC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYI
# KwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAy
# MDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMI
# MA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEA2etvwTCvx5f8fWwq
# 3eufBMPHgCqAduQw1Cj6RQbAIg1dLfLUZRx2qwr9HWDpN/u03HWrQ2kqTUlO6lQl
# 8d0TEq2S6EcD7zaVPvIhKn9jvh2onTdEJPhD7yihBdMzPGJ7B8StUu3xZ595udxJ
# PSLrKkq/zukJiTEzbhtupsz9X4zlUGmkJSztH5wROLP/MQDUBtkv++Je0eavIDQI
# Z34+31z5p2xh+bup7lQydLR/9gmYQQyQSoZcLPIsr52H5SwWLR3iWR1wT5mrkk2M
# gd6xfXDO0ZUC29fQNgNl03ZZnWST6E4xuVRX8vyfVhbOE//ldCdiXTcB9cSuf7UR
# q3KWJ/N3cKEnXG4YbvphtaCJFecO8KLAOq9Ql69VFjWrLjLi+VUppKG1t1+A/IZ5
# 4n9hxIE405zQM1NZuMxsvnSp4gQLSUdKkvatFg1W7eGwfMbyfm7kJBqM/DH0/Omx
# kh4VM0fJUXqS6MjhWj0287/MXw63jggyPgztRf1lrhDAZ/kHvXHns6NpfneDFPi/
# Oge8QFcX2oKYdGBcEttGiYl8OfrRqXO/t2kJVAi5DTrafIhkqexfHO4oVvRONdbD
# o4WkbVuyNek6jkMweTKyuJvEeivhjPl1mNXIcA3IqjRtKsCVV6KFxobkXvhJlPwW
# 3IcBboiAtznD/cP5HWhsOEpnbVYwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZ
# AAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVa
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEF
# AAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1
# V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9
# alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmv
# Haus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928
# jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3t
# pK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEe
# HT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26o
# ElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4C
# vEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ug
# poMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXps
# xREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0C
# AwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYE
# FCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtT
# NRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNo
# dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5o
# dG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBD
# AEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZW
# y4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5t
# aWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAt
# MDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0y
# My5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pc
# FLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpT
# Td2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0j
# VOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3
# +SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmR
# sqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSw
# ethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5b
# RAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmx
# aQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsX
# HRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0
# W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0
# HVUzWLOhcGbyoYIC1DCCAj0CAQEwggEAoYHYpIHVMIHSMQswCQYDVQQGEwJVUzET
# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
# TWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFu
# ZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOkQw
# ODItNEJGRC1FRUJBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2
# aWNloiMKAQEwBwYFKw4DAhoDFQAcOf9zP7fJGQhQIl9Jsvd2OdASpqCBgzCBgKR+
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUAAgUA
# 6R0EtTAiGA8yMDIzMTIwODEwNTc1N1oYDzIwMjMxMjA5MTA1NzU3WjB0MDoGCisG
# AQQBhFkKBAExLDAqMAoCBQDpHQS1AgEAMAcCAQACAgJaMAcCAQACAhFwMAoCBQDp
# HlY1AgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMH
# oSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAHEFQNSfAeqETSQu0NtrC
# eAZvqZEGLpWQ/nRO1rYJk11KM87gIq3l23wbHnG+5lc9jYTLisIjO2+SS4DPXDpq
# bmNBF1ZZlKYAfgz6ACwajYFrP7BzUboc/Sm14/aj6Q47HeBvaafoATWb0lh10jFJ
# 2MAY4xYoavNGyrslNM2RlrcxggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzET
# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
# TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T
# dGFtcCBQQ0EgMjAxMAITMwAAAdzB4IzCX1hejgABAAAB3DANBglghkgBZQMEAgEF
# AKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEi
# BCCfMNhE3nOQT7hcbSWjJq4yOX4Q/tRz9CYKJjUtm1wnTjCB+gYLKoZIhvcNAQkQ
# Ai8xgeowgecwgeQwgb0EIFOnF4pq2UQ/jLypnOO5YvQ67QirEQsOFfZMvKXEgg03
# MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
# BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEm
# MCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAHcweCM
# wl9YXo4AAQAAAdwwIgQgwNxsU3lhHW3aptfr4j7/Z4AKvS5IpImvnmXbspnzq+ow
# DQYJKoZIhvcNAQELBQAEggIAXm+u80J2+sb8OqRd3kL2aFQkUBAujewS4G16dMzh
# 9rSz96yDGrMexi2GjbKVwauwn/qRkhA1X5P1ex/zE1QSvJiEFqtfZBJYsxAXnyre
# n+XG4hD0y1sxiAdoWIV2f1OPIp6RzH9Bk5fdRW0r1VXL2UYptq05Cil02FD4VxUd
# k7glUFrf5B1pyE/yMT4YGQbGFhsRO2zNxSQL5+Y1m76NSEWaO2afrHr90XW28WSI
# PjIkBxVn+zYP4k+FL0KsVG9ERRV150OLdVoyWvaKGtmaI1vEPKbVEMOoe4bk574p
# PYQNZPJEcGGO34g2uoeGNSu+vryamSKkaHMU9eW+86Aw2Y3Isp+a//Pjhc+Uxqfg
# 8JorGDU050citvdWUCvXXCNl6abCtQWyc51oJ/0UswJkF8wQ798mr2ZGRV9bazeS
# AeZcDIo05MZX/dUxdch9gEDucy+HeNsuv0UZarKCl2NW+jOC7so05H7Utgtg0AXQ
# kMbm6Iz6bA4RGeM9ClHXkC77ZqIJmxtO6zmMTlAkJeABZQSHPbNQmpBmF0Tpthk6
# w0gxcVAX6XvRUGmEP/oKL4lMpSrNcHHBUDZ++0+wbCB1Q4DVPD/bpCQIGqlcrFvG
# PXNYp0Kllun9r/BGmsDsxR99zxFlbNY8PrEH4UMX8x16VfUOfcpjopVax3ssag8I
# nrM=
# SIG # End signature block