Obs/bin/ObsDep/content/Powershell/ObservabilityHelpers.psm1

<###################################################
 # #
 # Copyright (c) Microsoft. All rights reserved. #
 # #
 ##################################################>


# Import Observability EventSource
$observabilityNugetPath = "$PSScriptRoot\..\.."
Add-Type -Path "$observabilityNugetPath\lib\net472\Microsoft.AzureStack.Observability.ObservabilityDeployment.dll"
Import-Module "$observabilityNugetPath\content\Powershell\ObservabilityConstants.psm1"


# Create and setup new Observability volume. Returns tuple with computer name and $true on success, or error information $_ on failure.
function Add-ObservabilityVolumeWithRetry
{
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')]
    param (
        # Fully qualified path to the VHD file that should be created.
        # An exception is thwrown if that VHD file already exist. This approach is chosen
        # to prevent issues, should the VHD file be still mounted.
        [string]
        [Parameter(Mandatory=$true)] 
        $Path, 

        # Folder access path for mounted drive.
        [string] 
        [Parameter(Mandatory=$true)] 
        $AccessPath,
        
        # Volume name for the partition that is created within the VHD
        [string] 
        [Parameter(Mandatory=$true)] 
        $VolumeLabel,

        # Maximum size in bytes for the VHD file.
        [int64] 
        [Parameter(Mandatory=$true)] 
        $Size,

        # Optional. Specifies that the VHD should be static.
        [switch]
        [Parameter(Mandatory=$false)]
        $StaticSize,

        # Optional. Number of times to retry operation before failing.
        [int]
        [Parameter(Mandatory=$false)]
        $Retries = 5,

        # Optional. Seconds to sleep before retry
        [int]
        [Parameter(Mandatory=$false)]
        $RetrySleepTimeInSeconds = 10
    )

    if(Test-Path $Path)
    { 
        if((Get-DiskImage $Path).Attached)
        {
            Write-ObservabilityLog "VHD file is already mounted."
        }
        else
        {
            Write-ObservabilityLog "Existing unmounted VHD file found. Mounting $Path"
            Mount-VHD -Path $Path -NoDriveLetter -Verbose
        }
    }
    else
    {
        $retryAttempt = 0
        $success = $false
        $hostName = $env:COMPUTERNAME
        while(-not($success) -and ($retryAttempt -lt $Retries))
        {
            $retryAttempt = $retryAttempt + 1
            try
            {
                Write-ObservabilityLog "Trying to create Observability Volume. Attempt $retryAttempt of $Retries"
                if($StaticSize)
                {
                    New-ObservabilityVolume -Path $Path -AccessPath $AccessPath -VolumeLabel $VolumeLabel -Size $Size -StaticSize
                }
                else
                {
                    New-ObservabilityVolume -Path $Path -AccessPath $AccessPath -VolumeLabel $VolumeLabel -Size $Size
                }
                Write-ObservabilityVolumeCreationStopTelemetry `
                    -ComputerName $hostName `
                    -Message "Observability volume setup on host $hostName succeeded."
                $success = $true
            }
            catch
            {
                $exceptionMessage = $_.Exception.Message
                Write-ObservabilityVolumeCreationStopTelemetry `
                    -ComputerName $hostName `
                    -Message "Observability volume setup on host $hostName failed with exception $exceptionMessage." `
                    -ExceptionDetails $exceptionMessage
                if ($retryAttempt -lt $Retries)
                {
                    Write-ObservabilityErrorLog "Failure during VHD creation: '$exceptionMessage'. Retrying."
                }
                else
                {
                    # All retries failed.
                    throw $_
                }
                Start-Sleep -Seconds $RetrySleepTimeInSeconds
            }
        }
    }
}

# Function to create a data VHD file, which contains an empty, NTFS formatted partition. Returns $true on success, $false on failure
function New-ObservabilityVolume
{
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')]
    param (
        # Fully qualified path to the VHD file that should be created.
        # An exception is thwrown if that VHD file already exist. This approach is chosen
        # to prevent issues, should the VHD file be still mounted.
        [string]
        [Parameter(Mandatory=$true)] 
        $Path, 
        
        # Folder access path for mounted drive.
        [string] 
        [Parameter(Mandatory=$true)] 
        $AccessPath,

        # Volume name for the partition that is created within the VHD.
        [string] 
        [Parameter(Mandatory=$true)] 
        $VolumeLabel,

        # Maximum size in bytes for the VHD file.
        [int64] 
        [Parameter(Mandatory=$true)] 
        $Size,

        # Optional. Specifies that the VHD should be static.
        [switch]
        [Parameter(Mandatory=$false)]
        $StaticSize
    )

    Set-StrictMode -Version Latest
    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
    Import-Module Hyper-V
    $disk = $null

    try 
    {
        Write-ObservabilityLog ("Creating new VHD $Path with the following properties: Max size: $Size bytes, Static: $StaticSize.")
        if ($StaticSize)
        {
            New-VHD -Path $Path -SizeBytes $Size -Fixed -Verbose | Out-Null
        }
        else
        {
            New-VHD -Path $Path -SizeBytes $Size -Dynamic -Verbose | Out-Null
        }
    }
    catch
    {
        Remove-ObservabilityVolume -Path $Path -AccessPath $AccessPath
        throw ("Error creating the data VHD file at {0}. Error: {1}" -f $Path, $_)
    }

    try
    {
        Write-ObservabilityLog ("Mounting a data VHD {0}" -f $Path)
        $disk = Mount-VHD -Path $Path -Passthru -NoDriveLetter -Verbose

        Write-ObservabilityLog "Initializing mounted VHD for disk number $($disk.Number)"
        $null = Initialize-Disk -Number $disk.Number -Verbose

        Write-ObservabilityLog "Creating new partition. for disk number $($disk.Number)"
        $part = New-Partition -DiskNumber $disk.Number -UseMaximumSize -Verbose
            
        Write-ObservabilityLog "Formatting volume."
        $null = Format-Volume -Partition $part -FileSystem NTFS -NewFileSystemLabel $VolumeLabel -Force -Confirm:$false -Verbose

        Write-ObservabilityLog ("Setting attribute '{0}' to '{1}'." -f "NoDefaultDriveLetter", "true")
        Set-Partition $part.DiskNumber $part.PartitionNumber -NoDefaultDriveLetter $true -Verbose

        $diskString = Out-String -InputObject $disk
        Write-ObservabilityLog $diskString
    }
    catch
    {
        Remove-ObservabilityVolume -Path $Path -AccessPath $AccessPath
        throw New-Object -TypeName System.ApplicationException -ArgumentList ($("Mounting VHD and formatting the partition failed. Error: {0}" -f $_.Exception.Message), $_.Exception)
    }
}

# Dismount and Delete vhdx file at given path
function Remove-ObservabilityVolume
{
    param(
        [string]
        [Parameter(Mandatory=$true)] 
        $Path,

        [string]
        [Parameter(Mandatory=$true)] 
        $AccessPath
    )
    if(Test-Path $Path)
    {
        try
        {
            if((Get-DiskImage $Path).Attached)
            {
                Dismount-VHD -Path $Path -Verbose
            }
        }
        catch {}
        $null = Remove-Item -Path $Path -Force
    }
    if(Test-Path $AccessPath)
    {
        $null = Remove-Item -Path $AccessPath -Force
    }
}

# Tests if path is an empty directory(including hidden files) Note: Will return false if path is not a directory
function Test-DirectoryIsEmpty
{
    param(
        [string]
        [Parameter(Mandatory=$true)] 
        $Path
    )
    return (Get-ChildItem $Path -Force).Count -eq 0
}

# Create and set quotas for each subfolder of the Observability volume. Returns tuple with computer name and $true on success, or error information $_ on failure.
function New-VolumeFoldersAndPrunerWithRetry
{
    param (
        # Folder access path for mounted drive.
        [string]
        [parameter(Mandatory=$true)] 
        $AccessPath, 

        # For folders with enforced quota, when the folder is this percent full cleanup will be initiated.
        [int]
        [Parameter(Mandatory=$true)]
        $CleanupThresholdPercent,

        # For folders with enforced quota, then cleanup occurs files will be pruned until this fullness percentage is reached.
        [int]
        [Parameter(Mandatory=$true)]
        $FreeSpaceThresholdPercent,

        # Frequency for which each folder pruning scheduled task executes
        [int]
        [Parameter(Mandatory=$true)]
        $PurgeFolderFrequencyInMinutes,

        # Name of subfolder config file to read
        [string]
        [Parameter(Mandatory=$true)] 
        $SubFolderConfigFileName,
        
        # Optional. Number of times to retry operation before failing.
        [int]
        [Parameter(Mandatory=$false)]
        $Retries = 5,

        # Optional. Seconds to sleep before retry
        [int]
        [Parameter(Mandatory=$false)]
        $RetrySleepTimeInSeconds = 10
    )
    $retryAttempt = 0
    $success = $false
    while(-not($success) -and ($retryAttempt -lt $Retries))
    {
        $retryAttempt = $retryAttempt + 1
        try
        {
            Write-ObservabilityLog "Trying to setup Observability Folder quotas. Attempt $retryAttempt of $Retries"
            New-VolumeFoldersAndPruner `
                -AccessPath $AccessPath `
                -CleanupThresholdPercent $CleanupThresholdPercent `
                -FreeSpaceThresholdPercent $FreeSpaceThresholdPercent `
                -PurgeFolderFrequencyInMinutes $PurgeFolderFrequencyInMinutes `
                -SubFolderConfigFileName $SubFolderConfigFileName
            Write-ObservabilityLog "Observability volume folder and pruner setup on host $env:COMPUTERNAME succeeded."
            $success = $true
        }
        catch
        {
            if ($retryAttempt -lt $Retries)
            {
                $exceptionMessage = $_.Exception.Message
                Write-ObservabilityErrorLog "Failure during quota setup: '$exceptionMessage'. Retrying."
            }
            else
            {
                # All retries failed.
                throw $_
            }
            Start-Sleep -Seconds $RetrySleepTimeInSeconds
        }
    }
}

# Create each subfolder of observability volume and set folder quota if specified.
function New-VolumeFoldersAndPruner
{
    param (
        # Folder access path for mounted drive.
        [string]
        [parameter(Mandatory=$true)] 
        $AccessPath, 

        # When a folder is this percent full cleanup will be initiated.
        [int]
        [Parameter(Mandatory=$true)]
        $CleanupThresholdPercent,

        # For folders with enforced quota, then cleanup occurs files will be pruned until this fullness percentage is reached.
        [int]
        [Parameter(Mandatory=$true)]
        $FreeSpaceThresholdPercent,

        # Frequency for which each folder cleanup scheduled task executes
        [int]
        [Parameter(Mandatory=$true)]
        $PurgeFolderFrequencyInMinutes,

        # Name of subfolder config file to read
        [string]
        [Parameter(Mandatory=$true)] 
        $SubFolderConfigFileName
    )

    $observabilityNugetPath = "$PSScriptRoot\..\.."
    $pruneObservabilityPath = "$observabilityNugetPath\content\Powershell\PruneObservabilityVolume.ps1"
    $subFolders = Get-ObservabilityFolders -SubFolderConfigFileName $SubFolderConfigFileName
    foreach ($subFolder in $subFolders)
    {
        $subFolderPath = Join-Path -Path $AccessPath -ChildPath $subFolder.Name
        if(-not (Test-Path $subFolderPath)){
            Write-ObservabilityLog ("Creating empty directory at $subFolderPath.")
            New-Item -Path $subFolderPath -ItemType "Directory" | Out-Null
        }
    }

    # Create Scheduled task to prune observability volume
    $taskName = "ObservabilityVolumePruner"
    if(Test-ScheduledTaskExists -TaskName $taskName)
    {
        Unregister-ScheduledTask -TaskName $taskName -Confirm:$false | Out-Null
    }

    Write-ObservabilityLog "Creating new scheduled task $taskName."
    $frequency = New-TimeSpan -Minutes $PurgeFolderFrequencyInMinutes
    $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount
    $trigger = New-ScheduledTaskTrigger -RepetitionInterval $frequency -Once -At "12:00 AM"
    $action = New-ScheduledTaskAction `
        -Execute "powershell.exe"  `
        -Argument "-Command $pruneObservabilityPath -AccessPath $AccessPath -CleanupThresholdPercent $CleanupThresholdPercent -FreeSpaceThresholdPercent $FreeSpaceThresholdPercent -SubFolderConfigFileName $SubFolderConfigFileName"
    $settings = New-ScheduledTaskSettingsSet 
    $task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -Principal $principal
    Register-ScheduledTask -TaskName $taskName -TaskPath "Microsoft\AzureStack\Observability" -InputObject $task
    Write-ObservabilityLog "Creating new scheduled task $taskName succeeded."

    Add-VolumeFolderQuotaStatisticsScheduledTask -AccessPath $AccessPath -SubFolderConfigFileName $SubFolderConfigFileName
}

function Set-FolderQuotas
{
    param (
        # Folder access path for mounted drive.
        [string]
        [parameter(Mandatory=$true)] 
        $AccessPath, 

        # Name of subfolder config file to read
        [string]
        [Parameter(Mandatory=$true)] 
        $SubFolderConfigFileName
    )
    $subFolders = Get-ObservabilityFolders -SubFolderConfigFileName $SubFolderConfigFileName
    foreach($subFolder in $subFolders)
    {
        $subFolderPath = Join-Path -Path $AccessPath -ChildPath $subFolder.Name
        if(Test-Path $subFolderPath){
            if(Test-PathHasQuota -Path $subFolderPath)
            {
                Remove-FsrmQuota -Path $subFolderPath -Confirm:$false -Verbose | Out-Null
            }
        }
        else
        {
            Write-ObservabilityLog ("Creating empty directory at $subFolderPath.")
            New-Item -Path $subFolderPath -ItemType "Directory" -Verbose | Out-Null
        }

        $subFolderSizeInMB = [int64]::Parse($subFolder.SizeInMB)
        $subFolderSizeInBytes = $subFolderSizeInMB * 1MB
        Write-ObservabilityLog "Creating new FSRM quota at path $subFolderPath of size $subFolderSizeInBytes bytes."
        New-FsrmQuota -Path $subFolderPath -Size $subFolderSizeInBytes -ErrorAction Stop -Verbose | Out-Null
        Write-ObservabilityLog "Creating FSRM quota at path $subFolderPath succeeded."
    }
}

# Returns true if a given task exists and false otherwise.
function Test-ScheduledTaskExists
{
    param(
        # Absolute path of folder to test
        [string]
        [Parameter(Mandatory=$true)] 
        $TaskName
    )
    try
    {
        if(Get-ScheduledTask -TaskName $TaskName -ErrorAction Stop)
        {
            return $true
        }
    }
    catch {}
    return $false
}

# Returns true if a given folder has a quota and false otherwise.
function Test-PathHasQuota
{
    param(
        # Absolute path of folder to test
        [string]
        [Parameter(Mandatory=$true)] 
        $Path
    )
    $quotas = Get-FsrmQuota -ErrorAction Stop -Verbose
    if ($quotas -is [System.Array])
    {
        foreach ($quota in $quotas)
        {
            if ($quota.Path -eq $Path)
            {
                return $true
            }
        }
        return $false
    }
    else
    {
        return $quotas.Path -eq $Path
    }
}

# Remove oldest files from folder until free space threshold is reached
function Remove-ObservabilityFolderOldestFiles
{
    param(
        # Folder access path for mounted drive.
        [string]
        [parameter(Mandatory=$true)] 
        $AccessPath, 

        # When a folder is this percent full cleanup will be initiated.
        [int]
        [Parameter(Mandatory=$true)]
        $CleanupThresholdPercent,

        # When cleanup occurs files will be pruned until this fullness percentage is reached.
        [int]
        [Parameter(Mandatory=$true)]
        $FreeSpaceThresholdPercent,

        # Name of subfolder config file to read
        [string]
        [Parameter(Mandatory=$true)] 
        $SubFolderConfigFileName
    )
    
    $subFolders = Get-ObservabilityFolders -SubFolderConfigFileName $SubFolderConfigFileName
    foreach($subFolder in $subFolders)
    {
        $subFolderPath = Join-Path -Path $AccessPath -ChildPath $subFolder.Name
        $subFolderSizeInMB = [int64]::Parse($subFolder.SizeInMB)
        $quotaSize = $subFolderSizeInMB * 1MB
        $files = Get-ChildItem -Path $subFolderPath -Recurse -Attributes "!Directory" | Sort-Object -Property "LastWriteTime"
        $directorySize = ($files | Measure-Object -Property "Length" -Sum).Sum
        $freeSpaceSize = $quotaSize * $FreeSpaceThresholdPercent / 100
        $cleanupThresholdSize = $quotaSize * $CleanupThresholdPercent / 100
        if($directorySize -gt $cleanupThresholdSize)
        {
            Write-ObservabilityLog "Cleanup Trigger Threshold Percent of $CleanupThresholdPercent reached."
            Write-ObservabilityLog "Cleanup of oldest files from $subFolderPath started."
            foreach ($file in $files)
            {
                $filePath = $file.FullName
                $fileSize = $file.Length
                $exceptionDetails = ""
                $success = $false
                try
                {
                    Write-ObservabilityLog "Removing file at $filePath of size $fileSize bytes."
                    Remove-Item -Path $filePath -Force
                    $directorySize = $directorySize - $fileSize
                    $success = $true
                }
                catch
                {
                    # Next activation of scheduled task will retry delete.
                    $exceptionDetails = $_
                    $success = $false
                }
                finally
                {
                    Write-ObservabilityVolumePrunerFileDeletionTelemetry `
                        -ComputerName $ENV:COMPUTERNAME `
                        -DirectoryPath $subFolderPath `
                        -DirectorySize $directorySize `
                        -FilePath $filePath `
                        -FileSize $fileSize `
                        -Success $success `
                        -ExceptionDetails $exceptionDetails
                }

                if ($directorySize -lt $freeSpaceSize)
                {
                    Write-ObservabilityLog "Acceptable volume size of $freeSpaceSize reached. Stopping cleanup."
                    break
                }
            }
            Write-ObservabilityLog "Cleanup of oldest files from $subFolderPath completed."
        }
    }
}

# Returns object array of the subfolders within the Observability volume.
# Folders should include name and folder size as percentage of volume size.
function Get-ObservabilityFolders
{
    param(
        # Name of subfolder config file to read
        [string]
        [Parameter(Mandatory=$true)] 
        $SubFolderConfigFileName
    )
    $observabilityNugetPath = "$PSScriptRoot\..\.."
    $observabilityFoldersFilePath = "$observabilityNugetPath\content\Configuration\$SubFolderConfigFileName"
    return Get-Content $observabilityFoldersFilePath | ConvertFrom-Json
}

# Creates a scheduled task to mount the Observability volume on startup, and to attempt to do so every hour.
function Add-MountVolumeScheduledTask
{
    param (
        # Fully qualified path to the VHD file that should be created.
        # An exception is thrown if that VHD file already exist. This approach is chosen
        # to prevent issues, should the VHD file be still mounted.
        [string]
        [Parameter(Mandatory=$true)] 
        $Path,

        # Credential under which the scheduled task should run. If not supplied, the system account is used.
        [PSCredential]
        [Parameter(Mandatory=$false)]
        $Credential
    )
    $taskName = "MountObservabilityVolume"
    if(Test-ScheduledTaskExists -TaskName $taskName)
    {
        Unregister-ScheduledTask -TaskName $taskName -Confirm:$false | Out-Null
    }

    # Setup scheduled task to mount VHD on startup
    $observabilityNugetPath = "$PSScriptRoot\..\.."
    $mountScriptPath = "$observabilityNugetPath\content\Powershell\MountObservabilityVolume.ps1"
    Write-ObservabilityLog "Creating new scheduled task $taskName."

    $frequency = New-TimeSpan -Minutes 30
    $hourlyTrigger = New-ScheduledTaskTrigger -RepetitionInterval $frequency -Once -At "12:00 AM"
    $startupTrigger= New-ScheduledTaskTrigger -AtStartup
    $triggers = @($startupTrigger, $hourlyTrigger)
    $action = New-ScheduledTaskAction `
        -Execute "powershell.exe"  `
        -Argument "-Command $mountScriptPath -Path $Path"
    $settings = New-ScheduledTaskSettingsSet 
    if($Credential)
    {
        $principal = New-ScheduledTaskPrincipal -LogonType S4U -UserId $Credential.UserName
        $task = New-ScheduledTask -Action $action -Trigger $triggers -Settings $settings -Principal $principal
        Register-ScheduledTask `
            -TaskName $taskName `
            -TaskPath "Microsoft\AzureStack\Observability" `
            -InputObject $task `
            -User $Credential.UserName `
            -Password $Credential.GetNetworkCredential().Password
    }
    else
    {
        $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount
        $task = New-ScheduledTask -Action $action -Trigger $triggers -Settings $settings -Principal $principal
        Register-ScheduledTask -TaskName $taskName -TaskPath "Microsoft\AzureStack\Observability" -InputObject $task
    }
    Write-ObservabilityLog "Creating new scheduled task $taskName succeeded."
}

function Add-VolumeAccessPath
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $AccessPath,
    
        [string]
        [Parameter(Mandatory=$true)]
        $VolumePath
    )
    Write-ObservabilityLog ("Adding access path at $AccessPath.")
    if(-not (Get-DiskImage $VolumePath).Attached)
    {
        Mount-VHD -Path $VolumePath -Verbose -ErrorAction Stop
    }
    $diskNumber = (Get-DiskImage $VolumePath).Number

    $part = Get-Partition -DiskNumber $diskNumber -PartitionNumber 2

    if (Test-Path $AccessPath)
    {
        if (Test-AccessPathExists -AccessPath $AccessPath -VolumeFilePath $VolumePath)
        {
            Write-ObservabilityLog ("Access path at $AccessPath already exists. Noop.")
        }
        elseif(-Not(Test-DirectoryIsEmpty -Path $AccessPath))
        {
            throw "$AccessPath is not an empty directory. It cannot be used as a folder mount point."
        }
    }
    else
    {
        Write-ObservabilityLog ("Creating empty directory at $AccessPath.")
        New-Item -Path $AccessPath -ItemType "Directory" -Verbose | Out-Null

        if (Test-AccessPathExists -AccessPath $AccessPath -VolumeFilePath $VolumePath)
        {
            Write-ObservabilityLog ("Access path at $AccessPath already exists. Removing access path $AccessPath from $VolumePath.")
            Remove-Partitionaccesspath -InputObject $part -AccessPath $AccessPath -Verbose | Out-null
        }

        Add-PartitionAccessPath -InputObject $part -AccessPath $AccessPath -Verbose | Out-Null
        Write-ObservabilityLog ("Successfully added access path at $AccessPath.")
    }
}

# For a given mounted volume, return true if it has the specified access path and false if not.
function Test-AccessPathExists
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $AccessPath,

        [string]
        [Parameter(Mandatory=$true)]
        $VolumeFilePath
    )
    $disk = Get-VHD $VolumeFilePath
    $part = Get-Partition -Disknumber $disk.Number -PartitionNumber 2
    $matchedAccessPath = $part.AccessPaths | Where-Object {$_ -clike "$AccessPath*"}
    if($matchedAccessPath)
    {
        return $true
    }
    else
    {
        return $false
    }
}

