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 |