function Write-ObservabilityLog
{
    param(
        [string]
        [Parameter(Mandatory=$true)] 
        $Message
    )
    if (Get-Command Trace-Execution -ErrorAction SilentlyContinue)
    {
        Trace-Execution $Message
    }
    [Microsoft.AzureStack.Observability.ObservabilityEventSource]::Log.WriteInformational($Message)
}

function Write-ObservabilityErrorLog
{
    param(
        [string]
        [Parameter(Mandatory=$true)] 
        $Message
    )
    if (Get-Command Trace-Execution -ErrorAction SilentlyContinue)
    {
        Trace-Execution $Message
    }
    [Microsoft.AzureStack.Observability.ObservabilityEventSource]::Log.WriteError($Message)
}

function Write-ObservabilityVolumeCreationStartTelemetry
{
    param(
        [string]
        [Parameter(Mandatory=$true)] 
        $ComputerNames,

        [string]
        [Parameter(Mandatory=$true)] 
        $VolumeFilePath,

        [string]
        [Parameter(Mandatory=$true)] 
        $VolumeAccessPath,

        [string]
        [Parameter(Mandatory=$true)] 
        $VolumeLabel,

        [string]
        [Parameter(Mandatory=$true)] 
        $VolumeSize
    )

    [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ObservabilityVolumeCreationStart(
        $ComputerNames,
        $VolumeFilePath,
        $VolumeAccessPath,
        $VolumeLabel,
        $VolumeSize
    )
    $volumeInfo = @"
        Starting Observability Volume Setup
        ComputerNames: $ComputerNames
        Volume path: $volumeFilePath
        Volume folder mount access path: $VolumeAccessPath
        Volume label: $VolumeLabel
        Volume size in bytes: $VolumeSize
"@

    Write-ObservabilityLog -Message $volumeInfo
}

function Write-ObservabilityVolumeCreationStopTelemetry
{
    param(
        [string]
        [Parameter(Mandatory=$true)] 
        $ComputerName,

        [string]
        [Parameter(Mandatory=$true)] 
        $Message,

        [string]
        [Parameter(Mandatory=$false)] 
        $ExceptionDetails = ""
    )

    [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ObservabilityVolumeCreationStop($ComputerName, $Message, $ExceptionDetails)

    if($ExceptionDetails)
    {
        Write-ObservabilityErrorLog -Message $Message
    }
    else
    {
        Write-ObservabilityLog -Message $Message
    }
}

function Write-ObservabilityVolumeMountedByScheduledTaskTelemetry
{
    param(
        [string]
        [Parameter(Mandatory=$true)] 
        $ComputerName,

        [string]
        [Parameter(Mandatory=$true)] 
        $VolumeFilePath,

        [bool]
        [Parameter(Mandatory=$true)]
        $Success,

        [Parameter(Mandatory=$false)]
        $ExceptionDetails = ""
    )

    [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ObservabilityVolumeMountedByScheduledTask(
        $ComputerName,
        $VolumeFilePath,
        $Success,
        $ExceptionDetails
    )
    $message = @"
        Observability Volume Mounted by Scheduled Task
        ComputerName: $ComputerName
        Volume file path: $VolumeFilePath
        Success: $Success
        Exception Details (if applicable): $ExceptionDetails
"@

    if($ExceptionDetails)
    {
        Write-ObservabilityErrorLog -Message $Message
    }
    else
    {
        Write-ObservabilityLog -Message $Message
    }
}

function Write-ObservabilityVolumePrunerFileDeletionTelemetry
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $ComputerName,

        [string]
        [Parameter(Mandatory=$true)]
        $DirectoryPath,

        [int64]
        [Parameter(Mandatory=$true)]
        $DirectorySize,

        [string]
        [Parameter(Mandatory=$true)]
        $FilePath,

        [int64]
        [Parameter(Mandatory=$true)]
        $FileSize,

        [bool]
        [Parameter(Mandatory=$true)]
        $Success,

        [Parameter(Mandatory=$false)]
        $ExceptionDetails = ""
    )
    [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ObservabilityVolumePrunerFileDeletion(
        $ComputerName,
        $DirectoryPath,
        $DirectorySize,
        $FilePath,
        $FileSize,
        $Success,
        $ExceptionDetails
    )
        $message = @"
        Observability Volume Pruner File Deletion
        ComputerName: $ComputerName
        Directory path: $DirectoryPath
        Directory size in bytes: $DirectorySize
        File path: $FilePath
        File Size in bytes: $FileSize
        Success: $Success
        Exception Details (if applicable): $ExceptionDetails
"@

    if($success)
    {
        Write-ObservabilityLog $message
    }
    else
    {
        Write-ObservabilityErrorLog $message
    }
}

function Write-BootstrapNodeIdAndHardwareIdHashTelemetry
{
    param(
        # Node ID assigned to node before cluster joining
        [string]
        [parameter(Mandatory=$true)]
        $BootstrapNodeId
    )
    $hardwareHashId = Get-HardwareIdHash
    [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.BootstrapNodeIdAndHardwareIdHash(
        $BootstrapNodeId,
        $hardwareHashId
    )
    Write-ObservabilityLog "Bootstrap Node ID: $BootstrapNodeId Hardware Hash ID: $hardwareHashId"
}

function Add-VolumeFolderQuotaStatisticsScheduledTask
{
    param(
        # Folder access path for observability drive
        [string]
        [parameter(Mandatory=$true)] 
        $AccessPath,

        # Name of subfolder config file to read
        [string]
        [Parameter(Mandatory=$true)] 
        $SubFolderConfigFileName
    )

    $taskName = "ObservabilityVolumeQuotaStatistics"
    if(Test-ScheduledTaskExists -TaskName $taskName)
    {
        Unregister-ScheduledTask -TaskName $taskName -Confirm:$false | Out-Null
    }

    # Setup scheduled task to mount VHD on startup
    $observabilityNugetPath = "$PSScriptRoot\..\.."
    $scriptPath = "$observabilityNugetPath\content\Powershell\WriteObservabilityVolumeQuotaStatistics.ps1"
    Write-ObservabilityLog "Creating new scheduled task $taskName."

    $frequency = New-TimeSpan -Hours 6
    $trigger = New-ScheduledTaskTrigger -RepetitionInterval $frequency -Once -At "12:00 AM"
    $action = New-ScheduledTaskAction `
        -Execute "powershell.exe"  `
        -Argument "-Command $scriptPath -AccessPath $AccessPath -SubFolderConfigFileName $SubFolderConfigFileName"
    $settings = New-ScheduledTaskSettingsSet 
    $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount
    $task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -Principal $principal
    Register-ScheduledTask -TaskName $taskName -TaskPath "Microsoft\AzureStack\Observability" -InputObject $task
    Write-ObservabilityLog "Creating new scheduled task $taskName succeeded."
}

function Install-ArcForServerAgent
{
    param(
        [string]
        [parameter(Mandatory=$true)] 
        $AgentMsiPath,

        [string]
        [Parameter(Mandatory=$true)] 
        $AccessToken,

        [string]
        [Parameter(Mandatory=$true)] 
        $SubscriptionId,

        [string]
        [Parameter(Mandatory=$true)] 
        $TenantId,

        [string]
        [Parameter(Mandatory=$true)] 
        $ResourceGroupName,

        [string]
        [Parameter(Mandatory=$true)] 
        $ResourceName,

        [string]
        [Parameter(Mandatory=$true)] 
        $EnvironmentName,

        [string]
        [Parameter(Mandatory=$true)] 
        $Region,

        [string]
        [Parameter(Mandatory=$false)] 
        $ProxyUrl
    )
    # NOTE: This Add-Type will not work if called from TelemetryAndDiagnostics ARC extension
    $observabilityNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Observability.ObservabilityDeployment"
    Add-Type -Path "$observabilityNugetPath\lib\net472\Microsoft.AzureStack.Observability.ObservabilityCommon.dll"

    $timestamp = [DateTime]::Now.ToString("yyyyMMdd-HHmmss")
    $logPath = (New-Item -Path "$env:LocalRootFolderPath" -ItemType Directory -Force).FullName
    $logFile = Join-Path -Path $logPath -ChildPath "ArcForServerInstall_${timestamp}.txt"

    $AgentWebLink = ""
    $arcAgentNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.AzureConnectedMachineAgent"
    $nugetStoreAgentMsiPath = Join-Path -Path $arcAgentNugetPath  -childPath "content\$($ObservabilityConfigConstants.ArcForServerMsiFileName)"
    $AgentMsiPath = Join-Path -Path $AgentMsiPath -ChildPath $ObservabilityConfigConstants.ArcForServerMsiFileName

    Write-ObservabilityLog "$env:COMPUTERNAME Copying file $nugetStoreAgentMsiPath to destination $AgentMsiPath"
    Copy-Item -Path $nugetStoreAgentMsiPath -Destination $AgentMsiPath -Force

    $programFiles = [Environment]::GetFolderPath([System.Environment+SpecialFolder]::ProgramFiles)
    $AgentExePath = Join-Path -Path $programFiles -ChildPath $ObservabilityConfigConstants.ArcForServerExePath

    Write-ObservabilityLog "$env:COMPUTERNAME Starting Arc-for-server agent install AgentWebLink: $AgentWebLink AgentMsiPath: $AgentMsiPath AgentExePath: $AgentExePath logs: $logFile"
    $res = $false

    try {
        $arcContext = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcContext
        $arcContext.AccessToken = $AccessToken
        $arcContext.SubscriptionId = $SubscriptionId
        $arcContext.ResourceGroup = $ResourceGroupName
        $arcContext.Location = $Region
        $arcContext.Cloud = $EnvironmentName
        $arcContext.ResourceName = $ResourceName
        $arcContext.TenantId = $TenantId

        Write-ObservabilityLog "AgentWebLink: $AgentWebLink AgentMsiPath: $AgentMsiPath AgentExePath: $AgentExePath"
        $arcAgent = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcAgent
        
        if ($ProxyUrl)
        {
            $res = $arcAgent.Onboard($arcContext, $AgentWebLink, $AgentMsiPath, $logFile, $AgentExePath, $ProxyUrl)
        }
        else
        {
            $res = $arcAgent.Onboard($arcContext, $AgentWebLink, $AgentMsiPath, $logFile, $AgentExePath)
        }
    }
    catch {
        Write-ObservabilityErrorLog "$env:COMPUTERNAME Failed to install/configure ArcAgent Exception: $_"
    }
    finally {
        if (Test-Path $AgentMsiPath) {
            Remove-Item -Path $AgentMsiPath -Verbose -ErrorAction Ignore
          }
    }

    Write-ObservabilityLog "Arc-for-server agent install $env:COMPUTERNAME. Status $res"
    if($res -eq $false )
    {
        throw "Arc Agent failed to install or connect. Please check the Observability eventlog on the host"
    }
}

function Uninstall-ArcForServerAgent
{
    param(
        [string]
        [parameter(Mandatory=$true)]
        $AgentMsiPath,

        [string]
        [Parameter(Mandatory=$true)]
        $AccessToken
    )
    
    # NOTE: This Add-Type will not work if called from TelemetryAndDiagnostics ARC extension
    $observabilityNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Observability.ObservabilityDeployment"
    Add-Type -Path "$observabilityNugetPath\lib\net472\Microsoft.AzureStack.Observability.ObservabilityCommon.dll"

    $timestamp = [DateTime]::Now.ToString("yyyyMMdd-HHmmss")
    $logPath = (New-Item -Path "$env:LocalRootFolderPath" -ItemType Directory -Force).FullName
    $logFile = Join-Path -Path $logPath -ChildPath "ArcForServerUninstall_${timestamp}.txt"

    $arcAgentNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.AzureConnectedMachineAgent"
    $nugetStoreAgentMsiPath = Join-Path -Path $arcAgentNugetPath  -childPath "content\$($ObservabilityConfigConstants.ArcForServerMsiFileName)"
    $AgentMsiPath = Join-Path -Path $AgentMsiPath -ChildPath $ObservabilityConfigConstants.ArcForServerMsiFileName

    Write-ObservabilityLog "$env:COMPUTERNAME Copying file $nugetStoreAgentMsiPath to destination $AgentMsiPath"
    Copy-Item -Path $nugetStoreAgentMsiPath -Destination $AgentMsiPath -Force

    $programFiles = [Environment]::GetFolderPath([System.Environment+SpecialFolder]::ProgramFiles)
    $AgentExePath = Join-Path -Path $programFiles -ChildPath $ObservabilityConfigConstants.ArcForServerExePath

    Write-ObservabilityLog "Starting Arc-for-server agent uninstall on $env:COMPUTERNAME. AgentMsiPath: $AgentMsiPath AgentExePath: $AgentExePath logs: $logFile"
    $arcContext = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcContext
    $arcContext.AccessToken = $AccessToken

    Write-ObservabilityLog "AgentMsiPath: $AgentMsiPath AgentExePath: $AgentExePath"
    $arcAgent = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcAgent
    $res = $arcAgent.Offboard($arcContext, $logFile, $AgentExePath, $AgentMsiPath)
    Write-ObservabilityLog "Arc-for-server agent uninstall $env:COMPUTERNAME. Status $res"

    return $res
}

function Update-ArcForServerAgent
{
    param(
        [string]
        [parameter(Mandatory=$true)] 
        $AgentMsiPath
    )
    # NOTE: This Add-Type will not work if called from TelemetryAndDiagnostics ARC extension
    $observabilityNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Observability.ObservabilityDeployment"
    Add-Type -Path "$observabilityNugetPath\lib\net472\Microsoft.AzureStack.Observability.ObservabilityCommon.dll"

    $timestamp = [DateTime]::Now.ToString("yyyyMMdd-HHmmss")
    $logPath = (New-Item -Path "$env:LocalRootFolderPath" -ItemType Directory -Force).FullName
    $logFile = Join-Path -Path $logPath -ChildPath "ArcForServerUpdate_${timestamp}.txt"

    $AgentWebLink = ""
    $arcAgentNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.AzureConnectedMachineAgent"
    $nugetStoreAgentMsiPath = Join-Path -Path $arcAgentNugetPath  -childPath "content\$($ObservabilityConfigConstants.ArcForServerMsiFileName)"
    $AgentMsiPath = Join-Path -Path $AgentMsiPath -ChildPath $ObservabilityConfigConstants.ArcForServerMsiFileName

    Write-ObservabilityLog "$env:COMPUTERNAME Copying file $nugetStoreAgentMsiPath to destination $AgentMsiPath"
    Copy-Item -Path $nugetStoreAgentMsiPath -Destination $AgentMsiPath -Force

    Write-ObservabilityLog "$env:COMPUTERNAME Starting Arc-for-server agent update AgentWebLink: $AgentWebLink AgentMsiPath: $AgentMsiPath logs: $logFile"
    $res = $false

    try {
        Write-ObservabilityLog "AgentWebLink: $AgentWebLink AgentMsiPath: $AgentMsiPath"
        $arcAgent = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcAgent
        $res = $arcAgent.Update($AgentWebLink, $AgentMsiPath, $logFile)
    }
    catch {
        Write-ObservabilityErrorLog "$env:COMPUTERNAME Failed to update ArcAgent Exception: $_"
    }
    finally {
        if (Test-Path $AgentMsiPath) {
            Remove-Item -Path $AgentMsiPath -Verbose -ErrorAction Ignore
          }
    }

    Write-ObservabilityLog "Arc-for-server agent update $env:COMPUTERNAME. Status $res"
    if($res -eq $false )
    {
        throw "Arc Agent failed to update. Please check the Observability eventlog on the host"
    }
}

function Connect-ArcForServerAgent
{
    param(
        [string]
        [Parameter(Mandatory=$true)] 
        $AccessToken,

        [string]
        [Parameter(Mandatory=$true)] 
        $SubscriptionId,

        [string]
        [Parameter(Mandatory=$true)] 
        $TenantId,

        [string]
        [Parameter(Mandatory=$true)] 
        $ResourceGroupName,

        [string]
        [Parameter(Mandatory=$true)] 
        $ResourceName,

        [string]
        [Parameter(Mandatory=$true)] 
        $EnvironmentName,

        [string]
        [Parameter(Mandatory=$true)] 
        $Region,

        [string]
        [Parameter(Mandatory=$false)]
        $ProxyUrl
    )
    # NOTE: This Add-Type will not work if called from TelemetryAndDiagnostics ARC extension
    $observabilityNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Observability.ObservabilityDeployment"
    Add-Type -Path "$observabilityNugetPath\lib\net472\Microsoft.AzureStack.Observability.ObservabilityCommon.dll"

    $programFiles = [Environment]::GetFolderPath([System.Environment+SpecialFolder]::ProgramFiles)
    $AgentExePath = Join-Path -Path $programFiles -ChildPath $ObservabilityConfigConstants.ArcForServerExePath

    Write-ObservabilityLog "$env:COMPUTERNAME Starting Arc-for-server agent connection AgentExePath: $AgentExePath"
    $res = $false

    try {
        $arcContext = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcContext
        $arcContext.AccessToken = $AccessToken
        $arcContext.SubscriptionId = $SubscriptionId
        $arcContext.ResourceGroup = $ResourceGroupName
        $arcContext.Location = $Region
        $arcContext.Cloud = $EnvironmentName
        $arcContext.ResourceName = $ResourceName
        $arcContext.TenantId = $TenantId

        Write-ObservabilityLog "AgentExePath: $AgentExePath"
        $arcAgent = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcAgent
        
        if($ProxyUrl)
        {
            $res = $arcAgent.Connect($arcContext, $AgentExePath, $ProxyUrl)
        }
        else
        {
            $res = $arcAgent.Connect($arcContext, $AgentExePath)
        }
    }
    catch {
        Write-ObservabilityErrorLog "$env:COMPUTERNAME Failed to connect ArcAgent Exception: $_"
    }

    Write-ObservabilityLog "Arc-for-server agent connection for $env:COMPUTERNAME. Status $res"
    if($res -eq $false )
    {
        throw "Arc Agent failed to connect. Please check the Observability eventlog on the host"
    }
}

function Install-ArcForServerExtensions
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $AccessToken,

        [string]
        [Parameter(Mandatory=$true)]
        $SubscriptionId,

        [string]
        [Parameter(Mandatory=$true)]
        $TenantId,

        [string]
        [Parameter(Mandatory=$true)]
        $AccountId,

        [string]
        [Parameter(Mandatory=$true)]
        $ResourceGroupName,

        [string]
        [Parameter(Mandatory=$true)]
        $ResourceName,

        [string]
        [Parameter(Mandatory=$true)]
        $EnvironmentName,

        [string]
        [Parameter(Mandatory=$true)]
        $Region,

        [string]
        [Parameter(Mandatory=$false)]
        $Type = $ObservabilityConfigConstants.ExtensionType
    )

    Write-ObservabilityLog "$env:COMPUTERNAME Starting Arc-for-server extension install."

    Write-ObservabilityLog "$env:COMPUTERNAME Install required modules."

    Import-Module Az.Accounts -Force
    Import-Module Az.ConnectedMachine -Force

    Write-ObservabilityLog "$env:COMPUTERNAME Authenticating to Azure with Access token. $($AccessToken.Length)"
    Connect-AzAccount -AccessToken $AccessToken -SubscriptionId $SubscriptionId -AccountId $AccountId

    $publisher = $ObservabilityConfigConstants.ExtensionPublisher
    if ($Type -eq "EdgeRemoteSupport") {
        $extensionName = "AzureEdgeRemoteSupport"
    } else {
        $extensionName = "AzureEdge" + $Type
    }
    
    Write-ObservabilityLog "$env:COMPUTERNAME Checking if [$Type] extension is already installed."
    $extension = Get-AzConnectedMachineExtension -ResourceGroupName $ResourceGroupName `
                                                -MachineName $ResourceName `
                                                -ErrorAction SilentlyContinue `
                                                | Where-Object {$Type -eq $_.MachineExtensionType}
    # Install the extension only if it not present or publisher doesnt match
    if($null -eq $extension -or $extension.Publisher -ne $publisher)
    {
        Write-ObservabilityLog "$env:COMPUTERNAME Going to install [$Type] extension."
        New-AzConnectedMachineExtension -Name $extensionName `
                                        -ResourceGroupName $ResourceGroupName `
                                        -MachineName $ResourceName `
                                        -Location $Region `
                                        -Publisher $publisher `
                                        -ExtensionType $Type `
                                        -EnableAutomaticUpgrade `
                                        -ErrorAction Stop

        Write-ObservabilityLog "$env:COMPUTERNAME Checking the installed [$Type] extension version."
        $extension = Get-AzConnectedMachineExtension -Name $extensionName `
                                        -ResourceGroupName $ResourceGroupName `
                                        -MachineName $ResourceName `
                                        -ErrorAction SilentlyContinue
        if($null -eq $extension -or $extension.ProvisioningState -ne "Succeeded" -or $extension.Publisher -ne $publisher)
        {
            throw "$env:COMPUTERNAME extension installation failed. $($extension.Name) ProvisioningState: $($extension.ProvisioningState) Publisher: $($extension.Publisher)"
        }
    }

    # Fail the interface if extension is not found in good state
    if($null -eq $extension -or $extension.ProvisioningState -ne "Succeeded" -or $extension.Publisher -ne $publisher)
    {
        throw "$env:COMPUTERNAME Extension installation found failed. $($extension.Name) ProvisioningState: $($extension.ProvisioningState) Publisher: $($extension.Publisher)"
    }

    Write-ObservabilityLog "$env:COMPUTERNAME Finished Arc-for-server extension install. Status $($extension.ProvisioningState)"
}

function Uninstall-ArcForServerExtensions
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $AccessToken,

        [string]
        [Parameter(Mandatory=$true)]
        $SubscriptionId,

        [string]
        [Parameter(Mandatory=$true)]
        $AccountId,

        [string]
        [Parameter(Mandatory=$true)]
        $ResourceGroupName,

        [string]
        [Parameter(Mandatory=$true)]
        $CloudId
    )

    Write-ObservabilityLog "$env:COMPUTERNAME Arc-for-server agent extension uninstall start"

    Import-Module Az.Accounts -Force
    Import-Module Az.ConnectedMachine -Force

    $ResourceName = $env:COMPUTERNAME + "-" + $CloudId
    Write-ObservabilityLog "$env:COMPUTERNAME Authenticating to Azure with Access token."
    Connect-AzAccount -AccessToken $AccessToken -SubscriptionId $SubscriptionId -AccountId $AccountId
    $extensionName = $ObservabilityConfigConstants.ExtensionType

    Write-ObservabilityLog "$env:COMPUTERNAME Checking if $extensionName extension is already installed on resource $ResourceName."
    $extension = Get-AzConnectedMachineExtension -Name $extensionName `
                                    -ResourceGroupName $ResourceGroupName `
                                    -MachineName $ResourceName `
                                    -ErrorAction SilentlyContinue

    if($null -ne $extension)
    {
        Write-ObservabilityLog "$env:COMPUTERNAME Starting $extensionName extension uninstallation on resource $ResourceName"
        Remove-AzConnectedMachineExtension -MachineName $ResourceName `
                                           -Name $extensionName `
                                           -ResourceGroupName $ResourceGroupName `
                                           -SubscriptionId $SubscriptionId

        Write-ObservabilityLog "$env:COMPUTERNAME Checking if $extensionName extension if it is uninstalled on resource $ResourceName."
        $extension = Get-AzConnectedMachineExtension -Name $extensionName `
                                        -ResourceGroupName $ResourceGroupName `
                                        -MachineName $ResourceName `
                                        -ErrorAction SilentlyContinue
        if($null -ne $extension)
        {
            throw "$env:COMPUTERNAME $extensionName extension uninstallation failed. Name: $($extension.Name) ProvisioningState: $($extension.ProvisioningState) Publisher: $($extension.Publisher)"
        }
    }

    Write-ObservabilityLog "$env:COMPUTERNAME Arc-for-server agent extension uninstall complete"
}

function Invoke-CachedTelemetryFilesParsing
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $LogOrchestratorNugetPath,

        [string]
        [Parameter(Mandatory=$true)]
        $CorrelationId
    )

    $result = $false
    try
    {
        $transcriptFileName = "{0}.{1:yyyy-MM-dd}.log" -f "Invoke-CachedTelemetryFilesParsing", $(Get-Date)
        $transcriptFilePath = Join-Path -Path "$env:LocalRootFolderPath" -ChildPath $transcriptFileName
        Start-Transcript -Path $transcriptFilePath -Append | Out-Null

        Write-ObservabilityLog "$env:COMPUTERNAME Cached Telemetry Files Parsing start"

        $parsingEnginePath = Join-Path -Path $LogOrchestratorNugetPath -ChildPath $ObservabilityConfigConstants.ParsingEngineInternalPath
        $configFilePath = Join-Path -Path $observabilityNugetPath -ChildPath $ObservabilityConfigConstants.TelemetryCacheConfigurationInternalPath
        Write-ObservabilityLog "$env:COMPUTERNAME CachedTelemetryFilesParsing parsingEnginePath: $parsingEnginePath configFilePath: $configFilePath"

        $telemetryCompnentsCache = Get-Content $configFilePath | ConvertFrom-Json

        Write-ObservabilityLog "$env:COMPUTERNAME Found Components count $($telemetryCompnentsCache.Components) from $configFilePath."
        foreach($component in $telemetryCompnentsCache.Components)
        {
            $cacheLogPath = $component.CachePath
            $exceptionMsg = ""
            $pathFound = $false
            try
            {
                Write-ObservabilityLog "$env:COMPUTERNAME Processing Telemetry cache for Component Name: $($component.Name) RegistryPath: $($component.RegistryPath) Path $cacheLogPath."
                if(-not [string]::IsNullOrEmpty($component.RegistryPath))
                {
                    Write-ObservabilityLog "$env:COMPUTERNAME Going to resolve registry path for Component Name: $($component.Name) RegistryPath: $($component.RegistryPath)."
                    $registryProperty = Get-ItemProperty -Path $ObservabilityConfigConstants.TelemetryRegistryPath -Name $component.RegistryPath -ErrorAction SilentlyContinue
                    if($registryProperty)
                    {
                        $registryLogPath = $registryProperty.$($component.RegistryPath)
                    }

                    Write-ObservabilityLog "$env:COMPUTERNAME Going to resolve registry path for Component Name: $($component.Name) RegistryPath: $($component.RegistryPath) Resolved Path $registryLogPath."
                    $cacheLogPath = $registryLogPath
                }

                Write-CachedTelemetryParsingStartTelemetry -ComponentName $component.Name -TelemetryCachePath $cacheLogPath -RegistryPath $component.RegistryPath
                Write-ObservabilityLog "$env:COMPUTERNAME Processing Telemetry cache for Component Name: $($component.Name) Path: $cacheLogPath."
                $cachePathJson = ConvertTo-Json -InputObject $cacheLogPath
                $parsingEngineArg = 'IngestionInfo:{"GenerateTelemetryEvents":"true","LogDirectoryPath":' + $cachePathJson + ',"TracingContext":"' + $CorrelationId + '"}'

                $pathFound = if (($cacheLogPath) -and (Test-Path -Path $cacheLogPath) ) { $true } else { $false }
                if($pathFound)
                {
                    $currentDir = [System.IO.Path]::GetDirectoryName($parsingEnginePath)
                    Write-ObservabilityLog "$env:COMPUTERNAME Going to start ParsingEngine $parsingEnginePath with Arguments: $parsingEngineArg CurrentDirectory: $currentDir."
                    Start-Process -FilePath $parsingEnginePath -NoNewWindow -Wait -ArgumentList $parsingEngineArg -Passthru -WorkingDirectory $currentDir
                    Write-ObservabilityLog "$env:COMPUTERNAME ParsingEngine finished execution for component $($component.Name) Going to read result file from $currentDir"

                    $resultFilePath = Join-Path -Path $currentDir -ChildPath $ObservabilityConfigConstants.ParsingEngineResultFileName
                    Write-ObservabilityLog "$env:COMPUTERNAME ParsingEngine execution result file for component $component is $resultFilePath"
                    $parsingEngineResultStr = Get-Content $resultFilePath
                    Write-ObservabilityLog "$env:COMPUTERNAME ParsingEngine execution result for component $($component.Name) is $parsingEngineResultStr"
                    $parsingEngineResult = $parsingEngineResultStr | ConvertFrom-Json
                    $result = $parsingEngineResult.Failed -eq $false
                }
                else
                {
                    $result = $true
                    Write-ObservabilityLog "$env:COMPUTERNAME Component $($component.Name) Telemetry cache path $cacheLogPath not found. So skipping parsing"
                }
            }
            catch
            {
                $exceptionMsg = $_.Exception.Message.ToString()
            }
            finally
            {
                Write-CachedTelemetryParsingStopTelemetry -ComponentName $component.Name `
                                                          -TelemetryCachePath $cacheLogPath `
                                                          -RegistryPath $component.RegistryPath `
                                                          -PathFound $pathFound `
                                                          -ParserResult $parsingEngineResultStr `
                                                          -ExceptionDetails $exceptionMsg
            }
        }
    }
    finally
    {
        Stop-Transcript | Out-Null
        Write-ObservabilityLog "$env:COMPUTERNAME Cached Telemetry Files Parsing end Result: $result"
    }

    return $result
}

function Write-ArcForServerInstallationStartTelemetry
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $ComputerNames
    )

    [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ArcForServerInstallationStart(
        $ComputerNames
    )
    $info = @"
        Starting Arc-for-Server agent installation
        ComputerNames: $ComputerNames
"@

    Write-ObservabilityLog -Message $info
}

function Write-ArcForServerInstallationStopTelemetry
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $ComputerName,

        [string]
        [Parameter(Mandatory=$true)]
        $Message,

        [string]
        [Parameter(Mandatory=$false)]
        $ExceptionDetails = ""
    )

    [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ArcForServerInstallationStop($ComputerName, $Message, $ExceptionDetails)

    if($ExceptionDetails)
    {
        Write-ObservabilityErrorLog -Message $Message
    }
    else
    {
        Write-ObservabilityLog -Message $Message
    }
}

function Get-Sha256Hash
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $ClearString
    )

    $hasher = [System.Security.Cryptography.HashAlgorithm]::Create('sha256')
    $hash = $hasher.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($ClearString))
    $hashString = [System.BitConverter]::ToString($hash)
    $hashString = $hashString.Replace('-', '')
    return $hashString
}

function Get-HardwareIdHash
{
    $computerInfo = Get-ComputerInfo
    $manufacturer = $computerInfo.CsManufacturer
    $model = $computerInfo.CsModel
    $serialNumber = $computerInfo.BiosSeralNumber
    return (Get-Sha256Hash -ClearString "$manufacturer-$model-$serialNumber").toLower()
}

function Set-GMAScenarioRegistryKeyToBootstrap
{
    # Import GMATenantJsonHelper
    $gmaPackageContentPath = Join-Path $(Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Observability.GenevaMonitoringAgent") -ChildPath "content"
    Import-Module "$gmaPackageContentPath\GMATenantJsonHelper.psm1" -DisableNameChecking

    # $MiscConstants imported from GMATenantJsonHelper
    $gmaScenarioRegKeyPath = $MiscConstants.GMAScenarioRegKey.Path
    $gmaScenarioRegKeyName = $MiscConstants.GMAScenarioRegKey.Name
    $gmaScenarioBootstrapValue = $MiscConstants.GMAScenarioRegKey.Bootstrap
    $gmaScenarioRegKeyPropertyType = $MiscConstants.GMAScenarioRegKey.PropertyType
    if(-not (Test-Path $gmaScenarioRegKeyPath))
    {
        Write-ObservabilityLog "Creating GMAScenario registry path at $gmaScenarioRegKeyPath"
        New-Item -Path $gmaScenarioRegKeyPath -Force
    }
    New-ItemProperty `
        -Path $gmaScenarioRegKeyPath `
        -Name $gmaScenarioRegKeyName `
        -PropertyType $gmaScenarioRegKeyPropertyType `
        -Value $gmaScenarioBootstrapValue `
        -Force
    Write-ObservabilityLog "Set $gmaScenarioRegKeyName at $gmaScenarioRegKeyPath to $gmaScenarioBootstrapValue"
}

function Write-CachedTelemetryParsingStartTelemetry
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $ComponentName,

        [string]
        [Parameter(Mandatory=$false)]
        $TelemetryCachePath,

        [string]
        [Parameter(Mandatory=$false)]
        $RegistryPath

    )

    [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.CachedTelemetryParsingStart(
        $ComponentName,
        $TelemetryCachePath
    )
    $info = @"
        Starting Cached Telemetry file parsing
        ComponentName: $ComponentName
        TelemetryCachePath: $TelemetryCachePath
        RegistryPath: $RegistryPath
"@

    Write-ObservabilityLog -Message $info
}

function Write-CachedTelemetryParsingStopTelemetry
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $ComponentName,

        [string]
        [Parameter(Mandatory=$false)]
        $TelemetryCachePath,

        [string]
        [Parameter(Mandatory=$false)]
        $RegistryPath,

        [bool]
        [Parameter(Mandatory=$true)]
        $PathFound,

        [string]
        [Parameter(Mandatory=$false)]
        $ParserResult,

        [string]
        [Parameter(Mandatory=$false)]
        $ExceptionDetails = ""
    )

    [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.CachedTelemetryParsingStop($ComponentName, $TelemetryCachePath, $PathFound, $ParserResult, $ExceptionDetails)

        $info = @"
        Finished Cached Telemetry file parsing
        ComponentName: $ComponentName
        TelemetryCachePath: $TelemetryCachePath
        RegistryPath: $RegistryPath
        PathFound: $PathFound
        ParserResult: $ParserResult
        ExceptionDetails: $ExceptionDetails
"@


    if($ExceptionDetails)
    {
        Write-ObservabilityErrorLog -Message $info
    }
    else
    {
        Write-ObservabilityLog -Message $info
    }
}

function Get-ArcResourceId
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $SubscriptionId,

        [string]
        [Parameter(Mandatory=$true)]
        $ResourceGroupName,

        [string]
        [Parameter(Mandatory=$true)]
        $CloudId
    )

    $resourceName = Get-ArcResourceName

    Write-ObservabilityLog "Generating Arc resource id with SubscriptionId $SubscriptionId ResourceGroupName $ResourceGroupName ResourceName $resourceName"

    $arcResourceIdFormat = $ObservabilityConfigConstants.ArcForServerResourceIdFormat
    $arcResourceId = [string]::Format($arcResourceIdFormat, $SubscriptionId, $ResourceGroupName, $resourceName)
    Write-ObservabilityLog "Get-ArcResourceId returning $arcResourceId"

    return $arcResourceId
}

function Get-ArcResourceName
{
    # Resouce name has to be host name for device registration to work
    $resourceName = hostname
    Write-ObservabilityLog "Get-ArcResourceName returning $resourceName"

    return $resourceName
}

# Start Arc Remote Support agent if the EdgeRemoteSupport extension has been in listener mode.
# Remove listener mode reg key
# No-op if extension was not in listener mode
function StartArcRemoteSupportAgent
{
    $success = $false
    $paths = (Get-ChildItem "C:\Packages\Plugins\Microsoft.AzureStack.Observability*EdgeRemoteSupport*\*\" -ErrorAction SilentlyContinue).FullName
    $extError = ""
    foreach ($extRootPath in $paths)
    {
        Write-ObservabilityLog "Found EdgeRemoteSupport extension at $extRootPath."
        if ($success)
        {
            Write-ObservabilityLog "Arc Extension Remote Support Agent already started. Ignoring extension at $extRootPath."
        }
        else 
        {
            try {
                Import-Module (Join-Path -Path $extRootPath -ChildPath 'Common\ExtensionCommon.psd1') `
                    -DisableNameChecking `
                    -Force `
                    -Verbose:$false
                $constantsName = "RemoteSupportConstants"
                $RemoteSupportConstants = Get-Constants -Constants $constantsName
                $logFile = Get-HandlerLogFile -Constants $constantsName

                # Check if extension has been in listener mode
                $listenerModeRegKeyPath = $RemoteSupportConstants.ListenerModeRegKey.Path
                $listenerModeRegKeyName = $RemoteSupportConstants.ListenerModeRegKey.Name

                $listenerModeRegKeyValue = (Get-ItemProperty -Path $listenerModeRegKeyPath -ErrorAction SilentlyContinue).$listenerModeRegKeyName
                if ($listenerModeRegKeyValue -eq 1) {
                    Write-ObservabilityLog "Bringing EdgeRemoteSupport extension out of listener mode."

                    # Register Remote Support Agent
                    Write-ObservabilityLog "Registering Remote Support Agent"
                    Register-RemoteSupportAgent -LogFile $logFile

                    # Start Remote Support Agent
                    Write-ObservabilityLog "Starting Remote Support Agent"
                    Start-RemoteSupportAgent -LogFile $logFile

                    # Removing listener mode registry key
                    Remove-RegistryKey `
                        -path $RemoteSupportConstants.ListenerModeRegKey.Path `
                        -Name $RemoteSupportConstants.ListenerModeRegKey.Name `
                        -LogFile $LogFile
                    
                    Write-ObservabilityLog "Remote Support Agent successfully registered and started."
                    $success = $true
                } else {
                    Write-ObservabilityLog "EdgeRemoteSupport Extension was not in listener mode."
                    $success = $true
                }
            }
            catch {
                Write-ObservabilityErrorLog "Starting Arc Remote Support Agent failed with error $_"
                $extError = $_
            }
        }
    }
    if (-not $success) 
    {
        if ($extError)
        {
            throw "Starting Arc Remote Support Agent failed with error $extError"
        } else 
        {
            $errMsg = "Could not start Arc Remote Support Agent because no EdgeRemoteSupport Extension was found at C:\Packages\Plugins."
            Write-ObservabilityErrorLog $errMsg
            throw $errMsg
        }
    }
}

function Test-IsCIEnv
{
    $CIRegKey = @{
        Path = 'HKLM:\Software\Microsoft\SQMClient\'
        Name = 'IsCIEnv'
    }
    $ObsCIRegKey = @{
        Path = 'HKLM:\Software\Microsoft\AzureStack\Observability\'
        Name = 'IsCIEnv'
        PropertyType = 'DWORD'
    }
    $ciKeyExists = $null -ne (Get-ItemProperty -Path $CIRegKey.Path -Name $ObsCIRegKey.Name -ErrorAction SilentlyContinue)
    $obsCiKeyExists = $null -ne (Get-ItemProperty -Path $ObsCIRegKey.Path -Name $ObsCIRegKey.Name -ErrorAction SilentlyContinue)
    if ($ciKeyExists -and -not $obsCiKeyExists)
    {
        Write-ObservabilityLog "Registry key $($CIRegKey.Name) exists at path $($CIRegKey.Path). Copying to path $($ObsCIRegKey.Path)."
        $ciKeyValue = (Get-ItemProperty -Path $CIRegKey.Path -Name $CIRegKey.Name).$($CIRegKey.Name)
        if (-not (Test-Path -Path $ObsCIRegKey.Path))
        {
            New-Item `
                -Path $ObsCIRegKey.Path `
                -Force `
                -Verbose:$false `
                | Out-Null
        }
        New-ItemProperty `
            -Path $ObsCIRegKey.Path `
            -Name $ObsCIRegKey.Name `
            -PropertyType $ObsCIRegKey.PropertyType `
            -Value $ciKeyValue `
            -Force `
            | Out-Null
        Write-ObservabilityLog "Created Registry key $($ObsCIRegKey.Name) at path $($ObsCIRegKey.Path) with value $ciKeyValue."
    }
    return $ciKeyExists -or $obsCiKeyExists
}

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

    return $registrationArcAEndpoint
}

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

Export-ModuleMember -Function Add-MountVolumeScheduledTask
Export-ModuleMember -Function Add-ObservabilityVolumeWithRetry
Export-ModuleMember -Function Add-VolumeAccessPath
Export-ModuleMember -Function Add-VolumeFolderQuotaStatisticsScheduledTask
Export-ModuleMember -Function Get-ObservabilityFolders
Export-ModuleMember -Function Get-Sha256Hash
Export-ModuleMember -Function Remove-ObservabilityFolderOldestFiles
Export-ModuleMember -Function Set-FolderQuotas
Export-ModuleMember -Function Set-GMAScenarioRegistryKeyToBootstrap
Export-ModuleMember -Function New-VolumeFoldersAndPrunerWithRetry
Export-ModuleMember -Function Test-IsCIEnv
Export-ModuleMember -Function Write-ObservabilityErrorLog
Export-ModuleMember -Function Write-ObservabilityLog
Export-ModuleMember -Function Write-BootstrapNodeIdAndHardwareIdHashTelemetry
Export-ModuleMember -Function Write-ObservabilityVolumeCreationStartTelemetry
Export-ModuleMember -Function Write-ObservabilityVolumeCreationStopTelemetry
Export-ModuleMember -Function Write-ObservabilityVolumeMountedByScheduledTaskTelemetry
Export-ModuleMember -Function Write-ObservabilityVolumePrunerFileDeletionTelemetry
Export-ModuleMember -Function Install-ArcForServerAgent
Export-ModuleMember -Function Uninstall-ArcForServerAgent
Export-ModuleMember -Function Update-ArcForServerAgent
Export-ModuleMember -Function Connect-ArcForServerAgent
Export-ModuleMember -Function Uninstall-ArcForServerExtensions
Export-ModuleMember -Function Install-ArcForServerExtensions
Export-ModuleMember -Function Write-ArcForServerInstallationStartTelemetry
Export-ModuleMember -Function Write-ArcForServerInstallationStopTelemetry
Export-ModuleMember -Function Get-ArcResourceId
Export-ModuleMember -Function Get-ArcResourceName
Export-ModuleMember -Function Invoke-CachedTelemetryFilesParsing
Export-ModuleMember -Function StartArcRemoteSupportAgent
Export-ModuleMember -Function Get-ArcAEndpoint 
Export-ModuleMember -Function Test-IsArcADeployment
# SIG # Begin signature block
# MIInvwYJKoZIhvcNAQcCoIInsDCCJ6wCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBcAbw4e20Z31cK
# mqbIZRskYFufMz8VrX/zQsRwa2ZXJKCCDXYwggX0MIID3KADAgECAhMzAAADrzBA
# DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA
# hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG
# 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN
# xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL
# go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB
# tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd
# mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ
# 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY
# 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp
# XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn
# TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT
# e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG
# OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O
# PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk
# ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx
# HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt
# CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGZ8wghmbAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIFbRYHKLaz2gN9gnaCsgmvMC
# NfjD77HDu+EaHNIoWKnfMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAj/7ocIc5GhbxwcWoKz4kBUmrQA4Im2i1/cQPrAxMCAWpHqYiPzvVO+I/
# cjKyVPC30JwO4FgpmOet1jyCzWMYJ2LhOpcDP6D4/qjcOTHvOkHFnfQxowpxRSXo
# ctinGWfFtp6HYSFwN5oh6ga0TVb5ptb4gh3gdZKGweyZ7+aH3Ad4TXdElWAqXe28
# m4ukdTMyDVmoora4a6TiYzYvkD9JEfiGLQE+fjHhbkQ1NwQfhDkH0xW5/ayIMVIB
# OT//o7tsqLtcxXV7ZQn8IQlmTtnB7NZgAbPrtCpB1Vm0VLWfC95OOJJwrA3Ug1VR
# MsIDVReLDxasMDwR8nnSLr4P+EkbT6GCFykwghclBgorBgEEAYI3AwMBMYIXFTCC
# FxEGCSqGSIb3DQEHAqCCFwIwghb+AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq
# hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCDeSszcQJKW99iMPNz92sJ08A9EHLVPH/sXJkCeYlvIfwIGZjOrzANo
# GBMyMDI0MDUxNjE4NDUxOC41NzJaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO
# Ojg2REYtNEJCQy05MzM1MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT
# ZXJ2aWNloIIReDCCBycwggUPoAMCAQICEzMAAAHdXVcdldStqhsAAQAAAd0wDQYJ
# KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjMx
# MDEyMTkwNzA5WhcNMjUwMTEwMTkwNzA5WjCB0jELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl
# cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4NkRGLTRC
# QkMtOTMzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKhOA5RE6i53nHURH4lnfKLp
# +9JvipuTtctairCxMUSrPSy5CWK2DtriQP+T52HXbN2g7AktQ1pQZbTDGFzK6d03
# vYYNrCPuJK+PRsP2FPVDjBXy5mrLRFzIHHLaiAaobE5vFJuoxZ0ZWdKMCs8acjhH
# UmfaY+79/CR7uN+B4+xjJqwvdpU/mp0mAq3earyH+AKmv6lkrQN8zgrcbCgHwsqv
# vqT6lEFqYpi7uKn7MAYbSeLe0pMdatV5EW6NVnXMYOTRKuGPfyfBKdShualLo88k
# G7qa2mbA5l77+X06JAesMkoyYr4/9CgDFjHUpcHSODujlFBKMi168zRdLerdpW0b
# BX9EDux2zBMMaEK8NyxawCEuAq7++7ktFAbl3hUKtuzYC1FUZuUl2Bq6U17S4CKs
# qR3itLT9qNcb2pAJ4jrIDdll5Tgoqef5gpv+YcvBM834bXFNwytd3ujDD24P9Dd8
# xfVJvumjsBQQkK5T/qy3HrQJ8ud1nHSvtFVi5Sa/ubGuYEpS8gF6GDWN5/KbveFk
# dsoTVIPo8pkWhjPs0Q7nA5+uBxQB4zljEjKz5WW7BA4wpmFm24fhBmRjV4Nbp+n7
# 8cgAjvDSfTlA6DYBcv2kx1JH2dIhaRnSeOXePT6hMF0Il598LMu0rw35ViUWcAQk
# UNUTxRnqGFxz5w+ZusMDAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUbqL1toyPUdpF
# yyHSDKWj0I4lw/EwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD
# VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
# cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG
# CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw
# MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBAC5U2bINLgXIHWbM
# cqVuf9jkUT/K8zyLBvu5h8JrqYR2z/eaO2yo1Ooc9Shyvxbe9GZDu7kkUzxSyJ1I
# ZksZZw6FDq6yZNT3PEjAEnREpRBL8S+mbXg+O4VLS0LSmb8XIZiLsaqZ0fDEcv3H
# eA+/y/qKnCQWkXghpaEMwGMQzRkhGwcGdXr1zGpQ7HTxvfu57xFxZX1MkKnWFENJ
# 6urd+4teUgXj0ngIOx//l3XMK3Ht8T2+zvGJNAF+5/5qBk7nr079zICbFXvxtidN
# N5eoXdW+9rAIkS+UGD19AZdBrtt6dZ+OdAquBiDkYQ5kVfUMKS31yHQOGgmFxuCO
# zTpWHalrqpdIllsy8KNsj5U9sONiWAd9PNlyEHHbQZDmi9/BNlOYyTt0YehLbDov
# mZUNazk79Od/A917mqCdTqrExwBGUPbMP+/vdYUqaJspupBnUtjOf/76DAhVy8e/
# e6zR98PkplmliO2brL3Q3rD6+ZCVdrGM9Rm6hUDBBkvYh+YjmGdcQ5HB6WT9Rec8
# +qDHmbhLhX4Zdaard5/OXeLbgx2f7L4QQQj3KgqjqDOWInVhNE1gYtTWLHe4882d
# /k7Lui0K1g8EZrKD7maOrsJLKPKlegceJ9FCqY1sDUKUhRa0EHUW+ZkKLlohKrS7
# FwjdrINWkPBgbQznCjdE2m47QjTbMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ
# mQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh
# dGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1
# WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEB
# BQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjK
# NVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhg
# fWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJp
# rx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/d
# vI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka9
# 7aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKR
# Hh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9itu
# qBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyO
# ArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItb
# oKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6
# bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6t
# AgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQW
# BBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacb
# UzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYz
# aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnku
# aHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIA
# QwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2
# VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwu
# bWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEw
# LTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93
# d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYt
# MjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/q
# XBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6
# U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVt
# I1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis
# 9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTp
# kbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0
# sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138e
# W0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJ
# sWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7
# Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0
# dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQ
# tB1VM1izoXBm8qGCAtQwggI9AgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxh
# bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4
# NkRGLTRCQkMtOTMzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZaIjCgEBMAcGBSsOAwIaAxUANiNHGWXbNaDPxnyiDbEOciSjFhCggYMwgYCk
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF
# AOnwnfEwIhgPMjAyNDA1MTYyMzAwMDFaGA8yMDI0MDUxNzIzMDAwMVowdDA6Bgor
# BgEEAYRZCgQBMSwwKjAKAgUA6fCd8QIBADAHAgEAAgIEnjAHAgEAAgIRbDAKAgUA
# 6fHvcQIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAID
# B6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAHnT0LxSY/nWLIhldpba
# s+/PI5nwSlUEjVz2T8+za4oOWpPvEXFZubjQpKV+rZcSz8dC3W9E3H0XfRCVX8Hc
# nAUMQOUovNoxn0wyleMRSJKOdzhZ4nvVCpnfUVS08zkwMS06Gcqv4txhUWFZ3zEQ
# 8KQKBIpbgaxeKYGCxMYijGxrMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgUENBIDIwMTACEzMAAAHdXVcdldStqhsAAQAAAd0wDQYJYIZIAWUDBAIB
# BQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQx
# IgQgwSW7XN5HQ7TqwhxSR5qw+lSb+LmgQ0u5lsisYrDVcj8wgfoGCyqGSIb3DQEJ
# EAIvMYHqMIHnMIHkMIG9BCBh/w4tmmWsT3iZnHtH0Vk37UCN02lRxY+RiON6wDFj
# ZjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB3V1X
# HZXUraobAAEAAAHdMCIEIGw/GMnaHgOLTlJ1AOPfbuCj5wPO8mWTx5OYG0qVMVp+
# MA0GCSqGSIb3DQEBCwUABIICADkrGNOCd8SlMd5+D3IlBiSCxO8HURx+vnQ5HI0g
# OZ4ohsTaaVeATj54Nff3OyXTZSpI0yg/7afzmGqQ3Iv6PXvMF16IIBJcfYHFoVpC
# zEVM8MT8DGKmhzXk+mAr1+/yvrCVoepobxUc1q2Zh54pswUVtHoE5+frIybj84q7
# pMUcjUEpOcRfOEzFwgDVaNN74SKY41sI1y5Gh4sB4HGdisHhDJonNBWeyqIIohVE
# ljc7Rkal7Bj+4rwiWmhSKW/u88wOCElGuwYnQQYXGxYEK8T/QxDejqsk2EoqXBQ+
# baVzJvLBKc+K8m9Nj4yQwiY6fXKoIkCWG5OGB2gGavHoqSroDLJiW8PPQLhw/1tJ
# ckc6k0GRuA8RuCZP+pBTID728Je7uVAu/wg1qVM1t1rHrhVRY32ls75MPiICpBY0
# 81J11kGSmgcdsBlZYR0+5+IEIW0xmmwglhsOY9CpmhANZEBb6gaR3R0rPNbvyG6+
# 5N/o7EDcCnMqUOAwSv6Quv/M2DQ+SnIohW9eAJhGzSJ61EDPoXfeaR4lU4BvwmdR
# Y2UVxmsLMyl+kgkqVLE8oWbyMJVjR99XTpP9Grulb9gaww27kCV28c2HBb1LLV2z
# ghKafY7prdmBW/Okaoyb7fKf9Q0RTC56PGeT8TRE0w88YETdQoC8tPpoBsPHk3aU
# FRAa
# SIG # End signature block